Spaces:
Runtime error
Runtime error
File size: 2,705 Bytes
46252cd | 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 | /**
* Per-request async context, propagated to every log line and audit record produced while handling a
* request. Today it carries the X-Request-ID plus the resolved API key and client IP, so audit log
* rows are stamped with who made the call and from where WITHOUT every controller having to thread
* `@CurrentApiKey()` + `req.ip` into each `auditService.log*()` call (most call sites legitimately
* don't know the key — they fire from deep inside services). Backed by a dedicated AsyncLocalStorage
* instance — independent of the existing plugin/hook ALS instances (multiple ALS instances coexist fine).
*/
import { AsyncLocalStorage } from 'node:async_hooks';
export interface RequestContext {
requestId: string;
/** The API key that authenticated this request, if any (@Public routes have none). */
apiKeyId?: string;
apiKeyName?: string;
/** The real client IP (ProxyAwareThrottlerGuard's notion, honoring TRUSTED_PROXIES). */
ipAddress?: string;
}
const requestContextStorage = new AsyncLocalStorage<RequestContext>();
/** Run `fn` (and every async continuation it starts) with `requestId` as the active context. */
export function runWithRequestId<T>(requestId: string, fn: () => T): T {
return requestContextStorage.run({ requestId }, fn);
}
/** The active request id, or `undefined` when not running inside a request scope. */
export function getRequestId(): string | undefined {
return requestContextStorage.getStore()?.requestId;
}
/**
* Stamp the resolved API key + client IP into the active request context so downstream audit log
* writes (which typically happen inside services without DI access to the key) can attribute the
* action. No-op outside a request scope (e.g. worker/cron), so callers don't need to guard. The
* guard/middleware call this once they've resolved the key; services never need to.
*/
export function setRequestActor(actor: { apiKeyId?: string; apiKeyName?: string; ipAddress?: string }): void {
const store = requestContextStorage.getStore();
if (!store) return; // not in a request scope — nothing to stamp
if (actor.apiKeyId !== undefined) store.apiKeyId = actor.apiKeyId;
if (actor.apiKeyName !== undefined) store.apiKeyName = actor.apiKeyName;
if (actor.ipAddress !== undefined) store.ipAddress = actor.ipAddress;
}
/** The active request's resolved actor (API key + IP), or `undefined` outside a request scope. */
export function getRequestActor(): { apiKeyId?: string; apiKeyName?: string; ipAddress?: string } | undefined {
const store = requestContextStorage.getStore();
if (!store) return undefined;
return { apiKeyId: store.apiKeyId, apiKeyName: store.apiKeyName, ipAddress: store.ipAddress };
}
|