@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 +116 -3
- package/dist/auth.d.ts +116 -3
- package/dist/auth.js +91 -16
- package/dist/auth.js.map +1 -1
- package/dist/auth.mjs +86 -16
- package/dist/auth.mjs.map +1 -1
- package/dist/email-templates.d.mts +210 -0
- package/dist/email-templates.d.ts +210 -0
- package/dist/email-templates.js +345 -0
- package/dist/email-templates.js.map +1 -0
- package/dist/email-templates.mjs +304 -0
- package/dist/email-templates.mjs.map +1 -0
- package/dist/{env-jqNJdZVt.d.mts → env-CYKVNpLl.d.mts} +7 -88
- package/dist/{env-jqNJdZVt.d.ts → env-CYKVNpLl.d.ts} +7 -88
- package/dist/index.d.mts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +43 -27
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +43 -27
- package/dist/index.mjs.map +1 -1
- package/dist/security-BvLXaQkv.d.mts +88 -0
- package/dist/security-BvLXaQkv.d.ts +88 -0
- package/package.json +6 -1
package/dist/auth.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { R as RateLimitStore, a as RateLimitRule, b as RateLimitOptions } from './env-
|
|
2
|
-
export {
|
|
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-
|
|
2
|
-
export {
|
|
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
|
-
|
|
1247
|
-
{
|
|
1248
|
-
|
|
1249
|
-
|
|
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
|
-
|
|
1282
|
-
{
|
|
1283
|
-
|
|
1284
|
-
|
|
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
|