Files
edgeever/apps/api/src/request-auth.ts
T

71 lines
2.2 KiB
TypeScript

import type { AppContext, AuditActor, AuthContext } from "./api-context";
import { AppError } from "./app-error";
import { forbidden, unauthorized } from "./http-errors";
export const ALL_TOKEN_SCOPES = [
"read:notebooks",
"write:notebooks",
"read:memos",
"write:memos",
"read:resources",
"write:resources",
"read:tags",
"write:tags",
] as const;
export type TokenScope = (typeof ALL_TOKEN_SCOPES)[number];
export const getAuditActor = (c: AppContext): AuditActor => {
const auth = c.get("auth");
return {
actorType: auth?.actorType ?? "user",
actorId: auth?.actorId ?? null,
};
};
export const getActorLabel = (c: AppContext) => {
const auth = c.get("auth");
return auth?.actorId ? `${auth.actorType}:${auth.actorId}` : auth?.username ?? "user";
};
export const getWorkspaceId = (c: AppContext) => c.get("auth").workspaceId;
export const requireOwner = (c: AppContext) => {
const auth = c.get("auth");
return auth?.kind === "user" && auth.role === "owner"
? null
: forbidden(c, "Only the instance owner can manage users.");
};
export const requireUser = (c: AppContext) => {
const auth = c.get("auth");
return auth?.kind === "user"
? null
: forbidden(c, "Only an interactive user session can manage this resource.");
};
export const hasScopes = (auth: AuthContext, scopes: TokenScope[]) =>
auth.kind === "user" || scopes.every((scope) => auth.scopes.includes(scope));
export const requireScopes = (c: AppContext, ...scopes: TokenScope[]) => {
const auth = c.get("auth");
if (!auth) return unauthorized(c, "Authentication required.");
return hasScopes(auth, scopes)
? null
: forbidden(c, `Missing required scope: ${scopes.join(", ")}`);
};
export const assertScope = (auth: AuthContext, scope: TokenScope) => {
if (!hasScopes(auth, [scope])) {
throw new AppError("forbidden", `Missing required scope: ${scope}`, 403);
}
};
export const isTokenScope = (scope: string): scope is TokenScope =>
(ALL_TOKEN_SCOPES as readonly string[]).includes(scope);
export const normalizeTokenScopes = (scopes: string[]) => {
const normalized = Array.from(new Set(scopes.map((scope) => scope.trim()).filter(Boolean)));
return normalized.some((scope) => !isTokenScope(scope)) ? null : normalized as TokenScope[];
};