Spaces:
Runtime error
Runtime error
File size: 2,701 Bytes
cd8bd0a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | import { verifyAccessToken } from "@/lib/db/accessTokens";
import { scopeSatisfies, type AccessScope } from "@/lib/accessTokens/scopes";
import { inferRequiredScope } from "@/server/authz/accessScopes";
/**
* Shared evaluation of a scoped CLI access token (`oma_...`) for remote mode.
*
* Used by BOTH the central authz pipeline (`managementPolicy` β the authoritative
* gate wired through `src/proxy.ts`) and the route-level `requireManagementAuth`
* (defense-in-depth + lets `/api/cli/whoami` learn its own scope). Keeping the
* logic here means the two gates can never drift.
*
* Returns a neutral verdict the caller maps to its own response shape
* (`allow()/reject()` in the policy, `null`/Response in requireManagementAuth).
*/
/** Prefix that distinguishes a CLI access token from an inference API key. */
export const ACCESS_TOKEN_PREFIX = "oma_";
export type AccessTokenVerdict =
| { kind: "absent" } // no oma_ bearer present β caller continues other auth paths
| { kind: "error" } // auth backend (DB) threw β 503, not an auth failure
| { kind: "invalid" } // oma_ present but unknown/expired/revoked β 401
| { kind: "insufficient"; have: AccessScope; need: AccessScope } // valid but scope too low β 403
| { kind: "ok"; scope: AccessScope; id: string; name: string }; // authorized β allow
/** Read a Bearer token from the Authorization header (header-only; never URL). */
export function extractBearer(request: Request): string | null {
const header = request.headers.get("authorization") || "";
const match = /^Bearer\s+(.+)$/i.exec(header.trim());
return match ? match[1].trim() : null;
}
function safePathname(url: string): string {
try {
return new URL(url).pathname;
} catch {
return "/";
}
}
/**
* Evaluate the request's access-token credential and required scope.
* Pure w.r.t. the request (only side effect is `verifyAccessToken` stamping
* `last_used_at`). Never throws β DB failures surface as `{ kind: "error" }`.
*/
export function evaluateAccessTokenAuth(request: Request): AccessTokenVerdict {
const bearer = extractBearer(request);
if (!bearer || !bearer.startsWith(ACCESS_TOKEN_PREFIX)) {
return { kind: "absent" };
}
let verified: ReturnType<typeof verifyAccessToken>;
try {
verified = verifyAccessToken(bearer);
} catch {
return { kind: "error" };
}
if (!verified) return { kind: "invalid" };
const need = inferRequiredScope(request.method, safePathname(request.url));
if (!scopeSatisfies(verified.scope, need)) {
return { kind: "insufficient", have: verified.scope, need };
}
return { kind: "ok", scope: verified.scope, id: verified.id, name: verified.name };
}
|