Files
openmaic/OpenMAIC/lib/persistence/server-auth.ts
2026-08-16 14:58:47 +08:00

44 lines
2.0 KiB
TypeScript

/**
* DEVELOPMENT-ONLY authentication for the embedded persistence route.
*
* The token is NOT a secret: NEXT_PUBLIC_PERSISTENCE_TOKEN is compiled into
* the public browser bundle, so it is fully visible to every visitor and
* provides no confidentiality and no user isolation — anyone who can load the
* page can read and write EVERY learner partition and all documents by
* supplying an arbitrary x-learner-key. Its only purpose is to keep unrelated
* network scanners out of a trusted-network endpoint. Suitable only for
* localhost or trusted-network, single-user deployments. Production must
* replace this module with real session verification and derive learner
* identity from server-controlled claims.
*/
import { createHash, timingSafeEqual } from 'node:crypto';
import type { IncomingMessage } from 'node:http';
import type { AssetPrincipal } from '@openmaic/storage';
import type { RuntimeHttpPrincipal } from '@openmaic/storage/server';
type PersistencePrincipal = RuntimeHttpPrincipal & Partial<Pick<AssetPrincipal, 'key'>>;
function singleHeader(value: string | string[] | undefined): string | undefined {
return Array.isArray(value) ? value[0] : value;
}
function secureEqual(left: string, right: string): boolean {
const leftDigest = createHash('sha256').update(left).digest();
const rightDigest = createHash('sha256').update(right).digest();
return timingSafeEqual(leftDigest, rightDigest);
}
export async function authenticatePersistenceRequest(
req: IncomingMessage,
): Promise<PersistencePrincipal | undefined> {
const token = process.env.PERSISTENCE_DEV_TOKEN;
const authorization = singleHeader(req.headers.authorization);
if (!token || !authorization || !secureEqual(authorization, `Bearer ${token}`)) return undefined;
const learnerKey = singleHeader(req.headers['x-learner-key']);
// The authenticated anonymous learner partition is also the required asset
// principal. Without `key`, the asset contract correctly denies all access.
return learnerKey ? { key: learnerKey, learnerKey } : {};
}