@warlock.js/auth 5.30.0 → 5.32.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/CHANGELOG.md +18 -1
- package/esm/commands/auth-cleanup-command.d.mts +1 -2
- package/esm/commands/auth-cleanup-command.d.mts.map +1 -1
- package/esm/commands/auth-purge-never-expiring-command.d.mts +1 -2
- package/esm/commands/auth-purge-never-expiring-command.d.mts.map +1 -1
- package/esm/commands/auth-purge-never-expiring-command.mjs.map +1 -1
- package/esm/commands/jwt-secret-generator-command.d.mts +1 -2
- package/esm/commands/jwt-secret-generator-command.d.mts.map +1 -1
- package/esm/commands/jwt-secret-generator-command.mjs.map +1 -1
- package/esm/contracts/auth-contract.d.mts +1 -3
- package/esm/contracts/auth-contract.d.mts.map +1 -1
- package/esm/contracts/index.d.mts +2 -1
- package/esm/contracts/providers.d.mts +82 -50
- package/esm/contracts/providers.d.mts.map +1 -1
- package/esm/contracts/request-locals.d.mts +2 -3
- package/esm/contracts/request-locals.d.mts.map +1 -1
- package/esm/contracts/types.d.mts +59 -43
- package/esm/contracts/types.d.mts.map +1 -1
- package/esm/errors/auth-provider-sdk-missing.error.d.mts +1 -3
- package/esm/errors/auth-provider-sdk-missing.error.d.mts.map +1 -1
- package/esm/errors/email-not-verified.error.d.mts +1 -3
- package/esm/errors/email-not-verified.error.d.mts.map +1 -1
- package/esm/errors/index.d.mts +2 -1
- package/esm/errors/index.mjs +1 -1
- package/esm/errors/invalid-one-time-token.error.d.mts +1 -3
- package/esm/errors/invalid-one-time-token.error.d.mts.map +1 -1
- package/esm/errors/invalid-passkey.error.d.mts +1 -3
- package/esm/errors/invalid-passkey.error.d.mts.map +1 -1
- package/esm/errors/invalid-provider-callback.error.d.mts +1 -3
- package/esm/errors/invalid-provider-callback.error.d.mts.map +1 -1
- package/esm/errors/notifications-unavailable.error.d.mts +1 -3
- package/esm/errors/notifications-unavailable.error.d.mts.map +1 -1
- package/esm/errors/provider-email-not-verified.error.d.mts +1 -3
- package/esm/errors/provider-email-not-verified.error.d.mts.map +1 -1
- package/esm/index.d.mts +8 -0
- package/esm/localization/auth-default-translations.d.mts +1 -3
- package/esm/localization/auth-default-translations.d.mts.map +1 -1
- package/esm/localization/auth-translations.type.d.mts +11 -7
- package/esm/localization/auth-translations.type.d.mts.map +1 -1
- package/esm/localization/index.mjs +1 -1
- package/esm/localization/register-auth-translations.d.mts +1 -2
- package/esm/localization/register-auth-translations.d.mts.map +1 -1
- package/esm/middleware/auth.middleware.d.mts +10 -10
- package/esm/middleware/auth.middleware.d.mts.map +1 -1
- package/esm/middleware/auth.middleware.mjs +2 -2
- package/esm/middleware/auth.middleware.mjs.map +1 -1
- package/esm/middleware/csrf-origin-check.d.mts +4 -6
- package/esm/middleware/csrf-origin-check.d.mts.map +1 -1
- package/esm/middleware/csrf-origin-check.mjs +1 -1
- package/esm/middleware/csrf-origin-check.mjs.map +1 -1
- package/esm/middleware/index.d.mts +2 -1
- package/esm/middleware/index.mjs +1 -1
- package/esm/middleware/login-throttle.middleware.d.mts +4 -6
- package/esm/middleware/login-throttle.middleware.d.mts.map +1 -1
- package/esm/middleware/login-throttle.middleware.mjs +1 -1
- package/esm/middleware/login-throttle.middleware.mjs.map +1 -1
- package/esm/middleware/one-time-token-throttle.middleware.d.mts +2 -4
- package/esm/middleware/one-time-token-throttle.middleware.d.mts.map +1 -1
- package/esm/middleware/otp-throttle.middleware.d.mts +2 -4
- package/esm/middleware/otp-throttle.middleware.d.mts.map +1 -1
- package/esm/middleware/require-verified-email.middleware.d.mts +1 -3
- package/esm/middleware/require-verified-email.middleware.d.mts.map +1 -1
- package/esm/models/access-token/access-token.model.d.mts +6 -30
- package/esm/models/access-token/access-token.model.d.mts.map +1 -1
- package/esm/models/access-token/index.d.mts +2 -1
- package/esm/models/access-token/index.mjs +1 -1
- package/esm/models/auth-token-family/auth-token-family.model.d.mts +2 -28
- package/esm/models/auth-token-family/auth-token-family.model.d.mts.map +1 -1
- package/esm/models/auth-token-family/index.d.mts +2 -1
- package/esm/models/auth-token-family/index.mjs +1 -1
- package/esm/models/auth.model.d.mts +1 -3
- package/esm/models/auth.model.d.mts.map +1 -1
- package/esm/models/index.d.mts +8 -2
- package/esm/models/index.d.mts.map +1 -1
- package/esm/models/index.mjs +1 -1
- package/esm/models/one-time-token/index.d.mts +2 -1
- package/esm/models/one-time-token/index.mjs +1 -1
- package/esm/models/one-time-token/one-time-token.model.d.mts +3 -37
- package/esm/models/one-time-token/one-time-token.model.d.mts.map +1 -1
- package/esm/models/passkey-credential/index.d.mts +2 -1
- package/esm/models/passkey-credential/index.mjs +1 -1
- package/esm/models/passkey-credential/passkey-credential.model.d.mts +2 -32
- package/esm/models/passkey-credential/passkey-credential.model.d.mts.map +1 -1
- package/esm/models/provider-account/index.d.mts +2 -1
- package/esm/models/provider-account/index.mjs +1 -1
- package/esm/models/provider-account/provider-account.model.d.mts +2 -28
- package/esm/models/provider-account/provider-account.model.d.mts.map +1 -1
- package/esm/models/refresh-token/index.d.mts +2 -1
- package/esm/models/refresh-token/index.mjs +1 -1
- package/esm/models/refresh-token/refresh-token.model.d.mts +11 -55
- package/esm/models/refresh-token/refresh-token.model.d.mts.map +1 -1
- package/esm/otp/index.d.mts +2 -1
- package/esm/otp/index.mjs +1 -1
- package/esm/otp/otp.d.mts +2 -4
- package/esm/otp/otp.d.mts.map +1 -1
- package/esm/otp/otp.mjs +2 -1
- package/esm/otp/otp.mjs.map +1 -1
- package/esm/passkeys/index.d.mts +2 -1
- package/esm/passkeys/index.mjs +1 -1
- package/esm/passkeys/passkey-authentication.d.mts +2 -4
- package/esm/passkeys/passkey-authentication.d.mts.map +1 -1
- package/esm/passkeys/passkey-authentication.mjs +2 -1
- package/esm/passkeys/passkey-authentication.mjs.map +1 -1
- package/esm/passkeys/passkey-registration.d.mts +3 -4
- package/esm/passkeys/passkey-registration.d.mts.map +1 -1
- package/esm/passkeys/simplewebauthn.d.mts +2 -3
- package/esm/passkeys/simplewebauthn.d.mts.map +1 -1
- package/esm/passkeys/simplewebauthn.mjs +1 -1
- package/esm/providers/apple-provider.d.mts +5 -9
- package/esm/providers/apple-provider.d.mts.map +1 -1
- package/esm/providers/discord-provider.d.mts +2 -7
- package/esm/providers/discord-provider.d.mts.map +1 -1
- package/esm/providers/facebook-provider.d.mts +2 -7
- package/esm/providers/facebook-provider.d.mts.map +1 -1
- package/esm/providers/github-provider.d.mts +2 -7
- package/esm/providers/github-provider.d.mts.map +1 -1
- package/esm/providers/google-provider.d.mts +5 -10
- package/esm/providers/google-provider.d.mts.map +1 -1
- package/esm/providers/index.d.mts +2 -1
- package/esm/providers/index.mjs +1 -1
- package/esm/providers/linkedin-provider.d.mts +3 -8
- package/esm/providers/linkedin-provider.d.mts.map +1 -1
- package/esm/providers/provider-login.d.mts +3 -5
- package/esm/providers/provider-login.d.mts.map +1 -1
- package/esm/providers/provider-login.mjs +3 -2
- package/esm/providers/provider-login.mjs.map +1 -1
- package/esm/providers/provider-state-cookie.mjs +3 -2
- package/esm/providers/provider-state-cookie.mjs.map +1 -1
- package/esm/providers/x-provider.d.mts +2 -7
- package/esm/providers/x-provider.d.mts.map +1 -1
- package/esm/services/auth-events.d.mts +5 -6
- package/esm/services/auth-events.d.mts.map +1 -1
- package/esm/services/auth.service.d.mts +10 -9
- package/esm/services/auth.service.d.mts.map +1 -1
- package/esm/services/current-user.d.mts +1 -3
- package/esm/services/current-user.d.mts.map +1 -1
- package/esm/services/email-verification.d.mts +3 -5
- package/esm/services/email-verification.d.mts.map +1 -1
- package/esm/services/generate-jwt-secret.d.mts +1 -2
- package/esm/services/generate-jwt-secret.d.mts.map +1 -1
- package/esm/services/index.d.mts +2 -1
- package/esm/services/index.mjs +1 -1
- package/esm/services/jwt.d.mts +8 -29
- package/esm/services/jwt.d.mts.map +1 -1
- package/esm/services/jwt.mjs +2 -2
- package/esm/services/jwt.mjs.map +1 -1
- package/esm/services/one-time-tokens.d.mts +6 -6
- package/esm/services/one-time-tokens.d.mts.map +1 -1
- package/esm/services/password-reset.d.mts +2 -4
- package/esm/services/password-reset.d.mts.map +1 -1
- package/esm/services/resolve-request-user.d.mts +19 -14
- package/esm/services/resolve-request-user.d.mts.map +1 -1
- package/esm/services/resolve-request-user.mjs.map +1 -1
- package/esm/services/token-family-operation.mjs +1 -1
- package/esm/session/index.d.mts +2 -1
- package/esm/session/index.mjs +1 -1
- package/esm/session/page-session.d.mts +26 -18
- package/esm/session/page-session.d.mts.map +1 -1
- package/esm/utils/auth-error-codes.d.mts +11 -22
- package/esm/utils/auth-error-codes.d.mts.map +1 -1
- package/esm/utils/index.mjs +1 -1
- package/esm/utils/safe-equal.d.mts +1 -2
- package/esm/utils/safe-equal.d.mts.map +1 -1
- package/esm/utils/token-expiry.d.mts +3 -4
- package/esm/utils/token-expiry.d.mts.map +1 -1
- package/llms-full.txt +189 -111
- package/package.json +8 -8
- package/skills/auth-basics/SKILL.md +10 -13
- package/skills/customize-token-storage/SKILL.md +24 -19
- package/skills/customize-user-type/SKILL.md +3 -1
- package/skills/index.md +4 -1
- package/skills/login-with-providers/SKILL.md +54 -29
- package/skills/manage-tokens/SKILL.md +2 -2
- package/skills/overview/SKILL.md +10 -1
- package/skills/protect-routes/SKILL.md +23 -14
- package/skills/register-user/SKILL.md +4 -4
- package/skills/run-auth-commands/SKILL.md +1 -1
- package/skills/throttle-login-attempts/SKILL.md +51 -22
- package/skills/verify-email-and-reset-password/SKILL.md +7 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register-auth-translations.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/localization/register-auth-translations.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"register-auth-translations.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/localization/register-auth-translations.ts"],"mappings":";;;;;;;;;;;;;wBAgBgB"}
|
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
import { AuthMiddlewareOptions, TokenFrom } from "../contracts/types.mjs";
|
|
2
2
|
import { Middleware } from "@warlock.js/core";
|
|
3
|
-
|
|
4
3
|
//#region ../../packages/auth/src/middleware/auth.middleware.d.ts
|
|
5
4
|
/**
|
|
6
5
|
* Well-known symbol under which `authMiddleware()` tags the middleware it
|
|
7
6
|
* returns. Shared via the global symbol registry so other packages can read the
|
|
8
7
|
* descriptor structurally (`Symbol.for("warlock.auth")`) without importing auth.
|
|
9
8
|
*/
|
|
10
|
-
declare const AUTH_MIDDLEWARE_DESCRIPTOR: unique symbol;
|
|
9
|
+
export declare const AUTH_MIDDLEWARE_DESCRIPTOR: unique symbol;
|
|
11
10
|
/** A credential source an auth middleware reads: the Authorization header or a named cookie. */
|
|
12
|
-
type AuthMiddlewareDescriptorSource = "header" | {
|
|
11
|
+
export type AuthMiddlewareDescriptorSource = "header" | {
|
|
13
12
|
cookie: string;
|
|
14
13
|
};
|
|
15
14
|
/**
|
|
16
15
|
* Frozen, JSON-safe description of how an `authMiddleware()` instance
|
|
17
16
|
* authenticates, stored (non-enumerably) under {@link AUTH_MIDDLEWARE_DESCRIPTOR}.
|
|
18
17
|
*/
|
|
19
|
-
type AuthMiddlewareDescriptor = {
|
|
20
|
-
/** Resolved credential sources; with dual sources the header comes first. */
|
|
18
|
+
export type AuthMiddlewareDescriptor = {
|
|
19
|
+
/** Resolved credential sources; with dual sources the header comes first. */
|
|
20
|
+
sources: AuthMiddlewareDescriptorSource[];
|
|
21
|
+
/** Resolved allowed user types; an empty array means any authenticated user. */
|
|
21
22
|
userTypes: string[];
|
|
22
23
|
};
|
|
23
24
|
/**
|
|
@@ -41,10 +42,9 @@ type AuthMiddlewareDescriptor = {
|
|
|
41
42
|
* router.get("/back-office", authMiddleware(["admin", "staff"]), backOfficeController);
|
|
42
43
|
* router.get("/browser-account", authMiddleware([], "cookie:token"), accountController);
|
|
43
44
|
*/
|
|
44
|
-
declare function authMiddleware(): Middleware;
|
|
45
|
-
declare function authMiddleware(options: AuthMiddlewareOptions): Middleware;
|
|
46
|
-
declare function authMiddleware(allowedUserType: string | string[], options?: AuthMiddlewareOptions): Middleware;
|
|
47
|
-
declare function authMiddleware(allowedUserType: string | string[], tokenFrom?: TokenFrom): Middleware;
|
|
45
|
+
export declare function authMiddleware(): Middleware;
|
|
46
|
+
export declare function authMiddleware(options: AuthMiddlewareOptions): Middleware;
|
|
47
|
+
export declare function authMiddleware(allowedUserType: string | string[], options?: AuthMiddlewareOptions): Middleware;
|
|
48
|
+
export declare function authMiddleware(allowedUserType: string | string[], tokenFrom?: TokenFrom): Middleware;
|
|
48
49
|
//#endregion
|
|
49
|
-
export { AUTH_MIDDLEWARE_DESCRIPTOR, AuthMiddlewareDescriptor, AuthMiddlewareDescriptorSource, authMiddleware };
|
|
50
50
|
//# sourceMappingURL=auth.middleware.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.middleware.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/auth.middleware.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"auth.middleware.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/auth.middleware.ts"],"mappings":";;;;;;;;qBAsBa;;YAGD;EAA8C;;;;;;YAM9C;;EAEV,SAAS;;EAET;;;;;;;;;;;;;;;;;;;;;;;wBAoGc,kBAAkB;wBAClB,eAAe,SAAS,wBAAwB;wBAChD,eACd,oCACA,UAAU,wBACT;wBACa,eACd,oCACA,YAAY,YACX"}
|
|
@@ -3,7 +3,7 @@ import { CsrfOriginMismatchError, assertCsrfOriginAllowed, requiresCsrfOriginChe
|
|
|
3
3
|
import { authConfig } from "../services/auth-config.mjs";
|
|
4
4
|
import { readCredential, resolveRequestUserOutcome } from "../services/resolve-request-user.mjs";
|
|
5
5
|
import { localizedLoginPath } from "./localized-login-path.mjs";
|
|
6
|
-
import { config, t } from "@warlock.js/core";
|
|
6
|
+
import { config, markAuthGuard, t } from "@warlock.js/core";
|
|
7
7
|
import { log } from "@warlock.js/logger";
|
|
8
8
|
|
|
9
9
|
//#region ../../packages/auth/src/middleware/auth.middleware.ts
|
|
@@ -129,7 +129,7 @@ function authMiddleware(userTypeOrOptions, legacyTokenFromOrOptions) {
|
|
|
129
129
|
value: descriptor,
|
|
130
130
|
enumerable: false
|
|
131
131
|
});
|
|
132
|
-
return auth;
|
|
132
|
+
return markAuthGuard(auth, !optional);
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.middleware.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/auth.middleware.ts"],"sourcesContent":["import { config, type HttpContext, t, type Middleware } from \"@warlock.js/core\";\nimport { log } from \"@warlock.js/logger\";\nimport type {\n AuthCredentialDescriptor,\n AuthMiddlewareOptions,\n TokenFrom,\n} from \"../contracts/types\";\nimport { authConfig } from \"../services/auth-config\";\nimport { readCredential, resolveRequestUserOutcome } from \"../services/resolve-request-user\";\nimport { AuthErrorCodes } from \"../utils/auth-error-codes\";\nimport {\n assertCsrfOriginAllowed,\n CsrfOriginMismatchError,\n requiresCsrfOriginCheck,\n} from \"./csrf-origin-check\";\nimport { localizedLoginPath } from \"./localized-login-path\";\n\n/**\n * Well-known symbol under which `authMiddleware()` tags the middleware it\n * returns. Shared via the global symbol registry so other packages can read the\n * descriptor structurally (`Symbol.for(\"warlock.auth\")`) without importing auth.\n */\nexport const AUTH_MIDDLEWARE_DESCRIPTOR = Symbol.for(\"warlock.auth\");\n\n/** A credential source an auth middleware reads: the Authorization header or a named cookie. */\nexport type AuthMiddlewareDescriptorSource = \"header\" | { cookie: string };\n\n/**\n * Frozen, JSON-safe description of how an `authMiddleware()` instance\n * authenticates, stored (non-enumerably) under {@link AUTH_MIDDLEWARE_DESCRIPTOR}.\n */\nexport type AuthMiddlewareDescriptor = {\n /** Resolved credential sources; with dual sources the header comes first. */\n sources: AuthMiddlewareDescriptorSource[];\n /** Resolved allowed user types; an empty array means any authenticated user. */\n userTypes: string[];\n};\n\n/** The 401 body shape every rejection in this middleware carries. */\ntype UnauthorizedPayload = { error: string; errorCode: string };\n\n/**\n * Reject an unauthenticated request, choosing the representation by ROUTE KIND.\n *\n * An API route always gets the JSON 401 it has always gotten. A PAGE route\n * (`request.route.isPage` — the React-SSR pages) instead redirects a logged-out\n * browser to the configured login path with a `returnUrl`, so a human\n * navigating to a guarded page lands on the login screen rather than reading a\n * raw JSON error blob — the defect in finding b9ab9804.\n *\n * The redirect is OPT-IN and backward-compatible: with no `auth.pageAuth.loginPath`\n * configured, even a page route falls back to the JSON 401, so an app that has\n * not asked for page-login UX keeps exactly its current behavior. The API 401\n * contract is never changed for API routes.\n *\n * `returnUrl` carries `request.url` — the relative path + query the browser was\n * on — never the absolute URL: a relative target cannot be turned into an\n * open-redirect off this app's origin.\n */\nfunction rejectUnauthorized(\n { request, response }: HttpContext,\n payload: UnauthorizedPayload,\n redirect?: AuthMiddlewareOptions[\"redirect\"],\n) {\n const loginPath = redirect?.to ?? authConfig.pageAuth.loginPath();\n const returnUrlParam = redirect?.returnUrlParam ?? authConfig.pageAuth.returnUrlParam();\n\n if (loginPath && request.route?.isPage) {\n // Under an active `web.localeRouting` strategy, the login destination\n // itself must carry the request's locale prefix — otherwise an anonymous\n // `/ar/admin` bounces to the DEFAULT locale's `/login` rather than\n // `/ar/login`, even though `returnUrl` below correctly points back at\n // `/ar/admin`. See `./localized-login-path.ts`.\n const destination = localizedLoginPath(loginPath, request.locale);\n const separator = destination.includes(\"?\") ? \"&\" : \"?\";\n const returnUrl = encodeURIComponent(request.url);\n\n return response.redirect(`${destination}${separator}${returnUrlParam}=${returnUrl}`);\n }\n\n return response.unauthorized(payload);\n}\n\nfunction tokenFromDescriptor(descriptor: Partial<AuthCredentialDescriptor>): TokenFrom {\n if (!descriptor.source || descriptor.source === \"header\") return \"header\";\n\n if (!descriptor.key) {\n throw new Error(\"authMiddleware cookie credentials require a cookie key.\");\n }\n\n return `cookie:${descriptor.key}`;\n}\n\nfunction descriptorSource(tokenFrom: TokenFrom): AuthMiddlewareDescriptorSource {\n return tokenFrom === \"header\" ? \"header\" : { cookie: tokenFrom.slice(\"cookie:\".length) };\n}\n\nfunction resolveAllowedTypes(value: string | string[] | undefined): string[] {\n if (value !== undefined) return Array.isArray(value) ? value : [value];\n\n const configuredDefault = config.key(\"auth.defaultUserType\") as string | undefined;\n\n if (configuredDefault) return [configuredDefault];\n\n const userTypes = config.key(\"auth.userType\", {}) as Record<string, unknown>;\n const configuredTypes = Object.keys(userTypes);\n\n if (configuredTypes.length === 1) return configuredTypes;\n\n throw new Error(\n \"authMiddleware requires a user type when auth.defaultUserType is unset and multiple user types exist.\",\n );\n}\n\n/**\n * Build a route gate. Authentication is required unless optional is explicit.\n *\n * An omitted user type selects the configured default; an explicit argument\n * selects which user types may pass:\n * - `[]` — any authenticated user (token required, type not checked).\n * - `\"admin\"` / `[\"admin\", \"staff\"]` — token required AND the user's\n * `userType` must be one of the listed types.\n *\n * `{ optional: true }` allows anonymous requests. All other forms reject\n * missing or invalid credentials with 401, or a configured page redirect.\n *\n * `tokenFrom` selects one credential source and defaults to `\"header\"`.\n * A single source avoids making credential precedence depend on array order.\n *\n * @example\n * router.get(\"/account\", authMiddleware([]), accountController);\n * router.get(\"/admin\", authMiddleware(\"admin\"), adminController);\n * router.get(\"/back-office\", authMiddleware([\"admin\", \"staff\"]), backOfficeController);\n * router.get(\"/browser-account\", authMiddleware([], \"cookie:token\"), accountController);\n */\nexport function authMiddleware(): Middleware;\nexport function authMiddleware(options: AuthMiddlewareOptions): Middleware;\nexport function authMiddleware(\n allowedUserType: string | string[],\n options?: AuthMiddlewareOptions,\n): Middleware;\nexport function authMiddleware(\n allowedUserType: string | string[],\n tokenFrom?: TokenFrom,\n): Middleware;\nexport function authMiddleware(\n userTypeOrOptions?: string | string[] | AuthMiddlewareOptions,\n legacyTokenFromOrOptions?: TokenFrom | AuthMiddlewareOptions,\n): Middleware {\n const options =\n typeof userTypeOrOptions === \"object\" && !Array.isArray(userTypeOrOptions)\n ? userTypeOrOptions\n : typeof legacyTokenFromOrOptions === \"object\"\n ? legacyTokenFromOrOptions\n : undefined;\n const selectedUserType =\n typeof userTypeOrOptions === \"string\" || Array.isArray(userTypeOrOptions)\n ? userTypeOrOptions\n : undefined;\n const allowedTypes = resolveAllowedTypes(selectedUserType);\n const legacyTokenFrom =\n typeof legacyTokenFromOrOptions === \"string\" ? legacyTokenFromOrOptions : undefined;\n const dualSources = options?.sources?.length ? options.sources.map(tokenFromDescriptor) : undefined;\n\n if (dualSources && options?.source) {\n throw new Error(\"authMiddleware accepts either `source` or `sources`, not both.\");\n }\n\n const dualHeader = dualSources?.includes(\"header\") ?? false;\n const dualCookies = dualSources?.filter(source => source !== \"header\") ?? [];\n\n if (dualSources && (dualCookies.length > 1 || dualSources.length - dualCookies.length > 1)) {\n throw new Error(\"authMiddleware `sources` accepts at most one header and one cookie.\");\n }\n\n // With dual sources the configured cookie is the base credential; the header\n // is chosen per request, ahead of it.\n const cookieTokenFrom: TokenFrom = dualSources\n ? (dualCookies[0] ?? \"header\")\n : options\n ? tokenFromDescriptor(options)\n : (legacyTokenFrom ?? \"header\");\n const tokenFrom = cookieTokenFrom;\n const refreshCredential = options?.refresh ? tokenFromDescriptor(options.refresh) : undefined;\n const renewalUserType = allowedTypes.length === 1 ? allowedTypes[0] : undefined;\n\n if (\n refreshCredential &&\n (tokenFrom === \"header\" || refreshCredential === \"header\" || !renewalUserType)\n ) {\n throw new Error(\n \"authMiddleware automatic renewal requires one allowed user type and cookie access and refresh credentials.\",\n );\n }\n\n const optional = options?.optional === true;\n\n const auth: Middleware = async ({ request, response }) => {\n const reject = (payload: UnauthorizedPayload) =>\n rejectUnauthorized({ request, response }, payload, options?.redirect);\n\n // A present Authorization header always wins and never falls back to the\n // cookie: the credential used is the header, so no renewal and no CSRF check.\n const headerWins = dualHeader && Boolean(request.authorizationValue);\n const usedTokenFrom: TokenFrom = headerWins ? \"header\" : tokenFrom;\n const usedRefresh = headerWins ? undefined : refreshCredential;\n\n // Renewal can mint cookies, so a cookie-authenticated unsafe request must\n // pass the same CSRF check even when its access cookie is missing.\n if (\n (readCredential(request, usedTokenFrom) ||\n (usedRefresh && readCredential(request, usedRefresh))) &&\n requiresCsrfOriginCheck(usedTokenFrom, request.method)\n ) {\n try {\n assertCsrfOriginAllowed(request);\n } catch (error) {\n if (!(error instanceof CsrfOriginMismatchError)) throw error;\n\n log.error(\"http\", \"auth\", error);\n return response.forbidden({\n error: t(\"auth.errors.csrfOriginMismatch\"),\n errorCode: AuthErrorCodes.CsrfOriginMismatch,\n });\n }\n }\n\n const outcome = await resolveRequestUserOutcome(request, response, {\n tokenFrom: usedTokenFrom,\n refreshCredential: usedRefresh,\n overlapMs: options?.refresh?.overlapMs,\n allowedTypes,\n renewalUserType,\n });\n\n if (outcome.user) {\n request.locals.user = outcome.user;\n return;\n }\n\n if (outcome.failure === \"forbidden\") {\n return response.forbidden({\n error: t(\"auth.errors.unauthorized\"),\n errorCode: AuthErrorCodes.Unauthorized,\n });\n }\n\n if (optional) return;\n\n if (outcome.failure === \"missing\") {\n return reject({\n error: t(\"auth.errors.missingAccessToken\"),\n errorCode: AuthErrorCodes.MissingAccessToken,\n });\n }\n\n if (outcome.failure === \"unauthorized\") {\n return reject({\n error: t(\"auth.errors.unauthorized\"),\n errorCode: AuthErrorCodes.Unauthorized,\n });\n }\n\n return reject({\n error: t(\"auth.errors.invalidAccessToken\"),\n errorCode: AuthErrorCodes.InvalidAccessToken,\n });\n };\n\n const descriptorSources: AuthMiddlewareDescriptorSource[] = dualSources\n ? [...(dualHeader ? ([\"header\"] as const) : []), ...dualCookies.map(descriptorSource)]\n : [descriptorSource(tokenFrom)];\n const descriptor: AuthMiddlewareDescriptor = Object.freeze({\n sources: Object.freeze(descriptorSources.map(source =>\n typeof source === \"string\" ? source : Object.freeze({ ...source }),\n )) as AuthMiddlewareDescriptorSource[],\n userTypes: Object.freeze([...allowedTypes]) as string[],\n });\n\n // Non-enumerable so the tag never shows up in logs, spreads or JSON.\n Object.defineProperty(auth, AUTH_MIDDLEWARE_DESCRIPTOR, { value: descriptor, enumerable: false });\n\n return auth;\n}\n"],"mappings":";;;;;;;;;;;;;;AAsBA,MAAa,6BAA6B,OAAO,IAAI,cAAc;;;;;;;;;;;;;;;;;;;AAqCnE,SAAS,mBACP,EAAE,SAAS,YACX,SACA,UACA;CACA,MAAM,YAAY,UAAU,MAAM,WAAW,SAAS,UAAU;CAChE,MAAM,iBAAiB,UAAU,kBAAkB,WAAW,SAAS,eAAe;CAEtF,IAAI,aAAa,QAAQ,OAAO,QAAQ;EAMtC,MAAM,cAAc,mBAAmB,WAAW,QAAQ,MAAM;EAChE,MAAM,YAAY,YAAY,SAAS,GAAG,IAAI,MAAM;EACpD,MAAM,YAAY,mBAAmB,QAAQ,GAAG;EAEhD,OAAO,SAAS,SAAS,GAAG,cAAc,YAAY,eAAe,GAAG,WAAW;CACrF;CAEA,OAAO,SAAS,aAAa,OAAO;AACtC;AAEA,SAAS,oBAAoB,YAA0D;CACrF,IAAI,CAAC,WAAW,UAAU,WAAW,WAAW,UAAU,OAAO;CAEjE,IAAI,CAAC,WAAW,KACd,MAAM,IAAI,MAAM,yDAAyD;CAG3E,OAAO,UAAU,WAAW;AAC9B;AAEA,SAAS,iBAAiB,WAAsD;CAC9E,OAAO,cAAc,WAAW,WAAW,EAAE,QAAQ,UAAU,MAAM,CAAgB,EAAE;AACzF;AAEA,SAAS,oBAAoB,OAAgD;CAC3E,IAAI,UAAU,QAAW,OAAO,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;CAErE,MAAM,oBAAoB,OAAO,IAAI,sBAAsB;CAE3D,IAAI,mBAAmB,OAAO,CAAC,iBAAiB;CAEhD,MAAM,YAAY,OAAO,IAAI,iBAAiB,CAAC,CAAC;CAChD,MAAM,kBAAkB,OAAO,KAAK,SAAS;CAE7C,IAAI,gBAAgB,WAAW,GAAG,OAAO;CAEzC,MAAM,IAAI,MACR,uGACF;AACF;AAiCA,SAAgB,eACd,mBACA,0BACY;CACZ,MAAM,UACJ,OAAO,sBAAsB,YAAY,CAAC,MAAM,QAAQ,iBAAiB,IACrE,oBACA,OAAO,6BAA6B,WAClC,2BACA;CAKR,MAAM,eAAe,oBAHnB,OAAO,sBAAsB,YAAY,MAAM,QAAQ,iBAAiB,IACpE,oBACA,MACmD;CACzD,MAAM,kBACJ,OAAO,6BAA6B,WAAW,2BAA2B;CAC5E,MAAM,cAAc,SAAS,SAAS,SAAS,QAAQ,QAAQ,IAAI,mBAAmB,IAAI;CAE1F,IAAI,eAAe,SAAS,QAC1B,MAAM,IAAI,MAAM,gEAAgE;CAGlF,MAAM,aAAa,aAAa,SAAS,QAAQ,KAAK;CACtD,MAAM,cAAc,aAAa,QAAO,WAAU,WAAW,QAAQ,KAAK,CAAC;CAE3E,IAAI,gBAAgB,YAAY,SAAS,KAAK,YAAY,SAAS,YAAY,SAAS,IACtF,MAAM,IAAI,MAAM,qEAAqE;CAUvF,MAAM,YAL6B,cAC9B,YAAY,MAAM,WACnB,UACE,oBAAoB,OAAO,IAC1B,mBAAmB;CAE1B,MAAM,oBAAoB,SAAS,UAAU,oBAAoB,QAAQ,OAAO,IAAI;CACpF,MAAM,kBAAkB,aAAa,WAAW,IAAI,aAAa,KAAK;CAEtE,IACE,sBACC,cAAc,YAAY,sBAAsB,YAAY,CAAC,kBAE9D,MAAM,IAAI,MACR,4GACF;CAGF,MAAM,WAAW,SAAS,aAAa;CAEvC,MAAM,OAAmB,OAAO,EAAE,SAAS,eAAe;EACxD,MAAM,UAAU,YACd,mBAAmB;GAAE;GAAS;EAAS,GAAG,SAAS,SAAS,QAAQ;EAItE,MAAM,aAAa,cAAc,QAAQ,QAAQ,kBAAkB;EACnE,MAAM,gBAA2B,aAAa,WAAW;EACzD,MAAM,cAAc,aAAa,SAAY;EAI7C,KACG,eAAe,SAAS,aAAa,KACnC,eAAe,eAAe,SAAS,WAAW,MACrD,wBAAwB,eAAe,QAAQ,MAAM,GAErD,IAAI;GACF,wBAAwB,OAAO;EACjC,SAAS,OAAO;GACd,IAAI,EAAE,iBAAiB,0BAA0B,MAAM;GAEvD,IAAI,MAAM,QAAQ,QAAQ,KAAK;GAC/B,OAAO,SAAS,UAAU;IACxB,OAAO,EAAE,gCAAgC;IACzC;GACF,CAAC;EACH;EAGF,MAAM,UAAU,MAAM,0BAA0B,SAAS,UAAU;GACjE,WAAW;GACX,mBAAmB;GACnB,WAAW,SAAS,SAAS;GAC7B;GACA;EACF,CAAC;EAED,IAAI,QAAQ,MAAM;GAChB,QAAQ,OAAO,OAAO,QAAQ;GAC9B;EACF;EAEA,IAAI,QAAQ,YAAY,aACtB,OAAO,SAAS,UAAU;GACxB,OAAO,EAAE,0BAA0B;GACnC;EACF,CAAC;EAGH,IAAI,UAAU;EAEd,IAAI,QAAQ,YAAY,WACtB,OAAO,OAAO;GACZ,OAAO,EAAE,gCAAgC;GACzC;EACF,CAAC;EAGH,IAAI,QAAQ,YAAY,gBACtB,OAAO,OAAO;GACZ,OAAO,EAAE,0BAA0B;GACnC;EACF,CAAC;EAGH,OAAO,OAAO;GACZ,OAAO,EAAE,gCAAgC;GACzC;EACF,CAAC;CACH;CAEA,MAAM,oBAAsD,cACxD,CAAC,GAAI,aAAc,CAAC,QAAQ,IAAc,CAAC,GAAI,GAAG,YAAY,IAAI,gBAAgB,CAAC,IACnF,CAAC,iBAAiB,SAAS,CAAC;CAChC,MAAM,aAAuC,OAAO,OAAO;EACzD,SAAS,OAAO,OAAO,kBAAkB,KAAI,WAC3C,OAAO,WAAW,WAAW,SAAS,OAAO,OAAO,EAAE,GAAG,OAAO,CAAC,CACnE,CAAC;EACD,WAAW,OAAO,OAAO,CAAC,GAAG,YAAY,CAAC;CAC5C,CAAC;CAGD,OAAO,eAAe,MAAM,4BAA4B;EAAE,OAAO;EAAY,YAAY;CAAM,CAAC;CAEhG,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"auth.middleware.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/auth.middleware.ts"],"sourcesContent":["import { config, type HttpContext, markAuthGuard, t, type Middleware } from \"@warlock.js/core\";\nimport { log } from \"@warlock.js/logger\";\nimport type {\n AuthCredentialDescriptor,\n AuthMiddlewareOptions,\n TokenFrom,\n} from \"../contracts/types\";\nimport { authConfig } from \"../services/auth-config\";\nimport { readCredential, resolveRequestUserOutcome } from \"../services/resolve-request-user\";\nimport { AuthErrorCodes } from \"../utils/auth-error-codes\";\nimport {\n assertCsrfOriginAllowed,\n CsrfOriginMismatchError,\n requiresCsrfOriginCheck,\n} from \"./csrf-origin-check\";\nimport { localizedLoginPath } from \"./localized-login-path\";\n\n/**\n * Well-known symbol under which `authMiddleware()` tags the middleware it\n * returns. Shared via the global symbol registry so other packages can read the\n * descriptor structurally (`Symbol.for(\"warlock.auth\")`) without importing auth.\n */\nexport const AUTH_MIDDLEWARE_DESCRIPTOR = Symbol.for(\"warlock.auth\");\n\n/** A credential source an auth middleware reads: the Authorization header or a named cookie. */\nexport type AuthMiddlewareDescriptorSource = \"header\" | { cookie: string };\n\n/**\n * Frozen, JSON-safe description of how an `authMiddleware()` instance\n * authenticates, stored (non-enumerably) under {@link AUTH_MIDDLEWARE_DESCRIPTOR}.\n */\nexport type AuthMiddlewareDescriptor = {\n /** Resolved credential sources; with dual sources the header comes first. */\n sources: AuthMiddlewareDescriptorSource[];\n /** Resolved allowed user types; an empty array means any authenticated user. */\n userTypes: string[];\n};\n\n/** The 401 body shape every rejection in this middleware carries. */\ntype UnauthorizedPayload = { error: string; errorCode: string };\n\n/**\n * Reject an unauthenticated request, choosing the representation by ROUTE KIND.\n *\n * An API route always gets the JSON 401 it has always gotten. A PAGE route\n * (`request.route.isPage` — the React-SSR pages) instead redirects a logged-out\n * browser to the configured login path with a `returnUrl`, so a human\n * navigating to a guarded page lands on the login screen rather than reading a\n * raw JSON error blob — the defect in finding b9ab9804.\n *\n * The redirect is OPT-IN and backward-compatible: with no `auth.pageAuth.loginPath`\n * configured, even a page route falls back to the JSON 401, so an app that has\n * not asked for page-login UX keeps exactly its current behavior. The API 401\n * contract is never changed for API routes.\n *\n * `returnUrl` carries `request.url` — the relative path + query the browser was\n * on — never the absolute URL: a relative target cannot be turned into an\n * open-redirect off this app's origin.\n */\nfunction rejectUnauthorized(\n { request, response }: HttpContext,\n payload: UnauthorizedPayload,\n redirect?: AuthMiddlewareOptions[\"redirect\"],\n) {\n const loginPath = redirect?.to ?? authConfig.pageAuth.loginPath();\n const returnUrlParam = redirect?.returnUrlParam ?? authConfig.pageAuth.returnUrlParam();\n\n if (loginPath && request.route?.isPage) {\n // Under an active `web.localeRouting` strategy, the login destination\n // itself must carry the request's locale prefix — otherwise an anonymous\n // `/ar/admin` bounces to the DEFAULT locale's `/login` rather than\n // `/ar/login`, even though `returnUrl` below correctly points back at\n // `/ar/admin`. See `./localized-login-path.ts`.\n const destination = localizedLoginPath(loginPath, request.locale);\n const separator = destination.includes(\"?\") ? \"&\" : \"?\";\n const returnUrl = encodeURIComponent(request.url);\n\n return response.redirect(`${destination}${separator}${returnUrlParam}=${returnUrl}`);\n }\n\n return response.unauthorized(payload);\n}\n\nfunction tokenFromDescriptor(descriptor: Partial<AuthCredentialDescriptor>): TokenFrom {\n if (!descriptor.source || descriptor.source === \"header\") return \"header\";\n\n if (!descriptor.key) {\n throw new Error(\"authMiddleware cookie credentials require a cookie key.\");\n }\n\n return `cookie:${descriptor.key}`;\n}\n\nfunction descriptorSource(tokenFrom: TokenFrom): AuthMiddlewareDescriptorSource {\n return tokenFrom === \"header\" ? \"header\" : { cookie: tokenFrom.slice(\"cookie:\".length) };\n}\n\nfunction resolveAllowedTypes(value: string | string[] | undefined): string[] {\n if (value !== undefined) return Array.isArray(value) ? value : [value];\n\n const configuredDefault = config.key(\"auth.defaultUserType\") as string | undefined;\n\n if (configuredDefault) return [configuredDefault];\n\n const userTypes = config.key(\"auth.userType\", {}) as Record<string, unknown>;\n const configuredTypes = Object.keys(userTypes);\n\n if (configuredTypes.length === 1) return configuredTypes;\n\n throw new Error(\n \"authMiddleware requires a user type when auth.defaultUserType is unset and multiple user types exist.\",\n );\n}\n\n/**\n * Build a route gate. Authentication is required unless optional is explicit.\n *\n * An omitted user type selects the configured default; an explicit argument\n * selects which user types may pass:\n * - `[]` — any authenticated user (token required, type not checked).\n * - `\"admin\"` / `[\"admin\", \"staff\"]` — token required AND the user's\n * `userType` must be one of the listed types.\n *\n * `{ optional: true }` allows anonymous requests. All other forms reject\n * missing or invalid credentials with 401, or a configured page redirect.\n *\n * `tokenFrom` selects one credential source and defaults to `\"header\"`.\n * A single source avoids making credential precedence depend on array order.\n *\n * @example\n * router.get(\"/account\", authMiddleware([]), accountController);\n * router.get(\"/admin\", authMiddleware(\"admin\"), adminController);\n * router.get(\"/back-office\", authMiddleware([\"admin\", \"staff\"]), backOfficeController);\n * router.get(\"/browser-account\", authMiddleware([], \"cookie:token\"), accountController);\n */\nexport function authMiddleware(): Middleware;\nexport function authMiddleware(options: AuthMiddlewareOptions): Middleware;\nexport function authMiddleware(\n allowedUserType: string | string[],\n options?: AuthMiddlewareOptions,\n): Middleware;\nexport function authMiddleware(\n allowedUserType: string | string[],\n tokenFrom?: TokenFrom,\n): Middleware;\nexport function authMiddleware(\n userTypeOrOptions?: string | string[] | AuthMiddlewareOptions,\n legacyTokenFromOrOptions?: TokenFrom | AuthMiddlewareOptions,\n): Middleware {\n const options =\n typeof userTypeOrOptions === \"object\" && !Array.isArray(userTypeOrOptions)\n ? userTypeOrOptions\n : typeof legacyTokenFromOrOptions === \"object\"\n ? legacyTokenFromOrOptions\n : undefined;\n const selectedUserType =\n typeof userTypeOrOptions === \"string\" || Array.isArray(userTypeOrOptions)\n ? userTypeOrOptions\n : undefined;\n const allowedTypes = resolveAllowedTypes(selectedUserType);\n const legacyTokenFrom =\n typeof legacyTokenFromOrOptions === \"string\" ? legacyTokenFromOrOptions : undefined;\n const dualSources = options?.sources?.length\n ? options.sources.map(tokenFromDescriptor)\n : undefined;\n\n if (dualSources && options?.source) {\n throw new Error(\"authMiddleware accepts either `source` or `sources`, not both.\");\n }\n\n const dualHeader = dualSources?.includes(\"header\") ?? false;\n const dualCookies = dualSources?.filter((source) => source !== \"header\") ?? [];\n\n if (dualSources && (dualCookies.length > 1 || dualSources.length - dualCookies.length > 1)) {\n throw new Error(\"authMiddleware `sources` accepts at most one header and one cookie.\");\n }\n\n // With dual sources the configured cookie is the base credential; the header\n // is chosen per request, ahead of it.\n const cookieTokenFrom: TokenFrom = dualSources\n ? (dualCookies[0] ?? \"header\")\n : options\n ? tokenFromDescriptor(options)\n : (legacyTokenFrom ?? \"header\");\n const tokenFrom = cookieTokenFrom;\n const refreshCredential = options?.refresh ? tokenFromDescriptor(options.refresh) : undefined;\n const renewalUserType = allowedTypes.length === 1 ? allowedTypes[0] : undefined;\n\n if (\n refreshCredential &&\n (tokenFrom === \"header\" || refreshCredential === \"header\" || !renewalUserType)\n ) {\n throw new Error(\n \"authMiddleware automatic renewal requires one allowed user type and cookie access and refresh credentials.\",\n );\n }\n\n const optional = options?.optional === true;\n\n const auth: Middleware = async ({ request, response }) => {\n const reject = (payload: UnauthorizedPayload) =>\n rejectUnauthorized({ request, response }, payload, options?.redirect);\n\n // A present Authorization header always wins and never falls back to the\n // cookie: the credential used is the header, so no renewal and no CSRF check.\n const headerWins = dualHeader && Boolean(request.authorizationValue);\n const usedTokenFrom: TokenFrom = headerWins ? \"header\" : tokenFrom;\n const usedRefresh = headerWins ? undefined : refreshCredential;\n\n // Renewal can mint cookies, so a cookie-authenticated unsafe request must\n // pass the same CSRF check even when its access cookie is missing.\n if (\n (readCredential(request, usedTokenFrom) ||\n (usedRefresh && readCredential(request, usedRefresh))) &&\n requiresCsrfOriginCheck(usedTokenFrom, request.method)\n ) {\n try {\n assertCsrfOriginAllowed(request);\n } catch (error) {\n if (!(error instanceof CsrfOriginMismatchError)) throw error;\n\n log.error(\"http\", \"auth\", error);\n return response.forbidden({\n error: t(\"auth.errors.csrfOriginMismatch\"),\n errorCode: AuthErrorCodes.CsrfOriginMismatch,\n });\n }\n }\n\n const outcome = await resolveRequestUserOutcome(request, response, {\n tokenFrom: usedTokenFrom,\n refreshCredential: usedRefresh,\n overlapMs: options?.refresh?.overlapMs,\n allowedTypes,\n renewalUserType,\n });\n\n if (outcome.user) {\n request.locals.user = outcome.user;\n return;\n }\n\n if (outcome.failure === \"forbidden\") {\n return response.forbidden({\n error: t(\"auth.errors.unauthorized\"),\n errorCode: AuthErrorCodes.Unauthorized,\n });\n }\n\n if (optional) return;\n\n if (outcome.failure === \"missing\") {\n return reject({\n error: t(\"auth.errors.missingAccessToken\"),\n errorCode: AuthErrorCodes.MissingAccessToken,\n });\n }\n\n if (outcome.failure === \"unauthorized\") {\n return reject({\n error: t(\"auth.errors.unauthorized\"),\n errorCode: AuthErrorCodes.Unauthorized,\n });\n }\n\n return reject({\n error: t(\"auth.errors.invalidAccessToken\"),\n errorCode: AuthErrorCodes.InvalidAccessToken,\n });\n };\n\n const descriptorSources: AuthMiddlewareDescriptorSource[] = dualSources\n ? [...(dualHeader ? ([\"header\"] as const) : []), ...dualCookies.map(descriptorSource)]\n : [descriptorSource(tokenFrom)];\n const descriptor: AuthMiddlewareDescriptor = Object.freeze({\n sources: Object.freeze(\n descriptorSources.map((source) =>\n typeof source === \"string\" ? source : Object.freeze({ ...source }),\n ),\n ) as AuthMiddlewareDescriptorSource[],\n userTypes: Object.freeze([...allowedTypes]) as string[],\n });\n\n // Non-enumerable so the tag never shows up in logs, spreads or JSON.\n Object.defineProperty(auth, AUTH_MIDDLEWARE_DESCRIPTOR, { value: descriptor, enumerable: false });\n\n // Tells `warlock dev` and `warlock inspect` that routes behind this\n // middleware are authenticated. `{ optional: true }`\n // lets anonymous requests through, so it does not count as a guard.\n return markAuthGuard(auth, !optional);\n}\n"],"mappings":"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"}
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { TokenFrom } from "../contracts/types.mjs";
|
|
2
2
|
import { AuthErrorCodes } from "../utils/auth-error-codes.mjs";
|
|
3
3
|
import { Request } from "@warlock.js/core";
|
|
4
|
-
|
|
5
4
|
//#region ../../packages/auth/src/middleware/csrf-origin-check.d.ts
|
|
6
5
|
/** Why {@link assertCsrfOriginAllowed} refused a request. */
|
|
7
|
-
type CsrfOriginMismatchReason = "origin-mismatch" | "referer-mismatch" | "missing-origin-and-referer";
|
|
6
|
+
export type CsrfOriginMismatchReason = "origin-mismatch" | "referer-mismatch" | "missing-origin-and-referer";
|
|
8
7
|
/**
|
|
9
8
|
* A cookie-authenticated, unsafe-method request whose `Origin`/`Referer`
|
|
10
9
|
* failed the check. `reason` names exactly why,
|
|
11
10
|
* so an error transformer or an incident log never has to string-match the
|
|
12
11
|
* message.
|
|
13
12
|
*/
|
|
14
|
-
declare class CsrfOriginMismatchError extends Error {
|
|
13
|
+
export declare class CsrfOriginMismatchError extends Error {
|
|
15
14
|
readonly reason: CsrfOriginMismatchReason;
|
|
16
15
|
readonly code = AuthErrorCodes.CsrfOriginMismatch;
|
|
17
16
|
constructor(reason: CsrfOriginMismatchReason);
|
|
@@ -23,7 +22,7 @@ declare class CsrfOriginMismatchError extends Error {
|
|
|
23
22
|
* only a cookie-sourced credential can be silently replayed cross-site by a
|
|
24
23
|
* browser, so only it needs the check.
|
|
25
24
|
*/
|
|
26
|
-
declare function requiresCsrfOriginCheck(tokenFrom: TokenFrom, method: string): boolean;
|
|
25
|
+
export declare function requiresCsrfOriginCheck(tokenFrom: TokenFrom, method: string): boolean;
|
|
27
26
|
/**
|
|
28
27
|
* CSRF Origin check for a cookie-authenticated unsafe-method request (lead
|
|
29
28
|
* decision 3). Allowed when `Origin` — or, when `Origin` is absent, `Referer`
|
|
@@ -43,7 +42,6 @@ declare function requiresCsrfOriginCheck(tokenFrom: TokenFrom, method: string):
|
|
|
43
42
|
* Callers gate this behind {@link requiresCsrfOriginCheck} — this function
|
|
44
43
|
* itself does not re-check the token source or the method.
|
|
45
44
|
*/
|
|
46
|
-
declare function assertCsrfOriginAllowed(request: Request): void;
|
|
45
|
+
export declare function assertCsrfOriginAllowed(request: Request): void;
|
|
47
46
|
//#endregion
|
|
48
|
-
export { CsrfOriginMismatchError, CsrfOriginMismatchReason, assertCsrfOriginAllowed, requiresCsrfOriginCheck };
|
|
49
47
|
//# sourceMappingURL=csrf-origin-check.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"csrf-origin-check.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/csrf-origin-check.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"csrf-origin-check.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/csrf-origin-check.ts"],"mappings":";;;;;YAQY;;;;;;;qBAWC,gCAAgC;WAGf,QAAQ;WAF3B,OAAI,eAAA;EAEb,YAA4B,QAAQ;;;;;;;;;wBAatB,wBAAwB,WAAW,WAAW;;;;;;;;;;;;;;;;;;;;wBAuB9C,wBAAwB,SAAS"}
|
|
@@ -3,7 +3,7 @@ import { resolveCsrfOriginVerdict } from "@warlock.js/core";
|
|
|
3
3
|
|
|
4
4
|
//#region ../../packages/auth/src/middleware/csrf-origin-check.ts
|
|
5
5
|
/** HTTP methods the CSRF Origin check exempts — reads, never state changes. */
|
|
6
|
-
const SAFE_METHODS = new Set([
|
|
6
|
+
const SAFE_METHODS = /* @__PURE__ */ new Set([
|
|
7
7
|
"GET",
|
|
8
8
|
"HEAD",
|
|
9
9
|
"OPTIONS"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"csrf-origin-check.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/csrf-origin-check.ts"],"sourcesContent":["import { resolveCsrfOriginVerdict, type Request } from \"@warlock.js/core\";\nimport type { TokenFrom } from \"../contracts/types\";\nimport { AuthErrorCodes } from \"../utils/auth-error-codes\";\n\n/** HTTP methods the CSRF Origin check exempts — reads, never state changes. */\nconst SAFE_METHODS = new Set([\"GET\", \"HEAD\", \"OPTIONS\"]);\n\n/** Why {@link assertCsrfOriginAllowed} refused a request. */\nexport type CsrfOriginMismatchReason =\n | \"origin-mismatch\"\n | \"referer-mismatch\"\n | \"missing-origin-and-referer\";\n\n/**\n * A cookie-authenticated, unsafe-method request whose `Origin`/`Referer`\n * failed the check. `reason` names exactly why,\n * so an error transformer or an incident log never has to string-match the\n * message.\n */\nexport class CsrfOriginMismatchError extends Error {\n readonly code = AuthErrorCodes.CsrfOriginMismatch;\n\n constructor(public readonly reason: CsrfOriginMismatchReason) {\n super(`CSRF Origin check failed: ${reason}`);\n this.name = \"CsrfOriginMismatchError\";\n }\n}\n\n/**\n * Whether a request is in scope for the CSRF Origin check at all — only a\n * `cookie:`-sourced credential on an unsafe method (POST/PUT/PATCH/DELETE) is.\n * Header-token auth and safe methods (GET/HEAD/OPTIONS) are always exempt:\n * only a cookie-sourced credential can be silently replayed cross-site by a\n * browser, so only it needs the check.\n */\nexport function requiresCsrfOriginCheck(tokenFrom: TokenFrom, method: string): boolean {\n return tokenFrom.startsWith(\"cookie:\") && !SAFE_METHODS.has(method.toUpperCase());\n}\n\n/**\n * CSRF Origin check for a cookie-authenticated unsafe-method request (lead\n * decision 3). Allowed when `Origin` — or, when `Origin` is absent, `Referer`\n * — names the request's own origin or an entry in `auth.csrf.allowedOrigins`.\n * Throws {@link CsrfOriginMismatchError} otherwise, including when BOTH\n * headers are absent.\n *\n * Delegates the actual same-origin/allowedOrigins/trustProxy-aware\n * comparison to `@warlock.js/core`'s `resolveCsrfOriginVerdict`\n * (`core/src/http/csrf-origin-policy.ts`) — SECURITY card 8a752ab2 moved that\n * logic down into `core` so this middleware's check and core's own default\n * CSRF-Origin guard (`core/src/http/csrf-default-guard.ts`, which now runs\n * even earlier, ahead of this middleware, for cookie-carrying unsafe-method\n * requests generally) share one implementation instead of two copies that\n * could drift apart.\n *\n * Callers gate this behind {@link requiresCsrfOriginCheck} — this function\n * itself does not re-check the token source or the method.\n */\nexport function assertCsrfOriginAllowed(request: Request): void {\n const verdict = resolveCsrfOriginVerdict(request);\n\n if (!verdict.allowed) {\n throw new CsrfOriginMismatchError(verdict.reason);\n }\n}\n"],"mappings":";;;;;AAKA,MAAM,
|
|
1
|
+
{"version":3,"file":"csrf-origin-check.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/csrf-origin-check.ts"],"sourcesContent":["import { resolveCsrfOriginVerdict, type Request } from \"@warlock.js/core\";\nimport type { TokenFrom } from \"../contracts/types\";\nimport { AuthErrorCodes } from \"../utils/auth-error-codes\";\n\n/** HTTP methods the CSRF Origin check exempts — reads, never state changes. */\nconst SAFE_METHODS = new Set([\"GET\", \"HEAD\", \"OPTIONS\"]);\n\n/** Why {@link assertCsrfOriginAllowed} refused a request. */\nexport type CsrfOriginMismatchReason =\n | \"origin-mismatch\"\n | \"referer-mismatch\"\n | \"missing-origin-and-referer\";\n\n/**\n * A cookie-authenticated, unsafe-method request whose `Origin`/`Referer`\n * failed the check. `reason` names exactly why,\n * so an error transformer or an incident log never has to string-match the\n * message.\n */\nexport class CsrfOriginMismatchError extends Error {\n readonly code = AuthErrorCodes.CsrfOriginMismatch;\n\n constructor(public readonly reason: CsrfOriginMismatchReason) {\n super(`CSRF Origin check failed: ${reason}`);\n this.name = \"CsrfOriginMismatchError\";\n }\n}\n\n/**\n * Whether a request is in scope for the CSRF Origin check at all — only a\n * `cookie:`-sourced credential on an unsafe method (POST/PUT/PATCH/DELETE) is.\n * Header-token auth and safe methods (GET/HEAD/OPTIONS) are always exempt:\n * only a cookie-sourced credential can be silently replayed cross-site by a\n * browser, so only it needs the check.\n */\nexport function requiresCsrfOriginCheck(tokenFrom: TokenFrom, method: string): boolean {\n return tokenFrom.startsWith(\"cookie:\") && !SAFE_METHODS.has(method.toUpperCase());\n}\n\n/**\n * CSRF Origin check for a cookie-authenticated unsafe-method request (lead\n * decision 3). Allowed when `Origin` — or, when `Origin` is absent, `Referer`\n * — names the request's own origin or an entry in `auth.csrf.allowedOrigins`.\n * Throws {@link CsrfOriginMismatchError} otherwise, including when BOTH\n * headers are absent.\n *\n * Delegates the actual same-origin/allowedOrigins/trustProxy-aware\n * comparison to `@warlock.js/core`'s `resolveCsrfOriginVerdict`\n * (`core/src/http/csrf-origin-policy.ts`) — SECURITY card 8a752ab2 moved that\n * logic down into `core` so this middleware's check and core's own default\n * CSRF-Origin guard (`core/src/http/csrf-default-guard.ts`, which now runs\n * even earlier, ahead of this middleware, for cookie-carrying unsafe-method\n * requests generally) share one implementation instead of two copies that\n * could drift apart.\n *\n * Callers gate this behind {@link requiresCsrfOriginCheck} — this function\n * itself does not re-check the token source or the method.\n */\nexport function assertCsrfOriginAllowed(request: Request): void {\n const verdict = resolveCsrfOriginVerdict(request);\n\n if (!verdict.allowed) {\n throw new CsrfOriginMismatchError(verdict.reason);\n }\n}\n"],"mappings":";;;;;AAKA,MAAM,+BAAe,IAAI,IAAI;CAAC;CAAO;CAAQ;AAAS,CAAC;;;;;;;AAcvD,IAAa,0BAAb,cAA6C,MAAM;CAGjD,YAAY,AAAgB,QAAkC;EAC5D,MAAM,6BAA6B,QAAQ;EADjB;;EAE1B,KAAK,OAAO;CACd;AACF;;;;;;;;AASA,SAAgB,wBAAwB,WAAsB,QAAyB;CACrF,OAAO,UAAU,WAAW,SAAS,KAAK,CAAC,aAAa,IAAI,OAAO,YAAY,CAAC;AAClF;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,wBAAwB,SAAwB;CAC9D,MAAM,UAAU,yBAAyB,OAAO;CAEhD,IAAI,CAAC,QAAQ,SACX,MAAM,IAAI,wBAAwB,QAAQ,MAAM;AAEpD"}
|
|
@@ -3,4 +3,5 @@ import { CsrfOriginMismatchError, CsrfOriginMismatchReason, assertCsrfOriginAllo
|
|
|
3
3
|
import { LoginThrottleIdentifier, LoginThrottleOptions, loginThrottleMiddleware, throttleIdentifierHash } from "./login-throttle.middleware.mjs";
|
|
4
4
|
import { tokenConsumeThrottleMiddleware, tokenIssueThrottleMiddleware } from "./one-time-token-throttle.middleware.mjs";
|
|
5
5
|
import { otpRequestThrottleMiddleware, otpVerifyThrottleMiddleware } from "./otp-throttle.middleware.mjs";
|
|
6
|
-
import { requireVerifiedEmail } from "./require-verified-email.middleware.mjs";
|
|
6
|
+
import { requireVerifiedEmail } from "./require-verified-email.middleware.mjs";
|
|
7
|
+
export { AUTH_MIDDLEWARE_DESCRIPTOR, AuthMiddlewareDescriptor, AuthMiddlewareDescriptorSource, CsrfOriginMismatchError, CsrfOriginMismatchReason, LoginThrottleIdentifier, LoginThrottleOptions, assertCsrfOriginAllowed, authMiddleware, loginThrottleMiddleware, otpRequestThrottleMiddleware, otpVerifyThrottleMiddleware, requireVerifiedEmail, requiresCsrfOriginCheck, throttleIdentifierHash, tokenConsumeThrottleMiddleware, tokenIssueThrottleMiddleware };
|
package/esm/middleware/index.mjs
CHANGED
|
@@ -5,4 +5,4 @@ import { tokenConsumeThrottleMiddleware, tokenIssueThrottleMiddleware } from "./
|
|
|
5
5
|
import { otpRequestThrottleMiddleware, otpVerifyThrottleMiddleware } from "./otp-throttle.middleware.mjs";
|
|
6
6
|
import { requireVerifiedEmail } from "./require-verified-email.middleware.mjs";
|
|
7
7
|
|
|
8
|
-
export {
|
|
8
|
+
export { AUTH_MIDDLEWARE_DESCRIPTOR, CsrfOriginMismatchError, assertCsrfOriginAllowed, authMiddleware, loginThrottleMiddleware, otpRequestThrottleMiddleware, otpVerifyThrottleMiddleware, requireVerifiedEmail, requiresCsrfOriginCheck, throttleIdentifierHash, tokenConsumeThrottleMiddleware, tokenIssueThrottleMiddleware };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Middleware, Request, Response } from "@warlock.js/core";
|
|
2
|
-
|
|
3
2
|
//#region ../../packages/auth/src/middleware/login-throttle.middleware.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* Built-in identifier kinds the throttle knows how to extract from a request.
|
|
@@ -11,7 +10,7 @@ import { Middleware, Request, Response } from "@warlock.js/core";
|
|
|
11
10
|
*
|
|
12
11
|
* Lockout trips when ANY tracked identifier crosses `max` — defense-in-depth.
|
|
13
12
|
*/
|
|
14
|
-
type LoginThrottleIdentifier = "email" | "ip";
|
|
13
|
+
export type LoginThrottleIdentifier = "email" | "ip";
|
|
15
14
|
/**
|
|
16
15
|
* Options for {@link loginThrottleMiddleware}.
|
|
17
16
|
*
|
|
@@ -22,7 +21,7 @@ type LoginThrottleIdentifier = "email" | "ip";
|
|
|
22
21
|
* @example
|
|
23
22
|
* loginThrottleMiddleware({ max: 5, window: "15m", lockoutDuration: "15m" });
|
|
24
23
|
*/
|
|
25
|
-
type LoginThrottleOptions = {
|
|
24
|
+
export type LoginThrottleOptions = {
|
|
26
25
|
/**
|
|
27
26
|
* Failures allowed within the window before the identifier is locked.
|
|
28
27
|
*
|
|
@@ -88,7 +87,7 @@ type LoginThrottleOptions = {
|
|
|
88
87
|
*
|
|
89
88
|
* Exported for tests and for apps that inspect throttle keys.
|
|
90
89
|
*/
|
|
91
|
-
declare function throttleIdentifierHash(kind: string, value: string): string;
|
|
90
|
+
export declare function throttleIdentifierHash(kind: string, value: string): string;
|
|
92
91
|
/**
|
|
93
92
|
* Failure-aware login throttle. Locks an identifier (account, IP, or both)
|
|
94
93
|
* after too many failed login attempts within a window — without penalising
|
|
@@ -125,7 +124,6 @@ declare function throttleIdentifierHash(kind: string, value: string): string;
|
|
|
125
124
|
* middleware: [loginThrottleMiddleware({ by: ["ip"], max: 10, window: "1m" })],
|
|
126
125
|
* });
|
|
127
126
|
*/
|
|
128
|
-
declare function loginThrottleMiddleware(options?: LoginThrottleOptions): Middleware;
|
|
127
|
+
export declare function loginThrottleMiddleware(options?: LoginThrottleOptions): Middleware;
|
|
129
128
|
//#endregion
|
|
130
|
-
export { LoginThrottleIdentifier, LoginThrottleOptions, loginThrottleMiddleware, throttleIdentifierHash };
|
|
131
129
|
//# sourceMappingURL=login-throttle.middleware.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login-throttle.middleware.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/login-throttle.middleware.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"login-throttle.middleware.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/login-throttle.middleware.ts"],"mappings":";;;;;;;;;;;;YAkBY;;;;;;;;;;;YAYA;;;;;;EAMV;;;;;;EAOA;;;;;;EAOA;;;;;;EAOA,KAAK;;;;;;;;EASL;;;;EAKA;;;;;;;;EASA,aAAa,UAAU;;;;;;EAOvB,YAAY,SAAS;;;;;;;;;;;;;;;;wBAwCP,uBAAuB,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBA0IrC,wBAAwB,UAAS,uBAA4B"}
|
|
@@ -170,7 +170,7 @@ function loginThrottleMiddleware(options = {}) {
|
|
|
170
170
|
ttl: window,
|
|
171
171
|
onConflict: "create"
|
|
172
172
|
});
|
|
173
|
-
if (await cache.increment(key) >= max) {
|
|
173
|
+
if (await cache.increment(key, 1, { ttl: window }) >= max) {
|
|
174
174
|
const lockSeconds = durationSeconds(lockoutDuration);
|
|
175
175
|
await cache.set(lockKey(identifier), lockSeconds === void 0 ? true : Date.now() + lockSeconds * 1e3, { ttl: lockoutDuration });
|
|
176
176
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login-throttle.middleware.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/login-throttle.middleware.ts"],"sourcesContent":["import { createHash, createHmac } from \"node:crypto\";\nimport { cache } from \"@warlock.js/cache\";\nimport { t, type Middleware, type Request, type Response } from \"@warlock.js/core\";\nimport { log } from \"@warlock.js/logger\";\nimport ms from \"ms\";\nimport { authConfig } from \"../services/auth-config\";\nimport { AuthErrorCodes } from \"../utils/auth-error-codes\";\n\n/**\n * Built-in identifier kinds the throttle knows how to extract from a request.\n *\n * - `\"email\"` tracks per-account, so an attacker can't spray one user from\n * many IPs.\n * - `\"ip\"` tracks per-source, so an attacker can't stuff many accounts from\n * one IP.\n *\n * Lockout trips when ANY tracked identifier crosses `max` — defense-in-depth.\n */\nexport type LoginThrottleIdentifier = \"email\" | \"ip\";\n\n/**\n * Options for {@link loginThrottleMiddleware}.\n *\n * Failure-aware by design: only counts requests whose response is non-2xx\n * (or whatever `isFailure` says) and resets on success, so a user who types\n * a wrong password three times then the right one is not penalised.\n *\n * @example\n * loginThrottleMiddleware({ max: 5, window: \"15m\", lockoutDuration: \"15m\" });\n */\nexport type LoginThrottleOptions = {\n /**\n * Failures allowed within the window before the identifier is locked.\n *\n * @default 5\n */\n max?: number;\n\n /**\n * Counting window. `ms`-format string (`\"15m\"`, `\"1h\"`) or seconds as a number.\n *\n * @default \"15m\"\n */\n window?: string | number;\n\n /**\n * Lockout duration once the threshold is tripped. `ms`-format string or seconds.\n *\n * @default \"15m\"\n */\n lockoutDuration?: string | number;\n\n /**\n * Identifiers tracked. Each is counted and locked independently.\n *\n * @default [\"email\", \"ip\"]\n */\n by?: LoginThrottleIdentifier[];\n\n /**\n * Credential field consulted when `\"email\"` is tracked. Switch to\n * `\"username\"` (or any other input field) for systems that don't\n * authenticate by email.\n *\n * @default \"email\"\n */\n identifierKey?: string;\n\n /**\n * Override the default error message returned with the 429 response.\n */\n errorMessage?: string;\n\n /**\n * Decide whether the response represents a failed login. Defaults to\n * \"non-2xx is a failure\"; override if your controller signals failure\n * through a 200-style body.\n *\n * @default (response) => !response.isOk\n */\n isFailure?: (response: Response) => boolean;\n\n /**\n * Compute the identifiers tracked for this request. Override to derive the\n * account key from a header, a JWT subject, or any other shape that does\n * not match the built-in `email` / `ip` extraction.\n */\n identify?: (request: Request) => string[];\n};\n\nconst COUNTER_PREFIX = \"auth.throttle.count.\";\nconst LOCK_PREFIX = \"auth.throttle.lock.\";\n\nconst counterKey = (identifier: string): string => `${COUNTER_PREFIX}${identifier}`;\nconst lockKey = (identifier: string): string => `${LOCK_PREFIX}${identifier}`;\n\n/**\n * The key a throttle identifier is hashed with: an HMAC key derived from\n * `auth.accessToken.secret` (the same root the OTP code MAC uses), or\n * `undefined` when no secret is configured.\n */\nfunction throttleHashKey(): Buffer | undefined {\n let secret: string | undefined;\n\n try {\n secret = authConfig.accessToken.secret();\n } catch {\n return undefined;\n }\n\n return createHmac(\"sha256\", secret).update(\"@warlock.js/auth:throttle\").digest();\n}\n\n/**\n * Hash of a throttle identifier (`kind` is `\"email\"`, `\"ip\"`, ...). The value\n * is trimmed and lower-cased first, so `\" Sara@x.io\"` and `\"sara@x.io\"` share\n * one bucket.\n *\n * Identifiers become part of cache keys, and cache drivers log their keys: a\n * raw email or IP there is personal data in a log line. A plain SHA-256 of an\n * email is reversible by anyone with a list of addresses, so the hash is\n * keyed (HMAC) with a key derived from `auth.accessToken.secret`. Without a\n * configured secret it falls back to SHA-256, which still keeps the address\n * out of the key.\n *\n * Exported for tests and for apps that inspect throttle keys.\n */\nexport function throttleIdentifierHash(kind: string, value: string): string {\n const normalized = `${kind}:${value.trim().toLowerCase()}`;\n const key = throttleHashKey();\n\n return key\n ? createHmac(\"sha256\", key).update(normalized).digest(\"hex\")\n : createHash(\"sha256\").update(normalized).digest(\"hex\");\n}\n\nlet warnedNullDriver = false;\n\n/**\n * The `null` cache driver stores nothing: every count reads back as 1 and no\n * lock is ever found, so the throttle silently never trips. Say so once.\n */\nfunction warnIfNullDriver(): void {\n if (warnedNullDriver) return;\n\n const driverName = (cache as { currentDriver?: { name?: string } }).currentDriver?.name;\n\n if (driverName !== \"null\") return;\n\n warnedNullDriver = true;\n log.warn(\n \"auth\",\n \"login-throttle\",\n \"The cache driver is `null`, so login, token and OTP throttles never lock. Use a real cache driver (redis, pg, or memory for one process).\",\n );\n}\n\n/**\n * A lock value is the lockout deadline (epoch ms). `true`, written by 5.29\n * and earlier, still counts as locked until the key expires.\n */\nfunction isLocked(value: unknown): boolean {\n if (typeof value === \"number\") {\n return value > Date.now();\n }\n\n return Boolean(value);\n}\n\n/**\n * `ms`'s honest runtime signature (see `services/auth-config.ts`): it returns\n * `undefined` for text it cannot parse and throws for `\"\"`.\n */\nconst parseMs = ms as unknown as (value: unknown) => number | string | undefined;\n\n/**\n * `\"15m\"` / `\"1h\"` / seconds as a number → seconds, or `undefined` when the\n * value cannot be parsed (the cache rejects such a TTL anyway).\n */\nfunction durationSeconds(duration: string | number): number | undefined {\n if (typeof duration === \"number\") {\n return Number.isFinite(duration) && duration > 0 ? duration : undefined;\n }\n\n let milliseconds: number | string | undefined;\n\n try {\n milliseconds = parseMs(duration);\n } catch {\n milliseconds = undefined;\n }\n\n return typeof milliseconds === \"number\" && Number.isFinite(milliseconds) && milliseconds > 0\n ? milliseconds / 1000\n : undefined;\n}\n\n/**\n * Built-in identifier extractor — reads the configured credential field from\n * the request body for the per-account key, and `request.detectIp()` for the\n * per-source key. Untruthy values are dropped; a request that yields zero\n * identifiers is left untracked so the middleware no-ops for it.\n */\nfunction buildDefaultIdentifiers(\n request: Request,\n tracked: LoginThrottleIdentifier[],\n credentialField: string,\n): string[] {\n const identifiers: string[] = [];\n\n if (tracked.includes(\"email\")) {\n const value = request.input(credentialField);\n\n if (typeof value === \"string\" && value.trim().length > 0) {\n identifiers.push(`email.${throttleIdentifierHash(\"email\", value)}`);\n }\n }\n\n if (tracked.includes(\"ip\")) {\n const ip = request.detectIp();\n\n if (ip) {\n identifiers.push(`ip.${throttleIdentifierHash(\"ip\", ip)}`);\n }\n }\n\n return identifiers;\n}\n\n/**\n * Failure-aware login throttle. Locks an identifier (account, IP, or both)\n * after too many failed login attempts within a window — without penalising\n * a user who eventually types the right password.\n *\n * Mechanism (per identifier):\n *\n * 1. **Before the controller** — reject with `429` if a lock key is set,\n * skipping the DB lookup AND the bcrypt verify (this also neutralises the\n * CPU-DoS angle of brute-forcing).\n * 2. **After the controller** — observe the outcome via `response.onSent`.\n * On failure (non-2xx by default), bump a fixed-window counter; once it\n * reaches `max`, set a lock key with `lockoutDuration` TTL. On success,\n * clear both the counter and the lock for that identifier.\n *\n * Storage is `@warlock.js/cache` (a peer dependency of `@warlock.js/auth`,\n * transitively guaranteed via `@warlock.js/core`). Lockout state is shared\n * across replicas — correct security model, because an attacker can't bypass\n * by round-robin to a different pod. Every cache call is guarded so a\n * misconfigured driver fails open (a throttle outage must never escalate\n * into a full auth outage).\n *\n * @example\n * import { loginThrottleMiddleware } from \"@warlock.js/auth\";\n * import { router } from \"@warlock.js/core\";\n *\n * router.post(\"/auth/login\", loginController, {\n * middleware: [loginThrottleMiddleware({ max: 5, window: \"15m\" })],\n * });\n *\n * @example\n * // Per-IP only — avoid account-lockout-as-DoS on anonymous-attack vectors\n * router.post(\"/auth/refresh-token\", refreshController, {\n * middleware: [loginThrottleMiddleware({ by: [\"ip\"], max: 10, window: \"1m\" })],\n * });\n */\nexport function loginThrottleMiddleware(options: LoginThrottleOptions = {}): Middleware {\n const max = options.max ?? 5;\n const window = options.window ?? \"15m\";\n const lockoutDuration = options.lockoutDuration ?? \"15m\";\n const tracked = options.by ?? [\"email\", \"ip\"];\n const credentialField = options.identifierKey ?? \"email\";\n\n return async ({ request, response }) => {\n const identifiers = options.identify\n ? options.identify(request)\n : buildDefaultIdentifiers(request, tracked, credentialField);\n\n if (identifiers.length === 0) return;\n\n warnIfNullDriver();\n\n try {\n for (const identifier of identifiers) {\n const locked = isLocked(await cache.get(lockKey(identifier)));\n\n if (locked) {\n return response.tooManyRequests({\n error: options.errorMessage ?? t(\"auth.errors.tooManyAttempts\"),\n errorCode: AuthErrorCodes.TooManyAttempts,\n });\n }\n }\n } catch (error) {\n // Fail open — a cache outage (driver not initialised, network blip)\n // must never escalate into a full auth outage. Log and let the request\n // through; the onSent recorder below is independently guarded so an\n // outage there is also harmless.\n log.error(\"auth\", \"login-throttle\", error);\n\n return;\n }\n\n response.onSent(async (resolvedResponse: Response) => {\n try {\n const failed = options.isFailure\n ? options.isFailure(resolvedResponse)\n : !resolvedResponse.isOk;\n\n if (!failed) {\n for (const identifier of identifiers) {\n await cache.remove(counterKey(identifier));\n await cache.remove(lockKey(identifier));\n }\n\n return;\n }\n\n for (const identifier of identifiers) {\n const key = counterKey(identifier);\n\n // Open the window on the first failure: the create-only write means\n // only one of several racing servers sets the TTL, and an existing\n // window keeps its deadline (fixed window). The bump itself is an\n // atomic increment, so concurrent failures across servers are never\n // lost (redis INCRBY / pg upsert keep the window's TTL).\n await cache.set(key, 0, { ttl: window, onConflict: \"create\" });\n const count = await cache.increment(key);\n\n if (count >= max) {\n const lockSeconds = durationSeconds(lockoutDuration);\n\n // The lock stores its own deadline: a driver with sliding\n // expiration (`memoryExtended`) pushes the key's TTL forward on\n // every read, but the deadline in the value does not move, so a\n // client that keeps retrying is still released after\n // `lockoutDuration`.\n await cache.set(\n lockKey(identifier),\n lockSeconds === undefined ? true : Date.now() + lockSeconds * 1000,\n { ttl: lockoutDuration },\n );\n }\n }\n } catch (error) {\n log.error(\"auth\", \"login-throttle\", error);\n }\n });\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"login-throttle.middleware.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/login-throttle.middleware.ts"],"sourcesContent":["import { createHash, createHmac } from \"node:crypto\";\nimport { cache } from \"@warlock.js/cache\";\nimport { t, type Middleware, type Request, type Response } from \"@warlock.js/core\";\nimport { log } from \"@warlock.js/logger\";\nimport ms from \"ms\";\nimport { authConfig } from \"../services/auth-config\";\nimport { AuthErrorCodes } from \"../utils/auth-error-codes\";\n\n/**\n * Built-in identifier kinds the throttle knows how to extract from a request.\n *\n * - `\"email\"` tracks per-account, so an attacker can't spray one user from\n * many IPs.\n * - `\"ip\"` tracks per-source, so an attacker can't stuff many accounts from\n * one IP.\n *\n * Lockout trips when ANY tracked identifier crosses `max` — defense-in-depth.\n */\nexport type LoginThrottleIdentifier = \"email\" | \"ip\";\n\n/**\n * Options for {@link loginThrottleMiddleware}.\n *\n * Failure-aware by design: only counts requests whose response is non-2xx\n * (or whatever `isFailure` says) and resets on success, so a user who types\n * a wrong password three times then the right one is not penalised.\n *\n * @example\n * loginThrottleMiddleware({ max: 5, window: \"15m\", lockoutDuration: \"15m\" });\n */\nexport type LoginThrottleOptions = {\n /**\n * Failures allowed within the window before the identifier is locked.\n *\n * @default 5\n */\n max?: number;\n\n /**\n * Counting window. `ms`-format string (`\"15m\"`, `\"1h\"`) or seconds as a number.\n *\n * @default \"15m\"\n */\n window?: string | number;\n\n /**\n * Lockout duration once the threshold is tripped. `ms`-format string or seconds.\n *\n * @default \"15m\"\n */\n lockoutDuration?: string | number;\n\n /**\n * Identifiers tracked. Each is counted and locked independently.\n *\n * @default [\"email\", \"ip\"]\n */\n by?: LoginThrottleIdentifier[];\n\n /**\n * Credential field consulted when `\"email\"` is tracked. Switch to\n * `\"username\"` (or any other input field) for systems that don't\n * authenticate by email.\n *\n * @default \"email\"\n */\n identifierKey?: string;\n\n /**\n * Override the default error message returned with the 429 response.\n */\n errorMessage?: string;\n\n /**\n * Decide whether the response represents a failed login. Defaults to\n * \"non-2xx is a failure\"; override if your controller signals failure\n * through a 200-style body.\n *\n * @default (response) => !response.isOk\n */\n isFailure?: (response: Response) => boolean;\n\n /**\n * Compute the identifiers tracked for this request. Override to derive the\n * account key from a header, a JWT subject, or any other shape that does\n * not match the built-in `email` / `ip` extraction.\n */\n identify?: (request: Request) => string[];\n};\n\nconst COUNTER_PREFIX = \"auth.throttle.count.\";\nconst LOCK_PREFIX = \"auth.throttle.lock.\";\n\nconst counterKey = (identifier: string): string => `${COUNTER_PREFIX}${identifier}`;\nconst lockKey = (identifier: string): string => `${LOCK_PREFIX}${identifier}`;\n\n/**\n * The key a throttle identifier is hashed with: an HMAC key derived from\n * `auth.accessToken.secret` (the same root the OTP code MAC uses), or\n * `undefined` when no secret is configured.\n */\nfunction throttleHashKey(): Buffer | undefined {\n let secret: string | undefined;\n\n try {\n secret = authConfig.accessToken.secret();\n } catch {\n return undefined;\n }\n\n return createHmac(\"sha256\", secret).update(\"@warlock.js/auth:throttle\").digest();\n}\n\n/**\n * Hash of a throttle identifier (`kind` is `\"email\"`, `\"ip\"`, ...). The value\n * is trimmed and lower-cased first, so `\" Sara@x.io\"` and `\"sara@x.io\"` share\n * one bucket.\n *\n * Identifiers become part of cache keys, and cache drivers log their keys: a\n * raw email or IP there is personal data in a log line. A plain SHA-256 of an\n * email is reversible by anyone with a list of addresses, so the hash is\n * keyed (HMAC) with a key derived from `auth.accessToken.secret`. Without a\n * configured secret it falls back to SHA-256, which still keeps the address\n * out of the key.\n *\n * Exported for tests and for apps that inspect throttle keys.\n */\nexport function throttleIdentifierHash(kind: string, value: string): string {\n const normalized = `${kind}:${value.trim().toLowerCase()}`;\n const key = throttleHashKey();\n\n return key\n ? createHmac(\"sha256\", key).update(normalized).digest(\"hex\")\n : createHash(\"sha256\").update(normalized).digest(\"hex\");\n}\n\nlet warnedNullDriver = false;\n\n/**\n * The `null` cache driver stores nothing: every count reads back as 1 and no\n * lock is ever found, so the throttle silently never trips. Say so once.\n */\nfunction warnIfNullDriver(): void {\n if (warnedNullDriver) return;\n\n const driverName = (cache as { currentDriver?: { name?: string } }).currentDriver?.name;\n\n if (driverName !== \"null\") return;\n\n warnedNullDriver = true;\n log.warn(\n \"auth\",\n \"login-throttle\",\n \"The cache driver is `null`, so login, token and OTP throttles never lock. Use a real cache driver (redis, pg, or memory for one process).\",\n );\n}\n\n/**\n * A lock value is the lockout deadline (epoch ms). `true`, written by 5.29\n * and earlier, still counts as locked until the key expires.\n */\nfunction isLocked(value: unknown): boolean {\n if (typeof value === \"number\") {\n return value > Date.now();\n }\n\n return Boolean(value);\n}\n\n/**\n * `ms`'s honest runtime signature (see `services/auth-config.ts`): it returns\n * `undefined` for text it cannot parse and throws for `\"\"`.\n */\nconst parseMs = ms as unknown as (value: unknown) => number | string | undefined;\n\n/**\n * `\"15m\"` / `\"1h\"` / seconds as a number → seconds, or `undefined` when the\n * value cannot be parsed (the cache rejects such a TTL anyway).\n */\nfunction durationSeconds(duration: string | number): number | undefined {\n if (typeof duration === \"number\") {\n return Number.isFinite(duration) && duration > 0 ? duration : undefined;\n }\n\n let milliseconds: number | string | undefined;\n\n try {\n milliseconds = parseMs(duration);\n } catch {\n milliseconds = undefined;\n }\n\n return typeof milliseconds === \"number\" && Number.isFinite(milliseconds) && milliseconds > 0\n ? milliseconds / 1000\n : undefined;\n}\n\n/**\n * Built-in identifier extractor — reads the configured credential field from\n * the request body for the per-account key, and `request.detectIp()` for the\n * per-source key. Untruthy values are dropped; a request that yields zero\n * identifiers is left untracked so the middleware no-ops for it.\n */\nfunction buildDefaultIdentifiers(\n request: Request,\n tracked: LoginThrottleIdentifier[],\n credentialField: string,\n): string[] {\n const identifiers: string[] = [];\n\n if (tracked.includes(\"email\")) {\n const value = request.input(credentialField);\n\n if (typeof value === \"string\" && value.trim().length > 0) {\n identifiers.push(`email.${throttleIdentifierHash(\"email\", value)}`);\n }\n }\n\n if (tracked.includes(\"ip\")) {\n const ip = request.detectIp();\n\n if (ip) {\n identifiers.push(`ip.${throttleIdentifierHash(\"ip\", ip)}`);\n }\n }\n\n return identifiers;\n}\n\n/**\n * Failure-aware login throttle. Locks an identifier (account, IP, or both)\n * after too many failed login attempts within a window — without penalising\n * a user who eventually types the right password.\n *\n * Mechanism (per identifier):\n *\n * 1. **Before the controller** — reject with `429` if a lock key is set,\n * skipping the DB lookup AND the bcrypt verify (this also neutralises the\n * CPU-DoS angle of brute-forcing).\n * 2. **After the controller** — observe the outcome via `response.onSent`.\n * On failure (non-2xx by default), bump a fixed-window counter; once it\n * reaches `max`, set a lock key with `lockoutDuration` TTL. On success,\n * clear both the counter and the lock for that identifier.\n *\n * Storage is `@warlock.js/cache` (a peer dependency of `@warlock.js/auth`,\n * transitively guaranteed via `@warlock.js/core`). Lockout state is shared\n * across replicas — correct security model, because an attacker can't bypass\n * by round-robin to a different pod. Every cache call is guarded so a\n * misconfigured driver fails open (a throttle outage must never escalate\n * into a full auth outage).\n *\n * @example\n * import { loginThrottleMiddleware } from \"@warlock.js/auth\";\n * import { router } from \"@warlock.js/core\";\n *\n * router.post(\"/auth/login\", loginController, {\n * middleware: [loginThrottleMiddleware({ max: 5, window: \"15m\" })],\n * });\n *\n * @example\n * // Per-IP only — avoid account-lockout-as-DoS on anonymous-attack vectors\n * router.post(\"/auth/refresh-token\", refreshController, {\n * middleware: [loginThrottleMiddleware({ by: [\"ip\"], max: 10, window: \"1m\" })],\n * });\n */\nexport function loginThrottleMiddleware(options: LoginThrottleOptions = {}): Middleware {\n const max = options.max ?? 5;\n const window = options.window ?? \"15m\";\n const lockoutDuration = options.lockoutDuration ?? \"15m\";\n const tracked = options.by ?? [\"email\", \"ip\"];\n const credentialField = options.identifierKey ?? \"email\";\n\n return async ({ request, response }) => {\n const identifiers = options.identify\n ? options.identify(request)\n : buildDefaultIdentifiers(request, tracked, credentialField);\n\n if (identifiers.length === 0) return;\n\n warnIfNullDriver();\n\n try {\n for (const identifier of identifiers) {\n const locked = isLocked(await cache.get(lockKey(identifier)));\n\n if (locked) {\n return response.tooManyRequests({\n error: options.errorMessage ?? t(\"auth.errors.tooManyAttempts\"),\n errorCode: AuthErrorCodes.TooManyAttempts,\n });\n }\n }\n } catch (error) {\n // Fail open — a cache outage (driver not initialised, network blip)\n // must never escalate into a full auth outage. Log and let the request\n // through; the onSent recorder below is independently guarded so an\n // outage there is also harmless.\n log.error(\"auth\", \"login-throttle\", error);\n\n return;\n }\n\n response.onSent(async (resolvedResponse: Response) => {\n try {\n const failed = options.isFailure\n ? options.isFailure(resolvedResponse)\n : !resolvedResponse.isOk;\n\n if (!failed) {\n for (const identifier of identifiers) {\n await cache.remove(counterKey(identifier));\n await cache.remove(lockKey(identifier));\n }\n\n return;\n }\n\n for (const identifier of identifiers) {\n const key = counterKey(identifier);\n\n // Open the window on the first failure: the create-only write means\n // only one of several racing servers sets the TTL, and an existing\n // window keeps its deadline (fixed window). The bump itself is an\n // atomic increment, so concurrent failures across servers are never\n // lost (redis INCRBY / pg upsert keep the window's TTL). Passing\n // the TTL on the bump also expires a key recreated after the seed.\n await cache.set(key, 0, { ttl: window, onConflict: \"create\" });\n const count = await cache.increment(key, 1, { ttl: window });\n\n if (count >= max) {\n const lockSeconds = durationSeconds(lockoutDuration);\n\n // The lock stores its own deadline: a driver with sliding\n // expiration (`memoryExtended`) pushes the key's TTL forward on\n // every read, but the deadline in the value does not move, so a\n // client that keeps retrying is still released after\n // `lockoutDuration`.\n await cache.set(\n lockKey(identifier),\n lockSeconds === undefined ? true : Date.now() + lockSeconds * 1000,\n { ttl: lockoutDuration },\n );\n }\n }\n } catch (error) {\n log.error(\"auth\", \"login-throttle\", error);\n }\n });\n };\n}\n"],"mappings":";;;;;;;;;AA0FA,MAAM,iBAAiB;AACvB,MAAM,cAAc;AAEpB,MAAM,cAAc,eAA+B,GAAG,iBAAiB;AACvE,MAAM,WAAW,eAA+B,GAAG,cAAc;;;;;;AAOjE,SAAS,kBAAsC;CAC7C,IAAI;CAEJ,IAAI;EACF,SAAS,WAAW,YAAY,OAAO;CACzC,QAAQ;EACN;CACF;CAEA,OAAO,WAAW,UAAU,MAAM,CAAC,CAAC,OAAO,2BAA2B,CAAC,CAAC,OAAO;AACjF;;;;;;;;;;;;;;;AAgBA,SAAgB,uBAAuB,MAAc,OAAuB;CAC1E,MAAM,aAAa,GAAG,KAAK,GAAG,MAAM,KAAK,CAAC,CAAC,YAAY;CACvD,MAAM,MAAM,gBAAgB;CAE5B,OAAO,MACH,WAAW,UAAU,GAAG,CAAC,CAAC,OAAO,UAAU,CAAC,CAAC,OAAO,KAAK,IACzD,WAAW,QAAQ,CAAC,CAAC,OAAO,UAAU,CAAC,CAAC,OAAO,KAAK;AAC1D;AAEA,IAAI,mBAAmB;;;;;AAMvB,SAAS,mBAAyB;CAChC,IAAI,kBAAkB;CAItB,IAFoB,MAAgD,eAAe,SAEhE,QAAQ;CAE3B,mBAAmB;CACnB,IAAI,KACF,QACA,kBACA,2IACF;AACF;;;;;AAMA,SAAS,SAAS,OAAyB;CACzC,IAAI,OAAO,UAAU,UACnB,OAAO,QAAQ,KAAK,IAAI;CAG1B,OAAO,QAAQ,KAAK;AACtB;;;;;AAMA,MAAM,UAAU;;;;;AAMhB,SAAS,gBAAgB,UAA+C;CACtE,IAAI,OAAO,aAAa,UACtB,OAAO,OAAO,SAAS,QAAQ,KAAK,WAAW,IAAI,WAAW;CAGhE,IAAI;CAEJ,IAAI;EACF,eAAe,QAAQ,QAAQ;CACjC,QAAQ;EACN,eAAe;CACjB;CAEA,OAAO,OAAO,iBAAiB,YAAY,OAAO,SAAS,YAAY,KAAK,eAAe,IACvF,eAAe,MACf;AACN;;;;;;;AAQA,SAAS,wBACP,SACA,SACA,iBACU;CACV,MAAM,cAAwB,CAAC;CAE/B,IAAI,QAAQ,SAAS,OAAO,GAAG;EAC7B,MAAM,QAAQ,QAAQ,MAAM,eAAe;EAE3C,IAAI,OAAO,UAAU,YAAY,MAAM,KAAK,CAAC,CAAC,SAAS,GACrD,YAAY,KAAK,SAAS,uBAAuB,SAAS,KAAK,GAAG;CAEtE;CAEA,IAAI,QAAQ,SAAS,IAAI,GAAG;EAC1B,MAAM,KAAK,QAAQ,SAAS;EAE5B,IAAI,IACF,YAAY,KAAK,MAAM,uBAAuB,MAAM,EAAE,GAAG;CAE7D;CAEA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,SAAgB,wBAAwB,UAAgC,CAAC,GAAe;CACtF,MAAM,MAAM,QAAQ,OAAO;CAC3B,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,kBAAkB,QAAQ,mBAAmB;CACnD,MAAM,UAAU,QAAQ,MAAM,CAAC,SAAS,IAAI;CAC5C,MAAM,kBAAkB,QAAQ,iBAAiB;CAEjD,OAAO,OAAO,EAAE,SAAS,eAAe;EACtC,MAAM,cAAc,QAAQ,WACxB,QAAQ,SAAS,OAAO,IACxB,wBAAwB,SAAS,SAAS,eAAe;EAE7D,IAAI,YAAY,WAAW,GAAG;EAE9B,iBAAiB;EAEjB,IAAI;GACF,KAAK,MAAM,cAAc,aAGvB,IAFe,SAAS,MAAM,MAAM,IAAI,QAAQ,UAAU,CAAC,CAElD,GACP,OAAO,SAAS,gBAAgB;IAC9B,OAAO,QAAQ,gBAAgB,EAAE,6BAA6B;IAC9D;GACF,CAAC;EAGP,SAAS,OAAO;GAKd,IAAI,MAAM,QAAQ,kBAAkB,KAAK;GAEzC;EACF;EAEA,SAAS,OAAO,OAAO,qBAA+B;GACpD,IAAI;IAKF,IAAI,EAJW,QAAQ,YACnB,QAAQ,UAAU,gBAAgB,IAClC,CAAC,iBAAiB,OAET;KACX,KAAK,MAAM,cAAc,aAAa;MACpC,MAAM,MAAM,OAAO,WAAW,UAAU,CAAC;MACzC,MAAM,MAAM,OAAO,QAAQ,UAAU,CAAC;KACxC;KAEA;IACF;IAEA,KAAK,MAAM,cAAc,aAAa;KACpC,MAAM,MAAM,WAAW,UAAU;KAQjC,MAAM,MAAM,IAAI,KAAK,GAAG;MAAE,KAAK;MAAQ,YAAY;KAAS,CAAC;KAG7D,IAAI,MAFgB,MAAM,UAAU,KAAK,GAAG,EAAE,KAAK,OAAO,CAAC,KAE9C,KAAK;MAChB,MAAM,cAAc,gBAAgB,eAAe;MAOnD,MAAM,MAAM,IACV,QAAQ,UAAU,GAClB,gBAAgB,SAAY,OAAO,KAAK,IAAI,IAAI,cAAc,KAC9D,EAAE,KAAK,gBAAgB,CACzB;KACF;IACF;GACF,SAAS,OAAO;IACd,IAAI,MAAM,QAAQ,kBAAkB,KAAK;GAC3C;EACF,CAAC;CACH;AACF"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { LoginThrottleOptions } from "./login-throttle.middleware.mjs";
|
|
2
2
|
import { Middleware } from "@warlock.js/core";
|
|
3
|
-
|
|
4
3
|
//#region ../../packages/auth/src/middleware/one-time-token-throttle.middleware.d.ts
|
|
5
4
|
/**
|
|
6
5
|
* Throttle for routes that ISSUE a verification or reset token ("send
|
|
@@ -16,7 +15,7 @@ import { Middleware } from "@warlock.js/core";
|
|
|
16
15
|
* middleware: [tokenIssueThrottleMiddleware()],
|
|
17
16
|
* });
|
|
18
17
|
*/
|
|
19
|
-
declare function tokenIssueThrottleMiddleware(options?: LoginThrottleOptions): Middleware;
|
|
18
|
+
export declare function tokenIssueThrottleMiddleware(options?: LoginThrottleOptions): Middleware;
|
|
20
19
|
/**
|
|
21
20
|
* Throttle for routes that CONSUME a token ("verify email", "reset
|
|
22
21
|
* password"). Built on {@link loginThrottleMiddleware}: per IP (the body
|
|
@@ -30,7 +29,6 @@ declare function tokenIssueThrottleMiddleware(options?: LoginThrottleOptions): M
|
|
|
30
29
|
* middleware: [tokenConsumeThrottleMiddleware()],
|
|
31
30
|
* });
|
|
32
31
|
*/
|
|
33
|
-
declare function tokenConsumeThrottleMiddleware(options?: LoginThrottleOptions): Middleware;
|
|
32
|
+
export declare function tokenConsumeThrottleMiddleware(options?: LoginThrottleOptions): Middleware;
|
|
34
33
|
//#endregion
|
|
35
|
-
export { tokenConsumeThrottleMiddleware, tokenIssueThrottleMiddleware };
|
|
36
34
|
//# sourceMappingURL=one-time-token-throttle.middleware.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"one-time-token-throttle.middleware.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/one-time-token-throttle.middleware.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"one-time-token-throttle.middleware.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/one-time-token-throttle.middleware.ts"],"mappings":";;;;;;;;;;;;;;;;;wBAiBgB,6BAA6B,UAAS,uBAA4B;;;;;;;;;;;;;;wBAwBlE,+BAA+B,UAAS,uBAA4B"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { LoginThrottleOptions } from "./login-throttle.middleware.mjs";
|
|
2
2
|
import { Middleware } from "@warlock.js/core";
|
|
3
|
-
|
|
4
3
|
//#region ../../packages/auth/src/middleware/otp-throttle.middleware.d.ts
|
|
5
4
|
/**
|
|
6
5
|
* Throttle for the route that SENDS a login code. {@link tokenIssueThrottleMiddleware}
|
|
@@ -12,7 +11,7 @@ import { Middleware } from "@warlock.js/core";
|
|
|
12
11
|
* middleware: [otpRequestThrottleMiddleware()],
|
|
13
12
|
* });
|
|
14
13
|
*/
|
|
15
|
-
declare function otpRequestThrottleMiddleware(options?: LoginThrottleOptions): Middleware;
|
|
14
|
+
export declare function otpRequestThrottleMiddleware(options?: LoginThrottleOptions): Middleware;
|
|
16
15
|
/**
|
|
17
16
|
* Throttle for the route that VERIFIES a login code. {@link loginThrottleMiddleware}
|
|
18
17
|
* keyed on `phone` and IP, failure-aware: 5 failures per 15m, then a 15m lockout
|
|
@@ -24,7 +23,6 @@ declare function otpRequestThrottleMiddleware(options?: LoginThrottleOptions): M
|
|
|
24
23
|
* middleware: [otpVerifyThrottleMiddleware()],
|
|
25
24
|
* });
|
|
26
25
|
*/
|
|
27
|
-
declare function otpVerifyThrottleMiddleware(options?: LoginThrottleOptions): Middleware;
|
|
26
|
+
export declare function otpVerifyThrottleMiddleware(options?: LoginThrottleOptions): Middleware;
|
|
28
27
|
//#endregion
|
|
29
|
-
export { otpRequestThrottleMiddleware, otpVerifyThrottleMiddleware };
|
|
30
28
|
//# sourceMappingURL=otp-throttle.middleware.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"otp-throttle.middleware.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/otp-throttle.middleware.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"otp-throttle.middleware.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/otp-throttle.middleware.ts"],"mappings":";;;;;;;;;;;;;wBAcgB,6BAA6B,UAAS,uBAA4B;;;;;;;;;;;;wBAelE,4BAA4B,UAAS,uBAA4B"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Middleware } from "@warlock.js/core";
|
|
2
|
-
|
|
3
2
|
//#region ../../packages/auth/src/middleware/require-verified-email.middleware.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* Opt-in route gate that rejects a user who has not verified their email with
|
|
@@ -12,7 +11,6 @@ import { Middleware } from "@warlock.js/core";
|
|
|
12
11
|
* middleware: [authMiddleware("user"), requireVerifiedEmail()],
|
|
13
12
|
* });
|
|
14
13
|
*/
|
|
15
|
-
declare function requireVerifiedEmail(): Middleware;
|
|
14
|
+
export declare function requireVerifiedEmail(): Middleware;
|
|
16
15
|
//#endregion
|
|
17
|
-
export { requireVerifiedEmail };
|
|
18
16
|
//# sourceMappingURL=require-verified-email.middleware.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"require-verified-email.middleware.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/require-verified-email.middleware.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"require-verified-email.middleware.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/middleware/require-verified-email.middleware.ts"],"mappings":";;;;;;;;;;;;;wBAegB,wBAAwB"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Auth } from "../auth.model.mjs";
|
|
2
2
|
import { Model } from "@warlock.js/cascade";
|
|
3
|
-
|
|
4
3
|
//#region ../../packages/auth/src/models/access-token/access-token.model.d.ts
|
|
5
4
|
/**
|
|
6
5
|
* Seal schema for the persisted access-token record. Exported so an override
|
|
@@ -10,26 +9,15 @@ import { Model } from "@warlock.js/cascade";
|
|
|
10
9
|
* `last_access` and the `is_active` soft-revoke flag were removed — neither was
|
|
11
10
|
* ever read; access tokens are revoked by deleting the row.
|
|
12
11
|
*/
|
|
13
|
-
declare const accessTokenSchema: import("@warlock.js/seal").ObjectValidator<{
|
|
12
|
+
export declare const accessTokenSchema: import("@warlock.js/seal").ObjectValidator<{
|
|
14
13
|
token: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
15
14
|
user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
|
|
16
|
-
user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
15
|
+
user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
16
|
+
/** Null for tokens issued before family tracking was introduced. */
|
|
17
17
|
family_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
|
|
18
18
|
isOptional: true;
|
|
19
19
|
};
|
|
20
20
|
expires_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date>;
|
|
21
|
-
}> & import("@warlock.js/seal").StandardSchemaV1<{
|
|
22
|
-
family_id?: string | undefined;
|
|
23
|
-
token: string;
|
|
24
|
-
user_id: string | number | boolean;
|
|
25
|
-
user_type: string;
|
|
26
|
-
expires_at: Date;
|
|
27
|
-
}, {
|
|
28
|
-
family_id?: string | undefined;
|
|
29
|
-
token: string;
|
|
30
|
-
user_id: string | number | boolean;
|
|
31
|
-
user_type: string;
|
|
32
|
-
expires_at: Date;
|
|
33
21
|
}>;
|
|
34
22
|
/**
|
|
35
23
|
* Persisted access-token record + the data layer for access tokens.
|
|
@@ -41,28 +29,17 @@ declare const accessTokenSchema: import("@warlock.js/seal").ObjectValidator<{
|
|
|
41
29
|
* override can rename/add columns by registering under
|
|
42
30
|
* `config.auth.accessToken.model`.
|
|
43
31
|
*/
|
|
44
|
-
declare class AccessToken extends Model {
|
|
32
|
+
export declare class AccessToken extends Model {
|
|
45
33
|
static table: string;
|
|
46
34
|
static schema: import("@warlock.js/seal").ObjectValidator<{
|
|
47
35
|
token: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
48
36
|
user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
|
|
49
|
-
user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
37
|
+
user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
|
|
38
|
+
/** Null for tokens issued before family tracking was introduced. */
|
|
50
39
|
family_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
|
|
51
40
|
isOptional: true;
|
|
52
41
|
};
|
|
53
42
|
expires_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date>;
|
|
54
|
-
}> & import("@warlock.js/seal").StandardSchemaV1<{
|
|
55
|
-
family_id?: string | undefined;
|
|
56
|
-
token: string;
|
|
57
|
-
user_id: string | number | boolean;
|
|
58
|
-
user_type: string;
|
|
59
|
-
expires_at: Date;
|
|
60
|
-
}, {
|
|
61
|
-
family_id?: string | undefined;
|
|
62
|
-
token: string;
|
|
63
|
-
user_id: string | number | boolean;
|
|
64
|
-
user_type: string;
|
|
65
|
-
expires_at: Date;
|
|
66
43
|
}>;
|
|
67
44
|
/**
|
|
68
45
|
* The raw signed JWT is a bearer credential — never serialize it. `hidden`
|
|
@@ -153,5 +130,4 @@ declare class AccessToken extends Model {
|
|
|
153
130
|
static purgeNeverExpiring(): Promise<AccessToken[]>;
|
|
154
131
|
}
|
|
155
132
|
//#endregion
|
|
156
|
-
export { AccessToken, accessTokenSchema };
|
|
157
133
|
//# sourceMappingURL=access-token.model.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-token.model.d.mts","names":[],"sources":["../../../../../../../../../packages/auth/src/models/access-token/access-token.model.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"access-token.model.d.mts","names":[],"sources":["../../../../../../../../../packages/auth/src/models/access-token/access-token.model.ts"],"mappings":";;;;;;;;;;;qBAaa,8CAAiB;;;;;;;;;;;;;;;;;;;;qBAmBjB,oBAAoB;SACjB;SAEA,mCAAM;;;;;;;;;;;;;;;;SAQN;;;;;;;;SASA;;MAGH;;MAKA;;;;;;;;;;;;;;;;MAmBA;;;;;;;MAcA;;;;SAOG,MAAM,MAAM,MAAM,eAAe,WAAW,MAAM,oBAAiB,QAAA;;;;SAanE,YAAY,gBAAgB,QAAQ;;;;SAOpC,cAAc,MAAM,MAAM,gBAAa;;;;SAOvC,iBAAiB,MAAM,OAAI;;;;;;SASrB,6BAA6B,MAAM,MAAM,mBAAmB;;;;;SAS5D,gBAAgB;;;;;;;;;;;;SAqBhB,qBAAqB,QAAQ;;;;;;SAW7B,sBAAsB,QAAQ"}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
import { AccessToken, accessTokenSchema } from "./access-token.model.mjs";
|
|
1
|
+
import { AccessToken, accessTokenSchema } from "./access-token.model.mjs";
|
|
2
|
+
export { AccessToken, accessTokenSchema };
|