@lastshotlabs/bunshot 0.0.13 → 0.0.18
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/README.md +2816 -1747
- package/dist/adapters/memoryAuth.d.ts +7 -0
- package/dist/adapters/memoryAuth.js +177 -2
- package/dist/adapters/mongoAuth.js +94 -0
- package/dist/adapters/sqliteAuth.d.ts +9 -0
- package/dist/adapters/sqliteAuth.js +190 -2
- package/dist/app.d.ts +120 -2
- package/dist/app.js +104 -4
- package/dist/entrypoints/queue.d.ts +2 -2
- package/dist/entrypoints/queue.js +1 -1
- package/dist/index.d.ts +24 -8
- package/dist/index.js +15 -5
- package/dist/lib/appConfig.d.ts +81 -0
- package/dist/lib/appConfig.js +30 -0
- package/dist/lib/authAdapter.d.ts +54 -0
- package/dist/lib/authRateLimit.d.ts +2 -0
- package/dist/lib/authRateLimit.js +4 -0
- package/dist/lib/clientIp.d.ts +14 -0
- package/dist/lib/clientIp.js +52 -0
- package/dist/lib/constants.d.ts +4 -0
- package/dist/lib/constants.js +4 -0
- package/dist/lib/context.d.ts +2 -0
- package/dist/lib/createDtoMapper.d.ts +33 -0
- package/dist/lib/createDtoMapper.js +69 -0
- package/dist/lib/crypto.d.ts +11 -0
- package/dist/lib/crypto.js +22 -0
- package/dist/lib/emailVerification.d.ts +4 -0
- package/dist/lib/emailVerification.js +20 -12
- package/dist/lib/jwt.d.ts +1 -1
- package/dist/lib/jwt.js +19 -6
- package/dist/lib/mfaChallenge.d.ts +42 -0
- package/dist/lib/mfaChallenge.js +293 -0
- package/dist/lib/oauth.d.ts +14 -1
- package/dist/lib/oauth.js +19 -1
- package/dist/lib/oauthCode.d.ts +15 -0
- package/dist/lib/oauthCode.js +90 -0
- package/dist/lib/queue.d.ts +33 -0
- package/dist/lib/queue.js +98 -0
- package/dist/lib/resetPassword.js +12 -16
- package/dist/lib/roles.d.ts +4 -0
- package/dist/lib/roles.js +27 -0
- package/dist/lib/session.d.ts +12 -0
- package/dist/lib/session.js +165 -5
- package/dist/lib/tenant.d.ts +15 -0
- package/dist/lib/tenant.js +65 -0
- package/dist/lib/ws.js +5 -1
- package/dist/lib/zodToMongoose.d.ts +38 -0
- package/dist/lib/zodToMongoose.js +84 -0
- package/dist/middleware/bearerAuth.js +4 -3
- package/dist/middleware/botProtection.js +2 -2
- package/dist/middleware/cacheResponse.d.ts +1 -0
- package/dist/middleware/cacheResponse.js +18 -3
- package/dist/middleware/cors.d.ts +2 -0
- package/dist/middleware/cors.js +22 -8
- package/dist/middleware/csrf.d.ts +18 -0
- package/dist/middleware/csrf.js +115 -0
- package/dist/middleware/rateLimit.d.ts +2 -1
- package/dist/middleware/rateLimit.js +7 -5
- package/dist/middleware/requireRole.d.ts +14 -3
- package/dist/middleware/requireRole.js +46 -6
- package/dist/middleware/tenant.d.ts +5 -0
- package/dist/middleware/tenant.js +116 -0
- package/dist/models/AuthUser.d.ts +17 -0
- package/dist/models/AuthUser.js +17 -0
- package/dist/models/TenantRole.d.ts +15 -0
- package/dist/models/TenantRole.js +23 -0
- package/dist/routes/auth.d.ts +5 -3
- package/dist/routes/auth.js +173 -30
- package/dist/routes/jobs.d.ts +2 -0
- package/dist/routes/jobs.js +270 -0
- package/dist/routes/mfa.d.ts +5 -0
- package/dist/routes/mfa.js +616 -0
- package/dist/routes/oauth.js +378 -23
- package/dist/schemas/auth.d.ts +2 -0
- package/dist/schemas/auth.js +22 -1
- package/dist/server.d.ts +6 -0
- package/dist/server.js +19 -3
- package/dist/services/auth.d.ts +18 -5
- package/dist/services/auth.js +112 -18
- package/dist/services/mfa.d.ts +84 -0
- package/dist/services/mfa.js +543 -0
- package/dist/ws/index.js +3 -2
- package/docs/sections/adding-middleware/full.md +35 -0
- package/docs/sections/adding-models/full.md +125 -0
- package/docs/sections/adding-models/overview.md +13 -0
- package/docs/sections/adding-routes/full.md +182 -0
- package/docs/sections/adding-routes/overview.md +23 -0
- package/docs/sections/auth-flow/full.md +634 -0
- package/docs/sections/auth-flow/overview.md +10 -0
- package/docs/sections/cli/full.md +30 -0
- package/docs/sections/configuration/full.md +155 -0
- package/docs/sections/configuration/overview.md +17 -0
- package/docs/sections/configuration-example/full.md +117 -0
- package/docs/sections/configuration-example/overview.md +30 -0
- package/docs/sections/documentation/full.md +171 -0
- package/docs/sections/environment-variables/full.md +55 -0
- package/docs/sections/exports/full.md +92 -0
- package/docs/sections/extending-context/full.md +59 -0
- package/docs/sections/header.md +3 -0
- package/docs/sections/installation/full.md +6 -0
- package/docs/sections/jobs/full.md +140 -0
- package/docs/sections/jobs/overview.md +15 -0
- package/docs/sections/mongodb-connections/full.md +45 -0
- package/docs/sections/mongodb-connections/overview.md +7 -0
- package/docs/sections/multi-tenancy/full.md +66 -0
- package/docs/sections/multi-tenancy/overview.md +15 -0
- package/docs/sections/oauth/full.md +189 -0
- package/docs/sections/oauth/overview.md +16 -0
- package/docs/sections/package-development/full.md +7 -0
- package/docs/sections/peer-dependencies/full.md +47 -0
- package/docs/sections/quick-start/full.md +43 -0
- package/docs/sections/response-caching/full.md +117 -0
- package/docs/sections/response-caching/overview.md +13 -0
- package/docs/sections/roles/full.md +136 -0
- package/docs/sections/roles/overview.md +12 -0
- package/docs/sections/running-without-redis/full.md +16 -0
- package/docs/sections/running-without-redis-or-mongodb/full.md +60 -0
- package/docs/sections/stack/full.md +10 -0
- package/docs/sections/websocket/full.md +101 -0
- package/docs/sections/websocket/overview.md +5 -0
- package/docs/sections/websocket-rooms/full.md +97 -0
- package/docs/sections/websocket-rooms/overview.md +5 -0
- package/package.json +30 -9
package/dist/app.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
2
2
|
import type { MiddlewareHandler } from "hono";
|
|
3
3
|
import type { AppEnv } from "./lib/context";
|
|
4
|
-
import type { PrimaryField, EmailVerificationConfig, PasswordResetConfig } from "./lib/appConfig";
|
|
4
|
+
import type { PrimaryField, EmailVerificationConfig, PasswordResetConfig, PasswordPolicyConfig, RefreshTokenConfig, MfaConfig, MfaEmailOtpConfig, MfaWebAuthnConfig } from "./lib/appConfig";
|
|
5
5
|
import type { AuthAdapter } from "./lib/authAdapter";
|
|
6
6
|
import type { OAuthProviderConfig } from "./lib/oauth";
|
|
7
7
|
type StoreType = "redis" | "mongo" | "sqlite" | "memory";
|
|
@@ -60,6 +60,9 @@ export interface OAuthConfig {
|
|
|
60
60
|
providers?: OAuthProviderConfig;
|
|
61
61
|
/** Where to redirect after a successful OAuth login. Defaults to "/" */
|
|
62
62
|
postRedirect?: string;
|
|
63
|
+
/** Allowlist of redirect URLs. If set, the postRedirect URL is validated against this list.
|
|
64
|
+
* Relative paths (e.g., "/") are always allowed. Only absolute URLs are validated. */
|
|
65
|
+
allowedRedirectUrls?: string[];
|
|
63
66
|
}
|
|
64
67
|
export interface AuthRateLimitConfig {
|
|
65
68
|
/** Max login failures per window before the account is locked. Default: 10 per 15 min. */
|
|
@@ -92,6 +95,21 @@ export interface AuthRateLimitConfig {
|
|
|
92
95
|
windowMs?: number;
|
|
93
96
|
max?: number;
|
|
94
97
|
};
|
|
98
|
+
/** Max account deletion attempts per user per window. Default: 3 per hour. */
|
|
99
|
+
deleteAccount?: {
|
|
100
|
+
windowMs?: number;
|
|
101
|
+
max?: number;
|
|
102
|
+
};
|
|
103
|
+
/** Max MFA verification attempts per IP per window. Default: 10 per 15 min. */
|
|
104
|
+
mfaVerify?: {
|
|
105
|
+
windowMs?: number;
|
|
106
|
+
max?: number;
|
|
107
|
+
};
|
|
108
|
+
/** Max MFA email OTP resend attempts per IP per window. Default: 5 per minute. */
|
|
109
|
+
mfaResend?: {
|
|
110
|
+
windowMs?: number;
|
|
111
|
+
max?: number;
|
|
112
|
+
};
|
|
95
113
|
/**
|
|
96
114
|
* Store backend for auth rate limit counters.
|
|
97
115
|
* Defaults to "redis" when Redis is enabled, otherwise "memory".
|
|
@@ -132,10 +150,40 @@ export interface AuthConfig {
|
|
|
132
150
|
* Mounts POST /auth/forgot-password and POST /auth/reset-password.
|
|
133
151
|
*/
|
|
134
152
|
passwordReset?: PasswordResetConfig;
|
|
153
|
+
/** Password strength policy for registration and reset-password.
|
|
154
|
+
* Login is intentionally lenient (min 1) so users under older policies can still sign in.
|
|
155
|
+
* Defaults: minLength=8, requireLetter=true, requireDigit=true, requireSpecial=false. */
|
|
156
|
+
passwordPolicy?: PasswordPolicyConfig;
|
|
135
157
|
/** Rate limit configuration for built-in auth endpoints. */
|
|
136
158
|
rateLimit?: AuthRateLimitConfig;
|
|
137
159
|
/** Session concurrency and metadata persistence policy. */
|
|
138
160
|
sessionPolicy?: AuthSessionPolicyConfig;
|
|
161
|
+
/** Account deletion configuration. Enables DELETE /auth/me when the adapter supports deleteUser. */
|
|
162
|
+
accountDeletion?: AccountDeletionConfig;
|
|
163
|
+
/**
|
|
164
|
+
* Refresh token configuration. When set, login/register return short-lived access tokens
|
|
165
|
+
* (default 15 min) alongside long-lived refresh tokens (default 30 days). Mounts POST /auth/refresh.
|
|
166
|
+
* When not configured, the existing 7-day JWT behavior is unchanged.
|
|
167
|
+
*/
|
|
168
|
+
refreshTokens?: RefreshTokenConfig;
|
|
169
|
+
/**
|
|
170
|
+
* MFA/TOTP configuration. When set, enables MFA setup/verify/disable routes under /auth/mfa/*.
|
|
171
|
+
* Login returns { mfaRequired: true, mfaToken } when MFA is enabled for the user.
|
|
172
|
+
* OAuth logins skip MFA (the OAuth provider is treated as the second factor).
|
|
173
|
+
*/
|
|
174
|
+
mfa?: MfaConfig;
|
|
175
|
+
}
|
|
176
|
+
export interface AccountDeletionConfig {
|
|
177
|
+
/** Called before deletion. Throw to abort (e.g., active subscription check). */
|
|
178
|
+
onBeforeDelete?: (userId: string) => Promise<void>;
|
|
179
|
+
/** Called after auth data is deleted. Runs at execution time — query current state, not a snapshot. */
|
|
180
|
+
onAfterDelete?: (userId: string) => Promise<void>;
|
|
181
|
+
/** When true, deletion is queued as a BullMQ job instead of running synchronously. Requires Redis + BullMQ. */
|
|
182
|
+
queued?: boolean;
|
|
183
|
+
/** Grace period in seconds before queued deletion executes. Default: 0 (immediate). */
|
|
184
|
+
gracePeriod?: number;
|
|
185
|
+
/** Called when deletion is scheduled (queued + gracePeriod > 0). Use to send a confirmation/cancel email. */
|
|
186
|
+
onDeletionScheduled?: (userId: string, email: string, cancelToken: string) => Promise<void>;
|
|
139
187
|
}
|
|
140
188
|
export interface AuthSessionPolicyConfig {
|
|
141
189
|
/** Max simultaneous active sessions per user. Oldest is evicted when exceeded. Default: 6. */
|
|
@@ -156,7 +204,7 @@ export interface AuthSessionPolicyConfig {
|
|
|
156
204
|
*/
|
|
157
205
|
trackLastActive?: boolean;
|
|
158
206
|
}
|
|
159
|
-
export type { PrimaryField, EmailVerificationConfig, PasswordResetConfig };
|
|
207
|
+
export type { PrimaryField, EmailVerificationConfig, PasswordResetConfig, RefreshTokenConfig, MfaConfig, MfaEmailOtpConfig, MfaWebAuthnConfig };
|
|
160
208
|
export interface BotProtectionConfig {
|
|
161
209
|
/**
|
|
162
210
|
* List of IPv4 CIDRs (e.g. "198.51.100.0/24"), IPv4 addresses, or IPv6 addresses to block outright.
|
|
@@ -172,9 +220,23 @@ export interface BotProtectionConfig {
|
|
|
172
220
|
*/
|
|
173
221
|
fingerprintRateLimit?: boolean;
|
|
174
222
|
}
|
|
223
|
+
export interface CsrfConfig {
|
|
224
|
+
/** Enable CSRF protection for cookie-authenticated state-changing requests. */
|
|
225
|
+
enabled: boolean;
|
|
226
|
+
/** Paths exempt from CSRF checks (in addition to built-in OAuth callback exemptions). Uses prefix matching when path ends with "*". */
|
|
227
|
+
exemptPaths?: string[];
|
|
228
|
+
/** Also validate Origin header against CORS origins. Default: true. */
|
|
229
|
+
checkOrigin?: boolean;
|
|
230
|
+
}
|
|
175
231
|
export interface SecurityConfig {
|
|
176
232
|
/** CORS origins. Defaults to "*" */
|
|
177
233
|
cors?: string | string[];
|
|
234
|
+
/** Additional security headers to set via Hono's secureHeaders middleware.
|
|
235
|
+
* Pass a Content-Security-Policy, Permissions-Policy, etc. */
|
|
236
|
+
headers?: {
|
|
237
|
+
contentSecurityPolicy?: string;
|
|
238
|
+
permissionsPolicy?: string;
|
|
239
|
+
};
|
|
178
240
|
/** Global rate limit. Defaults to 100 req / 60s */
|
|
179
241
|
rateLimit?: {
|
|
180
242
|
windowMs: number;
|
|
@@ -193,6 +255,18 @@ export interface SecurityConfig {
|
|
|
193
255
|
* Runs before IP rate limiting so blocked IPs are rejected immediately.
|
|
194
256
|
*/
|
|
195
257
|
botProtection?: BotProtectionConfig;
|
|
258
|
+
/**
|
|
259
|
+
* Trusted proxy configuration for IP extraction.
|
|
260
|
+
* - `false` (default): use socket-level IP only, ignore X-Forwarded-For entirely.
|
|
261
|
+
* - A number N: trust N proxy hops — take the Nth-from-right IP in the X-Forwarded-For chain.
|
|
262
|
+
*/
|
|
263
|
+
trustProxy?: false | number;
|
|
264
|
+
/**
|
|
265
|
+
* CSRF protection for cookie-based auth. Opt-in.
|
|
266
|
+
* Uses signed double-submit cookie pattern with HMAC-SHA256.
|
|
267
|
+
* Only validates when the auth cookie is present on state-changing requests.
|
|
268
|
+
*/
|
|
269
|
+
csrf?: CsrfConfig;
|
|
196
270
|
}
|
|
197
271
|
export interface ModelSchemasConfig {
|
|
198
272
|
/**
|
|
@@ -217,6 +291,46 @@ export interface ModelSchemasConfig {
|
|
|
217
291
|
*/
|
|
218
292
|
registration?: "auto" | "explicit";
|
|
219
293
|
}
|
|
294
|
+
export interface JobsConfig {
|
|
295
|
+
/** Enable the job status endpoint. Default: false. */
|
|
296
|
+
statusEndpoint?: boolean;
|
|
297
|
+
/**
|
|
298
|
+
* Auth protection for job endpoints.
|
|
299
|
+
* - `"userAuth"` — requires authenticated user session (cookie/token).
|
|
300
|
+
* - `"none"` — no auth (not recommended for production).
|
|
301
|
+
* - `MiddlewareHandler[]` — custom middleware stack (e.g., `[userAuth, requireRole("admin")]`).
|
|
302
|
+
*
|
|
303
|
+
* Default: `"none"`. You must explicitly configure auth.
|
|
304
|
+
*/
|
|
305
|
+
auth?: "userAuth" | "none" | import("hono").MiddlewareHandler<AppEnv>[];
|
|
306
|
+
/** Required roles for accessing job endpoints. Only works when auth includes userAuth. */
|
|
307
|
+
roles?: string[];
|
|
308
|
+
/** Whitelist of queue names exposed. Default: [] (nothing exposed). */
|
|
309
|
+
allowedQueues?: string[];
|
|
310
|
+
/** When using userAuth, restrict job visibility to the user who created it. Default: false. */
|
|
311
|
+
scopeToUser?: boolean;
|
|
312
|
+
}
|
|
313
|
+
export interface TenantConfig {
|
|
314
|
+
[key: string]: unknown;
|
|
315
|
+
}
|
|
316
|
+
export interface TenancyConfig {
|
|
317
|
+
/** How tenant is identified. */
|
|
318
|
+
resolution: "header" | "subdomain" | "path";
|
|
319
|
+
/** Header name when resolution is "header". Default: "x-tenant-id". */
|
|
320
|
+
headerName?: string;
|
|
321
|
+
/** Path segment index when resolution is "path". Default: 0. */
|
|
322
|
+
pathSegment?: number;
|
|
323
|
+
/** Callback to validate/load tenant. Return null to reject. */
|
|
324
|
+
onResolve?: (tenantId: string) => Promise<TenantConfig | null>;
|
|
325
|
+
/** TTL in ms for caching onResolve results (LRU cache). Default: 60_000. Set 0 to disable. */
|
|
326
|
+
cacheTtlMs?: number;
|
|
327
|
+
/** Max entries in tenant resolution cache. Default: 500. */
|
|
328
|
+
cacheMaxSize?: number;
|
|
329
|
+
/** Paths that skip tenant resolution. Uses startsWith matching. Default: ["/health", "/docs", "/openapi.json"]. */
|
|
330
|
+
exemptPaths?: string[];
|
|
331
|
+
/** HTTP status when onResolve returns null. Default: 403. */
|
|
332
|
+
rejectionStatus?: 403 | 404;
|
|
333
|
+
}
|
|
220
334
|
export interface CreateAppConfig {
|
|
221
335
|
/** Absolute path to the service's routes directory (use import.meta.dir + "/routes") */
|
|
222
336
|
routesDir: string;
|
|
@@ -237,5 +351,9 @@ export interface CreateAppConfig {
|
|
|
237
351
|
middleware?: MiddlewareHandler<AppEnv>[];
|
|
238
352
|
/** Database connection and store routing configuration */
|
|
239
353
|
db?: DbConfig;
|
|
354
|
+
/** Job status endpoint configuration. Requires BullMQ + Redis. */
|
|
355
|
+
jobs?: JobsConfig;
|
|
356
|
+
/** Multi-tenancy configuration. When set, tenant middleware resolves tenant on each request. */
|
|
357
|
+
tenancy?: TenancyConfig;
|
|
240
358
|
}
|
|
241
359
|
export declare const createApp: (config: CreateAppConfig) => Promise<OpenAPIHono<AppEnv>>;
|
package/dist/app.js
CHANGED
|
@@ -7,8 +7,8 @@ import { HttpError } from "./lib/HttpError";
|
|
|
7
7
|
import { rateLimit } from "./middleware/rateLimit";
|
|
8
8
|
import { bearerAuth } from "./middleware/bearerAuth";
|
|
9
9
|
import { identify } from "./middleware/identify";
|
|
10
|
-
import { HEADER_USER_TOKEN } from "./lib/constants";
|
|
11
|
-
import { setAppName, setAppRoles, setDefaultRole, setPrimaryField, setEmailVerificationConfig, setPasswordResetConfig, setMaxSessions, setPersistSessionMetadata, setIncludeInactiveSessions, setTrackLastActive } from "./lib/appConfig";
|
|
10
|
+
import { HEADER_USER_TOKEN, HEADER_REFRESH_TOKEN, HEADER_CSRF_TOKEN } from "./lib/constants";
|
|
11
|
+
import { setAppName, setAppRoles, setDefaultRole, setPrimaryField, setEmailVerificationConfig, setPasswordResetConfig, setPasswordPolicy, setMaxSessions, setPersistSessionMetadata, setIncludeInactiveSessions, setTrackLastActive, setRefreshTokenConfig, setMfaConfig, setCsrfEnabled } from "./lib/appConfig";
|
|
12
12
|
import { setEmailVerificationStore } from "./lib/emailVerification";
|
|
13
13
|
import { setPasswordResetStore } from "./lib/resetPassword";
|
|
14
14
|
import { setAuthRateLimitStore } from "./lib/authRateLimit";
|
|
@@ -16,6 +16,7 @@ import { setAuthAdapter } from "./lib/authAdapter";
|
|
|
16
16
|
import { mongoAuthAdapter } from "./adapters/mongoAuth";
|
|
17
17
|
import { memoryAuthAdapter } from "./adapters/memoryAuth";
|
|
18
18
|
import { initOAuthProviders, getConfiguredOAuthProviders, setOAuthStateStore } from "./lib/oauth";
|
|
19
|
+
import { setOAuthCodeStore } from "./lib/oauthCode";
|
|
19
20
|
import { createOAuthRouter } from "./routes/oauth";
|
|
20
21
|
import { connectMongo, connectAuthMongo, connectAppMongo } from "./lib/mongo";
|
|
21
22
|
import { connectRedis } from "./lib/redis";
|
|
@@ -26,7 +27,13 @@ export const createApp = async (config) => {
|
|
|
26
27
|
const { routesDir, app: appConfig = {}, auth: authConfig = {}, security: securityConfig = {}, middleware = [], db = {}, } = config;
|
|
27
28
|
const appName = appConfig.name ?? "Bun Core API";
|
|
28
29
|
const openApiVersion = appConfig.version ?? "1.0.0";
|
|
30
|
+
// Trust-proxy for IP extraction
|
|
31
|
+
const { setTrustProxy } = await import("./lib/clientIp");
|
|
32
|
+
setTrustProxy(securityConfig.trustProxy ?? false);
|
|
29
33
|
const corsOrigins = securityConfig.cors ?? "*";
|
|
34
|
+
if (corsOrigins === "*" && process.env.NODE_ENV === "production") {
|
|
35
|
+
console.warn("[security] CORS is set to wildcard (*) in production. Configure security.cors with specific origins to restrict cross-origin access.");
|
|
36
|
+
}
|
|
30
37
|
const rlConfig = securityConfig.rateLimit ?? { windowMs: 60_000, max: 100 };
|
|
31
38
|
const botCfg = securityConfig.botProtection ?? {};
|
|
32
39
|
const enableBearerAuth = securityConfig.bearerAuth !== false;
|
|
@@ -37,6 +44,29 @@ export const createApp = async (config) => {
|
|
|
37
44
|
const explicitAuthAdapter = authConfig.adapter;
|
|
38
45
|
const oauthProviders = authConfig.oauth?.providers;
|
|
39
46
|
const postOAuthRedirect = authConfig.oauth?.postRedirect ?? "/";
|
|
47
|
+
const allowedRedirectUrls = authConfig.oauth?.allowedRedirectUrls;
|
|
48
|
+
// Validate postRedirect against allowlist at startup (not per-request)
|
|
49
|
+
if (allowedRedirectUrls && postOAuthRedirect !== "/") {
|
|
50
|
+
try {
|
|
51
|
+
const redirectUrl = new URL(postOAuthRedirect);
|
|
52
|
+
const allowed = allowedRedirectUrls.some((u) => {
|
|
53
|
+
try {
|
|
54
|
+
return new URL(u).origin === redirectUrl.origin;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
if (!allowed) {
|
|
61
|
+
throw new Error(`createApp: oauth.postRedirect "${postOAuthRedirect}" is not in the allowedRedirectUrls list. Add its origin to oauth.allowedRedirectUrls.`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
if (e instanceof Error && e.message.startsWith("createApp:"))
|
|
66
|
+
throw e;
|
|
67
|
+
// Relative path — always allowed
|
|
68
|
+
}
|
|
69
|
+
}
|
|
40
70
|
const roles = authConfig.roles ?? [];
|
|
41
71
|
const defaultRole = authConfig.defaultRole;
|
|
42
72
|
const primaryField = authConfig.primaryField ?? "email";
|
|
@@ -63,6 +93,7 @@ export const createApp = async (config) => {
|
|
|
63
93
|
}
|
|
64
94
|
setSessionStore(sessions);
|
|
65
95
|
setOAuthStateStore(oauthState);
|
|
96
|
+
setOAuthCodeStore(oauthState);
|
|
66
97
|
setCacheStore(cache);
|
|
67
98
|
if (mongo === "single")
|
|
68
99
|
await connectMongo();
|
|
@@ -104,12 +135,15 @@ export const createApp = async (config) => {
|
|
|
104
135
|
setEmailVerificationConfig(emailVerification ?? null);
|
|
105
136
|
setEmailVerificationStore(sessions);
|
|
106
137
|
setPasswordResetConfig(passwordReset ?? null);
|
|
138
|
+
setPasswordPolicy(authConfig.passwordPolicy ?? {});
|
|
107
139
|
setPasswordResetStore(sessions);
|
|
108
140
|
setAuthRateLimitStore(authRateLimit?.store ?? (enableRedis ? "redis" : "memory"));
|
|
109
141
|
setMaxSessions(sessionPolicy.maxSessions ?? 6);
|
|
110
142
|
setPersistSessionMetadata(sessionPolicy.persistSessionMetadata ?? true);
|
|
111
143
|
setIncludeInactiveSessions(sessionPolicy.includeInactiveSessions ?? false);
|
|
112
144
|
setTrackLastActive(sessionPolicy.trackLastActive ?? false);
|
|
145
|
+
setRefreshTokenConfig(authConfig.refreshTokens ?? null);
|
|
146
|
+
setMfaConfig(authConfig.mfa ?? null);
|
|
113
147
|
if (oauthProviders)
|
|
114
148
|
initOAuthProviders(oauthProviders);
|
|
115
149
|
const configuredOAuth = getConfiguredOAuthProviders();
|
|
@@ -124,8 +158,26 @@ export const createApp = async (config) => {
|
|
|
124
158
|
const bearerAuthBypass = [...DEFAULT_BYPASS, ...oauthBypass, ...extraBypass];
|
|
125
159
|
const app = new OpenAPIHono();
|
|
126
160
|
app.use(logger());
|
|
161
|
+
const headerOpts = {};
|
|
162
|
+
if (securityConfig.headers?.contentSecurityPolicy) {
|
|
163
|
+
headerOpts["Content-Security-Policy"] = securityConfig.headers.contentSecurityPolicy;
|
|
164
|
+
}
|
|
165
|
+
if (securityConfig.headers?.permissionsPolicy) {
|
|
166
|
+
headerOpts["Permissions-Policy"] = securityConfig.headers.permissionsPolicy;
|
|
167
|
+
}
|
|
127
168
|
app.use(secureHeaders());
|
|
128
|
-
|
|
169
|
+
if (Object.keys(headerOpts).length > 0) {
|
|
170
|
+
app.use(async (c, next) => {
|
|
171
|
+
await next();
|
|
172
|
+
for (const [k, v] of Object.entries(headerOpts)) {
|
|
173
|
+
c.res.headers.set(k, v);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
const corsAllowHeaders = ["Content-Type", "Authorization", HEADER_USER_TOKEN, HEADER_REFRESH_TOKEN];
|
|
178
|
+
if (securityConfig.csrf?.enabled)
|
|
179
|
+
corsAllowHeaders.push(HEADER_CSRF_TOKEN);
|
|
180
|
+
app.use(cors({ origin: corsOrigins, allowHeaders: corsAllowHeaders, exposeHeaders: ["x-cache"], credentials: true }));
|
|
129
181
|
if ((botCfg.blockList?.length ?? 0) > 0) {
|
|
130
182
|
const { botProtection } = await import("./middleware/botProtection");
|
|
131
183
|
app.use(botProtection({ blockList: botCfg.blockList }));
|
|
@@ -141,6 +193,36 @@ export const createApp = async (config) => {
|
|
|
141
193
|
});
|
|
142
194
|
}
|
|
143
195
|
app.use(identify);
|
|
196
|
+
// CSRF protection (after identify so we can check for auth cookie presence)
|
|
197
|
+
if (securityConfig.csrf?.enabled) {
|
|
198
|
+
setCsrfEnabled(true);
|
|
199
|
+
const { csrfProtection } = await import("./middleware/csrf");
|
|
200
|
+
const csrfExemptPaths = [
|
|
201
|
+
...oauthBypass.filter(p => p.includes("/callback")),
|
|
202
|
+
...(securityConfig.csrf.exemptPaths ?? []),
|
|
203
|
+
];
|
|
204
|
+
app.use(csrfProtection({
|
|
205
|
+
exemptPaths: csrfExemptPaths,
|
|
206
|
+
checkOrigin: securityConfig.csrf.checkOrigin ?? true,
|
|
207
|
+
allowedOrigins: corsOrigins,
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
// Tenant resolution middleware (after identify, before user middleware + routes)
|
|
211
|
+
if (config.tenancy) {
|
|
212
|
+
if (!config.tenancy.onResolve) {
|
|
213
|
+
if (process.env.NODE_ENV === "production") {
|
|
214
|
+
throw new Error("[security] Tenancy is configured without an onResolve callback. " +
|
|
215
|
+
"In production, onResolve is required to validate tenant IDs and prevent cross-tenant access. " +
|
|
216
|
+
"Provide tenancy.onResolve or remove the tenancy config.");
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
console.warn("[security] Tenancy is configured without an onResolve callback — " +
|
|
220
|
+
"tenant IDs will be trusted without validation. This is unsafe in production.");
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const { createTenantMiddleware } = await import("./middleware/tenant");
|
|
224
|
+
app.use(createTenantMiddleware(config.tenancy));
|
|
225
|
+
}
|
|
144
226
|
for (const mw of middleware)
|
|
145
227
|
app.use(mw);
|
|
146
228
|
setAppName(appName);
|
|
@@ -188,17 +270,35 @@ export const createApp = async (config) => {
|
|
|
188
270
|
continue; // mounted separately below via createAuthRouter
|
|
189
271
|
if (file === "oauth.ts")
|
|
190
272
|
continue; // mounted separately below
|
|
273
|
+
if (file === "mfa.ts")
|
|
274
|
+
continue; // mounted separately below when mfa is configured
|
|
275
|
+
if (file === "jobs.ts")
|
|
276
|
+
continue; // mounted separately below when jobs.statusEndpoint is true
|
|
191
277
|
const mod = await import(`${coreRoutesDir}/${file}`);
|
|
192
278
|
if (mod.router)
|
|
193
279
|
app.route("/", mod.router);
|
|
194
280
|
}
|
|
195
281
|
if (enableAuthRoutes) {
|
|
196
282
|
const { createAuthRouter } = await import(`${coreRoutesDir}/auth`);
|
|
197
|
-
app.route("/", createAuthRouter({ primaryField, emailVerification, passwordReset, rateLimit: authRateLimit }));
|
|
283
|
+
app.route("/", createAuthRouter({ primaryField, emailVerification, passwordReset, rateLimit: authRateLimit, accountDeletion: authConfig.accountDeletion, refreshTokens: authConfig.refreshTokens }));
|
|
198
284
|
}
|
|
199
285
|
if (configuredOAuth.length > 0) {
|
|
200
286
|
app.route("/", createOAuthRouter(configuredOAuth, postOAuthRedirect));
|
|
201
287
|
}
|
|
288
|
+
if (authConfig.mfa && enableAuthRoutes) {
|
|
289
|
+
const { setMfaChallengeStore, setMfaChallengeSqliteDb } = await import("./lib/mfaChallenge");
|
|
290
|
+
setMfaChallengeStore(sessions);
|
|
291
|
+
if (sessions === "sqlite") {
|
|
292
|
+
const { getDb } = await import("./adapters/sqliteAuth");
|
|
293
|
+
setMfaChallengeSqliteDb(getDb());
|
|
294
|
+
}
|
|
295
|
+
const { createMfaRouter } = await import(`${coreRoutesDir}/mfa`);
|
|
296
|
+
app.route("/", createMfaRouter({ rateLimit: authRateLimit }));
|
|
297
|
+
}
|
|
298
|
+
if (config.jobs?.statusEndpoint) {
|
|
299
|
+
const { createJobsRouter } = await import(`${coreRoutesDir}/jobs`);
|
|
300
|
+
app.route("/", createJobsRouter(config.jobs));
|
|
301
|
+
}
|
|
202
302
|
// Service routes — collect all, sort by optional exported `priority`, then mount
|
|
203
303
|
const serviceGlob = new Bun.Glob("**/*.ts");
|
|
204
304
|
const serviceFiles = [];
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { createQueue, createWorker } from "../lib/queue";
|
|
2
|
-
export type { Job } from "../lib/queue";
|
|
1
|
+
export { createQueue, createWorker, createCronWorker, cleanupStaleSchedulers, getRegisteredCronNames, createDLQHandler } from "../lib/queue";
|
|
2
|
+
export type { Job, CronSchedule, DLQOptions } from "../lib/queue";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { createQueue, createWorker } from "../lib/queue";
|
|
1
|
+
export { createQueue, createWorker, createCronWorker, cleanupStaleSchedulers, getRegisteredCronNames, createDLQHandler } from "../lib/queue";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,22 +1,33 @@
|
|
|
1
1
|
export { createApp } from "./app";
|
|
2
2
|
export { createServer } from "./server";
|
|
3
|
-
export type { CreateAppConfig, ModelSchemasConfig, DbConfig, AppMeta, AuthConfig, AuthRateLimitConfig, OAuthConfig, SecurityConfig, BotProtectionConfig, PrimaryField, EmailVerificationConfig, PasswordResetConfig } from "./app";
|
|
3
|
+
export type { CreateAppConfig, ModelSchemasConfig, DbConfig, AppMeta, AuthConfig, AuthRateLimitConfig, AccountDeletionConfig, OAuthConfig, SecurityConfig, CsrfConfig, BotProtectionConfig, PrimaryField, EmailVerificationConfig, PasswordResetConfig, RefreshTokenConfig, MfaConfig, MfaEmailOtpConfig, MfaWebAuthnConfig, JobsConfig, TenancyConfig, TenantConfig } from "./app";
|
|
4
|
+
export type { PasswordPolicyConfig } from "./lib/appConfig";
|
|
4
5
|
export type { CreateServerConfig, WsConfig } from "./server";
|
|
5
6
|
export { appConnection, authConnection, mongoose, connectMongo, connectAuthMongo, connectAppMongo, disconnectMongo } from "./lib/mongo";
|
|
6
7
|
export { connectRedis, disconnectRedis, getRedis } from "./lib/redis";
|
|
7
8
|
export { getAppRoles } from "./lib/appConfig";
|
|
8
9
|
export { HttpError } from "./lib/HttpError";
|
|
9
|
-
export { COOKIE_TOKEN, HEADER_USER_TOKEN } from "./lib/constants";
|
|
10
|
+
export { COOKIE_TOKEN, HEADER_USER_TOKEN, COOKIE_REFRESH_TOKEN, HEADER_REFRESH_TOKEN, COOKIE_CSRF_TOKEN, HEADER_CSRF_TOKEN } from "./lib/constants";
|
|
10
11
|
export { createRouter } from "./lib/context";
|
|
11
12
|
export { createRoute, withSecurity, registerSchema, registerSchemas } from "./lib/createRoute";
|
|
13
|
+
export { zodToMongoose } from "./lib/zodToMongoose";
|
|
14
|
+
export type { ZodToMongooseConfig, ZodToMongooseRefConfig } from "./lib/zodToMongoose";
|
|
15
|
+
export { createDtoMapper } from "./lib/createDtoMapper";
|
|
16
|
+
export type { DtoMapperConfig } from "./lib/createDtoMapper";
|
|
12
17
|
export type { AppEnv, AppVariables } from "./lib/context";
|
|
13
18
|
export { signToken, verifyToken } from "./lib/jwt";
|
|
14
19
|
export { log } from "./lib/logger";
|
|
15
20
|
export { createResetToken, consumeResetToken, setPasswordResetStore } from "./lib/resetPassword";
|
|
16
|
-
export {
|
|
17
|
-
export
|
|
21
|
+
export { timingSafeEqual, sha256 } from "./lib/crypto";
|
|
22
|
+
export { getClientIp, setTrustProxy } from "./lib/clientIp";
|
|
23
|
+
export { storeOAuthCode, consumeOAuthCode, setOAuthCodeStore } from "./lib/oauthCode";
|
|
24
|
+
export type { OAuthCodePayload } from "./lib/oauthCode";
|
|
25
|
+
export { createSession, getSession, deleteSession, getUserSessions, getActiveSessionCount, evictOldestSession, updateSessionLastActive, setSessionStore, deleteUserSessions, setRefreshToken, getSessionByRefreshToken, rotateRefreshToken } from "./lib/session";
|
|
26
|
+
export type { SessionMetadata, SessionInfo, RefreshResult } from "./lib/session";
|
|
18
27
|
export { createVerificationToken, getVerificationToken, deleteVerificationToken } from "./lib/emailVerification";
|
|
19
|
-
export {
|
|
28
|
+
export { createMfaChallenge, consumeMfaChallenge, replaceMfaChallengeOtp, setMfaChallengeStore, createWebAuthnRegistrationChallenge, consumeWebAuthnRegistrationChallenge, clearMemoryMfaChallenges } from "./lib/mfaChallenge";
|
|
29
|
+
export type { MfaChallengeData, MfaChallengeOptions, MfaChallengePurpose } from "./lib/mfaChallenge";
|
|
30
|
+
export { bustAuthLimit, trackAttempt, isLimited, clearMemoryRateLimitStore } from "./lib/authRateLimit";
|
|
20
31
|
export type { LimitOpts } from "./lib/authRateLimit";
|
|
21
32
|
export { validate } from "./lib/validate";
|
|
22
33
|
export { bearerAuth } from "./middleware/bearerAuth";
|
|
@@ -28,13 +39,18 @@ export type { RateLimitOptions } from "./middleware/rateLimit";
|
|
|
28
39
|
export { userAuth } from "./middleware/userAuth";
|
|
29
40
|
export { requireRole } from "./middleware/requireRole";
|
|
30
41
|
export { requireVerifiedEmail } from "./middleware/requireVerifiedEmail";
|
|
31
|
-
export {
|
|
42
|
+
export { csrfProtection, refreshCsrfToken, clearCsrfToken } from "./middleware/csrf";
|
|
43
|
+
export type { CsrfMiddlewareOptions } from "./middleware/csrf";
|
|
44
|
+
export { cacheResponse, bustCache, bustCachePattern, setCacheStore, getCacheModel } from "./middleware/cacheResponse";
|
|
32
45
|
export { buildFingerprint } from "./lib/fingerprint";
|
|
33
46
|
export { sqliteAuthAdapter, setSqliteDb, startSqliteCleanup } from "./adapters/sqliteAuth";
|
|
34
47
|
export { memoryAuthAdapter, clearMemoryStore } from "./adapters/memoryAuth";
|
|
35
|
-
export { setUserRoles, addUserRole, removeUserRole } from "./lib/roles";
|
|
36
|
-
export type { AuthAdapter, OAuthProfile } from "./lib/authAdapter";
|
|
48
|
+
export { setUserRoles, addUserRole, removeUserRole, getTenantRoles, setTenantRoles, addTenantRole, removeTenantRole } from "./lib/roles";
|
|
49
|
+
export type { AuthAdapter, OAuthProfile, WebAuthnCredential } from "./lib/authAdapter";
|
|
37
50
|
export type { OAuthProviderConfig } from "./lib/oauth";
|
|
38
51
|
export { websocket, createWsUpgradeHandler } from "./ws/index";
|
|
39
52
|
export type { SocketData } from "./ws/index";
|
|
40
53
|
export { publish, subscribe, unsubscribe, getSubscriptions, handleRoomActions, getRooms, getRoomSubscribers } from "./lib/ws";
|
|
54
|
+
export { createTenant, deleteTenant, getTenant, listTenants } from "./lib/tenant";
|
|
55
|
+
export type { TenantInfo, CreateTenantOptions } from "./lib/tenant";
|
|
56
|
+
export { invalidateTenantCache } from "./middleware/tenant";
|
package/dist/index.js
CHANGED
|
@@ -7,15 +7,21 @@ export { connectRedis, disconnectRedis, getRedis } from "./lib/redis";
|
|
|
7
7
|
// Lib utilities
|
|
8
8
|
export { getAppRoles } from "./lib/appConfig";
|
|
9
9
|
export { HttpError } from "./lib/HttpError";
|
|
10
|
-
export { COOKIE_TOKEN, HEADER_USER_TOKEN } from "./lib/constants";
|
|
10
|
+
export { COOKIE_TOKEN, HEADER_USER_TOKEN, COOKIE_REFRESH_TOKEN, HEADER_REFRESH_TOKEN, COOKIE_CSRF_TOKEN, HEADER_CSRF_TOKEN } from "./lib/constants";
|
|
11
11
|
export { createRouter } from "./lib/context";
|
|
12
12
|
export { createRoute, withSecurity, registerSchema, registerSchemas } from "./lib/createRoute";
|
|
13
|
+
export { zodToMongoose } from "./lib/zodToMongoose";
|
|
14
|
+
export { createDtoMapper } from "./lib/createDtoMapper";
|
|
13
15
|
export { signToken, verifyToken } from "./lib/jwt";
|
|
14
16
|
export { log } from "./lib/logger";
|
|
15
17
|
export { createResetToken, consumeResetToken, setPasswordResetStore } from "./lib/resetPassword";
|
|
16
|
-
export {
|
|
18
|
+
export { timingSafeEqual, sha256 } from "./lib/crypto";
|
|
19
|
+
export { getClientIp, setTrustProxy } from "./lib/clientIp";
|
|
20
|
+
export { storeOAuthCode, consumeOAuthCode, setOAuthCodeStore } from "./lib/oauthCode";
|
|
21
|
+
export { createSession, getSession, deleteSession, getUserSessions, getActiveSessionCount, evictOldestSession, updateSessionLastActive, setSessionStore, deleteUserSessions, setRefreshToken, getSessionByRefreshToken, rotateRefreshToken } from "./lib/session";
|
|
17
22
|
export { createVerificationToken, getVerificationToken, deleteVerificationToken } from "./lib/emailVerification";
|
|
18
|
-
export {
|
|
23
|
+
export { createMfaChallenge, consumeMfaChallenge, replaceMfaChallengeOtp, setMfaChallengeStore, createWebAuthnRegistrationChallenge, consumeWebAuthnRegistrationChallenge, clearMemoryMfaChallenges } from "./lib/mfaChallenge";
|
|
24
|
+
export { bustAuthLimit, trackAttempt, isLimited, clearMemoryRateLimitStore } from "./lib/authRateLimit";
|
|
19
25
|
export { validate } from "./lib/validate";
|
|
20
26
|
// Middleware
|
|
21
27
|
export { bearerAuth } from "./middleware/bearerAuth";
|
|
@@ -25,13 +31,17 @@ export { rateLimit } from "./middleware/rateLimit";
|
|
|
25
31
|
export { userAuth } from "./middleware/userAuth";
|
|
26
32
|
export { requireRole } from "./middleware/requireRole";
|
|
27
33
|
export { requireVerifiedEmail } from "./middleware/requireVerifiedEmail";
|
|
28
|
-
export {
|
|
34
|
+
export { csrfProtection, refreshCsrfToken, clearCsrfToken } from "./middleware/csrf";
|
|
35
|
+
export { cacheResponse, bustCache, bustCachePattern, setCacheStore, getCacheModel } from "./middleware/cacheResponse";
|
|
29
36
|
// Lib utilities (bot protection)
|
|
30
37
|
export { buildFingerprint } from "./lib/fingerprint";
|
|
31
38
|
// Models
|
|
32
39
|
export { sqliteAuthAdapter, setSqliteDb, startSqliteCleanup } from "./adapters/sqliteAuth";
|
|
33
40
|
export { memoryAuthAdapter, clearMemoryStore } from "./adapters/memoryAuth";
|
|
34
|
-
export { setUserRoles, addUserRole, removeUserRole } from "./lib/roles";
|
|
41
|
+
export { setUserRoles, addUserRole, removeUserRole, getTenantRoles, setTenantRoles, addTenantRole, removeTenantRole } from "./lib/roles";
|
|
35
42
|
// WebSocket
|
|
36
43
|
export { websocket, createWsUpgradeHandler } from "./ws/index";
|
|
37
44
|
export { publish, subscribe, unsubscribe, getSubscriptions, handleRoomActions, getRooms, getRoomSubscribers } from "./lib/ws";
|
|
45
|
+
// Tenancy
|
|
46
|
+
export { createTenant, deleteTenant, getTenant, listTenants } from "./lib/tenant";
|
|
47
|
+
export { invalidateTenantCache } from "./middleware/tenant";
|
package/dist/lib/appConfig.d.ts
CHANGED
|
@@ -13,6 +13,16 @@ export interface PasswordResetConfig {
|
|
|
13
13
|
/** Called with the user's email and the reset token. Use to send the reset email. */
|
|
14
14
|
onSend: (email: string, token: string) => Promise<void>;
|
|
15
15
|
}
|
|
16
|
+
export interface PasswordPolicyConfig {
|
|
17
|
+
/** Minimum password length. Defaults to 8. */
|
|
18
|
+
minLength?: number;
|
|
19
|
+
/** Require at least one letter (a–z or A–Z). Defaults to true. */
|
|
20
|
+
requireLetter?: boolean;
|
|
21
|
+
/** Require at least one digit (0–9). Defaults to true. */
|
|
22
|
+
requireDigit?: boolean;
|
|
23
|
+
/** Require at least one special character. Defaults to false. */
|
|
24
|
+
requireSpecial?: boolean;
|
|
25
|
+
}
|
|
16
26
|
export declare const setAppName: (name: string) => void;
|
|
17
27
|
export declare const getAppName: () => string;
|
|
18
28
|
export declare const setAppRoles: (roles: string[]) => void;
|
|
@@ -26,6 +36,8 @@ export declare const getEmailVerificationConfig: () => EmailVerificationConfig |
|
|
|
26
36
|
export declare const getTokenExpiry: () => number;
|
|
27
37
|
export declare const setPasswordResetConfig: (config: PasswordResetConfig | null) => void;
|
|
28
38
|
export declare const getPasswordResetConfig: () => PasswordResetConfig | null;
|
|
39
|
+
export declare const setPasswordPolicy: (config: PasswordPolicyConfig) => void;
|
|
40
|
+
export declare const getPasswordPolicy: () => PasswordPolicyConfig;
|
|
29
41
|
export declare const getResetTokenExpiry: () => number;
|
|
30
42
|
export declare const setMaxSessions: (n: number) => void;
|
|
31
43
|
export declare const getMaxSessions: () => number;
|
|
@@ -35,3 +47,72 @@ export declare const setIncludeInactiveSessions: (v: boolean) => void;
|
|
|
35
47
|
export declare const getIncludeInactiveSessions: () => boolean;
|
|
36
48
|
export declare const setTrackLastActive: (v: boolean) => void;
|
|
37
49
|
export declare const getTrackLastActive: () => boolean;
|
|
50
|
+
export interface RefreshTokenConfig {
|
|
51
|
+
/** Access token expiry in seconds. Default: 900 (15 min). */
|
|
52
|
+
accessTokenExpiry?: number;
|
|
53
|
+
/** Refresh token expiry in seconds. Default: 2_592_000 (30 days). */
|
|
54
|
+
refreshTokenExpiry?: number;
|
|
55
|
+
/** Grace window in seconds where the old refresh token still works after rotation.
|
|
56
|
+
* Prevents lockout when the client's network drops mid-refresh. Default: 30. */
|
|
57
|
+
rotationGraceSeconds?: number;
|
|
58
|
+
}
|
|
59
|
+
export declare const setRefreshTokenConfig: (config: RefreshTokenConfig | null) => void;
|
|
60
|
+
export declare const getRefreshTokenConfig: () => RefreshTokenConfig | null;
|
|
61
|
+
export declare const getAccessTokenExpiry: () => number;
|
|
62
|
+
export declare const getRefreshTokenExpiry: () => number;
|
|
63
|
+
export declare const getRotationGraceSeconds: () => number;
|
|
64
|
+
export interface MfaEmailOtpConfig {
|
|
65
|
+
/** Called with the user's email and the OTP code. Use to send the email. */
|
|
66
|
+
onSend: (email: string, code: string) => Promise<void>;
|
|
67
|
+
/** OTP code length. Default: 6. */
|
|
68
|
+
codeLength?: number;
|
|
69
|
+
}
|
|
70
|
+
export interface MfaWebAuthnConfig {
|
|
71
|
+
/** Relying Party ID — typically the domain (e.g. "example.com"). Required. */
|
|
72
|
+
rpId: string;
|
|
73
|
+
/** Relying Party name shown in browser prompts. Defaults to app name. */
|
|
74
|
+
rpName?: string;
|
|
75
|
+
/** Expected origin(s) — full origin URL(s) like "https://example.com". Required. */
|
|
76
|
+
origin: string | string[];
|
|
77
|
+
/** Supported attestation conveyance preference. Default: "none". */
|
|
78
|
+
attestationType?: "none" | "direct" | "enterprise";
|
|
79
|
+
/** Authenticator attachment preference. Default: undefined (allows both platform + cross-platform). */
|
|
80
|
+
authenticatorAttachment?: "platform" | "cross-platform";
|
|
81
|
+
/** User verification requirement. Default: "preferred". */
|
|
82
|
+
userVerification?: "required" | "preferred" | "discouraged";
|
|
83
|
+
/** Timeout for ceremonies in milliseconds. Default: 60000 (60s). */
|
|
84
|
+
timeout?: number;
|
|
85
|
+
/** Reject authentication when sign count goes backward (cloned key detection). Default: false (accept + warn). */
|
|
86
|
+
strictSignCount?: boolean;
|
|
87
|
+
}
|
|
88
|
+
export interface MfaConfig {
|
|
89
|
+
/** Issuer name shown in authenticator apps. Defaults to app name. */
|
|
90
|
+
issuer?: string;
|
|
91
|
+
/** TOTP algorithm. Default: "SHA1" (most compatible). */
|
|
92
|
+
algorithm?: "SHA1" | "SHA256" | "SHA512";
|
|
93
|
+
/** TOTP digits. Default: 6. */
|
|
94
|
+
digits?: number;
|
|
95
|
+
/** TOTP period in seconds. Default: 30. */
|
|
96
|
+
period?: number;
|
|
97
|
+
/** Number of recovery codes to generate. Default: 10. */
|
|
98
|
+
recoveryCodes?: number;
|
|
99
|
+
/** MFA challenge window in seconds. Default: 300 (5 min). */
|
|
100
|
+
challengeTtlSeconds?: number;
|
|
101
|
+
/** Email OTP configuration. When set, enables email-based MFA as an option. */
|
|
102
|
+
emailOtp?: MfaEmailOtpConfig;
|
|
103
|
+
/** WebAuthn/FIDO2 configuration. When set, enables security key MFA routes. */
|
|
104
|
+
webauthn?: MfaWebAuthnConfig;
|
|
105
|
+
}
|
|
106
|
+
export declare const setMfaConfig: (config: MfaConfig | null) => void;
|
|
107
|
+
export declare const getMfaConfig: () => MfaConfig | null;
|
|
108
|
+
export declare const getMfaIssuer: () => string;
|
|
109
|
+
export declare const getMfaAlgorithm: () => string;
|
|
110
|
+
export declare const getMfaDigits: () => number;
|
|
111
|
+
export declare const getMfaPeriod: () => number;
|
|
112
|
+
export declare const getMfaRecoveryCodeCount: () => number;
|
|
113
|
+
export declare const getMfaChallengeTtl: () => number;
|
|
114
|
+
export declare const getMfaEmailOtpConfig: () => MfaEmailOtpConfig | null;
|
|
115
|
+
export declare const getMfaEmailOtpCodeLength: () => number;
|
|
116
|
+
export declare const getMfaWebAuthnConfig: () => MfaWebAuthnConfig | null;
|
|
117
|
+
export declare const setCsrfEnabled: (v: boolean) => void;
|
|
118
|
+
export declare const getCsrfEnabled: () => boolean;
|
package/dist/lib/appConfig.js
CHANGED
|
@@ -4,6 +4,7 @@ let defaultRole = null;
|
|
|
4
4
|
let _primaryField = "email";
|
|
5
5
|
let _emailVerificationConfig = null;
|
|
6
6
|
let _passwordResetConfig = null;
|
|
7
|
+
let _passwordPolicy = {};
|
|
7
8
|
export const setAppName = (name) => { appName = name; };
|
|
8
9
|
export const getAppName = () => appName;
|
|
9
10
|
export const setAppRoles = (roles) => { appRoles = roles; };
|
|
@@ -18,6 +19,8 @@ const DEFAULT_TOKEN_EXPIRY = 60 * 60 * 24; // 24 hours
|
|
|
18
19
|
export const getTokenExpiry = () => _emailVerificationConfig?.tokenExpiry ?? DEFAULT_TOKEN_EXPIRY;
|
|
19
20
|
export const setPasswordResetConfig = (config) => { _passwordResetConfig = config; };
|
|
20
21
|
export const getPasswordResetConfig = () => _passwordResetConfig;
|
|
22
|
+
export const setPasswordPolicy = (config) => { _passwordPolicy = config; };
|
|
23
|
+
export const getPasswordPolicy = () => _passwordPolicy;
|
|
21
24
|
const DEFAULT_RESET_TOKEN_EXPIRY = 60 * 60; // 1 hour
|
|
22
25
|
export const getResetTokenExpiry = () => _passwordResetConfig?.tokenExpiry ?? DEFAULT_RESET_TOKEN_EXPIRY;
|
|
23
26
|
// ---------------------------------------------------------------------------
|
|
@@ -35,3 +38,30 @@ export const setIncludeInactiveSessions = (v) => { _includeInactiveSessions = v;
|
|
|
35
38
|
export const getIncludeInactiveSessions = () => _includeInactiveSessions;
|
|
36
39
|
export const setTrackLastActive = (v) => { _trackLastActive = v; };
|
|
37
40
|
export const getTrackLastActive = () => _trackLastActive;
|
|
41
|
+
let _refreshTokenConfig = null;
|
|
42
|
+
export const setRefreshTokenConfig = (config) => { _refreshTokenConfig = config; };
|
|
43
|
+
export const getRefreshTokenConfig = () => _refreshTokenConfig;
|
|
44
|
+
const DEFAULT_ACCESS_TOKEN_EXPIRY = 900; // 15 min
|
|
45
|
+
const DEFAULT_REFRESH_TOKEN_EXPIRY = 2_592_000; // 30 days
|
|
46
|
+
const DEFAULT_ROTATION_GRACE_SECONDS = 30;
|
|
47
|
+
export const getAccessTokenExpiry = () => _refreshTokenConfig?.accessTokenExpiry ?? DEFAULT_ACCESS_TOKEN_EXPIRY;
|
|
48
|
+
export const getRefreshTokenExpiry = () => _refreshTokenConfig?.refreshTokenExpiry ?? DEFAULT_REFRESH_TOKEN_EXPIRY;
|
|
49
|
+
export const getRotationGraceSeconds = () => _refreshTokenConfig?.rotationGraceSeconds ?? DEFAULT_ROTATION_GRACE_SECONDS;
|
|
50
|
+
let _mfaConfig = null;
|
|
51
|
+
export const setMfaConfig = (config) => { _mfaConfig = config; };
|
|
52
|
+
export const getMfaConfig = () => _mfaConfig;
|
|
53
|
+
export const getMfaIssuer = () => _mfaConfig?.issuer ?? getAppName();
|
|
54
|
+
export const getMfaAlgorithm = () => _mfaConfig?.algorithm ?? "SHA1";
|
|
55
|
+
export const getMfaDigits = () => _mfaConfig?.digits ?? 6;
|
|
56
|
+
export const getMfaPeriod = () => _mfaConfig?.period ?? 30;
|
|
57
|
+
export const getMfaRecoveryCodeCount = () => _mfaConfig?.recoveryCodes ?? 10;
|
|
58
|
+
export const getMfaChallengeTtl = () => _mfaConfig?.challengeTtlSeconds ?? 300;
|
|
59
|
+
export const getMfaEmailOtpConfig = () => _mfaConfig?.emailOtp ?? null;
|
|
60
|
+
export const getMfaEmailOtpCodeLength = () => _mfaConfig?.emailOtp?.codeLength ?? 6;
|
|
61
|
+
export const getMfaWebAuthnConfig = () => _mfaConfig?.webauthn ?? null;
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// CSRF config
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
let _csrfEnabled = false;
|
|
66
|
+
export const setCsrfEnabled = (v) => { _csrfEnabled = v; };
|
|
67
|
+
export const getCsrfEnabled = () => _csrfEnabled;
|