mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-11-04 09:35:24 +00:00
45 lines
1.4 KiB
TypeScript
45 lines
1.4 KiB
TypeScript
/**
|
|
* What a scoped API key may reach. Enforced where keys are authenticated, so a
|
|
* limited key cannot step around the gate through /plugin-api or a ws ticket. A
|
|
* key with no scopes has full access, which is every key made before this.
|
|
*/
|
|
|
|
export const API_KEY_SCOPES = ["operations:read", "operations:run"] as const;
|
|
|
|
const MCP_PATH = /^\/plugin-api\/[a-z0-9-]+\/mcp(\/|$)/;
|
|
const OPERATIONS_PATH = /^\/operations(\/|$)/;
|
|
|
|
export function parseKeyScopes(
|
|
raw: string | null | undefined,
|
|
): string[] | null {
|
|
if (!raw) return null;
|
|
try {
|
|
const value = JSON.parse(raw);
|
|
return Array.isArray(value)
|
|
? value.filter((scope): scope is string => typeof scope === "string")
|
|
: [];
|
|
} catch {
|
|
// Unreadable means no access, not full access.
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export function keyScopeAllows(
|
|
scopes: string[] | null,
|
|
method: string,
|
|
path: string,
|
|
): boolean {
|
|
if (scopes === null) return true;
|
|
const clean = path.split("?")[0];
|
|
if (MCP_PATH.test(clean)) {
|
|
return (
|
|
scopes.includes("operations:read") || scopes.includes("operations:run")
|
|
);
|
|
}
|
|
if (!OPERATIONS_PATH.test(clean)) return false;
|
|
if (scopes.includes("operations:run")) return true;
|
|
if (!scopes.includes("operations:read")) return false;
|
|
// The gate still checks each action's risk on a run.
|
|
return method === "GET" || /^\/operations\/[^/]+\/run$/.test(clean);
|
|
}
|