Spaces:
Runtime error
Runtime error
File size: 2,717 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 | import { type AccessScope } from "@/lib/accessTokens/scopes";
/**
* Required-scope inference for remote CLI access tokens.
*
* Policy (owner-confirmed 2026-06-19): infer from HTTP method —
* - GET/HEAD/OPTIONS → read
* - POST/PUT/PATCH/DELETE → write
* with two admin overrides:
* - ADMIN_SCOPE_PREFIXES: admin for ANY method (inherently sensitive surfaces).
* - ADMIN_MUTATION_PREFIXES: admin only when mutating; GET/HEAD stay read so a
* `read` token can still inspect status under these prefixes.
*
* This covers every existing management route with zero per-route edits. A NEW
* mutating route nasce exigindo `write` por padrão (não `admin`) — quando uma
* rota nova for sensível, adicione seu prefixo a uma das listas abaixo.
*
* Note: this only governs the access-token credential path. Dashboard JWT, the
* loopback CLI machine-id token, and manage-scope API keys remain full-access.
* Loopback-only routes that spawn processes are blocked before auth regardless.
*/
/** Sensitive management surfaces — require `admin` for ALL methods. */
export const ADMIN_SCOPE_PREFIXES: readonly string[] = [
"/api/cli/tokens", // access-token management (create/list/revoke)
"/api/oauth", // OAuth authorization flows
"/api/auth", // login / logout / session
"/api/policy", // policy engine
"/api/services", // embedded-service lifecycle (also loopback-blocked)
"/api/mcp", // MCP process surface (also loopback-blocked)
];
/** Require `admin` only for mutating methods; GET/HEAD under these stay `read`. */
export const ADMIN_MUTATION_PREFIXES: readonly string[] = [
"/api/providers", // POST add provider / rotate key = admin; GET status = read
"/api/cli-tools/apply", // writes config onto the host filesystem
];
const READ_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
function matchesPrefix(path: string, prefixes: readonly string[]): boolean {
// Exact match or a true path-segment boundary (`pre/...`). A bare
// `startsWith(pre)` would over-match lookalikes (e.g. "/api/auth" vs
// "/api/authz-inventory"), so it is intentionally NOT used.
return prefixes.some((pre) => path === pre || path.startsWith(pre + "/"));
}
/**
* Determine the minimum access-token scope required to call `method path`.
* Pure + deterministic — safe to unit test exhaustively.
*/
export function inferRequiredScope(method: string, path: string): AccessScope {
const m = (method || "GET").toUpperCase();
const p = path || "/";
if (matchesPrefix(p, ADMIN_SCOPE_PREFIXES)) return "admin";
const isMutation = !READ_METHODS.has(m);
if (isMutation && matchesPrefix(p, ADMIN_MUTATION_PREFIXES)) return "admin";
return isMutation ? "write" : "read";
}
|