Files
puter/src/backend/core/actor.ts
T

121 lines
4.3 KiB
TypeScript

/**
* Copyright (C) 2024-present Puter Technologies Inc.
*
* This file is part of Puter.
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { UserRow } from '../stores/user/UserStore';
export interface ActorApp {
uid: string;
id?: number;
}
/**
* Access-token wrapper. When set, this actor is acting *through* an access
* token issued by `issuer`. The token's row in `access_token_permissions`
* gates which permissions of the issuer it can exercise.
*/
export interface ActorAccessToken {
uid: string;
issuer: Actor;
authorized?: Actor | null;
/**
* Full-API-access ("personal access token") flag, set from the signed
* `full_access` JWT claim. Such a token may exercise everything its issuing
* user can do via the API and is admitted past `requireNonAccessTokenGate`
* — but is still rejected by `requireUserActor` / web-session gates, so it
* can never manage the account. Normal (scoped) access tokens leave this
* false and remain blocked from non-`allowAccessToken` routes.
*/
fullAccess?: boolean;
}
export interface Actor {
user: Partial<UserRow>;
app?: ActorApp | null;
/** True for the system actor; skips metering / quota tracking. */
system?: boolean;
accessToken?: ActorAccessToken | null;
/**
* Session reference when authenticated via a session token (user actors)
* or an app-under-user token that carries a session. Absent for system,
* raw-app, and pure access-token actors. Used for session introspection
* and targeted logout. `kind` mirrors the session row's kind (e.g.
* 'web', 'app', 'worker') so callers can gate on how the credential
* was minted without an extra session lookup.
*/
session?: { uid: string; kind?: string | null } | null;
}
/** UUID of the baked-in system user (see 0025 seed migration). */
export const SYSTEM_ACTOR_UUID = '5d4adce0-a381-4982-9c02-6e2540026238';
/** The default system actor used when no actor is supplied. */
export const SYSTEM_ACTOR: Actor = {
user: { uuid: SYSTEM_ACTOR_UUID, username: 'system' },
system: true,
};
export const isSystemActor = (actor: Actor | undefined | null): boolean => {
return !!actor?.system || actor?.user?.uuid === SYSTEM_ACTOR_UUID;
};
export const isAppActor = (actor: Actor | undefined | null): boolean => {
return !!actor?.app && !isAccessTokenActor(actor);
};
export const isAccessTokenActor = (
actor: Actor | undefined | null,
): boolean => {
return !!actor?.accessToken;
};
/**
* Stable identifier for an actor.
* Used as a cache key (e.g., permission scan cache) and for cycle detection.
*/
export const actorUid = (actor: Actor): string => {
if (actor.accessToken) {
const authorizedUid = actor.accessToken.authorized
? actorUid(actor.accessToken.authorized)
: '<none>';
return `access-token:${actorUid(actor.accessToken.issuer)}:${authorizedUid}:${actor.accessToken.uid}`;
}
if (isSystemActor(actor)) return 'system';
if (actor.app) return `app-under-user:${actor.user.uuid}:${actor.app.uid}`;
return `user:${actor.user.uuid}`;
};
/**
* Return a user-only actor for any app-under-user actor. For non-app actors,
* returns the actor unchanged.
*/
export const userRelatedActor = (actor: Actor): Actor => {
if (!actor.app && !actor.accessToken) return actor;
return { user: actor.user };
};
/**
* Walk the access-token issuer chain and return the first app identity found,
* or null if no app appears anywhere in the chain.
*/
export const effectiveActorApp = (actor: Actor): ActorApp | null => {
if (actor.app) return actor.app;
if (actor.accessToken) return effectiveActorApp(actor.accessToken.issuer);
return null;
};