Files
Epicnabbo-Catalogus-Updated…/src/lib/foundation/request-context.ts
T
openhands f6ad030c5b Add EpicNext CMS foundation layer and fix critical security gaps
- Create src/lib/foundation/ (860 LOC, 9 files): typed action wrappers,
  DbService with health checks, CSRF validation, safe redirects,
  AsyncLocalStorage request tracing, branded types, reusable Zod schemas
- Migrate moderation.ts and user-settings.ts to foundation patterns
- Fix abuse-guard.ts: bound in-memory Maps with LRU eviction (was unbounded)
- Fix access-guard.ts: separate try/catch per check, log degradation
  instead of blanket fail-open
- Replace raw redirect() calls with safeRedirect() in guard.ts and
  permissions.ts to prevent open-redirect attacks
- Add CSRF validation to api-handler.ts for mutating methods
- Add canonicalizeFormData() utility for FormData input sanitization
2026-07-13 12:03:49 +02:00

54 lines
1.3 KiB
TypeScript

import { AsyncLocalStorage } from "node:async_hooks";
import type { IpAddress, RequestId, UserId } from "./types";
export interface RequestStore {
requestId: RequestId;
ip: IpAddress;
userId: UserId | null;
startedAt: number;
}
const als = new AsyncLocalStorage<RequestStore>();
let counter = 0;
function generateRequestId(): RequestId {
counter = (counter + 1) & 0xffff;
return `${Date.now().toString(36)}-${counter.toString(36)}-${crypto.randomUUID().slice(0, 8)}` as RequestId;
}
export function createStore(ip: IpAddress): RequestStore {
return {
requestId: generateRequestId(),
ip,
userId: null,
startedAt: Date.now(),
};
}
export function runWithStore<T>(store: RequestStore, fn: () => T): T {
return als.run(store, fn);
}
export function getRequestStore(): RequestStore | null {
return als.getStore() ?? null;
}
export function getRequestId(): RequestId {
return als.getStore()?.requestId ?? (generateRequestId());
}
export function getClientIp(): IpAddress {
return als.getStore()?.ip ?? ("0.0.0.0" as IpAddress);
}
export function setContextUserId(userId: UserId): void {
const store = als.getStore();
if (store) store.userId = userId;
}
export function elapsed(): number {
const store = als.getStore();
return store ? Date.now() - store.startedAt : 0;
}