@digilogiclabs/platform-core 1.9.0 → 1.10.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.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as RateLimitStore, a as RateLimitRule, b as RateLimitOptions } from './env-DHPZR3Lv.mjs';
2
- export { al as AllowlistConfig, A as ApiError, d as ApiErrorCode, f as ApiErrorCodeType, h as ApiPaginatedResponse, Q as ApiSecurityConfig, V as ApiSecurityContext, g as ApiSuccessResponse, aD as AuditRequest, H as AuthCookiesConfig, N as AuthMethod, aL as BetaClientConfig, C as CommonApiErrors, ar as CommonRateLimits, ac as DateRangeInput, a6 as DateRangeSchema, ag as DeploymentStage, aa as EmailInput, $ as EmailSchema, E as EnvValidationConfig, p as EnvValidationResult, ai as FlagDefinition, aj as FlagDefinitions, ah as FlagValue, r as KEYCLOAK_DEFAULT_ROLES, F as KeycloakCallbacksConfig, K as KeycloakConfig, G as KeycloakJwtFields, q as KeycloakTokenSet, ae as LoginInput, a8 as LoginSchema, ay as OpsAuditActor, aA as OpsAuditEvent, aC as OpsAuditLoggerOptions, aB as OpsAuditRecord, az as OpsAuditResource, ab as PaginationInput, a5 as PaginationSchema, a0 as PasswordSchema, a3 as PersonNameSchema, a2 as PhoneSchema, aq as RateLimitCheckResult, P as RateLimitPreset, J as RedirectCallbackConfig, ak as ResolvedFlags, O as RouteAuditConfig, ad as SearchQueryInput, a7 as SearchQuerySchema, U as SecuritySession, af as SignupInput, a9 as SignupSchema, a1 as SlugSchema, aF as StandardAuditActionType, aE as StandardAuditActions, S as StandardRateLimitPresets, T as TokenRefreshResult, _ as WrapperPresets, ao as buildAllowlist, I as buildAuthCookies, Z as buildErrorBody, M as buildKeycloakCallbacks, e as buildPagination, Y as buildRateLimitHeaders, aw as buildRateLimitResponseHeaders, L as buildRedirectCallback, y as buildTokenRefreshParams, n as checkEnvVars, at as checkRateLimit, c as classifyError, aR as clearStoredBetaCode, aJ as createAuditActor, aK as createAuditLogger, aM as createBetaClient, an as createFeatureFlags, as as createMemoryRateLimitStore, a4 as createSafeTextSchema, am as detectStage, aG as extractAuditIp, aI as extractAuditRequestId, aH as extractAuditUserAgent, X as extractClientIp, aN as fetchBetaSettings, l as getBoolEnv, B as getEndSessionEndpoint, o as getEnvSummary, m as getIntEnv, k as getOptionalEnv, au as getRateLimitStatus, j as getRequiredEnv, aQ as getStoredBetaCode, z as getTokenEndpoint, w as hasAllRoles, u as hasAnyRole, t as hasRole, ap as isAllowlisted, i as isApiError, x as isTokenExpired, s as parseKeycloakRoles, D as refreshKeycloakToken, av as resetRateLimitForKey, ax as resolveIdentifier, W as resolveRateLimitIdentifier, aP as storeBetaCode, aO as validateBetaCode, v as validateEnvVars } from './env-DHPZR3Lv.mjs';
1
+ import { R as RateLimitStore, a as RateLimitRule, b as RateLimitOptions } from './env-CYKVNpLl.mjs';
2
+ export { al as AllowlistConfig, A as ApiError, d as ApiErrorCode, f as ApiErrorCodeType, h as ApiPaginatedResponse, Q as ApiSecurityConfig, V as ApiSecurityContext, g as ApiSuccessResponse, aD as AuditRequest, H as AuthCookiesConfig, N as AuthMethod, aL as BetaClientConfig, C as CommonApiErrors, ar as CommonRateLimits, ac as DateRangeInput, a6 as DateRangeSchema, ag as DeploymentStage, aa as EmailInput, $ as EmailSchema, E as EnvValidationConfig, p as EnvValidationResult, ai as FlagDefinition, aj as FlagDefinitions, ah as FlagValue, r as KEYCLOAK_DEFAULT_ROLES, F as KeycloakCallbacksConfig, K as KeycloakConfig, G as KeycloakJwtFields, q as KeycloakTokenSet, ae as LoginInput, a8 as LoginSchema, ay as OpsAuditActor, aA as OpsAuditEvent, aC as OpsAuditLoggerOptions, aB as OpsAuditRecord, az as OpsAuditResource, ab as PaginationInput, a5 as PaginationSchema, a0 as PasswordSchema, a3 as PersonNameSchema, a2 as PhoneSchema, aq as RateLimitCheckResult, P as RateLimitPreset, J as RedirectCallbackConfig, ak as ResolvedFlags, O as RouteAuditConfig, ad as SearchQueryInput, a7 as SearchQuerySchema, U as SecuritySession, af as SignupInput, a9 as SignupSchema, a1 as SlugSchema, aF as StandardAuditActionType, aE as StandardAuditActions, S as StandardRateLimitPresets, T as TokenRefreshResult, _ as WrapperPresets, ao as buildAllowlist, I as buildAuthCookies, Z as buildErrorBody, M as buildKeycloakCallbacks, e as buildPagination, Y as buildRateLimitHeaders, aw as buildRateLimitResponseHeaders, L as buildRedirectCallback, y as buildTokenRefreshParams, n as checkEnvVars, at as checkRateLimit, c as classifyError, aR as clearStoredBetaCode, aJ as createAuditActor, aK as createAuditLogger, aM as createBetaClient, an as createFeatureFlags, as as createMemoryRateLimitStore, a4 as createSafeTextSchema, am as detectStage, aG as extractAuditIp, aI as extractAuditRequestId, aH as extractAuditUserAgent, X as extractClientIp, aN as fetchBetaSettings, l as getBoolEnv, B as getEndSessionEndpoint, o as getEnvSummary, m as getIntEnv, k as getOptionalEnv, au as getRateLimitStatus, j as getRequiredEnv, aQ as getStoredBetaCode, z as getTokenEndpoint, w as hasAllRoles, u as hasAnyRole, t as hasRole, ap as isAllowlisted, i as isApiError, x as isTokenExpired, s as parseKeycloakRoles, D as refreshKeycloakToken, av as resetRateLimitForKey, ax as resolveIdentifier, W as resolveRateLimitIdentifier, aP as storeBetaCode, aO as validateBetaCode, v as validateEnvVars } from './env-CYKVNpLl.mjs';
3
3
  export { c as constantTimeEqual, b as containsHtml, a as containsUrls, e as escapeHtml, g as getCorrelationId, d as sanitizeApiError, s as stripHtml } from './security-BvLXaQkv.mjs';
4
4
  import 'zod';
5
5
 
@@ -172,5 +172,117 @@ declare function isValidBearerToken(request: {
172
172
  get(name: string): string | null;
173
173
  };
174
174
  }, secret: string | undefined): boolean;
175
+ /**
176
+ * Verify cron/sync endpoint authentication using a bearer token.
177
+ *
178
+ * Extracts the Bearer token from the Authorization header and compares
179
+ * it against `CRON_SECRET` (or a custom secret) using timing-safe comparison.
180
+ * Returns a 401/500 Response on failure, or `null` on success.
181
+ *
182
+ * @param request - Incoming request (must have headers.get)
183
+ * @param secret - Custom secret to compare against. Defaults to `process.env.CRON_SECRET`.
184
+ * @returns `null` if authorized, or a JSON Response (401/500) if not.
185
+ *
186
+ * @example
187
+ * ```typescript
188
+ * export async function POST(request: NextRequest) {
189
+ * const authError = verifyCronAuth(request)
190
+ * if (authError) return authError
191
+ * // ... handle cron job
192
+ * }
193
+ * ```
194
+ */
195
+ declare function verifyCronAuth(request: {
196
+ headers: {
197
+ get(name: string): string | null;
198
+ };
199
+ }, secret?: string): Response | null;
200
+ /**
201
+ * Extract the client IP address from a request, checking proxy headers.
202
+ *
203
+ * Checks in order:
204
+ * 1. `cf-connecting-ip` (Cloudflare)
205
+ * 2. `x-real-ip` (Nginx)
206
+ * 3. `x-forwarded-for` (first IP from comma-separated list)
207
+ * 4. Falls back to `'unknown'`
208
+ *
209
+ * @example
210
+ * ```typescript
211
+ * const ip = getClientIp(request)
212
+ * await auditLog({ action: 'login', ip })
213
+ * ```
214
+ */
215
+ declare function getClientIp(request: {
216
+ headers: {
217
+ get(name: string): string | null;
218
+ };
219
+ }): string;
220
+ /** Shape expected by rate limit response helpers. */
221
+ interface RateLimitResult {
222
+ limit: number;
223
+ remaining: number;
224
+ resetMs: number;
225
+ }
226
+ /**
227
+ * Create a 429 "Too Many Requests" response with RFC-compliant rate limit headers.
228
+ *
229
+ * @example
230
+ * ```typescript
231
+ * if (!result.allowed) {
232
+ * return rateLimitResponse(result)
233
+ * }
234
+ * ```
235
+ */
236
+ declare function rateLimitResponse(result: RateLimitResult): Response;
237
+ /**
238
+ * Add rate limit headers to an existing response.
239
+ *
240
+ * Use this to attach rate limit info to successful responses so clients
241
+ * can see their remaining quota.
242
+ *
243
+ * @example
244
+ * ```typescript
245
+ * const response = new Response(JSON.stringify(data), { status: 200 })
246
+ * return addRateLimitHeaders(response, result)
247
+ * ```
248
+ */
249
+ declare function addRateLimitHeaders(response: Response, result: RateLimitResult): Response;
250
+ /**
251
+ * Safe Zod validation — returns { success, data?, errors? } without throwing.
252
+ * Useful for API routes where you want to return validation errors as JSON.
253
+ *
254
+ * Uses structural typing for the schema parameter so platform-core
255
+ * doesn't depend on Zod directly.
256
+ *
257
+ * @example
258
+ * ```typescript
259
+ * const result = safeValidate(MySchema, await request.json())
260
+ * if (!result.success) {
261
+ * return new Response(JSON.stringify({ errors: result.errors }), { status: 400 })
262
+ * }
263
+ * const data = result.data // fully typed
264
+ * ```
265
+ */
266
+ declare function safeValidate<T>(schema: {
267
+ safeParse: (data: unknown) => {
268
+ success: boolean;
269
+ data?: T;
270
+ error?: {
271
+ issues: Array<{
272
+ path: (string | number)[];
273
+ message: string;
274
+ }>;
275
+ };
276
+ };
277
+ }, data: unknown): {
278
+ success: true;
279
+ data: T;
280
+ } | {
281
+ success: false;
282
+ errors: Array<{
283
+ field: string;
284
+ message: string;
285
+ }>;
286
+ };
175
287
 
176
- export { RateLimitOptions, RateLimitRule, RateLimitStore, type RedisRateLimitClient, type RedisRateLimitStoreOptions, createRedisRateLimitStore, enforceRateLimit, errorResponse, extractBearerToken, isValidBearerToken, zodErrorResponse };
288
+ export { RateLimitOptions, type RateLimitResult, RateLimitRule, RateLimitStore, type RedisRateLimitClient, type RedisRateLimitStoreOptions, addRateLimitHeaders, createRedisRateLimitStore, enforceRateLimit, errorResponse, extractBearerToken, getClientIp, isValidBearerToken, rateLimitResponse, safeValidate, verifyCronAuth, zodErrorResponse };
package/dist/auth.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as RateLimitStore, a as RateLimitRule, b as RateLimitOptions } from './env-DHPZR3Lv.js';
2
- export { al as AllowlistConfig, A as ApiError, d as ApiErrorCode, f as ApiErrorCodeType, h as ApiPaginatedResponse, Q as ApiSecurityConfig, V as ApiSecurityContext, g as ApiSuccessResponse, aD as AuditRequest, H as AuthCookiesConfig, N as AuthMethod, aL as BetaClientConfig, C as CommonApiErrors, ar as CommonRateLimits, ac as DateRangeInput, a6 as DateRangeSchema, ag as DeploymentStage, aa as EmailInput, $ as EmailSchema, E as EnvValidationConfig, p as EnvValidationResult, ai as FlagDefinition, aj as FlagDefinitions, ah as FlagValue, r as KEYCLOAK_DEFAULT_ROLES, F as KeycloakCallbacksConfig, K as KeycloakConfig, G as KeycloakJwtFields, q as KeycloakTokenSet, ae as LoginInput, a8 as LoginSchema, ay as OpsAuditActor, aA as OpsAuditEvent, aC as OpsAuditLoggerOptions, aB as OpsAuditRecord, az as OpsAuditResource, ab as PaginationInput, a5 as PaginationSchema, a0 as PasswordSchema, a3 as PersonNameSchema, a2 as PhoneSchema, aq as RateLimitCheckResult, P as RateLimitPreset, J as RedirectCallbackConfig, ak as ResolvedFlags, O as RouteAuditConfig, ad as SearchQueryInput, a7 as SearchQuerySchema, U as SecuritySession, af as SignupInput, a9 as SignupSchema, a1 as SlugSchema, aF as StandardAuditActionType, aE as StandardAuditActions, S as StandardRateLimitPresets, T as TokenRefreshResult, _ as WrapperPresets, ao as buildAllowlist, I as buildAuthCookies, Z as buildErrorBody, M as buildKeycloakCallbacks, e as buildPagination, Y as buildRateLimitHeaders, aw as buildRateLimitResponseHeaders, L as buildRedirectCallback, y as buildTokenRefreshParams, n as checkEnvVars, at as checkRateLimit, c as classifyError, aR as clearStoredBetaCode, aJ as createAuditActor, aK as createAuditLogger, aM as createBetaClient, an as createFeatureFlags, as as createMemoryRateLimitStore, a4 as createSafeTextSchema, am as detectStage, aG as extractAuditIp, aI as extractAuditRequestId, aH as extractAuditUserAgent, X as extractClientIp, aN as fetchBetaSettings, l as getBoolEnv, B as getEndSessionEndpoint, o as getEnvSummary, m as getIntEnv, k as getOptionalEnv, au as getRateLimitStatus, j as getRequiredEnv, aQ as getStoredBetaCode, z as getTokenEndpoint, w as hasAllRoles, u as hasAnyRole, t as hasRole, ap as isAllowlisted, i as isApiError, x as isTokenExpired, s as parseKeycloakRoles, D as refreshKeycloakToken, av as resetRateLimitForKey, ax as resolveIdentifier, W as resolveRateLimitIdentifier, aP as storeBetaCode, aO as validateBetaCode, v as validateEnvVars } from './env-DHPZR3Lv.js';
1
+ import { R as RateLimitStore, a as RateLimitRule, b as RateLimitOptions } from './env-CYKVNpLl.js';
2
+ export { al as AllowlistConfig, A as ApiError, d as ApiErrorCode, f as ApiErrorCodeType, h as ApiPaginatedResponse, Q as ApiSecurityConfig, V as ApiSecurityContext, g as ApiSuccessResponse, aD as AuditRequest, H as AuthCookiesConfig, N as AuthMethod, aL as BetaClientConfig, C as CommonApiErrors, ar as CommonRateLimits, ac as DateRangeInput, a6 as DateRangeSchema, ag as DeploymentStage, aa as EmailInput, $ as EmailSchema, E as EnvValidationConfig, p as EnvValidationResult, ai as FlagDefinition, aj as FlagDefinitions, ah as FlagValue, r as KEYCLOAK_DEFAULT_ROLES, F as KeycloakCallbacksConfig, K as KeycloakConfig, G as KeycloakJwtFields, q as KeycloakTokenSet, ae as LoginInput, a8 as LoginSchema, ay as OpsAuditActor, aA as OpsAuditEvent, aC as OpsAuditLoggerOptions, aB as OpsAuditRecord, az as OpsAuditResource, ab as PaginationInput, a5 as PaginationSchema, a0 as PasswordSchema, a3 as PersonNameSchema, a2 as PhoneSchema, aq as RateLimitCheckResult, P as RateLimitPreset, J as RedirectCallbackConfig, ak as ResolvedFlags, O as RouteAuditConfig, ad as SearchQueryInput, a7 as SearchQuerySchema, U as SecuritySession, af as SignupInput, a9 as SignupSchema, a1 as SlugSchema, aF as StandardAuditActionType, aE as StandardAuditActions, S as StandardRateLimitPresets, T as TokenRefreshResult, _ as WrapperPresets, ao as buildAllowlist, I as buildAuthCookies, Z as buildErrorBody, M as buildKeycloakCallbacks, e as buildPagination, Y as buildRateLimitHeaders, aw as buildRateLimitResponseHeaders, L as buildRedirectCallback, y as buildTokenRefreshParams, n as checkEnvVars, at as checkRateLimit, c as classifyError, aR as clearStoredBetaCode, aJ as createAuditActor, aK as createAuditLogger, aM as createBetaClient, an as createFeatureFlags, as as createMemoryRateLimitStore, a4 as createSafeTextSchema, am as detectStage, aG as extractAuditIp, aI as extractAuditRequestId, aH as extractAuditUserAgent, X as extractClientIp, aN as fetchBetaSettings, l as getBoolEnv, B as getEndSessionEndpoint, o as getEnvSummary, m as getIntEnv, k as getOptionalEnv, au as getRateLimitStatus, j as getRequiredEnv, aQ as getStoredBetaCode, z as getTokenEndpoint, w as hasAllRoles, u as hasAnyRole, t as hasRole, ap as isAllowlisted, i as isApiError, x as isTokenExpired, s as parseKeycloakRoles, D as refreshKeycloakToken, av as resetRateLimitForKey, ax as resolveIdentifier, W as resolveRateLimitIdentifier, aP as storeBetaCode, aO as validateBetaCode, v as validateEnvVars } from './env-CYKVNpLl.js';
3
3
  export { c as constantTimeEqual, b as containsHtml, a as containsUrls, e as escapeHtml, g as getCorrelationId, d as sanitizeApiError, s as stripHtml } from './security-BvLXaQkv.js';
4
4
  import 'zod';
5
5
 
@@ -172,5 +172,117 @@ declare function isValidBearerToken(request: {
172
172
  get(name: string): string | null;
173
173
  };
174
174
  }, secret: string | undefined): boolean;
175
+ /**
176
+ * Verify cron/sync endpoint authentication using a bearer token.
177
+ *
178
+ * Extracts the Bearer token from the Authorization header and compares
179
+ * it against `CRON_SECRET` (or a custom secret) using timing-safe comparison.
180
+ * Returns a 401/500 Response on failure, or `null` on success.
181
+ *
182
+ * @param request - Incoming request (must have headers.get)
183
+ * @param secret - Custom secret to compare against. Defaults to `process.env.CRON_SECRET`.
184
+ * @returns `null` if authorized, or a JSON Response (401/500) if not.
185
+ *
186
+ * @example
187
+ * ```typescript
188
+ * export async function POST(request: NextRequest) {
189
+ * const authError = verifyCronAuth(request)
190
+ * if (authError) return authError
191
+ * // ... handle cron job
192
+ * }
193
+ * ```
194
+ */
195
+ declare function verifyCronAuth(request: {
196
+ headers: {
197
+ get(name: string): string | null;
198
+ };
199
+ }, secret?: string): Response | null;
200
+ /**
201
+ * Extract the client IP address from a request, checking proxy headers.
202
+ *
203
+ * Checks in order:
204
+ * 1. `cf-connecting-ip` (Cloudflare)
205
+ * 2. `x-real-ip` (Nginx)
206
+ * 3. `x-forwarded-for` (first IP from comma-separated list)
207
+ * 4. Falls back to `'unknown'`
208
+ *
209
+ * @example
210
+ * ```typescript
211
+ * const ip = getClientIp(request)
212
+ * await auditLog({ action: 'login', ip })
213
+ * ```
214
+ */
215
+ declare function getClientIp(request: {
216
+ headers: {
217
+ get(name: string): string | null;
218
+ };
219
+ }): string;
220
+ /** Shape expected by rate limit response helpers. */
221
+ interface RateLimitResult {
222
+ limit: number;
223
+ remaining: number;
224
+ resetMs: number;
225
+ }
226
+ /**
227
+ * Create a 429 "Too Many Requests" response with RFC-compliant rate limit headers.
228
+ *
229
+ * @example
230
+ * ```typescript
231
+ * if (!result.allowed) {
232
+ * return rateLimitResponse(result)
233
+ * }
234
+ * ```
235
+ */
236
+ declare function rateLimitResponse(result: RateLimitResult): Response;
237
+ /**
238
+ * Add rate limit headers to an existing response.
239
+ *
240
+ * Use this to attach rate limit info to successful responses so clients
241
+ * can see their remaining quota.
242
+ *
243
+ * @example
244
+ * ```typescript
245
+ * const response = new Response(JSON.stringify(data), { status: 200 })
246
+ * return addRateLimitHeaders(response, result)
247
+ * ```
248
+ */
249
+ declare function addRateLimitHeaders(response: Response, result: RateLimitResult): Response;
250
+ /**
251
+ * Safe Zod validation — returns { success, data?, errors? } without throwing.
252
+ * Useful for API routes where you want to return validation errors as JSON.
253
+ *
254
+ * Uses structural typing for the schema parameter so platform-core
255
+ * doesn't depend on Zod directly.
256
+ *
257
+ * @example
258
+ * ```typescript
259
+ * const result = safeValidate(MySchema, await request.json())
260
+ * if (!result.success) {
261
+ * return new Response(JSON.stringify({ errors: result.errors }), { status: 400 })
262
+ * }
263
+ * const data = result.data // fully typed
264
+ * ```
265
+ */
266
+ declare function safeValidate<T>(schema: {
267
+ safeParse: (data: unknown) => {
268
+ success: boolean;
269
+ data?: T;
270
+ error?: {
271
+ issues: Array<{
272
+ path: (string | number)[];
273
+ message: string;
274
+ }>;
275
+ };
276
+ };
277
+ }, data: unknown): {
278
+ success: true;
279
+ data: T;
280
+ } | {
281
+ success: false;
282
+ errors: Array<{
283
+ field: string;
284
+ message: string;
285
+ }>;
286
+ };
175
287
 
176
- export { RateLimitOptions, RateLimitRule, RateLimitStore, type RedisRateLimitClient, type RedisRateLimitStoreOptions, createRedisRateLimitStore, enforceRateLimit, errorResponse, extractBearerToken, isValidBearerToken, zodErrorResponse };
288
+ export { RateLimitOptions, type RateLimitResult, RateLimitRule, RateLimitStore, type RedisRateLimitClient, type RedisRateLimitStoreOptions, addRateLimitHeaders, createRedisRateLimitStore, enforceRateLimit, errorResponse, extractBearerToken, getClientIp, isValidBearerToken, rateLimitResponse, safeValidate, verifyCronAuth, zodErrorResponse };
package/dist/auth.js CHANGED
@@ -38,6 +38,7 @@ __export(auth_exports, {
38
38
  StandardAuditActions: () => StandardAuditActions,
39
39
  StandardRateLimitPresets: () => StandardRateLimitPresets,
40
40
  WrapperPresets: () => WrapperPresets,
41
+ addRateLimitHeaders: () => addRateLimitHeaders,
41
42
  buildAllowlist: () => buildAllowlist,
42
43
  buildAuthCookies: () => buildAuthCookies,
43
44
  buildErrorBody: () => buildErrorBody,
@@ -72,6 +73,7 @@ __export(auth_exports, {
72
73
  extractClientIp: () => extractClientIp,
73
74
  fetchBetaSettings: () => fetchBetaSettings,
74
75
  getBoolEnv: () => getBoolEnv,
76
+ getClientIp: () => getClientIp,
75
77
  getCorrelationId: () => getCorrelationId,
76
78
  getEndSessionEndpoint: () => getEndSessionEndpoint,
77
79
  getEnvSummary: () => getEnvSummary,
@@ -89,15 +91,18 @@ __export(auth_exports, {
89
91
  isTokenExpired: () => isTokenExpired,
90
92
  isValidBearerToken: () => isValidBearerToken,
91
93
  parseKeycloakRoles: () => parseKeycloakRoles,
94
+ rateLimitResponse: () => rateLimitResponse,
92
95
  refreshKeycloakToken: () => refreshKeycloakToken,
93
96
  resetRateLimitForKey: () => resetRateLimitForKey,
94
97
  resolveIdentifier: () => resolveIdentifier,
95
98
  resolveRateLimitIdentifier: () => resolveRateLimitIdentifier,
99
+ safeValidate: () => safeValidate,
96
100
  sanitizeApiError: () => sanitizeApiError,
97
101
  storeBetaCode: () => storeBetaCode,
98
102
  stripHtml: () => stripHtml,
99
103
  validateBetaCode: () => validateBetaCode,
100
104
  validateEnvVars: () => validateEnvVars,
105
+ verifyCronAuth: () => verifyCronAuth,
101
106
  zodErrorResponse: () => zodErrorResponse
102
107
  });
103
108
  module.exports = __toCommonJS(auth_exports);
@@ -666,6 +671,12 @@ var CommonRateLimits = {
666
671
  limit: 10,
667
672
  windowSeconds: 3600,
668
673
  blockDurationSeconds: 3600
674
+ },
675
+ /** Beta code validation: 5/min with 5min block (prevents brute force guessing) */
676
+ betaValidation: {
677
+ limit: 5,
678
+ windowSeconds: 60,
679
+ blockDurationSeconds: 300
669
680
  }
670
681
  };
671
682
  function createMemoryRateLimitStore() {
@@ -1209,6 +1220,71 @@ function isValidBearerToken(request, secret) {
1209
1220
  if (!token) return false;
1210
1221
  return constantTimeEqual(token, secret);
1211
1222
  }
1223
+ function verifyCronAuth(request, secret) {
1224
+ const authHeader = request.headers.get("authorization");
1225
+ if (!authHeader?.startsWith("Bearer ")) {
1226
+ return new Response(
1227
+ JSON.stringify({ error: "Missing authorization" }),
1228
+ { status: 401, headers: { "Content-Type": "application/json" } }
1229
+ );
1230
+ }
1231
+ const token = authHeader.slice(7);
1232
+ const expectedSecret = secret ?? process.env.CRON_SECRET;
1233
+ if (!expectedSecret) {
1234
+ console.error("[verifyCronAuth] CRON_SECRET not configured");
1235
+ return new Response(
1236
+ JSON.stringify({ error: "Server configuration error" }),
1237
+ { status: 500, headers: { "Content-Type": "application/json" } }
1238
+ );
1239
+ }
1240
+ if (!constantTimeEqual(token, expectedSecret)) {
1241
+ return new Response(
1242
+ JSON.stringify({ error: "Invalid authorization" }),
1243
+ { status: 401, headers: { "Content-Type": "application/json" } }
1244
+ );
1245
+ }
1246
+ return null;
1247
+ }
1248
+ function getClientIp(request) {
1249
+ return request.headers.get("cf-connecting-ip") || request.headers.get("x-real-ip") || request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || "unknown";
1250
+ }
1251
+ function rateLimitResponse(result) {
1252
+ const retryAfter = Math.ceil(result.resetMs / 1e3);
1253
+ const resetTimestamp = Math.ceil(Date.now() / 1e3 + result.resetMs / 1e3);
1254
+ return new Response(
1255
+ JSON.stringify({ error: "Too many requests", retryAfter }),
1256
+ {
1257
+ status: 429,
1258
+ headers: {
1259
+ "Content-Type": "application/json",
1260
+ "X-RateLimit-Limit": String(result.limit),
1261
+ "X-RateLimit-Remaining": "0",
1262
+ "X-RateLimit-Reset": String(resetTimestamp),
1263
+ "Retry-After": String(retryAfter)
1264
+ }
1265
+ }
1266
+ );
1267
+ }
1268
+ function addRateLimitHeaders(response, result) {
1269
+ const resetTimestamp = Math.ceil(Date.now() / 1e3 + result.resetMs / 1e3);
1270
+ response.headers.set("X-RateLimit-Limit", String(result.limit));
1271
+ response.headers.set("X-RateLimit-Remaining", String(result.remaining));
1272
+ response.headers.set("X-RateLimit-Reset", String(resetTimestamp));
1273
+ return response;
1274
+ }
1275
+ function safeValidate(schema, data) {
1276
+ const result = schema.safeParse(data);
1277
+ if (result.success) {
1278
+ return { success: true, data: result.data };
1279
+ }
1280
+ return {
1281
+ success: false,
1282
+ errors: (result.error?.issues || []).map((issue) => ({
1283
+ field: issue.path.join("."),
1284
+ message: issue.message
1285
+ }))
1286
+ };
1287
+ }
1212
1288
 
1213
1289
  // src/auth/beta-client.ts
1214
1290
  var DEFAULT_CONFIG = {
@@ -1427,6 +1503,7 @@ function getEnvSummary(keys) {
1427
1503
  StandardAuditActions,
1428
1504
  StandardRateLimitPresets,
1429
1505
  WrapperPresets,
1506
+ addRateLimitHeaders,
1430
1507
  buildAllowlist,
1431
1508
  buildAuthCookies,
1432
1509
  buildErrorBody,
@@ -1461,6 +1538,7 @@ function getEnvSummary(keys) {
1461
1538
  extractClientIp,
1462
1539
  fetchBetaSettings,
1463
1540
  getBoolEnv,
1541
+ getClientIp,
1464
1542
  getCorrelationId,
1465
1543
  getEndSessionEndpoint,
1466
1544
  getEnvSummary,
@@ -1478,15 +1556,18 @@ function getEnvSummary(keys) {
1478
1556
  isTokenExpired,
1479
1557
  isValidBearerToken,
1480
1558
  parseKeycloakRoles,
1559
+ rateLimitResponse,
1481
1560
  refreshKeycloakToken,
1482
1561
  resetRateLimitForKey,
1483
1562
  resolveIdentifier,
1484
1563
  resolveRateLimitIdentifier,
1564
+ safeValidate,
1485
1565
  sanitizeApiError,
1486
1566
  storeBetaCode,
1487
1567
  stripHtml,
1488
1568
  validateBetaCode,
1489
1569
  validateEnvVars,
1570
+ verifyCronAuth,
1490
1571
  zodErrorResponse
1491
1572
  });
1492
1573
  //# sourceMappingURL=auth.js.map