@warlock.js/auth 5.30.0 → 5.31.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.
Files changed (35) hide show
  1. package/CHANGELOG.md +12 -1
  2. package/esm/commands/auth-purge-never-expiring-command.mjs.map +1 -1
  3. package/esm/commands/jwt-secret-generator-command.mjs.map +1 -1
  4. package/esm/middleware/auth.middleware.mjs +2 -2
  5. package/esm/middleware/auth.middleware.mjs.map +1 -1
  6. package/esm/middleware/login-throttle.middleware.mjs +1 -1
  7. package/esm/middleware/login-throttle.middleware.mjs.map +1 -1
  8. package/esm/models/access-token/access-token.model.d.mts +0 -24
  9. package/esm/models/access-token/access-token.model.d.mts.map +1 -1
  10. package/esm/models/auth-token-family/auth-token-family.model.d.mts +0 -24
  11. package/esm/models/auth-token-family/auth-token-family.model.d.mts.map +1 -1
  12. package/esm/models/one-time-token/one-time-token.model.d.mts +0 -32
  13. package/esm/models/one-time-token/one-time-token.model.d.mts.map +1 -1
  14. package/esm/models/passkey-credential/passkey-credential.model.d.mts +0 -28
  15. package/esm/models/passkey-credential/passkey-credential.model.d.mts.map +1 -1
  16. package/esm/models/provider-account/provider-account.model.d.mts +0 -24
  17. package/esm/models/provider-account/provider-account.model.d.mts.map +1 -1
  18. package/esm/models/refresh-token/refresh-token.model.d.mts +4 -48
  19. package/esm/models/refresh-token/refresh-token.model.d.mts.map +1 -1
  20. package/esm/services/resolve-request-user.d.mts.map +1 -1
  21. package/esm/services/resolve-request-user.mjs.map +1 -1
  22. package/llms-full.txt +189 -111
  23. package/package.json +8 -8
  24. package/skills/auth-basics/SKILL.md +10 -13
  25. package/skills/customize-token-storage/SKILL.md +24 -19
  26. package/skills/customize-user-type/SKILL.md +3 -1
  27. package/skills/index.md +4 -1
  28. package/skills/login-with-providers/SKILL.md +54 -29
  29. package/skills/manage-tokens/SKILL.md +2 -2
  30. package/skills/overview/SKILL.md +10 -1
  31. package/skills/protect-routes/SKILL.md +23 -14
  32. package/skills/register-user/SKILL.md +4 -4
  33. package/skills/run-auth-commands/SKILL.md +1 -1
  34. package/skills/throttle-login-attempts/SKILL.md +51 -22
  35. package/skills/verify-email-and-reset-password/SKILL.md +7 -5
package/CHANGELOG.md CHANGED
@@ -4,6 +4,17 @@ All notable changes to `@warlock.js/auth` are documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). `@warlock.js/*` packages are released in lockstep — every package shares the same version number, so a version below may list only the changes that affected this package.
6
6
 
7
+ ## 5.31.0 - 2026-10-18
8
+
9
+ ### Added
10
+
11
+ - `authMiddleware()` marks itself as an auth guard (`markAuthGuard()` from `@warlock.js/core`), so `warlock dev`'s unguarded-route warning recognises routes behind it; the `{ optional: true }` form is marked as not a guard. Docs and skills list what throttles auth endpoints by default (core's global limit, plus the throttles new apps attach to sign-in and password recovery) and how to add them to an existing app.
12
+ - Real Redis integration coverage for login failure counters and lockouts, including concurrent requests, preserved deadlines and counters recreated after a seeded key expires. Local runs opt in with `WARLOCK_REDIS_INTEGRATION=1`; CI requires a live Redis service. Tests use UUID prefixes and clean only their own keys.
13
+
14
+ ### Fixed
15
+
16
+ - Login, token and OTP throttles pass their counting-window TTL into each atomic increment. A counter that expires between the create-only seed and increment is recreated with an expiry rather than becoming permanent. Token middleware regression assertions cover the issue counters' one-hour window and the consume counter's fifteen-minute window.
17
+
7
18
  ## 5.30.0 - 2026-10-16
8
19
 
9
20
  ### Upgrading
@@ -12,7 +23,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
12
23
 
13
24
  ### Changed
14
25
 
15
- - **The login throttle keys accounts and IPs by a keyed hash** (`auth.throttle.count.email.<hmac>`, `auth.throttle.count.ip.<hmac>`) instead of the raw address or IP, so cache logs never show them. The HMAC key is derived from `auth.accessToken.secret` (as the OTP code MAC is), so a list of known addresses cannot reverse it; without a configured secret it falls back to SHA-256. The value is trimmed and lower-cased first, so `" Sara@x.io"` and `"sara@x.io"` share one bucket. Applies to `loginThrottleMiddleware` and every preset built on it (token issue/consume, OTP); a custom `identify` is used as is. `throttleIdentifierHash(kind, value)` is exported (FW-04).
26
+ - **The login throttle keys accounts and IPs by a keyed hash** (`auth.throttle.count.email.<hmac>`, `auth.throttle.count.ip.<hmac>`) instead of the raw address or IP, so cache logs never show them. The HMAC key is derived from `auth.accessToken.secret` (as the OTP code MAC is), so a list of known addresses cannot reverse it; without a configured secret it falls back to SHA-256. The value is trimmed and lower-cased first, so `" Sara@x.io"` and `"sara@x.io"` share one bucket. Applies to `loginThrottleMiddleware` and every preset built on it (token issue/consume, OTP); a custom `identify` is used as is. `throttleIdentifierHash(kind, value)` is exported.
16
27
  - A throttle lock stores its own deadline, so a cache driver with sliding expiration (`memoryExtended`) can no longer keep a retrying client locked past `lockoutDuration`. Locks written by 5.29 (`true`) are still honoured until they expire.
17
28
  - The throttle warns once when the cache driver is `null`, which stores nothing, so the throttle can never lock.
18
29
 
@@ -1 +1 @@
1
- {"version":3,"file":"auth-purge-never-expiring-command.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/commands/auth-purge-never-expiring-command.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { command } from \"@warlock.js/core\";\nimport { authService } from \"../services/auth.service\";\n\n/**\n * Register the `auth.purge-never-expiring` CLI command — remediation for the\n * pre-4.12.0 `expiresIn` defect (#25).\n *\n * **Why a command and not a migration.** The rows this removes cannot be\n * selected by a date predicate (an `Invalid Date` compares `false` against\n * every date, so `WHERE expires_at < now()` never matches one) and the\n * definitive signal — a token with no `exp` claim — lives inside the JWT\n * string, not in a column. A migration would also run once, silently, at deploy\n * time; an operator dealing with possibly-leaked credentials needs to *look*\n * first, then act, and to repeat it. Hence `--dry-run`, which is the default\n * posture this command is expected to be run in first.\n *\n * A migration is additionally the wrong shape for a fix that must reach both\n * supported stores: the same row is a different thing in MongoDB and Postgres,\n * and this goes through the models so a registered override\n * (`config.auth.accessToken.model`) is honoured.\n *\n * @example\n * ```bash\n * warlock auth.purge-never-expiring --dry-run # report only\n * warlock auth.purge-never-expiring # report, then delete\n * ```\n */\nexport function registerAuthPurgeNeverExpiringCommand() {\n return command({\n name: \"auth.purge-never-expiring\",\n description:\n \"Find (and delete) token rows that can never expire — an unusable expires_at, or a token with no exp claim\",\n options: [\n {\n text: \"--dry-run\",\n description: \"Report the affected rows without deleting anything\",\n type: \"boolean\",\n },\n ],\n preload: {\n env: true,\n config: [\"auth\", \"database\"],\n connectors: [\"database\"],\n },\n action: async (data: { options?: Record<string, unknown> } = {}) => {\n // Core camelCases flags (`--dry-run` ⇒ `dryRun`); both spellings are read\n // so the command does not depend on that detail staying true.\n //\n // Read fail-safe: the flag counts as set unless it is *explicitly* false\n // (`--dry-run=false`). An operator who asked to look before deleting and\n // got a delete because a flag arrived as `\"true\"` instead of `true` would\n // lose live credentials to a parsing detail.\n const flags = data.options ?? {};\n const flag = flags[\"dryRun\"] ?? flags[\"dry-run\"];\n const dryRun = flag !== undefined && flag !== false && flag !== \"false\";\n\n console.log(colors.cyan(\"🔎 Scanning for tokens that can never expire...\"));\n\n const { accessTokens, refreshTokens } = await authService.findNeverExpiringTokens();\n const total = accessTokens.length + refreshTokens.length;\n\n if (total === 0) {\n console.log(colors.green(\"✅ No never-expiring tokens found.\"));\n\n return;\n }\n\n console.log(\n colors.yellow(\n `⚠️ ${total} never-expiring token(s): ` +\n `${accessTokens.length} access, ${refreshTokens.length} refresh.`,\n ),\n );\n\n // Identify each row so an operator can correlate with their own audit\n // trail before (or after) the rows are gone. The token string itself is\n // never printed — it is a live credential until this command removes it.\n for (const token of accessTokens) {\n console.log(\n colors.yellow(\n ` access id=${token.id} user_id=${token.get(\"user_id\")} ` +\n `user_type=${token.get(\"user_type\")} expires_at=${String(token.get(\"expires_at\"))}`,\n ),\n );\n }\n\n for (const token of refreshTokens) {\n console.log(\n colors.yellow(\n ` refresh id=${token.id} user_id=${token.get(\"user_id\")} ` +\n `user_type=${token.get(\"user_type\")} expires_at=${String(token.get(\"expires_at\"))}`,\n ),\n );\n }\n\n if (dryRun) {\n console.log(\n colors.cyan(\"Dry run — nothing deleted. Re-run without --dry-run to revoke these tokens.\"),\n );\n\n return;\n }\n\n // Re-selects rather than deleting the rows just listed: the delete then\n // acts on the table's current state instead of a snapshot taken before\n // the operator read the report. A second full scan is affordable here —\n // this is a one-off remediation, not a request path.\n const purged = await authService.purgeNeverExpiringTokens();\n\n console.log(\n colors.green(\n `✅ Revoked ${purged.accessTokens} access token(s) and ` +\n `${purged.refreshTokens} refresh token(s). Affected users must log in again.`,\n ),\n );\n },\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,wCAAwC;CACtD,OAAO,QAAQ;EACb,MAAM;EACN,aACE;EACF,SAAS,CACP;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR,CACF;EACA,SAAS;GACP,KAAK;GACL,QAAQ,CAAC,QAAQ,UAAU;GAC3B,YAAY,CAAC,UAAU;EACzB;EACA,QAAQ,OAAO,OAA8C,CAAC,MAAM;GAQlE,MAAM,QAAQ,KAAK,WAAW,CAAC;GAC/B,MAAM,OAAO,MAAM,aAAa,MAAM;GACtC,MAAM,SAAS,SAAS,UAAa,SAAS,SAAS,SAAS;GAEhE,QAAQ,IAAI,OAAO,KAAK,iDAAiD,CAAC;GAE1E,MAAM,EAAE,cAAc,kBAAkB,MAAM,YAAY,wBAAwB;GAClF,MAAM,QAAQ,aAAa,SAAS,cAAc;GAElD,IAAI,UAAU,GAAG;IACf,QAAQ,IAAI,OAAO,MAAM,mCAAmC,CAAC;IAE7D;GACF;GAEA,QAAQ,IACN,OAAO,OACL,OAAO,MAAM,4BACR,aAAa,OAAO,WAAW,cAAc,OAAO,UAC3D,CACF;GAKA,KAAK,MAAM,SAAS,cAClB,QAAQ,IACN,OAAO,OACL,gBAAgB,MAAM,GAAG,WAAW,MAAM,IAAI,SAAS,EAAE,aAC1C,MAAM,IAAI,WAAW,EAAE,cAAc,OAAO,MAAM,IAAI,YAAY,CAAC,GACpF,CACF;GAGF,KAAK,MAAM,SAAS,eAClB,QAAQ,IACN,OAAO,OACL,gBAAgB,MAAM,GAAG,WAAW,MAAM,IAAI,SAAS,EAAE,aAC1C,MAAM,IAAI,WAAW,EAAE,cAAc,OAAO,MAAM,IAAI,YAAY,CAAC,GACpF,CACF;GAGF,IAAI,QAAQ;IACV,QAAQ,IACN,OAAO,KAAK,6EAA6E,CAC3F;IAEA;GACF;GAMA,MAAM,SAAS,MAAM,YAAY,yBAAyB;GAE1D,QAAQ,IACN,OAAO,MACL,aAAa,OAAO,aAAa,uBAC5B,OAAO,cAAc,qDAC5B,CACF;EACF;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"auth-purge-never-expiring-command.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/commands/auth-purge-never-expiring-command.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { command } from \"@warlock.js/core\";\nimport { authService } from \"../services/auth.service\";\n\n/**\n * Register the `auth.purge-never-expiring` CLI command — remediation for the\n * pre-4.12.0 `expiresIn` defect (#25).\n *\n * **Why a command and not a migration.** The rows this removes cannot be\n * selected by a date predicate (an `Invalid Date` compares `false` against\n * every date, so `WHERE expires_at < now()` never matches one) and the\n * definitive signal — a token with no `exp` claim — lives inside the JWT\n * string, not in a column. A migration would also run once, silently, at deploy\n * time; an operator dealing with possibly-leaked credentials needs to *look*\n * first, then act, and to repeat it. Hence `--dry-run`, which is the default\n * posture this command is expected to be run in first.\n *\n * A migration is additionally the wrong shape for a fix that must reach both\n * supported stores: the same row is a different thing in MongoDB and Postgres,\n * and this goes through the models so a registered override\n * (`config.auth.accessToken.model`) is honoured.\n *\n * @example\n * ```bash\n * warlock auth.purge-never-expiring --dry-run # report only\n * warlock auth.purge-never-expiring # report, then delete\n * ```\n */\nexport function registerAuthPurgeNeverExpiringCommand() {\n return command({\n name: \"auth.purge-never-expiring\",\n description:\n \"Find (and delete) token rows that can never expire — an unusable expires_at, or a token with no exp claim\",\n options: [\n {\n text: \"--dry-run\",\n description: \"Report the affected rows without deleting anything\",\n type: \"boolean\",\n },\n ],\n preload: {\n env: true,\n config: [\"auth\", \"database\"],\n connectors: [\"database\"],\n },\n action: async (data: { options?: Record<string, unknown> } = {}) => {\n // Core camelCases flags (`--dry-run` ⇒ `dryRun`); both spellings are read\n // so the command does not depend on that detail staying true.\n //\n // Read fail-safe: the flag counts as set unless it is *explicitly* false\n // (`--dry-run=false`). An operator who asked to look before deleting and\n // got a delete because a flag arrived as `\"true\"` instead of `true` would\n // lose live credentials to a parsing detail.\n const flags = data.options ?? {};\n const flag = flags[\"dryRun\"] ?? flags[\"dry-run\"];\n const dryRun = flag !== undefined && flag !== false && flag !== \"false\";\n\n console.log(colors.cyan(\"🔎 Scanning for tokens that can never expire...\"));\n\n const { accessTokens, refreshTokens } = await authService.findNeverExpiringTokens();\n const total = accessTokens.length + refreshTokens.length;\n\n if (total === 0) {\n console.log(colors.green(\"✅ No never-expiring tokens found.\"));\n\n return;\n }\n\n console.log(\n colors.yellow(\n `⚠️ ${total} never-expiring token(s): ` +\n `${accessTokens.length} access, ${refreshTokens.length} refresh.`,\n ),\n );\n\n // Identify each row so an operator can correlate with their own audit\n // trail before (or after) the rows are gone. The token string itself is\n // never printed — it is a live credential until this command removes it.\n for (const token of accessTokens) {\n console.log(\n colors.yellow(\n ` access id=${token.id} user_id=${token.get(\"user_id\")} ` +\n `user_type=${token.get(\"user_type\")} expires_at=${String(token.get(\"expires_at\"))}`,\n ),\n );\n }\n\n for (const token of refreshTokens) {\n console.log(\n colors.yellow(\n ` refresh id=${token.id} user_id=${token.get(\"user_id\")} ` +\n `user_type=${token.get(\"user_type\")} expires_at=${String(token.get(\"expires_at\"))}`,\n ),\n );\n }\n\n if (dryRun) {\n console.log(\n colors.cyan(\n \"Dry run — nothing deleted. Re-run without --dry-run to revoke these tokens.\",\n ),\n );\n\n return;\n }\n\n // Re-selects rather than deleting the rows just listed: the delete then\n // acts on the table's current state instead of a snapshot taken before\n // the operator read the report. A second full scan is affordable here —\n // this is a one-off remediation, not a request path.\n const purged = await authService.purgeNeverExpiringTokens();\n\n console.log(\n colors.green(\n `✅ Revoked ${purged.accessTokens} access token(s) and ` +\n `${purged.refreshTokens} refresh token(s). Affected users must log in again.`,\n ),\n );\n },\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,wCAAwC;CACtD,OAAO,QAAQ;EACb,MAAM;EACN,aACE;EACF,SAAS,CACP;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR,CACF;EACA,SAAS;GACP,KAAK;GACL,QAAQ,CAAC,QAAQ,UAAU;GAC3B,YAAY,CAAC,UAAU;EACzB;EACA,QAAQ,OAAO,OAA8C,CAAC,MAAM;GAQlE,MAAM,QAAQ,KAAK,WAAW,CAAC;GAC/B,MAAM,OAAO,MAAM,aAAa,MAAM;GACtC,MAAM,SAAS,SAAS,UAAa,SAAS,SAAS,SAAS;GAEhE,QAAQ,IAAI,OAAO,KAAK,iDAAiD,CAAC;GAE1E,MAAM,EAAE,cAAc,kBAAkB,MAAM,YAAY,wBAAwB;GAClF,MAAM,QAAQ,aAAa,SAAS,cAAc;GAElD,IAAI,UAAU,GAAG;IACf,QAAQ,IAAI,OAAO,MAAM,mCAAmC,CAAC;IAE7D;GACF;GAEA,QAAQ,IACN,OAAO,OACL,OAAO,MAAM,4BACR,aAAa,OAAO,WAAW,cAAc,OAAO,UAC3D,CACF;GAKA,KAAK,MAAM,SAAS,cAClB,QAAQ,IACN,OAAO,OACL,gBAAgB,MAAM,GAAG,WAAW,MAAM,IAAI,SAAS,EAAE,aAC1C,MAAM,IAAI,WAAW,EAAE,cAAc,OAAO,MAAM,IAAI,YAAY,CAAC,GACpF,CACF;GAGF,KAAK,MAAM,SAAS,eAClB,QAAQ,IACN,OAAO,OACL,gBAAgB,MAAM,GAAG,WAAW,MAAM,IAAI,SAAS,EAAE,aAC1C,MAAM,IAAI,WAAW,EAAE,cAAc,OAAO,MAAM,IAAI,YAAY,CAAC,GACpF,CACF;GAGF,IAAI,QAAQ;IACV,QAAQ,IACN,OAAO,KACL,6EACF,CACF;IAEA;GACF;GAMA,MAAM,SAAS,MAAM,YAAY,yBAAyB;GAE1D,QAAQ,IACN,OAAO,MACL,aAAa,OAAO,aAAa,uBAC5B,OAAO,cAAc,qDAC5B,CACF;EACF;CACF,CAAC;AACH"}
@@ -1 +1 @@
1
- {"version":3,"file":"jwt-secret-generator-command.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/commands/jwt-secret-generator-command.ts"],"sourcesContent":["import { command } from \"@warlock.js/core\";\r\nimport { generateJWTSecret } from \"../services/generate-jwt-secret\";\r\n\r\nexport function registerJWTSecretGeneratorCommand() {\r\n return command({\r\n name: \"jwt.generate\",\r\n description: \"Generate JWT Secret key in .env file\",\r\n action: generateJWTSecret,\r\n });\r\n}\r\n"],"mappings":";;;;AAGA,SAAgB,oCAAoC;CAClD,OAAO,QAAQ;EACb,MAAM;EACN,aAAa;EACb,QAAQ;CACV,CAAC;AACH"}
1
+ {"version":3,"file":"jwt-secret-generator-command.mjs","names":[],"sources":["../../../../../../../../packages/auth/src/commands/jwt-secret-generator-command.ts"],"sourcesContent":["import { command } from \"@warlock.js/core\";\nimport { generateJWTSecret } from \"../services/generate-jwt-secret\";\n\nexport function registerJWTSecretGeneratorCommand() {\n return command({\n name: \"jwt.generate\",\n description: \"Generate JWT Secret key in .env file\",\n action: generateJWTSecret,\n });\n}\n"],"mappings":";;;;AAGA,SAAgB,oCAAoC;CAClD,OAAO,QAAQ;EACb,MAAM;EACN,aAAa;EACb,QAAQ;CACV,CAAC;AACH"}
@@ -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":"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"}
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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"}
@@ -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":"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"}
@@ -18,18 +18,6 @@ declare const accessTokenSchema: import("@warlock.js/seal").ObjectValidator<{
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.
@@ -51,18 +39,6 @@ declare class AccessToken extends Model {
51
39
  isOptional: true;
52
40
  };
53
41
  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
42
  }>;
67
43
  /**
68
44
  * The raw signed JWT is a bearer credential — never serialize it. `hidden`
@@ -1 +1 @@
1
- {"version":3,"file":"access-token.model.d.mts","names":[],"sources":["../../../../../../../../../packages/auth/src/models/access-token/access-token.model.ts"],"mappings":";;;;;;AAaA;;;;;;cAAa,iBAAA,6BAAiB,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAmBjB,WAAA,SAAoB,KAAA;EAAA,OACjB,KAAA;EAAA,OAEA,MAAA,6BAAM,eAAA;;;yHAHG;;;;;;;;;;;;;;;;;;EAiHa;;;;;;EAAA,OAtGtB,MAAA;EAXsB;;;;;;;EAAA,OAoBtB,cAAA;;MAGH,MAAA;;MAKA,QAAA;;;;;;;;;;;;;;;;MAmBA,SAAA;;;;;;;MAcA,YAAA;;;;SAOG,KAAA,CAAM,IAAA,EAAM,IAAA,EAAM,KAAA,UAAe,SAAA,EAAW,IAAA,EAAM,QAAA,YAAiB,OAAA,CAAA,WAAA;EAxCtE;;;EAAA,OAqDG,WAAA,CAAY,KAAA,WAAgB,OAAA,CAAQ,WAAA;EAbxB;;;EAAA,OAoBZ,aAAA,CAAc,IAAA,EAAM,IAAA,EAAM,KAAA,WAAa,OAAA;EApBN;;;EAAA,OA2BjC,gBAAA,CAAiB,IAAA,EAAM,IAAA,GAAI,OAAA;EAd3B;;;;;EAAA,OAuBM,4BAAA,CAA6B,IAAA,EAAM,IAAA,EAAM,QAAA,WAAmB,OAAA;EAhBpD;;;;EAAA,OAyBR,YAAA,IAAgB,OAAA;EAlBL;;;;;;;;;;;EAAA,OAuCX,iBAAA,IAAqB,OAAA,CAAQ,WAAA;EAW7B;;;;AAAyC;EAAzC,OAAA,kBAAA,IAAsB,OAAA,CAAQ,WAAA;AAAA"}
1
+ {"version":3,"file":"access-token.model.d.mts","names":[],"sources":["../../../../../../../../../packages/auth/src/models/access-token/access-token.model.ts"],"mappings":";;;;;;AAaA;;;;;;cAAa,iBAAA,6BAAiB,eAAA;;;;;;;qGAO5B,IAAA,EAAA,IAAA;AAAA;;;;;;;;;;AAYF;cAAa,WAAA,SAAoB,KAAA;EAAA,OACjB,KAAA;EAAA,OAEA,MAAA,6BAAM,eAAA;;;yHAiE6D;;;;;;EA2B5C;;;;;;EAAA,OApFvB,MAAA;EAsIoC;;;;;;;EAAA,OA7HpC,cAAA;EAjBM;EAAA,IAoBT,MAAA;;MAKA,QAAA;;;;;;;;;;;;;;;;MAmBA,SAAA;EA3BG;;;;;;EAAA,IAyCH,YAAA;EAOS;;;EAAA,OAAN,KAAA,CAAM,IAAA,EAAM,IAAA,EAAM,KAAA,UAAe,SAAA,EAAW,IAAA,EAAM,QAAA,YAAiB,OAAA,CAAA,WAAA;EAAjB;;;EAAA,OAalD,WAAA,CAAY,KAAA,WAAgB,OAAA,CAAQ,WAAA;EAAxB;;;EAAA,OAOZ,aAAA,CAAc,IAAA,EAAM,IAAA,EAAM,KAAA,WAAa,OAAA;EAAnB;;;EAAA,OAOpB,gBAAA,CAAiB,IAAA,EAAM,IAAA,GAAI,OAAA;EAA3B;;;;;EAAA,OASM,4BAAA,CAA6B,IAAA,EAAM,IAAA,EAAM,QAAA,WAAmB,OAAA;EAA/B;;;;EAAA,OAS7B,YAAA,IAAgB,OAAA;EAqBhB;;;;;;;AAWyC;;;;EAXzC,OAAA,iBAAA,IAAqB,OAAA,CAAQ,WAAA;;;;;;SAW7B,kBAAA,IAAsB,OAAA,CAAQ,WAAA;AAAA"}
@@ -13,18 +13,6 @@ declare const authTokenFamilySchema: import("@warlock.js/seal").ObjectValidator<
13
13
  revoked_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date> & {
14
14
  isOptional: true;
15
15
  };
16
- }> & import("@warlock.js/seal").StandardSchemaV1<{
17
- revision?: number | undefined;
18
- revoked_at?: Date | undefined;
19
- family_id: string;
20
- user_id: string | number | boolean;
21
- user_type: string;
22
- }, {
23
- revision?: number | undefined;
24
- revoked_at?: Date | undefined;
25
- family_id: string;
26
- user_id: string | number | boolean;
27
- user_type: string;
28
16
  }>;
29
17
  /** Coordinates family-wide refresh, replay containment, and logout transitions. */
30
18
  declare class AuthTokenFamily extends Model {
@@ -39,18 +27,6 @@ declare class AuthTokenFamily extends Model {
39
27
  revoked_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date> & {
40
28
  isOptional: true;
41
29
  };
42
- }> & import("@warlock.js/seal").StandardSchemaV1<{
43
- revision?: number | undefined;
44
- revoked_at?: Date | undefined;
45
- family_id: string;
46
- user_id: string | number | boolean;
47
- user_type: string;
48
- }, {
49
- revision?: number | undefined;
50
- revoked_at?: Date | undefined;
51
- family_id: string;
52
- user_id: string | number | boolean;
53
- user_type: string;
54
30
  }>;
55
31
  /** Whether a family-wide revoke has made every member unusable. */
56
32
  get isRevoked(): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"auth-token-family.model.d.mts","names":[],"sources":["../../../../../../../../../packages/auth/src/models/auth-token-family/auth-token-family.model.ts"],"mappings":";;;;;cAKa,qBAAA,6BAAqB,eAAA;;;;;;;;;;;;;;;;;;;;;;;;cASrB,eAAA,SAAwB,KAAA;EAAA,OACrB,KAAA;EAAA,OAEA,MAAA,6BAAM,eAAA;;;;;;;;;;;;;;;;;;;;;;;;MAGT,SAAA;;SAKG,KAAA,CAAM,IAAA,EAAM,IAAA,EAAM,QAAA,WAAgB,OAAA,CAAA,eAAA;EAAA,OASlC,cAAA,CAAe,QAAA,WAAmB,OAAA,CAAQ,eAAA;EATR;EAAA,OAc5B,MAAA,CAAO,IAAA,EAAM,IAAA,EAAM,QAAA,WAAmB,OAAA,CAAQ,eAAA;EALlB;EAAA,OAqClC,SAAA,CAAU,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,eAAA;EAhCqB;;;;EAAA,OA0CpD,eAAA,CAAgB,QAAA,UAAkB,gBAAA,WAA2B,OAAA;EAAA;EAAA,OAQ7D,MAAA,CAAO,QAAA,UAAkB,gBAAA,WAA2B,OAAA;AAAA"}
1
+ {"version":3,"file":"auth-token-family.model.d.mts","names":[],"sources":["../../../../../../../../../packages/auth/src/models/auth-token-family/auth-token-family.model.ts"],"mappings":";;;;;cAKa,qBAAA,6BAAqB,eAAA;;;;;;;qGAMhC,IAAA,EAAA,IAAA;;;;;cAGW,eAAA,SAAwB,KAAA;EAAA,OACrB,KAAA;EAAA,OAEA,MAAA,6BAAM,eAAA;;;;;;;;;;;;MAGT,SAAA;EANA;EAAA,OAWG,KAAA,CAAM,IAAA,EAAM,IAAA,EAAM,QAAA,WAAgB,OAAA,CAAA,eAAA;EAAA,OASlC,cAAA,CAAe,QAAA,WAAmB,OAAA,CAAQ,eAAA;;SAKpC,MAAA,CAAO,IAAA,EAAM,IAAA,EAAM,QAAA,WAAmB,OAAA,CAAQ,eAAA;EAdxC;EAAA,OA8CZ,SAAA,CAAU,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,eAAA;EA9CG;;;;EAAA,OAwDlC,eAAA,CAAgB,QAAA,UAAkB,gBAAA,WAA2B,OAAA;EA1CjB;EAAA,OAkD5C,MAAA,CAAO,QAAA,UAAkB,gBAAA,WAA2B,OAAA;AAAA"}
@@ -24,22 +24,6 @@ declare const oneTimeTokenSchema: import("@warlock.js/seal").ObjectValidator<{
24
24
  attempts: import("@warlock.js/seal").IntValidator & import("@warlock.js/seal").StandardSchemaV1<number, number> & {
25
25
  isOptional: true;
26
26
  };
27
- }> & import("@warlock.js/seal").StandardSchemaV1<{
28
- user_id?: string | number | boolean | undefined;
29
- user_type?: string | undefined;
30
- consumed_at?: Date | undefined;
31
- attempts?: number | undefined;
32
- token_hash: string;
33
- purpose: string;
34
- expires_at: Date;
35
- }, {
36
- user_id?: string | number | boolean | undefined;
37
- user_type?: string | undefined;
38
- consumed_at?: Date | undefined;
39
- attempts?: number | undefined;
40
- token_hash: string;
41
- purpose: string;
42
- expires_at: Date;
43
27
  }>;
44
28
  /**
45
29
  * Persisted one-time secret: email-verification / password-reset token, OTP
@@ -68,22 +52,6 @@ declare class OneTimeToken extends Model {
68
52
  attempts: import("@warlock.js/seal").IntValidator & import("@warlock.js/seal").StandardSchemaV1<number, number> & {
69
53
  isOptional: true;
70
54
  };
71
- }> & import("@warlock.js/seal").StandardSchemaV1<{
72
- user_id?: string | number | boolean | undefined;
73
- user_type?: string | undefined;
74
- consumed_at?: Date | undefined;
75
- attempts?: number | undefined;
76
- token_hash: string;
77
- purpose: string;
78
- expires_at: Date;
79
- }, {
80
- user_id?: string | number | boolean | undefined;
81
- user_type?: string | undefined;
82
- consumed_at?: Date | undefined;
83
- attempts?: number | undefined;
84
- token_hash: string;
85
- purpose: string;
86
- expires_at: Date;
87
55
  }>;
88
56
  /**
89
57
  * The SHA-256 of a one-time secret. For a short OTP code the hash is
@@ -1 +1 @@
1
- {"version":3,"file":"one-time-token.model.d.mts","names":[],"sources":["../../../../../../../../../packages/auth/src/models/one-time-token/one-time-token.model.ts"],"mappings":";;;;;KAMY,mBAAA;AAAZ;;;;AAAA,cAWa,kBAAA,6BAAkB,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAoBlB,YAAA,SAAqB,KAAA;EAAA,OAClB,KAAA;EAAA,OAEA,MAAA,6BAAM,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2DZ;;;;;EAAA,OApDM,MAAA;EA2ED;;;;;;EAAA,OAnEC,cAAA;EAqFX;EAAA,IAlFQ,MAAA;EAiHuB;EAAA,IA5GvB,QAAA;EA8HA;EAAA,IAzHA,UAAA;EAiI6B;EAAA,IA5H7B,SAAA;EA4HkE;;;;;EA/GhE,eAAA,IAAmB,OAAA;EA9ClB;EAAA,OA0DA,KAAA,CACZ,IAAA,EAAM,IAAA,EACN,OAAA,EAAS,mBAAA,EACT,SAAA,UACA,SAAA,EAAW,IAAA,GAAI,OAAA,CAAA,YAAA;;;;;SAiBH,cAAA,CACZ,OAAA,EAAS,mBAAA,EACT,SAAA,UACA,SAAA,EAAW,IAAA,EACX,IAAA,GAAO,IAAA,GAAI,OAAA,CAAA,YAAA;;SAcC,aAAA,CACZ,IAAA,EAAM,IAAA,EACN,OAAA,EAAS,mBAAA,GACR,OAAA,CAAQ,YAAA;;;;;;EAYE,aAAA,CAAc,GAAA,WAAc,OAAA;;;;;;;SAmBrB,UAAA,IAAc,OAAA;;SAepB,UAAA,CACZ,SAAA,UACA,OAAA,EAAS,mBAAA,GACR,OAAA,CAAQ,YAAA;;;;;SAQG,mBAAA,CAAoB,IAAA,EAAM,IAAA,EAAM,OAAA,EAAS,mBAAA,GAAsB,OAAA;AAAA"}
1
+ {"version":3,"file":"one-time-token.model.d.mts","names":[],"sources":["../../../../../../../../../packages/auth/src/models/one-time-token/one-time-token.model.ts"],"mappings":";;;;;KAMY,mBAAA;AAAZ;;;;AAAA,cAWa,kBAAA,6BAAkB,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;cAoBlB,YAAA,SAAqB,KAAA;EAAA,OAClB,KAAA;EAAA,OAEA,MAAA,6BAAM,eAAA;;;;;;;;;;;;;;;;;;;;;;SAON,MAAA;EAqDH;;;;;;EAAA,OA7CG,cAAA;EAoED;EAAA,IAjEF,MAAA;EAgFH;EAAA,IA3EG,QAAA;EA6EA;EAAA,IAxEA,UAAA;EAoF8B;EAAA,IA/E9B,SAAA;EAmHA;;;;;EAtGE,eAAA,IAAmB,OAAA;EAjDA;EAAA,OA6DlB,KAAA,CACZ,IAAA,EAAM,IAAA,EACN,OAAA,EAAS,mBAAA,EACT,SAAA,UACA,SAAA,EAAW,IAAA,GAAI,OAAA,CAAA,YAAA;EAjEoB;;;;EAAA,OAkFvB,cAAA,CACZ,OAAA,EAAS,mBAAA,EACT,SAAA,UACA,SAAA,EAAW,IAAA,EACX,IAAA,GAAO,IAAA,GAAI,OAAA,CAAA,YAAA;;SAcC,aAAA,CACZ,IAAA,EAAM,IAAA,EACN,OAAA,EAAS,mBAAA,GACR,OAAA,CAAQ,YAAA;;;;;;EAYE,aAAA,CAAc,GAAA,WAAc,OAAA;;;;;;;SAmBrB,UAAA,IAAc,OAAA;;SAepB,UAAA,CACZ,SAAA,UACA,OAAA,EAAS,mBAAA,GACR,OAAA,CAAQ,YAAA;;;;;SAQG,mBAAA,CAAoB,IAAA,EAAM,IAAA,EAAM,OAAA,EAAS,mBAAA,GAAsB,OAAA;AAAA"}
@@ -14,20 +14,6 @@ declare const passkeyCredentialSchema: import("@warlock.js/seal").ObjectValidato
14
14
  };
15
15
  user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
16
16
  user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
17
- }> & import("@warlock.js/seal").StandardSchemaV1<{
18
- transports?: string[] | undefined;
19
- credential_id: string;
20
- public_key: string;
21
- counter: number;
22
- user_id: string | number | boolean;
23
- user_type: string;
24
- }, {
25
- transports?: string[] | undefined;
26
- credential_id: string;
27
- public_key: string;
28
- counter: number;
29
- user_id: string | number | boolean;
30
- user_type: string;
31
17
  }>;
32
18
  /**
33
19
  * A registered WebAuthn credential. `credential_id` and `public_key` are
@@ -47,20 +33,6 @@ declare class PasskeyCredential extends Model {
47
33
  };
48
34
  user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
49
35
  user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
50
- }> & import("@warlock.js/seal").StandardSchemaV1<{
51
- transports?: string[] | undefined;
52
- credential_id: string;
53
- public_key: string;
54
- counter: number;
55
- user_id: string | number | boolean;
56
- user_type: string;
57
- }, {
58
- transports?: string[] | undefined;
59
- credential_id: string;
60
- public_key: string;
61
- counter: number;
62
- user_id: string | number | boolean;
63
- user_type: string;
64
36
  }>;
65
37
  /**
66
38
  * Permanent regardless of the data source default — Mongo's driver default
@@ -1 +1 @@
1
- {"version":3,"file":"passkey-credential.model.d.mts","names":[],"sources":["../../../../../../../../../packages/auth/src/models/passkey-credential/passkey-credential.model.ts"],"mappings":";;;;;cAKa,uBAAA,6BAAuB,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAcvB,iBAAA,SAA0B,KAAA;EAAA,OACvB,KAAA;EAAA,OAEA,MAAA,6BAAM,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;SAQN,cAAA;;MAGH,YAAA;;MAKA,OAAA;;MAKA,MAAA;;MAKA,QAAA;;SAKG,kBAAA,CAAmB,YAAA,WAAuB,OAAA,CAAQ,iBAAA;;SAKlD,OAAA,CAAQ,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,iBAAA;;;;;EAQ9B,cAAA,CAAe,QAAA,UAAkB,UAAA,WAAqB,OAAA;AAAA"}
1
+ {"version":3,"file":"passkey-credential.model.d.mts","names":[],"sources":["../../../../../../../../../packages/auth/src/models/passkey-credential/passkey-credential.model.ts"],"mappings":";;;;;cAKa,uBAAA,6BAAuB,eAAA;;;;;;;;;;;;;;;;;cAcvB,iBAAA,SAA0B,KAAA;EAAA,OACvB,KAAA;EAAA,OAEA,MAAA,6BAAM,eAAA;;;;;;;;;;;;EAHS;;;;;;EAAA,OAWf,cAAA;EAoCqD;EAAA,IAjCxD,YAAA;EAd+B;EAAA,IAmB/B,OAAA;EAnB0B;EAAA,IAwB1B,MAAA;EArBG;EAAA,IA0BH,QAAA;;SAKG,kBAAA,CAAmB,YAAA,WAAuB,OAAA,CAAQ,iBAAA;;SAKlD,OAAA,CAAQ,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,iBAAA;;;;;EAQ9B,cAAA,CAAe,QAAA,UAAkB,UAAA,WAAqB,OAAA;AAAA"}
@@ -11,18 +11,6 @@ declare const providerAccountSchema: import("@warlock.js/seal").ObjectValidator<
11
11
  email: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
12
12
  isOptional: true;
13
13
  };
14
- }> & import("@warlock.js/seal").StandardSchemaV1<{
15
- email?: string | undefined;
16
- provider: string;
17
- provider_user_id: string;
18
- user_id: string | number | boolean;
19
- user_type: string;
20
- }, {
21
- email?: string | undefined;
22
- provider: string;
23
- provider_user_id: string;
24
- user_id: string | number | boolean;
25
- user_type: string;
26
14
  }>;
27
15
  /**
28
16
  * Links a provider identity (`provider` + the provider's stable user id) to
@@ -40,18 +28,6 @@ declare class ProviderAccount extends Model {
40
28
  email: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
41
29
  isOptional: true;
42
30
  };
43
- }> & import("@warlock.js/seal").StandardSchemaV1<{
44
- email?: string | undefined;
45
- provider: string;
46
- provider_user_id: string;
47
- user_id: string | number | boolean;
48
- user_type: string;
49
- }, {
50
- email?: string | undefined;
51
- provider: string;
52
- provider_user_id: string;
53
- user_id: string | number | boolean;
54
- user_type: string;
55
31
  }>;
56
32
  /** The linked user's id. */
57
33
  get userId(): any;
@@ -1 +1 @@
1
- {"version":3,"file":"provider-account.model.d.mts","names":[],"sources":["../../../../../../../../../packages/auth/src/models/provider-account/provider-account.model.ts"],"mappings":";;;;;cAKa,qBAAA,6BAAqB,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;cAcrB,eAAA,SAAwB,KAAA;EAAA,OACrB,KAAA;EAAA,OAEA,MAAA,6BAAM,eAAA;;;;;;;;;;;;;;;;;;;;;;MAGT,MAAA;;MAKA,QAAA;;SAKG,QAAA,CACZ,QAAA,UACA,cAAA,WACC,OAAA,CAAQ,eAAA;;SAKG,IAAA,CAAK,QAAA,UAAkB,cAAA,UAAwB,IAAA,EAAM,IAAA,EAAM,KAAA,YAAc,OAAA,CAAA,eAAA;AAAA"}
1
+ {"version":3,"file":"provider-account.model.d.mts","names":[],"sources":["../../../../../../../../../packages/auth/src/models/provider-account/provider-account.model.ts"],"mappings":";;;;;cAKa,qBAAA,6BAAqB,eAAA;;;;;;;;;;;;;;;cAcrB,eAAA,SAAwB,KAAA;EAAA,OACrB,KAAA;EAAA,OAEA,MAAA,6BAAM,eAAA;;;;;;;;;;MAGT,MAAA;EAaR;EAAA,IARQ,QAAA;EAa4E;EAAA,OARzE,QAAA,CACZ,QAAA,UACA,cAAA,WACC,OAAA,CAAQ,eAAA;EAnBwB;EAAA,OAwBrB,IAAA,CAAK,QAAA,UAAkB,cAAA,UAAwB,IAAA,EAAM,IAAA,EAAM,KAAA,YAAc,OAAA,CAAA,eAAA;AAAA"}
@@ -33,33 +33,11 @@ declare const refreshTokenSchema: import("@warlock.js/seal").ObjectValidator<{
33
33
  successor_refresh_token_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
34
34
  isOptional: true;
35
35
  };
36
- device_info: import("@warlock.js/seal").RecordValidator & {
36
+ device_info: import("@warlock.js/seal").StandardSchemaV1<Record<string, any>, Record<string, any>> & import("@warlock.js/seal").RecordValidator & {
37
37
  valueValidator: import("@warlock.js/seal").AnyValidator & import("@warlock.js/seal").StandardSchemaV1<any, any>;
38
- } & import("@warlock.js/seal").StandardSchemaV1<Record<string, any>, Record<string, any>> & {
38
+ } & {
39
39
  isOptional: true;
40
40
  };
41
- }> & import("@warlock.js/seal").StandardSchemaV1<{
42
- last_used_at?: Date | undefined;
43
- revoked_at?: Date | undefined;
44
- successor_access_token_id?: string | undefined;
45
- successor_refresh_token_id?: string | undefined;
46
- device_info?: Record<string, any> | undefined;
47
- token: string;
48
- user_id: string | number | boolean;
49
- user_type: string;
50
- family_id: string;
51
- expires_at: Date;
52
- }, {
53
- last_used_at?: Date | undefined;
54
- revoked_at?: Date | undefined;
55
- successor_access_token_id?: string | undefined;
56
- successor_refresh_token_id?: string | undefined;
57
- device_info?: Record<string, any> | undefined;
58
- token: string;
59
- user_id: string | number | boolean;
60
- user_type: string;
61
- family_id: string;
62
- expires_at: Date;
63
41
  }>;
64
42
  /**
65
43
  * Extra attributes captured when a refresh token is issued.
@@ -99,33 +77,11 @@ declare class RefreshToken extends Model {
99
77
  successor_refresh_token_id: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string> & {
100
78
  isOptional: true;
101
79
  };
102
- device_info: import("@warlock.js/seal").RecordValidator & {
80
+ device_info: import("@warlock.js/seal").StandardSchemaV1<Record<string, any>, Record<string, any>> & import("@warlock.js/seal").RecordValidator & {
103
81
  valueValidator: import("@warlock.js/seal").AnyValidator & import("@warlock.js/seal").StandardSchemaV1<any, any>;
104
- } & import("@warlock.js/seal").StandardSchemaV1<Record<string, any>, Record<string, any>> & {
82
+ } & {
105
83
  isOptional: true;
106
84
  };
107
- }> & import("@warlock.js/seal").StandardSchemaV1<{
108
- last_used_at?: Date | undefined;
109
- revoked_at?: Date | undefined;
110
- successor_access_token_id?: string | undefined;
111
- successor_refresh_token_id?: string | undefined;
112
- device_info?: Record<string, any> | undefined;
113
- token: string;
114
- user_id: string | number | boolean;
115
- user_type: string;
116
- family_id: string;
117
- expires_at: Date;
118
- }, {
119
- last_used_at?: Date | undefined;
120
- revoked_at?: Date | undefined;
121
- successor_access_token_id?: string | undefined;
122
- successor_refresh_token_id?: string | undefined;
123
- device_info?: Record<string, any> | undefined;
124
- token: string;
125
- user_id: string | number | boolean;
126
- user_type: string;
127
- family_id: string;
128
- expires_at: Date;
129
85
  }>;
130
86
  /**
131
87
  * The raw signed refresh JWT is a bearer credential — never serialize it.
@@ -1 +1 @@
1
- {"version":3,"file":"refresh-token.model.d.mts","names":[],"sources":["../../../../../../../../../packages/auth/src/models/refresh-token/refresh-token.model.ts"],"mappings":";;;;;;;AAkBA;;;;;;;;;;cAAa,kBAAA,6BAAkB,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAiBnB,wBAAA;EACV,QAAA;EACA,SAAA;EACA,UAAA,GAAa,UAAU;AAAA;;;;;;;;;;;cAaZ,YAAA,SAAqB,KAAA;EAAA,OAClB,KAAA;EAAA,OAEA,MAAA,6BAAM,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;SAQN,MAAA;EAsFY;;;;;;EAAA,OA9EZ,cAAA;EA0GgD;EAAA,IAvGnD,QAAA;EA4G2C;;;;;;;;;;EAAA,IA9F3C,SAAA;EAoI2C;;;;EAAA,IAxH3C,YAAA;EAuJ8C;EAAA,IAlJ9C,SAAA;EAsKyB;EAAA,IAjKzB,OAAA;EAiL8B;;;EA1K5B,MAAA,IAAU,OAAA;EAjEc;;;;;;;EA4ExB,cAAA,IAAkB,OAAA;;;;EAclB,UAAA,IAAc,OAAA;;;;SAOb,KAAA,CAAM,IAAA,EAAM,IAAA,EAAM,KAAA,UAAe,OAAA,EAAS,wBAAA,GAAwB,OAAA,CAAA,YAAA;;;;SAoBlE,WAAA,CAAY,KAAA,WAAgB,OAAA,CAAQ,YAAA;;;;;SAQpC,WAAA,CAAY,IAAA,EAAM,IAAA,EAAM,KAAA,WAAgB,OAAA,CAAQ,YAAA;;SAKhD,YAAA,CAAa,QAAA,WAAmB,OAAA,CAAQ,YAAA;;;;;SAQxC,eAAA,CAAgB,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,YAAA;;;;SAOrC,aAAA,CAAc,IAAA,EAAM,IAAA,EAAM,KAAA,WAAa,OAAA;;;;SAOvC,SAAA,CAAU,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,YAAA;;;;;;;;;SAgBzB,YAAA,CAAa,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,YAAA;;;;;SAgBlC,YAAA,CAAa,QAAA,WAAmB,OAAA,CAAQ,YAAA;;;;;;SAexC,UAAA,CAAW,IAAA,EAAM,IAAA,EAAM,GAAA,WAAc,OAAA;;;;;;SAoBrC,YAAA,IAAgB,OAAA,CAAQ,YAAA;;;;;;;SAgBxB,iBAAA,IAAqB,OAAA,CAAQ,YAAA;;;;;;;;;;SAe7B,kBAAA,IAAsB,OAAA,CAAQ,YAAA;AAAA"}
1
+ {"version":3,"file":"refresh-token.model.d.mts","names":[],"sources":["../../../../../../../../../packages/auth/src/models/refresh-token/refresh-token.model.ts"],"mappings":";;;;;;;AAkBA;;;;;;;;;;cAAa,kBAAA,6BAAkB,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;KAiBnB,wBAAA;EACV,QAAA;EACA,SAAA;EACA,UAAA,GAAa,UAAU;AAAA;;;;;;;;;;;cAaZ,YAAA,SAAqB,KAAA;EAAA,OAClB,KAAA;EAAA,OAEA,MAAA,6BAAM,eAAA;;;;;;;;;;;OAjBpB;;;;;;;;;;;;;;;;;;;SAyBc,MAAA;EAsF0C;;;;;;EAAA,OA9E1C,cAAA;EA0GwC;EAAA,IAvG3C,QAAA;EA4GmC;;;;;;;;;;EAAA,IA9FnC,SAAA;EAoImC;;;;EAAA,IAxHnC,YAAA;EA2KiC;EAAA,IAtKjC,SAAA;EAsLsC;EAAA,IAjLtC,OAAA;EAgMuC;;;EAzLrC,MAAA,IAAU,OAAA;EAjEc;;;;;;;EA4ExB,cAAA,IAAkB,OAAA;;;;EAclB,UAAA,IAAc,OAAA;;;;SAOb,KAAA,CAAM,IAAA,EAAM,IAAA,EAAM,KAAA,UAAe,OAAA,EAAS,wBAAA,GAAwB,OAAA,CAAA,YAAA;;;;SAoBlE,WAAA,CAAY,KAAA,WAAgB,OAAA,CAAQ,YAAA;;;;;SAQpC,WAAA,CAAY,IAAA,EAAM,IAAA,EAAM,KAAA,WAAgB,OAAA,CAAQ,YAAA;;SAKhD,YAAA,CAAa,QAAA,WAAmB,OAAA,CAAQ,YAAA;;;;;SAQxC,eAAA,CAAgB,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,YAAA;;;;SAOrC,aAAA,CAAc,IAAA,EAAM,IAAA,EAAM,KAAA,WAAa,OAAA;;;;SAOvC,SAAA,CAAU,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,YAAA;;;;;;;;;SAgBzB,YAAA,CAAa,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,YAAA;;;;;SAgBlC,YAAA,CAAa,QAAA,WAAmB,OAAA,CAAQ,YAAA;EApJjD;;;;;EAAA,OAmKS,UAAA,CAAW,IAAA,EAAM,IAAA,EAAM,GAAA,WAAc,OAAA;EA3H5C;;;;;EAAA,OA+IO,YAAA,IAAgB,OAAA,CAAQ,YAAA;EA1HxB;;;;;;EAAA,OA0IA,iBAAA,IAAqB,OAAA,CAAQ,YAAA;EAtHvB;;;;;;;;;EAAA,OAqIN,kBAAA,IAAsB,OAAA,CAAQ,YAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"resolve-request-user.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/services/resolve-request-user.ts"],"mappings":";;;;KAgBY,yBAAA;qEAEV,SAAA,GAAY,SAAA,EAFuB;EAInC,iBAAA,GAAoB,SAAA,EAFR;EAIZ,SAAA,WAQe;EANf,YAAA,aAMmC;EAJnC,QAAA,WARY;EAUZ,eAAA,WARoB;EAUpB,YAAA,GAAe,oBAAA;AAAA;;;;;KAOL,kBAAA;AAAA,KAEA,kBAAA;EACR,IAAA,EAAM,KAAA;EAAO,OAAA;AAAA;EAA0B,IAAA;EAAY,OAAA,EAAS,kBAAkB;AAAA;;iBAoBlE,cAAA,CAAe,OAAA,EAAS,OAAA,EAAS,SAAA,EAAW,SAAS;;;;;;;;;;AApBa;AAoBlF;iBAqBsB,yBAAA,CACpB,OAAA,EAAS,OAAA,EACT,QAAA,EAAU,QAAA,EACV,OAAA,GAAS,yBAAA,GACR,OAAA,CAAQ,kBAAA;;;;;;;;iBAmHK,kBAAA,CACd,OAAA,EAAS,OAAA,EACT,QAAA,EAAU,QAAA,EACV,OAAA,GAAS,yBAAA,GACR,OAAA"}
1
+ {"version":3,"file":"resolve-request-user.d.mts","names":[],"sources":["../../../../../../../../packages/auth/src/services/resolve-request-user.ts"],"mappings":";;;;KAgBY,yBAAA;qEAEV,SAAA,GAAY,SAAA,EAFuB;EAInC,iBAAA,GAAoB,SAAA,EAFR;EAIZ,SAAA,WAQe;EANf,YAAA,aAMmC;EAJnC,QAAA,WARY;EAUZ,eAAA,WARoB;EAUpB,YAAA,GAAe,oBAAA;AAAA;;;;;KAOL,kBAAA;AAAA,KAEA,kBAAA;EACN,IAAA,EAAM,KAAA;EAAO,OAAA;AAAA;EACb,IAAA;EAAY,OAAA,EAAS,kBAAkB;AAAA;;iBAoB7B,cAAA,CAAe,OAAA,EAAS,OAAA,EAAS,SAAA,EAAW,SAAS;;;;;;;;;;AApBxB;AAoB7C;iBAqBsB,yBAAA,CACpB,OAAA,EAAS,OAAA,EACT,QAAA,EAAU,QAAA,EACV,OAAA,GAAS,yBAAA,GACR,OAAA,CAAQ,kBAAA;;;;;;;;iBAmHK,kBAAA,CACd,OAAA,EAAS,OAAA,EACT,QAAA,EAAU,QAAA,EACV,OAAA,GAAS,yBAAA,GACR,OAAA"}