@digilogiclabs/platform-core 1.5.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/auth.d.ts CHANGED
@@ -1,1112 +1,175 @@
1
- import { z } from 'zod';
1
+ import { R as RateLimitStore, a as RateLimitRule, b as RateLimitOptions } from './env-DerQ7Da-.js';
2
+ export { as as AllowlistConfig, A as ApiError, k as ApiErrorCode, m as ApiErrorCodeType, o as ApiPaginatedResponse, _ as ApiSecurityConfig, a0 as ApiSecurityContext, n as ApiSuccessResponse, aK as AuditRequest, P as AuthCookiesConfig, W as AuthMethod, C as CommonApiErrors, ay as CommonRateLimits, aj as DateRangeInput, ad as DateRangeSchema, an as DeploymentStage, ah as EmailInput, a6 as EmailSchema, E as EnvValidationConfig, x as EnvValidationResult, ap as FlagDefinition, aq as FlagDefinitions, ao as FlagValue, z as KEYCLOAK_DEFAULT_ROLES, N as KeycloakCallbacksConfig, K as KeycloakConfig, O as KeycloakJwtFields, y as KeycloakTokenSet, al as LoginInput, af as LoginSchema, aF as OpsAuditActor, aH as OpsAuditEvent, aJ as OpsAuditLoggerOptions, aI as OpsAuditRecord, aG as OpsAuditResource, ai as PaginationInput, ac as PaginationSchema, a7 as PasswordSchema, aa as PersonNameSchema, a9 as PhoneSchema, ax as RateLimitCheckResult, Y as RateLimitPreset, S as RedirectCallbackConfig, ar as ResolvedFlags, X as RouteAuditConfig, ak as SearchQueryInput, ae as SearchQuerySchema, $ as SecuritySession, am as SignupInput, ag as SignupSchema, a8 as SlugSchema, aM as StandardAuditActionType, aL as StandardAuditActions, Z as StandardRateLimitPresets, T as TokenRefreshResult, a5 as WrapperPresets, av as buildAllowlist, Q as buildAuthCookies, a4 as buildErrorBody, V as buildKeycloakCallbacks, l as buildPagination, a3 as buildRateLimitHeaders, aD as buildRateLimitResponseHeaders, U as buildRedirectCallback, I as buildTokenRefreshParams, u as checkEnvVars, aA as checkRateLimit, i as classifyError, c as constantTimeEqual, f as containsHtml, d as containsUrls, aQ as createAuditActor, aR as createAuditLogger, au as createFeatureFlags, az as createMemoryRateLimitStore, ab as createSafeTextSchema, at as detectStage, e as escapeHtml, aN as extractAuditIp, aP as extractAuditRequestId, aO as extractAuditUserAgent, a2 as extractClientIp, r as getBoolEnv, h as getCorrelationId, L as getEndSessionEndpoint, w as getEnvSummary, t as getIntEnv, q as getOptionalEnv, aB as getRateLimitStatus, p as getRequiredEnv, J as getTokenEndpoint, G as hasAllRoles, F as hasAnyRole, D as hasRole, aw as isAllowlisted, j as isApiError, H as isTokenExpired, B as parseKeycloakRoles, M as refreshKeycloakToken, aC as resetRateLimitForKey, aE as resolveIdentifier, a1 as resolveRateLimitIdentifier, g as sanitizeApiError, s as stripHtml, v as validateEnvVars } from './env-DerQ7Da-.js';
3
+ import 'zod';
2
4
 
3
5
  /**
4
- * Keycloak Authentication Utilities
6
+ * Redis Rate Limit Store
5
7
  *
6
- * Framework-agnostic helpers for working with Keycloak OIDC tokens.
7
- * Used by Next.js apps (Auth.js middleware + API routes) and .NET services
8
- * share the same role model these helpers ensure consistent behavior.
9
- *
10
- * Edge-runtime compatible: uses atob() for JWT decoding, not Buffer.
11
- */
12
- /**
13
- * Keycloak provider configuration for Auth.js / NextAuth.
14
- * Everything needed to configure the Keycloak OIDC provider.
15
- */
16
- interface KeycloakConfig {
17
- /** Keycloak issuer URL (e.g. https://auth.example.com/realms/my-realm) */
18
- issuer: string;
19
- /** OAuth client ID registered in Keycloak */
20
- clientId: string;
21
- /** OAuth client secret */
22
- clientSecret: string;
23
- }
24
- /**
25
- * Token set returned by Keycloak after authentication or refresh.
26
- */
27
- interface KeycloakTokenSet {
28
- accessToken: string;
29
- refreshToken?: string;
30
- idToken?: string;
31
- /** Expiry as epoch milliseconds */
32
- expiresAt: number;
33
- /** Parsed realm roles (filtered, no Keycloak defaults) */
34
- roles: string[];
35
- }
36
- /**
37
- * Result of a token refresh attempt.
38
- */
39
- type TokenRefreshResult = {
40
- ok: true;
41
- tokens: KeycloakTokenSet;
42
- } | {
43
- ok: false;
44
- error: string;
45
- };
46
- /**
47
- * Default Keycloak roles that should be filtered out when checking
48
- * application-level roles. These are always present and not meaningful
49
- * for authorization decisions.
50
- */
51
- declare const KEYCLOAK_DEFAULT_ROLES: readonly ["offline_access", "uma_authorization"];
52
- /**
53
- * Parse realm roles from a Keycloak JWT access token.
54
- *
55
- * Supports two token formats:
56
- * - `realm_roles` (flat array) — Custom client mapper configuration
57
- * - `realm_access.roles` (nested) — Keycloak default format
58
- *
59
- * Uses atob() for Edge runtime compatibility (not Buffer.from).
60
- * Filters out Keycloak default roles automatically.
61
- *
62
- * @param accessToken - Raw JWT string from Keycloak
63
- * @param additionalDefaultRoles - Extra role names to filter (e.g. realm-specific defaults)
64
- * @returns Array of meaningful role names
8
+ * Production-ready `RateLimitStore` implementation using Redis sorted sets
9
+ * for accurate sliding window rate limiting. Works with ioredis or any
10
+ * Redis client that supports the required commands.
65
11
  *
66
12
  * @example
67
13
  * ```typescript
68
- * const roles = parseKeycloakRoles(account.access_token)
69
- * // ['admin', 'editor']
70
- *
71
- * // With realm-specific defaults
72
- * const roles = parseKeycloakRoles(token, ['default-roles-my-realm'])
73
- * ```
74
- */
75
- declare function parseKeycloakRoles(accessToken: string | undefined | null, additionalDefaultRoles?: string[]): string[];
76
- /**
77
- * Check if a user has a specific role.
78
- *
79
- * @example
80
- * ```typescript
81
- * if (hasRole(session.user.roles, 'admin')) {
82
- * // grant access
83
- * }
84
- * ```
85
- */
86
- declare function hasRole(roles: string[] | undefined | null, role: string): boolean;
87
- /**
88
- * Check if a user has ANY of the specified roles (OR logic).
89
- *
90
- * @example
91
- * ```typescript
92
- * if (hasAnyRole(session.user.roles, ['admin', 'editor'])) {
93
- * // grant access
94
- * }
95
- * ```
96
- */
97
- declare function hasAnyRole(roles: string[] | undefined | null, requiredRoles: string[]): boolean;
98
- /**
99
- * Check if a user has ALL of the specified roles (AND logic).
100
- *
101
- * @example
102
- * ```typescript
103
- * if (hasAllRoles(session.user.roles, ['admin', 'billing'])) {
104
- * // grant access to billing admin
105
- * }
106
- * ```
107
- */
108
- declare function hasAllRoles(roles: string[] | undefined | null, requiredRoles: string[]): boolean;
109
- /**
110
- * Check if a token needs refreshing.
111
- *
112
- * @param expiresAt - Token expiry as epoch milliseconds
113
- * @param bufferMs - Refresh this many ms before actual expiry (default: 60s)
114
- * @returns true if the token should be refreshed
115
- */
116
- declare function isTokenExpired(expiresAt: number | undefined | null, bufferMs?: number): boolean;
117
- /**
118
- * Build the URLSearchParams for a Keycloak token refresh request.
119
- *
120
- * This is the body for POST to `{issuer}/protocol/openid-connect/token`.
121
- * Framework-agnostic — use with fetch(), axios, or any HTTP client.
122
- *
123
- * @example
124
- * ```typescript
125
- * const params = buildTokenRefreshParams(config, refreshToken)
126
- * const response = await fetch(`${config.issuer}/protocol/openid-connect/token`, {
127
- * method: 'POST',
128
- * headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
129
- * body: params,
130
- * })
131
- * ```
132
- */
133
- declare function buildTokenRefreshParams(config: KeycloakConfig, refreshToken: string): URLSearchParams;
134
- /**
135
- * Get the Keycloak token endpoint URL for a given issuer.
136
- */
137
- declare function getTokenEndpoint(issuer: string): string;
138
- /**
139
- * Get the Keycloak end session (logout) endpoint URL.
140
- */
141
- declare function getEndSessionEndpoint(issuer: string): string;
142
- /**
143
- * Perform a token refresh against Keycloak and parse the result.
14
+ * import Redis from 'ioredis'
15
+ * import {
16
+ * createRedisRateLimitStore,
17
+ * checkRateLimit,
18
+ * CommonRateLimits,
19
+ * } from '@digilogiclabs/platform-core/auth'
144
20
  *
145
- * Returns a discriminated union — check `result.ok` before accessing tokens.
146
- * Automatically parses roles from the new access token.
21
+ * const redis = new Redis(process.env.REDIS_URL)
22
+ * const store = createRedisRateLimitStore(redis, { keyPrefix: 'myapp:' })
147
23
  *
148
- * @example
149
- * ```typescript
150
- * const result = await refreshKeycloakToken(config, currentRefreshToken)
151
- * if (result.ok) {
152
- * token.accessToken = result.tokens.accessToken
153
- * token.roles = result.tokens.roles
154
- * } else {
155
- * // Force re-login
156
- * token.error = 'RefreshTokenError'
157
- * }
24
+ * const result = await checkRateLimit('api-call', 'user:123', CommonRateLimits.apiGeneral, { store })
158
25
  * ```
159
26
  */
160
- declare function refreshKeycloakToken(config: KeycloakConfig, refreshToken: string, additionalDefaultRoles?: string[]): Promise<TokenRefreshResult>;
161
27
 
162
28
  /**
163
- * Next.js Auth.js + Keycloak Configuration Builder
164
- *
165
- * Generates Auth.js callbacks for Keycloak OIDC integration.
166
- * Handles JWT token storage, role parsing, token refresh, and session mapping.
167
- *
168
- * Edge-runtime compatible: uses only atob() and fetch(), no Node.js-only APIs.
169
- *
170
- * @example
171
- * ```typescript
172
- * // auth.config.ts (Edge-compatible)
173
- * import { buildKeycloakCallbacks } from '@digilogiclabs/platform-core'
174
- * import Keycloak from 'next-auth/providers/keycloak'
175
- *
176
- * const callbacks = buildKeycloakCallbacks({
177
- * issuer: process.env.AUTH_KEYCLOAK_ISSUER!,
178
- * clientId: process.env.AUTH_KEYCLOAK_ID!,
179
- * clientSecret: process.env.AUTH_KEYCLOAK_SECRET!,
180
- * defaultRoles: ['default-roles-my-realm'],
181
- * })
29
+ * Minimal Redis client interface.
182
30
  *
183
- * export const authConfig = {
184
- * providers: [Keycloak({ ... })],
185
- * session: { strategy: 'jwt' },
186
- * callbacks,
187
- * }
188
- * ```
31
+ * Compatible with ioredis, node-redis v4, and @upstash/redis.
32
+ * Only the commands used by the rate limiter are required.
189
33
  */
190
- interface KeycloakCallbacksConfig {
191
- /** Keycloak issuer URL */
192
- issuer: string;
193
- /** OAuth client ID */
194
- clientId: string;
195
- /** OAuth client secret */
196
- clientSecret: string;
197
- /** Realm-specific default roles to filter (e.g. ['default-roles-my-realm']) */
198
- defaultRoles?: string[];
199
- /** Enable debug logging (default: NODE_ENV === 'development') */
200
- debug?: boolean;
34
+ interface RedisRateLimitClient {
35
+ zremrangebyscore(key: string, min: number | string, max: number | string): Promise<number>;
36
+ zadd(key: string, score: number, member: string): Promise<number>;
37
+ zcard(key: string): Promise<number>;
38
+ expire(key: string, seconds: number): Promise<number>;
39
+ get(key: string): Promise<string | null>;
40
+ ttl(key: string): Promise<number>;
41
+ setex(key: string, seconds: number, value: string): Promise<string>;
42
+ del(...keys: string[]): Promise<number>;
201
43
  }
202
- /**
203
- * Extended JWT token shape used by the Keycloak callbacks.
204
- * These fields are added to the Auth.js JWT token during sign-in and refresh.
205
- */
206
- interface KeycloakJwtFields {
207
- id?: string;
208
- accessToken?: string;
209
- refreshToken?: string;
210
- idToken?: string;
211
- accessTokenExpires?: number;
212
- roles?: string[];
213
- error?: string;
214
- }
215
- interface AuthCookiesConfig {
216
- /** Production cookie domain (e.g. '.digilogiclabs.com'). Omit for default domain. */
217
- domain?: string;
218
- /** Include session token cookie config (default: true) */
219
- sessionToken?: boolean;
220
- /** Include callback URL cookie config (default: true) */
221
- callbackUrl?: boolean;
44
+ interface RedisRateLimitStoreOptions {
45
+ /** Prefix for all Redis keys (e.g., 'myapp:') */
46
+ keyPrefix?: string;
222
47
  }
223
48
  /**
224
- * Build Auth.js cookie configuration for cross-domain OIDC.
225
- *
226
- * Handles the common pattern of configuring cookies to work across
227
- * www and non-www domains (e.g. digilogiclabs.com and www.digilogiclabs.com).
49
+ * Create a Redis-backed rate limit store using sorted sets.
228
50
  *
229
- * PKCE and state cookies are always included for OAuth security.
230
- * Session token and callback URL cookies are included by default.
51
+ * Uses the sliding window algorithm:
52
+ * - Each request adds a timestamped entry to a sorted set
53
+ * - Old entries (outside the window) are pruned on each check
54
+ * - The set cardinality gives the request count
231
55
  *
232
- * @example
233
- * ```typescript
234
- * import { buildAuthCookies } from '@digilogiclabs/platform-core'
235
- *
236
- * export const authConfig = {
237
- * cookies: buildAuthCookies({ domain: '.digilogiclabs.com' }),
238
- * // ...
239
- * }
240
- * ```
56
+ * Blocks use simple key-value with TTL.
241
57
  */
242
- declare function buildAuthCookies(config?: AuthCookiesConfig): Record<string, {
243
- name: string;
244
- options: {
245
- httpOnly: boolean;
246
- sameSite: "lax";
247
- path: string;
248
- secure: boolean;
249
- domain: string | undefined;
250
- };
251
- }>;
252
- interface RedirectCallbackConfig {
253
- /** Allow www variant redirects (e.g. www.example.com ↔ example.com) */
254
- allowWwwVariant?: boolean;
255
- }
256
- /**
257
- * Build a standard Auth.js redirect callback.
258
- *
259
- * Handles common redirect patterns:
260
- * - Relative URLs → prefixed with baseUrl
261
- * - Same-origin URLs → allowed
262
- * - www variant URLs → optionally allowed
263
- * - Everything else → baseUrl
264
- *
265
- * @example
266
- * ```typescript
267
- * import { buildRedirectCallback } from '@digilogiclabs/platform-core'
268
- *
269
- * export const authConfig = {
270
- * callbacks: {
271
- * redirect: buildRedirectCallback({ allowWwwVariant: true }),
272
- * },
273
- * }
274
- * ```
275
- */
276
- declare function buildRedirectCallback(config?: RedirectCallbackConfig): ({ url, baseUrl }: {
277
- url: string;
278
- baseUrl: string;
279
- }) => Promise<string>;
280
- /**
281
- * Build Auth.js JWT and session callbacks for Keycloak.
282
- *
283
- * Returns an object with `jwt` and `session` callbacks that handle:
284
- * - Storing Keycloak tokens on initial sign-in
285
- * - Parsing realm roles from the access token
286
- * - Automatic token refresh when expired
287
- * - Mapping tokens/roles to the session object
288
- *
289
- * The callbacks are Edge-runtime compatible.
290
- */
291
- declare function buildKeycloakCallbacks(config: KeycloakCallbacksConfig): {
292
- /**
293
- * JWT callback — stores Keycloak tokens and handles refresh.
294
- *
295
- * Compatible with Auth.js v5 JWT callback signature.
296
- */
297
- jwt({ token, user, account, }: {
298
- token: Record<string, unknown>;
299
- user?: Record<string, unknown>;
300
- account?: Record<string, unknown> | null;
301
- }): Promise<Record<string, unknown>>;
302
- /**
303
- * Session callback — maps JWT fields to the session object.
304
- *
305
- * Compatible with Auth.js v5 session callback signature.
306
- */
307
- session({ session, token, }: {
308
- session: Record<string, unknown>;
309
- token: Record<string, unknown>;
310
- }): Promise<Record<string, unknown>>;
311
- };
58
+ declare function createRedisRateLimitStore(redis: RedisRateLimitClient, options?: RedisRateLimitStoreOptions): RateLimitStore;
312
59
 
313
60
  /**
314
- * API Security Types & Helpers
61
+ * Next.js API Route Helpers
315
62
  *
316
- * Framework-agnostic types and utilities for building composable
317
- * API security wrappers. These define the shared contract that
318
- * framework-specific implementations (Next.js, Express, .NET) follow.
63
+ * Shared utilities for Next.js API routes that all apps need.
64
+ * These wrap platform-core's framework-agnostic primitives into
65
+ * Next.js-specific patterns (NextResponse, NextRequest).
319
66
  *
320
- * The actual wrappers (withPublicApi, withAdminApi, etc.) live in each
321
- * app because they depend on framework-specific request/response types.
322
- * This module provides the shared types and logic they all use.
323
- */
324
- /**
325
- * How a request was authenticated.
326
- * Used by audit logging and authorization decisions.
327
- */
328
- type AuthMethod = "session" | "bearer_token" | "api_key" | "webhook_signature" | "cron_secret" | "none";
329
- /**
330
- * Audit configuration for a secured route.
331
- * Tells the security wrapper what to log.
332
- */
333
- interface RouteAuditConfig {
334
- /** The action being performed (e.g. 'game.server.create') */
335
- action: string;
336
- /** The resource type being acted on (e.g. 'game_server') */
337
- resourceType: string;
338
- }
339
- /**
340
- * Rate limit preset configuration.
341
- * Matches the structure used by all apps.
342
- */
343
- interface RateLimitPreset {
344
- /** Maximum requests allowed in the window */
345
- limit: number;
346
- /** Window duration in seconds */
347
- windowSeconds: number;
348
- /** Higher limit for authenticated users */
349
- authenticatedLimit?: number;
350
- /** Block duration when limit exceeded (seconds) */
351
- blockDurationSeconds?: number;
352
- }
353
- /**
354
- * Standard rate limit presets shared across all apps.
355
- *
356
- * Apps can extend with their own domain-specific presets:
357
- * ```typescript
358
- * const APP_RATE_LIMITS = {
359
- * ...StandardRateLimitPresets,
360
- * gameServerCreate: { limit: 5, windowSeconds: 3600, blockDurationSeconds: 3600 },
361
- * }
362
- * ```
363
- */
364
- declare const StandardRateLimitPresets: {
365
- /** General API: 100/min, 200/min authenticated */
366
- readonly apiGeneral: {
367
- readonly limit: 100;
368
- readonly windowSeconds: 60;
369
- readonly authenticatedLimit: 200;
370
- };
371
- /** Admin operations: 100/min (admins are trusted) */
372
- readonly adminAction: {
373
- readonly limit: 100;
374
- readonly windowSeconds: 60;
375
- };
376
- /** AI/expensive operations: 20/hour, 50/hour authenticated */
377
- readonly aiRequest: {
378
- readonly limit: 20;
379
- readonly windowSeconds: 3600;
380
- readonly authenticatedLimit: 50;
381
- };
382
- /** Auth attempts: 5/15min with 15min block */
383
- readonly authAttempt: {
384
- readonly limit: 5;
385
- readonly windowSeconds: 900;
386
- readonly blockDurationSeconds: 900;
387
- };
388
- /** Contact/public forms: 10/hour */
389
- readonly publicForm: {
390
- readonly limit: 10;
391
- readonly windowSeconds: 3600;
392
- readonly blockDurationSeconds: 1800;
393
- };
394
- /** Checkout/billing: 10/hour with 1hr block */
395
- readonly checkout: {
396
- readonly limit: 10;
397
- readonly windowSeconds: 3600;
398
- readonly blockDurationSeconds: 3600;
399
- };
400
- };
401
- /**
402
- * Configuration for a secured API handler.
403
- *
404
- * This is the framework-agnostic config shape. Each framework's
405
- * wrapper (withPublicApi, withAdminApi, etc.) maps to this structure.
406
- */
407
- interface ApiSecurityConfig {
408
- /** Whether authentication is required */
409
- requireAuth: boolean;
410
- /** Whether admin role is required */
411
- requireAdmin: boolean;
412
- /** Required roles (user must have at least one) */
413
- requireRoles?: string[];
414
- /** Allow legacy bearer token as alternative to session auth */
415
- allowBearerToken?: boolean;
416
- /** Rate limit preset name or custom config */
417
- rateLimit?: string | RateLimitPreset;
418
- /** Audit logging config */
419
- audit?: RouteAuditConfig;
420
- /** Human-readable operation name for logging */
421
- operation?: string;
422
- /** Skip rate limiting */
423
- skipRateLimit?: boolean;
424
- /** Skip audit logging */
425
- skipAudit?: boolean;
426
- }
427
- /**
428
- * Minimal session shape that all auth systems provide.
429
- * Maps to Auth.js Session, .NET ClaimsPrincipal, etc.
430
- */
431
- interface SecuritySession {
432
- user: {
433
- id: string;
434
- email?: string | null;
435
- name?: string | null;
436
- roles?: string[];
437
- };
438
- }
439
- /**
440
- * Context available to secured route handlers after all security
441
- * checks have passed. Framework wrappers extend this with their
442
- * own fields (e.g. NextRequest, validated body, etc.).
443
- */
444
- interface ApiSecurityContext {
445
- /** Authenticated session (null for public routes or token auth) */
446
- session: SecuritySession | null;
447
- /** How the request was authenticated */
448
- authMethod: AuthMethod;
449
- /** Whether the user has admin privileges */
450
- isAdmin: boolean;
451
- /** Request correlation ID */
452
- requestId: string;
453
- }
454
- /**
455
- * Determine the appropriate rate limit key for a request.
456
- *
457
- * Priority: user ID > email > IP address.
458
- * Authenticated users get their own bucket (and potentially higher limits).
459
- *
460
- * @param session - Current session (if any)
461
- * @param clientIp - Client IP address
462
- * @returns { identifier, isAuthenticated }
463
- */
464
- declare function resolveRateLimitIdentifier(session: SecuritySession | null, clientIp: string): {
465
- identifier: string;
466
- isAuthenticated: boolean;
467
- };
468
- /**
469
- * Extract the client IP from standard proxy headers.
470
- *
471
- * Checks (in order): CF-Connecting-IP, X-Real-IP, X-Forwarded-For.
472
- * Use this to ensure consistent IP extraction across all apps.
473
- *
474
- * @param getHeader - Header getter function
475
- * @returns Client IP or 'unknown'
476
- */
477
- declare function extractClientIp(getHeader: (name: string) => string | null | undefined): string;
478
- /**
479
- * Build the standard rate limit response headers.
480
- *
481
- * @returns Headers object with X-RateLimit-* headers
482
- */
483
- declare function buildRateLimitHeaders(limit: number, remaining: number, resetAtMs: number): Record<string, string>;
484
- /**
485
- * Build a standard error response body.
486
- * Ensures consistent error shape across all apps.
487
- */
488
- declare function buildErrorBody(error: string, extra?: Record<string, unknown>): Record<string, unknown>;
489
- /**
490
- * Pre-built security configurations for common route types.
491
- *
492
- * Use these as the base for framework-specific wrappers:
67
+ * @example
493
68
  * ```typescript
494
- * // In your app's api-security.ts
495
- * export function withPublicApi(config, handler) {
496
- * return createSecureHandler({
497
- * ...WrapperPresets.public,
498
- * ...config,
499
- * }, handler)
69
+ * import {
70
+ * enforceRateLimit,
71
+ * zodErrorResponse,
72
+ * errorResponse,
73
+ * extractBearerToken,
74
+ * isValidBearerToken,
75
+ * } from '@digilogiclabs/platform-core/auth'
76
+ *
77
+ * export async function POST(request: NextRequest) {
78
+ * const rl = await enforceRateLimit(request, 'submit', CommonRateLimits.publicForm)
79
+ * if (rl) return rl
80
+ *
81
+ * const parsed = schema.safeParse(await request.json())
82
+ * if (!parsed.success) return zodErrorResponse(parsed.error)
83
+ *
84
+ * try { ... }
85
+ * catch (error) { return errorResponse(error) }
500
86
  * }
501
87
  * ```
502
88
  */
503
- declare const WrapperPresets: {
504
- /** Public route: no auth, rate limited */
505
- readonly public: {
506
- readonly requireAuth: false;
507
- readonly requireAdmin: false;
508
- readonly rateLimit: "apiGeneral";
509
- };
510
- /** Authenticated route: requires session */
511
- readonly authenticated: {
512
- readonly requireAuth: true;
513
- readonly requireAdmin: false;
514
- readonly rateLimit: "apiGeneral";
515
- };
516
- /** Admin route: requires session with admin role */
517
- readonly admin: {
518
- readonly requireAuth: true;
519
- readonly requireAdmin: true;
520
- readonly rateLimit: "adminAction";
521
- };
522
- /** Legacy admin: accepts session OR bearer token */
523
- readonly legacyAdmin: {
524
- readonly requireAuth: true;
525
- readonly requireAdmin: true;
526
- readonly allowBearerToken: true;
527
- readonly rateLimit: "adminAction";
528
- };
529
- /** AI/expensive: requires auth, strict rate limit */
530
- readonly ai: {
531
- readonly requireAuth: true;
532
- readonly requireAdmin: false;
533
- readonly rateLimit: "aiRequest";
534
- };
535
- /** Cron: no rate limit, admin-level access */
536
- readonly cron: {
537
- readonly requireAuth: true;
538
- readonly requireAdmin: false;
539
- readonly skipRateLimit: true;
540
- readonly skipAudit: false;
541
- };
542
- };
543
-
544
- /**
545
- * Common Validation Schemas
546
- *
547
- * Reusable Zod schemas for request validation across all apps.
548
- * These are the building blocks — apps compose them into
549
- * route-specific schemas for their own domain logic.
550
- *
551
- * Requires `zod` as a peer dependency (already in platform-core).
552
- */
553
89
 
554
- /** Validated, normalized email address */
555
- declare const EmailSchema: z.ZodString;
556
- /** Password with minimum security requirements */
557
- declare const PasswordSchema: z.ZodString;
558
- /** URL-safe slug (lowercase alphanumeric + hyphens) */
559
- declare const SlugSchema: z.ZodString;
560
- /** Phone number (international format, flexible) */
561
- declare const PhoneSchema: z.ZodString;
562
- /** Human name (letters, spaces, hyphens, apostrophes) */
563
- declare const PersonNameSchema: z.ZodString;
564
90
  /**
565
- * Create a text schema that blocks HTML tags and links.
566
- * Use for user-facing text fields (contact forms, comments, etc.)
91
+ * Enforce rate limiting on a Next.js API request.
567
92
  *
568
- * @param options - Customize min/max length and error messages
93
+ * Returns a 429 NextResponse if the limit is exceeded, or `null` if allowed.
94
+ * Apps use this at the top of route handlers for early rejection.
569
95
  *
570
96
  * @example
571
97
  * ```typescript
572
- * const MessageSchema = createSafeTextSchema({ min: 10, max: 1000 })
573
- * const CommentSchema = createSafeTextSchema({ max: 500, allowUrls: true })
98
+ * const rateLimited = await enforceRateLimit(request, 'sync', CommonRateLimits.adminAction)
99
+ * if (rateLimited) return rateLimited
574
100
  * ```
575
101
  */
576
- declare function createSafeTextSchema(options?: {
577
- min?: number;
578
- max?: number;
579
- allowHtml?: boolean;
580
- allowUrls?: boolean;
581
- fieldName?: string;
582
- }): z.ZodType<string, z.ZodTypeDef, string>;
583
- /** Standard pagination query parameters */
584
- declare const PaginationSchema: z.ZodObject<{
585
- page: z.ZodDefault<z.ZodNumber>;
586
- limit: z.ZodDefault<z.ZodNumber>;
587
- sortBy: z.ZodOptional<z.ZodString>;
588
- sortOrder: z.ZodDefault<z.ZodEnum<["asc", "desc"]>>;
589
- }, "strip", z.ZodTypeAny, {
590
- limit: number;
591
- page: number;
592
- sortOrder: "asc" | "desc";
593
- sortBy?: string | undefined;
594
- }, {
595
- sortBy?: string | undefined;
596
- limit?: number | undefined;
597
- page?: number | undefined;
598
- sortOrder?: "asc" | "desc" | undefined;
599
- }>;
600
- /** Date range filter (ISO 8601 datetime strings) */
601
- declare const DateRangeSchema: z.ZodEffects<z.ZodObject<{
602
- startDate: z.ZodString;
603
- endDate: z.ZodString;
604
- }, "strip", z.ZodTypeAny, {
605
- startDate: string;
606
- endDate: string;
607
- }, {
608
- startDate: string;
609
- endDate: string;
610
- }>, {
611
- startDate: string;
612
- endDate: string;
613
- }, {
614
- startDate: string;
615
- endDate: string;
616
- }>;
617
- /** Search query with optional filters */
618
- declare const SearchQuerySchema: z.ZodObject<{
619
- query: z.ZodString;
620
- page: z.ZodDefault<z.ZodNumber>;
621
- limit: z.ZodDefault<z.ZodNumber>;
622
- }, "strip", z.ZodTypeAny, {
623
- query: string;
624
- limit: number;
625
- page: number;
626
- }, {
627
- query: string;
628
- limit?: number | undefined;
629
- page?: number | undefined;
630
- }>;
631
- /** Login credentials */
632
- declare const LoginSchema: z.ZodObject<{
633
- email: z.ZodString;
634
- password: z.ZodString;
635
- }, "strip", z.ZodTypeAny, {
636
- email: string;
637
- password: string;
638
- }, {
639
- email: string;
640
- password: string;
641
- }>;
642
- /** Signup with optional name */
643
- declare const SignupSchema: z.ZodObject<{
644
- email: z.ZodString;
645
- password: z.ZodString;
646
- name: z.ZodOptional<z.ZodString>;
647
- }, "strip", z.ZodTypeAny, {
648
- email: string;
649
- password: string;
650
- name?: string | undefined;
651
- }, {
652
- email: string;
653
- password: string;
654
- name?: string | undefined;
655
- }>;
656
- type EmailInput = z.infer<typeof EmailSchema>;
657
- type PaginationInput = z.infer<typeof PaginationSchema>;
658
- type DateRangeInput = z.infer<typeof DateRangeSchema>;
659
- type SearchQueryInput = z.infer<typeof SearchQuerySchema>;
660
- type LoginInput = z.infer<typeof LoginSchema>;
661
- type SignupInput = z.infer<typeof SignupSchema>;
662
-
663
- /**
664
- * Feature Flag System
665
- *
666
- * Generic, type-safe feature flag builder for staged rollout.
667
- * Each app defines its own flags; this module provides the
668
- * infrastructure for evaluating them by environment.
669
- *
670
- * @example
671
- * ```typescript
672
- * // Define your app's flags
673
- * const flags = createFeatureFlags({
674
- * STRIPE_PAYMENTS: {
675
- * development: true,
676
- * staging: true,
677
- * production: { envVar: 'ENABLE_PAYMENTS' },
678
- * },
679
- * PUBLIC_SIGNUP: {
680
- * development: true,
681
- * staging: false,
682
- * production: { envVar: 'ENABLE_PUBLIC_SIGNUP' },
683
- * },
684
- * AI_FEATURES: {
685
- * development: true,
686
- * staging: { envVar: 'ENABLE_AI' },
687
- * production: false,
688
- * },
689
- * })
690
- *
691
- * // Evaluate at runtime
692
- * const resolved = flags.resolve() // reads NODE_ENV + DEPLOYMENT_STAGE
693
- * if (resolved.STRIPE_PAYMENTS) { ... }
694
- *
695
- * // Check a single flag
696
- * if (flags.isEnabled('AI_FEATURES')) { ... }
697
- * ```
698
- */
699
- type DeploymentStage = "development" | "staging" | "preview" | "production";
700
- /**
701
- * How a flag is resolved in a given environment.
702
- * - `true` / `false` — hardcoded on/off
703
- * - `{ envVar: string }` — read from environment variable (truthy = "true")
704
- * - `{ envVar: string, default: boolean }` — with fallback
705
- */
706
- type FlagValue = boolean | {
707
- envVar: string;
708
- default?: boolean;
709
- };
710
- /**
711
- * Flag definition across deployment stages.
712
- */
713
- interface FlagDefinition {
714
- development: FlagValue;
715
- staging: FlagValue;
716
- production: FlagValue;
717
- }
718
- /**
719
- * Map of flag name to definition.
720
- */
721
- type FlagDefinitions<T extends string = string> = Record<T, FlagDefinition>;
722
- /**
723
- * Resolved flags — all booleans.
724
- */
725
- type ResolvedFlags<T extends string = string> = Record<T, boolean>;
726
- /**
727
- * Allowlist configuration for tester-gated access.
728
- */
729
- interface AllowlistConfig {
730
- /** Environment variable containing comma-separated emails */
731
- envVar?: string;
732
- /** Hardcoded fallback emails */
733
- fallback?: string[];
734
- }
735
- /**
736
- * Detect the current deployment stage from environment variables.
737
- */
738
- declare function detectStage(): DeploymentStage;
739
- /**
740
- * Create a type-safe feature flag system.
741
- *
742
- * @param definitions - Flag definitions per environment
743
- * @returns Feature flag accessor with resolve() and isEnabled()
744
- */
745
- declare function createFeatureFlags<T extends string>(definitions: FlagDefinitions<T>): {
746
- /**
747
- * Resolve all flags for the current environment.
748
- * Call this once at startup or per-request for dynamic flags.
749
- */
750
- resolve(stage?: DeploymentStage): ResolvedFlags<T>;
751
- /**
752
- * Check if a single flag is enabled.
753
- */
754
- isEnabled(flag: T, stage?: DeploymentStage): boolean;
755
- /**
756
- * Get the flag definitions (for introspection/admin UI).
757
- */
758
- definitions: FlagDefinitions<T>;
759
- };
760
- /**
761
- * Build an allowlist from environment variable + fallback emails.
762
- *
763
- * @example
764
- * ```typescript
765
- * const testers = buildAllowlist({
766
- * envVar: 'ADMIN_EMAILS',
767
- * fallback: ['admin@example.com'],
768
- * })
769
- * if (testers.includes(userEmail)) { ... }
770
- * ```
771
- */
772
- declare function buildAllowlist(config: AllowlistConfig): string[];
773
- /**
774
- * Check if an email is in the allowlist (case-insensitive).
775
- */
776
- declare function isAllowlisted(email: string, allowlist: string[]): boolean;
777
-
102
+ declare function enforceRateLimit(request: {
103
+ headers: {
104
+ get(name: string): string | null;
105
+ };
106
+ }, operation: string, rule: RateLimitRule, options?: {
107
+ /** Override identifier (default: extracted from x-forwarded-for) */
108
+ identifier?: string;
109
+ /** User ID for per-user limiting */
110
+ userId?: string;
111
+ /** Rate limit store and options */
112
+ rateLimitOptions?: RateLimitOptions;
113
+ }): Promise<Response | null>;
778
114
  /**
779
- * Standalone Rate Limiter
115
+ * Convert any error into a structured JSON response.
780
116
  *
781
- * Framework-agnostic sliding window rate limiter that works with
782
- * any storage backend (Redis, memory, etc.). Apps provide a storage
783
- * adapter; this module handles the algorithm and types.
117
+ * Uses platform-core's `classifyError()` to handle ApiError,
118
+ * Zod errors, PostgreSQL errors, and generic errors consistently.
784
119
  *
785
120
  * @example
786
121
  * ```typescript
787
- * import {
788
- * checkRateLimit,
789
- * createMemoryRateLimitStore,
790
- * CommonRateLimits,
791
- * } from '@digilogiclabs/platform-core'
792
- *
793
- * const store = createMemoryRateLimitStore()
794
- *
795
- * const result = await checkRateLimit('api-call', 'user:123', CommonRateLimits.apiGeneral, { store })
796
- * if (!result.allowed) {
797
- * // Return 429 with result.retryAfterSeconds
122
+ * catch (error) {
123
+ * return errorResponse(error)
798
124
  * }
799
125
  * ```
800
126
  */
801
- /** Configuration for a rate limit rule */
802
- interface RateLimitRule {
803
- /** Maximum requests allowed in the window */
804
- limit: number;
805
- /** Time window in seconds */
806
- windowSeconds: number;
807
- /** Different limit for authenticated users (optional) */
808
- authenticatedLimit?: number;
809
- /** Block duration in seconds when limit is exceeded (optional) */
810
- blockDurationSeconds?: number;
811
- }
812
- /** Result of a rate limit check */
813
- interface RateLimitCheckResult {
814
- /** Whether the request is allowed */
815
- allowed: boolean;
816
- /** Remaining requests in the current window */
817
- remaining: number;
818
- /** Unix timestamp (ms) when the window resets */
819
- resetAt: number;
820
- /** Current request count in the window */
821
- current: number;
822
- /** The limit that was applied */
823
- limit: number;
824
- /** Seconds until retry is allowed (for 429 Retry-After header) */
825
- retryAfterSeconds: number;
826
- }
127
+ declare function errorResponse(error: unknown, options?: {
128
+ isDevelopment?: boolean;
129
+ }): Response;
827
130
  /**
828
- * Storage backend for rate limiting.
131
+ * Convert a Zod validation error into a user-friendly 400 response.
829
132
  *
830
- * Implementations should support sorted-set-like semantics for
831
- * accurate sliding window rate limiting.
832
- */
833
- interface RateLimitStore {
834
- /**
835
- * Add an entry to the sliding window and return the current count.
836
- * Should atomically: remove entries older than windowStart,
837
- * add the new entry, and return the total count.
838
- */
839
- increment(key: string, windowMs: number, now: number): Promise<{
840
- count: number;
841
- }>;
842
- /** Check if a key is blocked and return remaining TTL */
843
- isBlocked(key: string): Promise<{
844
- blocked: boolean;
845
- ttlMs: number;
846
- }>;
847
- /** Set a temporary block on a key */
848
- setBlock(key: string, durationSeconds: number): Promise<void>;
849
- /** Remove all entries for a key (for reset/testing) */
850
- reset(key: string): Promise<void>;
851
- }
852
- /** Options for checkRateLimit */
853
- interface RateLimitOptions {
854
- /** Storage backend (defaults to in-memory if not provided) */
855
- store?: RateLimitStore;
856
- /** Whether the user is authenticated (uses authenticatedLimit if available) */
857
- isAuthenticated?: boolean;
858
- /** Logger for warnings/errors (optional) */
859
- logger?: {
860
- warn: (msg: string, meta?: Record<string, unknown>) => void;
861
- error: (msg: string, meta?: Record<string, unknown>) => void;
862
- };
863
- }
864
- /**
865
- * Common rate limit rules for typical operations.
866
- * Apps can extend with domain-specific presets.
133
+ * Extracts the first issue and returns a clear error message
134
+ * with the field path (e.g., "email: Invalid email").
867
135
  *
868
136
  * @example
869
137
  * ```typescript
870
- * const APP_LIMITS = {
871
- * ...CommonRateLimits,
872
- * serverCreate: { limit: 5, windowSeconds: 3600, blockDurationSeconds: 3600 },
873
- * }
138
+ * const parsed = schema.safeParse(await request.json())
139
+ * if (!parsed.success) return zodErrorResponse(parsed.error)
874
140
  * ```
875
141
  */
876
- declare const CommonRateLimits: {
877
- /** General API: 100/min, 200/min authenticated */
878
- readonly apiGeneral: {
879
- readonly limit: 100;
880
- readonly windowSeconds: 60;
881
- readonly authenticatedLimit: 200;
882
- };
883
- /** Admin actions: 100/min */
884
- readonly adminAction: {
885
- readonly limit: 100;
886
- readonly windowSeconds: 60;
887
- };
888
- /** Auth attempts: 10/15min with 30min block */
889
- readonly authAttempt: {
890
- readonly limit: 10;
891
- readonly windowSeconds: 900;
892
- readonly blockDurationSeconds: 1800;
893
- };
894
- /** AI/expensive requests: 20/hour, 50/hour authenticated */
895
- readonly aiRequest: {
896
- readonly limit: 20;
897
- readonly windowSeconds: 3600;
898
- readonly authenticatedLimit: 50;
899
- };
900
- /** Public form submissions: 5/hour with 1hr block */
901
- readonly publicForm: {
902
- readonly limit: 5;
903
- readonly windowSeconds: 3600;
904
- readonly blockDurationSeconds: 3600;
905
- };
906
- /** Checkout/billing: 10/hour with 1hr block */
907
- readonly checkout: {
908
- readonly limit: 10;
909
- readonly windowSeconds: 3600;
910
- readonly blockDurationSeconds: 3600;
911
- };
912
- };
913
- /**
914
- * In-memory rate limit store for testing and graceful degradation.
915
- * Not suitable for multi-process or distributed environments.
916
- */
917
- declare function createMemoryRateLimitStore(): RateLimitStore;
918
- /**
919
- * Check rate limit for an operation.
920
- *
921
- * @param operation - Name of the operation (e.g., 'server-create', 'api-call')
922
- * @param identifier - Who is making the request (e.g., 'user:123', 'ip:1.2.3.4')
923
- * @param rule - Rate limit configuration
924
- * @param options - Storage, auth status, logger
925
- * @returns Rate limit check result
926
- */
927
- declare function checkRateLimit(operation: string, identifier: string, rule: RateLimitRule, options?: RateLimitOptions): Promise<RateLimitCheckResult>;
928
- /**
929
- * Get current rate limit status without incrementing the counter.
930
- */
931
- declare function getRateLimitStatus(operation: string, identifier: string, rule: RateLimitRule, store?: RateLimitStore): Promise<RateLimitCheckResult | null>;
932
- /**
933
- * Reset rate limit for an identifier (for admin/testing).
934
- */
935
- declare function resetRateLimitForKey(operation: string, identifier: string, store?: RateLimitStore): Promise<void>;
936
- /**
937
- * Build standard rate limit headers for HTTP responses.
938
- */
939
- declare function buildRateLimitResponseHeaders(result: RateLimitCheckResult): Record<string, string>;
940
- /**
941
- * Resolve a rate limit identifier from a request-like context.
942
- * Prefers user ID > email > IP for most accurate limiting.
943
- */
944
- declare function resolveIdentifier(session: {
945
- user?: {
946
- id?: string;
947
- email?: string;
948
- };
949
- } | null | undefined, clientIp?: string): {
950
- identifier: string;
951
- isAuthenticated: boolean;
952
- };
953
-
142
+ declare function zodErrorResponse(error: {
143
+ issues: Array<{
144
+ path: (string | number)[];
145
+ message: string;
146
+ }>;
147
+ }): Response;
954
148
  /**
955
- * Audit Logging System
956
- *
957
- * Framework-agnostic audit logging for security-sensitive operations.
958
- * Provides structured audit events with actor, action, resource, and
959
- * outcome tracking. Apps provide their own persistence (Redis, DB, etc.)
960
- * via a simple callback; this module handles the event model and helpers.
961
- *
962
- * @example
963
- * ```typescript
964
- * import { createAuditLogger, StandardAuditActions } from '@digilogiclabs/platform-core'
149
+ * Extract a bearer token from the Authorization header.
965
150
  *
966
- * const audit = createAuditLogger({
967
- * persist: async (record) => {
968
- * await redis.setex(`audit:${record.id}`, 90 * 86400, JSON.stringify(record))
969
- * },
970
- * })
971
- *
972
- * await audit.log({
973
- * actor: { id: userId, email, type: 'user' },
974
- * action: StandardAuditActions.LOGIN_SUCCESS,
975
- * outcome: 'success',
976
- * })
977
- * ```
151
+ * @returns The token string, or null if not present/malformed.
978
152
  */
979
- /** Who performed the action */
980
- interface OpsAuditActor {
981
- id: string;
982
- email?: string;
983
- type: "user" | "admin" | "system" | "api_key" | "anonymous";
984
- sessionId?: string;
985
- }
986
- /** What resource was affected */
987
- interface OpsAuditResource {
988
- type: string;
989
- id: string;
990
- name?: string;
991
- }
992
- /** The audit event to log */
993
- interface OpsAuditEvent {
994
- actor: OpsAuditActor;
995
- action: string;
996
- resource?: OpsAuditResource;
997
- outcome: "success" | "failure" | "blocked" | "pending";
998
- metadata?: Record<string, unknown>;
999
- reason?: string;
1000
- }
1001
- /** Complete audit record with context */
1002
- interface OpsAuditRecord extends OpsAuditEvent {
1003
- id: string;
1004
- timestamp: string;
1005
- ip?: string;
1006
- userAgent?: string;
1007
- requestId?: string;
1008
- duration?: number;
1009
- }
1010
- /** Options for creating an audit logger */
1011
- interface OpsAuditLoggerOptions {
1012
- /**
1013
- * Persist an audit record to storage (Redis, DB, etc.).
1014
- * Called after console logging. Errors are caught and logged,
1015
- * never thrown — audit failures must not break operations.
1016
- */
1017
- persist?: (record: OpsAuditRecord) => Promise<void>;
1018
- /**
1019
- * Console logger for structured output.
1020
- * Defaults to console.log/console.warn.
1021
- */
1022
- logger?: {
1023
- info: (msg: string, meta?: Record<string, unknown>) => void;
1024
- warn: (msg: string, meta?: Record<string, unknown>) => void;
1025
- error: (msg: string, meta?: Record<string, unknown>) => void;
1026
- };
1027
- }
1028
- /** Request-like object for extracting IP, user agent, request ID */
1029
- interface AuditRequest {
153
+ declare function extractBearerToken(request: {
1030
154
  headers: {
1031
155
  get(name: string): string | null;
1032
156
  };
1033
- }
157
+ }): string | null;
1034
158
  /**
1035
- * Standard audit action constants.
1036
- * Apps should extend with domain-specific actions:
159
+ * Check if a request's bearer token matches a secret.
160
+ * Uses timing-safe comparison to prevent timing attacks.
1037
161
  *
1038
162
  * @example
1039
163
  * ```typescript
1040
- * const AppAuditActions = {
1041
- * ...StandardAuditActions,
1042
- * SERVER_CREATE: 'game.server.create',
1043
- * SERVER_DELETE: 'game.server.delete',
1044
- * } as const
164
+ * if (!isValidBearerToken(request, process.env.ADMIN_SECRET)) {
165
+ * return new Response('Unauthorized', { status: 401 })
166
+ * }
1045
167
  * ```
1046
168
  */
1047
- declare const StandardAuditActions: {
1048
- readonly LOGIN_SUCCESS: "auth.login.success";
1049
- readonly LOGIN_FAILURE: "auth.login.failure";
1050
- readonly LOGOUT: "auth.logout";
1051
- readonly SESSION_REFRESH: "auth.session.refresh";
1052
- readonly PASSWORD_CHANGE: "auth.password.change";
1053
- readonly PASSWORD_RESET: "auth.password.reset";
1054
- readonly CHECKOUT_START: "billing.checkout.start";
1055
- readonly CHECKOUT_COMPLETE: "billing.checkout.complete";
1056
- readonly SUBSCRIPTION_CREATE: "billing.subscription.create";
1057
- readonly SUBSCRIPTION_CANCEL: "billing.subscription.cancel";
1058
- readonly SUBSCRIPTION_UPDATE: "billing.subscription.update";
1059
- readonly PAYMENT_FAILED: "billing.payment.failed";
1060
- readonly ADMIN_LOGIN: "admin.login";
1061
- readonly ADMIN_USER_VIEW: "admin.user.view";
1062
- readonly ADMIN_USER_UPDATE: "admin.user.update";
1063
- readonly ADMIN_CONFIG_CHANGE: "admin.config.change";
1064
- readonly RATE_LIMIT_EXCEEDED: "security.rate_limit.exceeded";
1065
- readonly INVALID_INPUT: "security.input.invalid";
1066
- readonly UNAUTHORIZED_ACCESS: "security.access.unauthorized";
1067
- readonly OWNERSHIP_VIOLATION: "security.ownership.violation";
1068
- readonly WEBHOOK_SIGNATURE_INVALID: "security.webhook.signature_invalid";
1069
- readonly DATA_EXPORT: "data.export";
1070
- readonly DATA_DELETE: "data.delete";
1071
- readonly DATA_UPDATE: "data.update";
1072
- };
1073
- type StandardAuditActionType = (typeof StandardAuditActions)[keyof typeof StandardAuditActions];
1074
- /**
1075
- * Extract client IP from request headers.
1076
- * Checks common proxy headers in order of reliability.
1077
- */
1078
- declare function extractAuditIp(request?: AuditRequest): string | undefined;
1079
- /**
1080
- * Extract user agent from request.
1081
- */
1082
- declare function extractAuditUserAgent(request?: AuditRequest): string | undefined;
1083
- /**
1084
- * Extract or generate request ID.
1085
- */
1086
- declare function extractAuditRequestId(request?: AuditRequest): string;
1087
- /**
1088
- * Create an AuditActor from a session object.
1089
- * Works with Auth.js/NextAuth session shape.
1090
- */
1091
- declare function createAuditActor(session: {
1092
- user?: {
1093
- id?: string;
1094
- email?: string | null;
1095
- };
1096
- } | null | undefined): OpsAuditActor;
1097
- /**
1098
- * Create an audit logger instance.
1099
- *
1100
- * @param options - Persistence callback and optional logger
1101
- * @returns Audit logger with log() and createTimedAudit() methods
1102
- */
1103
- declare function createAuditLogger(options?: OpsAuditLoggerOptions): {
1104
- log: (event: OpsAuditEvent, request?: AuditRequest) => Promise<OpsAuditRecord>;
1105
- createTimedAudit: (event: Omit<OpsAuditEvent, "outcome">, request?: AuditRequest) => {
1106
- success: (metadata?: Record<string, unknown>) => Promise<OpsAuditRecord>;
1107
- failure: (reason: string, metadata?: Record<string, unknown>) => Promise<OpsAuditRecord>;
1108
- blocked: (reason: string, metadata?: Record<string, unknown>) => Promise<OpsAuditRecord>;
169
+ declare function isValidBearerToken(request: {
170
+ headers: {
171
+ get(name: string): string | null;
1109
172
  };
1110
- };
173
+ }, secret: string | undefined): boolean;
1111
174
 
1112
- export { type AllowlistConfig, type ApiSecurityConfig, type ApiSecurityContext, type AuditRequest, type AuthCookiesConfig, type AuthMethod, CommonRateLimits, type DateRangeInput, DateRangeSchema, type DeploymentStage, type EmailInput, EmailSchema, type FlagDefinition, type FlagDefinitions, type FlagValue, KEYCLOAK_DEFAULT_ROLES, type KeycloakCallbacksConfig, type KeycloakConfig, type KeycloakJwtFields, type KeycloakTokenSet, type LoginInput, LoginSchema, type OpsAuditActor, type OpsAuditEvent, type OpsAuditLoggerOptions, type OpsAuditRecord, type OpsAuditResource, type PaginationInput, PaginationSchema, PasswordSchema, PersonNameSchema, PhoneSchema, type RateLimitCheckResult, type RateLimitOptions, type RateLimitPreset, type RateLimitRule, type RateLimitStore, type RedirectCallbackConfig, type ResolvedFlags, type RouteAuditConfig, type SearchQueryInput, SearchQuerySchema, type SecuritySession, type SignupInput, SignupSchema, SlugSchema, type StandardAuditActionType, StandardAuditActions, StandardRateLimitPresets, type TokenRefreshResult, WrapperPresets, buildAllowlist, buildAuthCookies, buildErrorBody, buildKeycloakCallbacks, buildRateLimitHeaders, buildRateLimitResponseHeaders, buildRedirectCallback, buildTokenRefreshParams, checkRateLimit, createAuditActor, createAuditLogger, createFeatureFlags, createMemoryRateLimitStore, createSafeTextSchema, detectStage, extractAuditIp, extractAuditRequestId, extractAuditUserAgent, extractClientIp, getEndSessionEndpoint, getRateLimitStatus, getTokenEndpoint, hasAllRoles, hasAnyRole, hasRole, isAllowlisted, isTokenExpired, parseKeycloakRoles, refreshKeycloakToken, resetRateLimitForKey, resolveIdentifier, resolveRateLimitIdentifier };
175
+ export { RateLimitOptions, RateLimitRule, RateLimitStore, type RedisRateLimitClient, type RedisRateLimitStoreOptions, createRedisRateLimitStore, enforceRateLimit, errorResponse, extractBearerToken, isValidBearerToken, zodErrorResponse };