@digilogiclabs/platform-core 1.8.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,6 @@
1
- import { R as RateLimitStore, a as RateLimitRule, b as RateLimitOptions } from './env-jqNJdZVt.mjs';
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, aS as BetaClientConfig, 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, aY as clearStoredBetaCode, c as constantTimeEqual, f as containsHtml, d as containsUrls, aQ as createAuditActor, aR as createAuditLogger, aT as createBetaClient, 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, aU as fetchBetaSettings, r as getBoolEnv, h as getCorrelationId, L as getEndSessionEndpoint, w as getEnvSummary, t as getIntEnv, q as getOptionalEnv, aB as getRateLimitStatus, p as getRequiredEnv, aX as getStoredBetaCode, 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, aW as storeBetaCode, s as stripHtml, aV as validateBetaCode, v as validateEnvVars } from './env-jqNJdZVt.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
+ 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';
3
4
  import 'zod';
4
5
 
5
6
  /**
@@ -171,5 +172,117 @@ declare function isValidBearerToken(request: {
171
172
  get(name: string): string | null;
172
173
  };
173
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
+ };
174
287
 
175
- 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,6 @@
1
- import { R as RateLimitStore, a as RateLimitRule, b as RateLimitOptions } from './env-jqNJdZVt.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, aS as BetaClientConfig, 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, aY as clearStoredBetaCode, c as constantTimeEqual, f as containsHtml, d as containsUrls, aQ as createAuditActor, aR as createAuditLogger, aT as createBetaClient, 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, aU as fetchBetaSettings, r as getBoolEnv, h as getCorrelationId, L as getEndSessionEndpoint, w as getEnvSummary, t as getIntEnv, q as getOptionalEnv, aB as getRateLimitStatus, p as getRequiredEnv, aX as getStoredBetaCode, 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, aW as storeBetaCode, s as stripHtml, aV as validateBetaCode, v as validateEnvVars } from './env-jqNJdZVt.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
+ 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';
3
4
  import 'zod';
4
5
 
5
6
  /**
@@ -171,5 +172,117 @@ declare function isValidBearerToken(request: {
171
172
  get(name: string): string | null;
172
173
  };
173
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
+ };
174
287
 
175
- 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 = {
@@ -1242,14 +1318,11 @@ function createBetaClient(config = {}) {
1242
1318
  async function fetchBetaSettings(config = {}) {
1243
1319
  const cfg = { ...DEFAULT_CONFIG, ...config };
1244
1320
  try {
1245
- const response = await fetch(
1246
- `${cfg.baseUrl}${cfg.settingsEndpoint}`,
1247
- {
1248
- method: "GET",
1249
- headers: { "Content-Type": "application/json" },
1250
- cache: "no-store"
1251
- }
1252
- );
1321
+ const response = await fetch(`${cfg.baseUrl}${cfg.settingsEndpoint}`, {
1322
+ method: "GET",
1323
+ headers: { "Content-Type": "application/json" },
1324
+ cache: "no-store"
1325
+ });
1253
1326
  if (!response.ok) {
1254
1327
  throw new Error(`Failed to fetch beta settings: ${response.status}`);
1255
1328
  }
@@ -1277,14 +1350,11 @@ async function validateBetaCode(code, config = {}) {
1277
1350
  };
1278
1351
  }
1279
1352
  try {
1280
- const response = await fetch(
1281
- `${cfg.baseUrl}${cfg.validateEndpoint}`,
1282
- {
1283
- method: "POST",
1284
- headers: { "Content-Type": "application/json" },
1285
- body: JSON.stringify({ code: code.trim().toUpperCase() })
1286
- }
1287
- );
1353
+ const response = await fetch(`${cfg.baseUrl}${cfg.validateEndpoint}`, {
1354
+ method: "POST",
1355
+ headers: { "Content-Type": "application/json" },
1356
+ body: JSON.stringify({ code: code.trim().toUpperCase() })
1357
+ });
1288
1358
  if (response.status === 429) {
1289
1359
  return {
1290
1360
  valid: false,
@@ -1433,6 +1503,7 @@ function getEnvSummary(keys) {
1433
1503
  StandardAuditActions,
1434
1504
  StandardRateLimitPresets,
1435
1505
  WrapperPresets,
1506
+ addRateLimitHeaders,
1436
1507
  buildAllowlist,
1437
1508
  buildAuthCookies,
1438
1509
  buildErrorBody,
@@ -1467,6 +1538,7 @@ function getEnvSummary(keys) {
1467
1538
  extractClientIp,
1468
1539
  fetchBetaSettings,
1469
1540
  getBoolEnv,
1541
+ getClientIp,
1470
1542
  getCorrelationId,
1471
1543
  getEndSessionEndpoint,
1472
1544
  getEnvSummary,
@@ -1484,15 +1556,18 @@ function getEnvSummary(keys) {
1484
1556
  isTokenExpired,
1485
1557
  isValidBearerToken,
1486
1558
  parseKeycloakRoles,
1559
+ rateLimitResponse,
1487
1560
  refreshKeycloakToken,
1488
1561
  resetRateLimitForKey,
1489
1562
  resolveIdentifier,
1490
1563
  resolveRateLimitIdentifier,
1564
+ safeValidate,
1491
1565
  sanitizeApiError,
1492
1566
  storeBetaCode,
1493
1567
  stripHtml,
1494
1568
  validateBetaCode,
1495
1569
  validateEnvVars,
1570
+ verifyCronAuth,
1496
1571
  zodErrorResponse
1497
1572
  });
1498
1573
  //# sourceMappingURL=auth.js.map