Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/apps/api/src/auth.ts
T
2026-07-08 16:15:37 +02:00

48 lines
1.6 KiB
TypeScript

import { createHash, timingSafeEqual } from "node:crypto";
import type { ApiConfig } from "@hivegrid/config";
export type ServicePrincipal = {
type: "service-token";
};
export function extractBearerToken(request: Request) {
return request.headers.get("authorization")?.match(/^Bearer\s+(.+)$/i)?.[1];
}
export function extractApiKeyFromRequest(request: Request, body: Record<string, unknown> | undefined) {
if (typeof body?.apiKey === "string" && body.apiKey) {
return body.apiKey;
}
return request.headers.get("x-api-key");
}
export function isValidServiceToken(token: string, configOrToken: Pick<ApiConfig, "apiAccessToken"> | string | undefined) {
const configuredToken = typeof configOrToken === "string" ? configOrToken : configOrToken?.apiAccessToken;
if (!configuredToken || !token) {
return false;
}
// Constant-time comparison: hash both sides to a fixed length first so
// timingSafeEqual never throws on length mismatch and the comparison leaks
// neither the token contents nor its length.
const provided = createHash("sha256").update(token).digest();
const expected = createHash("sha256").update(configuredToken).digest();
return timingSafeEqual(provided, expected);
}
export function createServicePrincipal(): ServicePrincipal {
return {
type: "service-token",
};
}
export function authenticateServiceRequest(request: Request, configOrToken: Pick<ApiConfig, "apiAccessToken"> | string | undefined) {
const token = extractBearerToken(request);
if (!token || !isValidServiceToken(token, configOrToken)) {
return null;
}
return createServicePrincipal();
}