68 lines
1.6 KiB
TypeScript
68 lines
1.6 KiB
TypeScript
import "server-only";
|
|
|
|
import { parseBearerAccessKey } from "@/lib/access-keys/token";
|
|
import {
|
|
authenticateUserAccessKey,
|
|
type AccessKeyPrincipal,
|
|
type AccessKeyScope,
|
|
} from "@/lib/access-keys/service";
|
|
|
|
export class OpenApiAuthError extends Error {
|
|
status: number;
|
|
code: string;
|
|
|
|
constructor(message: string, status: number, code: string) {
|
|
super(message);
|
|
this.name = "OpenApiAuthError";
|
|
this.status = status;
|
|
this.code = code;
|
|
}
|
|
}
|
|
|
|
export async function requireOpenApiPrincipal(
|
|
request: Request,
|
|
requiredScope?: AccessKeyScope
|
|
): Promise<AccessKeyPrincipal> {
|
|
const token = parseBearerAccessKey(request.headers.get("authorization"));
|
|
if (!token) {
|
|
throw new OpenApiAuthError(
|
|
"Missing or invalid Bearer Access Key",
|
|
401,
|
|
"ACCESS_KEY_REQUIRED"
|
|
);
|
|
}
|
|
|
|
const principal = await authenticateUserAccessKey(token);
|
|
if (!principal) {
|
|
throw new OpenApiAuthError(
|
|
"Access Key is invalid, expired, or revoked",
|
|
401,
|
|
"ACCESS_KEY_INVALID"
|
|
);
|
|
}
|
|
|
|
if (requiredScope && !principal.scopes.includes(requiredScope)) {
|
|
throw new OpenApiAuthError(
|
|
`Access Key is missing scope: ${requiredScope}`,
|
|
403,
|
|
"ACCESS_KEY_SCOPE_REQUIRED"
|
|
);
|
|
}
|
|
|
|
return principal;
|
|
}
|
|
|
|
export function openApiErrorResponse(error: unknown) {
|
|
if (error instanceof OpenApiAuthError) {
|
|
return Response.json(
|
|
{ success: false, error: error.message, code: error.code },
|
|
{ status: error.status }
|
|
);
|
|
}
|
|
|
|
return Response.json(
|
|
{ success: false, error: "OpenAPI request failed", code: "OPENAPI_ERROR" },
|
|
{ status: 500 }
|
|
);
|
|
}
|