Spaces:
Runtime error
Runtime error
File size: 3,568 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 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | import { isAuthRequired, isDashboardSessionAuthenticated } from "@/shared/utils/apiAuth";
import { createErrorResponse } from "@/lib/api/errorResponse";
import { extractApiKey, isValidApiKey } from "@/sse/services/auth";
import { getApiKeyMetadata } from "@/lib/db/apiKeys";
import { isCliTokenAuthValid } from "@/lib/middleware/cliTokenAuth";
import { evaluateAccessTokenAuth } from "@/server/authz/accessTokenAuth";
import {
MANAGE_SCOPE,
hasManageScope as hasManageScopeShared,
} from "@/shared/constants/managementScopes";
export { MANAGE_SCOPE };
/**
* Check whether any of the supplied scopes authorizes management API access.
*
* Re-exported here for backwards compatibility with existing callers. The
* canonical definition lives in `@/shared/constants/managementScopes`.
*/
export function hasManageScope(scopes: string[] = []): boolean {
return hasManageScopeShared(scopes);
}
export async function requireManagementAuth(request: Request): Promise<Response | null> {
if (!(await isAuthRequired(request))) {
return null;
}
if (await isDashboardSessionAuthenticated(request)) {
return null;
}
// CLI machine-id token allows localhost CLI access without an explicit API key.
if (await isCliTokenAuthValid(request)) {
return null;
}
// Scoped CLI access token (remote mode). Intercepted BEFORE the API-key branch:
// these `oma_` tokens are management/CLI credentials, not inference API keys,
// and would otherwise be rejected by isValidApiKey. Same shared evaluation the
// central managementPolicy uses (no drift). Dashboard JWT, the loopback CLI
// token, and manage-scope API keys remain full-access above/below.
const accessVerdict = evaluateAccessTokenAuth(request);
switch (accessVerdict.kind) {
case "ok":
return null;
case "error":
return createErrorResponse({
status: 503,
message: "Service temporarily unavailable",
type: "server_error",
});
case "invalid":
return createErrorResponse({
status: 401,
message: "Invalid or expired access token",
type: "invalid_request",
});
case "insufficient":
return createErrorResponse({
status: 403,
message: `Access token scope '${accessVerdict.have}' is insufficient; '${accessVerdict.need}' required.`,
type: "invalid_request",
});
case "absent":
break; // no oma_ token → fall through to API-key auth
}
// Management auth never honours a URL-borne credential (header-only) — a token
// in the path/query must not authenticate a management route. See #3300 follow-up.
const apiKey = extractApiKey(request, { allowUrl: false });
if (apiKey) {
let meta: Awaited<ReturnType<typeof getApiKeyMetadata>>;
try {
if (!(await isValidApiKey(apiKey))) {
return createErrorResponse({
status: 403,
message: "Invalid management token",
type: "invalid_request",
});
}
meta = await getApiKeyMetadata(apiKey);
} catch {
return createErrorResponse({
status: 503,
message: "Service temporarily unavailable",
type: "server_error",
});
}
if (meta && hasManageScope(meta.scopes)) return null;
return createErrorResponse({
status: 403,
message: "API key lacks 'manage' scope. Enable it in the API Keys dashboard.",
type: "invalid_request",
});
}
return createErrorResponse({
status: 401,
message: "Authentication required",
type: "invalid_request",
});
}
|