@warlock.js/auth 4.16.0 → 5.0.1

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 (44) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/esm/commands/auth-cleanup-command.d.mts +1 -1
  3. package/esm/commands/auth-cleanup-command.d.mts.map +1 -1
  4. package/esm/commands/auth-purge-never-expiring-command.d.mts +1 -1
  5. package/esm/commands/auth-purge-never-expiring-command.d.mts.map +1 -1
  6. package/esm/commands/jwt-secret-generator-command.d.mts +1 -1
  7. package/esm/commands/jwt-secret-generator-command.d.mts.map +1 -1
  8. package/esm/contracts/index.d.mts +1 -1
  9. package/esm/contracts/types.d.mts +15 -2
  10. package/esm/contracts/types.d.mts.map +1 -1
  11. package/esm/contracts/types.mjs.map +1 -1
  12. package/esm/index.d.mts +3 -3
  13. package/esm/index.mjs +3 -3
  14. package/esm/middleware/auth.middleware.d.mts +8 -1
  15. package/esm/middleware/auth.middleware.d.mts.map +1 -1
  16. package/esm/middleware/auth.middleware.mjs +53 -37
  17. package/esm/middleware/auth.middleware.mjs.map +1 -1
  18. package/esm/middleware/login-throttle.middleware.mjs +1 -1
  19. package/esm/middleware/login-throttle.middleware.mjs.map +1 -1
  20. package/esm/models/access-token/access-token.model.d.mts +35 -5
  21. package/esm/models/access-token/access-token.model.d.mts.map +1 -1
  22. package/esm/models/auth.model.d.mts +1 -5
  23. package/esm/models/auth.model.d.mts.map +1 -1
  24. package/esm/models/index.d.mts +1 -1
  25. package/esm/models/index.d.mts.map +1 -1
  26. package/esm/models/refresh-token/refresh-token.model.d.mts +106 -4
  27. package/esm/models/refresh-token/refresh-token.model.d.mts.map +1 -1
  28. package/esm/services/auth-config.mjs +4 -0
  29. package/esm/services/auth-config.mjs.map +1 -1
  30. package/esm/services/auth.service.d.mts +11 -9
  31. package/esm/services/auth.service.d.mts.map +1 -1
  32. package/esm/services/auth.service.mjs +68 -41
  33. package/esm/services/auth.service.mjs.map +1 -1
  34. package/esm/services/index.d.mts +1 -1
  35. package/esm/services/index.mjs +1 -1
  36. package/esm/services/jwt.d.mts +21 -1
  37. package/esm/services/jwt.d.mts.map +1 -1
  38. package/esm/services/jwt.mjs +51 -2
  39. package/esm/services/jwt.mjs.map +1 -1
  40. package/esm/utils/auth-error-codes.d.mts +11 -1
  41. package/esm/utils/auth-error-codes.d.mts.map +1 -1
  42. package/esm/utils/auth-error-codes.mjs +9 -0
  43. package/esm/utils/auth-error-codes.mjs.map +1 -1
  44. package/package.json +7 -7
package/CHANGELOG.md CHANGED
@@ -4,8 +4,18 @@ 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.0.0 - 2026-08-25
8
+
9
+ ### Changed
10
+
11
+ - Authentication can now use an explicit header or named-cookie credential source and a configurable `canAuthenticate` policy; invalid credentials are distinguished from server and configuration failures instead of turning every verification error into a 401.
12
+
7
13
  ## 4.16.0 - 2026-08-18
8
14
 
15
+ ### Security
16
+
17
+ - **Login credential lookups are no longer vulnerable to NoSQL operator injection.** `authService.attemptLogin` forwards the request's credential fields (e.g. `email`, `phoneNumber`) into `Model.first(...)` to find the user, so a caller that passed request JSON straight through could smuggle a MongoDB operator object — `{ email: { $ne: null } }`, `{ email: { $regex: "^a" } }` — into the user-lookup filter, breaking its intended equality semantics and enabling account enumeration / targeted-lookup attacks (the password is still verified separately with bcrypt, so this was never a full bypass on its own). The fix lands in `@warlock.js/cascade` 4.16.0, whose query builder now rejects `$`-prefixed keys in equality position (`UnsafeFilterError`); because `attemptLogin`'s lookup routes through cascade, upgrading the family to 4.16.0 closes this with no change to your auth code. If you build user lookups by hand, keep passing scalars — or use the explicit operator API — rather than forwarding raw request objects.
18
+
9
19
  ### Dependencies
10
20
 
11
21
  - Bumped `@mongez/events` to `^2.2.7` (no breaking changes) and `@mongez/reinforcements` to `^4.0.1`. The reinforcements major makes `Random.string/nanoid/id/token/uuid` CSPRNG-backed (WebCrypto) and removes `Random.seed()` support. This package calls `Random.string(32)` (device `familyId` fallback) and `Random.token(32)` (JWT secret generation) — both are exactly the security-sensitive uses the CSPRNG backing is meant to strengthen, and neither relied on seeding; audited for `Random.seed(` with no hits, so no code changes were needed.
@@ -7,7 +7,7 @@
7
7
  * warlock auth:cleanup
8
8
  * ```
9
9
  */
10
- declare function registerAuthCleanupCommand(): any;
10
+ declare function registerAuthCleanupCommand(): import("@warlock.js/core").CLICommand;
11
11
  //#endregion
12
12
  export { registerAuthCleanupCommand };
13
13
  //# sourceMappingURL=auth-cleanup-command.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth-cleanup-command.d.mts","names":[],"sources":["../../../../../../../auth/src/commands/auth-cleanup-command.ts"],"mappings":";;AAYA;;;;AAA0C;;;iBAA1B,0BAAA"}
1
+ {"version":3,"file":"auth-cleanup-command.d.mts","names":[],"sources":["../../../../../../../auth/src/commands/auth-cleanup-command.ts"],"mappings":";;AAYA;;;;AAA0C;;;iBAA1B,0BAAA,+BAA0B,UAAA"}
@@ -23,7 +23,7 @@
23
23
  * warlock auth.purge-never-expiring # report, then delete
24
24
  * ```
25
25
  */
26
- declare function registerAuthPurgeNeverExpiringCommand(): any;
26
+ declare function registerAuthPurgeNeverExpiringCommand(): import("@warlock.js/core").CLICommand;
27
27
  //#endregion
28
28
  export { registerAuthPurgeNeverExpiringCommand };
29
29
  //# sourceMappingURL=auth-purge-never-expiring-command.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth-purge-never-expiring-command.d.mts","names":[],"sources":["../../../../../../../auth/src/commands/auth-purge-never-expiring-command.ts"],"mappings":";;AA4BA;;;;AAAqD;;;;;;;;;;;;;;;;;;;iBAArC,qCAAA"}
1
+ {"version":3,"file":"auth-purge-never-expiring-command.d.mts","names":[],"sources":["../../../../../../../auth/src/commands/auth-purge-never-expiring-command.ts"],"mappings":";;AA4BA;;;;AAAqD;;;;;;;;;;;;;;;;;;;iBAArC,qCAAA,+BAAqC,UAAA"}
@@ -1,5 +1,5 @@
1
1
  //#region ../auth/src/commands/jwt-secret-generator-command.d.ts
2
- declare function registerJWTSecretGeneratorCommand(): any;
2
+ declare function registerJWTSecretGeneratorCommand(): import("@warlock.js/core").CLICommand;
3
3
  //#endregion
4
4
  export { registerJWTSecretGeneratorCommand };
5
5
  //# sourceMappingURL=jwt-secret-generator-command.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"jwt-secret-generator-command.d.mts","names":[],"sources":["../../../../../../../auth/src/commands/jwt-secret-generator-command.ts"],"mappings":";iBAGgB,iCAAA"}
1
+ {"version":3,"file":"jwt-secret-generator-command.d.mts","names":[],"sources":["../../../../../../../auth/src/commands/jwt-secret-generator-command.ts"],"mappings":";iBAGgB,iCAAA,+BAAiC,UAAA"}
@@ -1,2 +1,2 @@
1
- import { AccessTokenConfig, AccessTokenOutput, AuthConfigurations, DeviceInfo, LegacyJwtConfig, LoginResult, LogoutWithoutTokenBehavior, NO_EXPIRATION, RefreshTokenConfig, TokenPair } from "./types.mjs";
1
+ import { AccessTokenConfig, AccessTokenOutput, AuthConfigurations, AuthCredentials, CanAuthenticate, DeviceInfo, LegacyJwtConfig, LoginResult, LogoutWithoutTokenBehavior, NO_EXPIRATION, RefreshTokenConfig, TokenFrom, TokenPair } from "./types.mjs";
2
2
  import { Authenticable } from "./auth-contract.mjs";
@@ -31,6 +31,14 @@ declare const NO_EXPIRATION = "100y";
31
31
  * - "error": Return an error requiring the refresh token
32
32
  */
33
33
  type LogoutWithoutTokenBehavior = "revoke-all" | "error";
34
+ /** The single request credential source an auth middleware instance accepts. */
35
+ type TokenFrom = "header" | `cookie:${string}`;
36
+ /** Application policy that decides whether a resolved user may authenticate. */
37
+ type CanAuthenticate = (user: Auth) => boolean | Promise<boolean>;
38
+ /** Password credentials plus the application-defined identity fields. */
39
+ type AuthCredentials = Record<string, unknown> & {
40
+ password: string;
41
+ };
34
42
  /**
35
43
  * Access-token configuration.
36
44
  *
@@ -134,6 +142,11 @@ type AuthConfigurations = {
134
142
  userType: {
135
143
  [userType: string]: ChildModel<Auth>;
136
144
  };
145
+ /**
146
+ * Decide whether a resolved user may authenticate or receive new tokens.
147
+ * @default () => true
148
+ */
149
+ canAuthenticate?: CanAuthenticate;
137
150
  /**
138
151
  * Access-token configuration (secret, expiry, algorithm, model override).
139
152
  */
@@ -194,12 +207,12 @@ type DeviceInfo = {
194
207
  /**
195
208
  * Access token payload
196
209
  */
197
- payload?: Record<string, any>;
210
+ payload?: Record<string, unknown>;
198
211
  };
199
212
  type LoginResult<UserType extends Auth> = {
200
213
  user: UserType;
201
214
  tokens: TokenPair;
202
215
  };
203
216
  //#endregion
204
- export { AccessTokenConfig, AccessTokenOutput, AuthConfigurations, DeviceInfo, LegacyJwtConfig, LoginResult, LogoutWithoutTokenBehavior, NO_EXPIRATION, RefreshTokenConfig, TokenPair };
217
+ export { AccessTokenConfig, AccessTokenOutput, AuthConfigurations, AuthCredentials, CanAuthenticate, DeviceInfo, LegacyJwtConfig, LoginResult, LogoutWithoutTokenBehavior, NO_EXPIRATION, RefreshTokenConfig, TokenFrom, TokenPair };
205
218
  //# sourceMappingURL=types.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../auth/src/contracts/types.ts"],"mappings":";;;;;;;;;;;AAyBA;;;;AAA0B;AAO1B;;;;AAAsC;AAQtC;;;;;cAfa,aAAA;;;;;;KAOD,0BAAA;AAuCZ;;;;;;AAAA,KA/BY,iBAAA;EA8CV;;;;;EAxCA,KAAA,UAAe,WAAA;EA+DgC;AAAA;AASjD;EApEE,MAAA;;;;;EAKA,SAAA,GAAY,SAAS;EAkErB;;;;;;;;;EAxDA,SAAA;AAAA;AAmEF;;;AAAA,KA7DY,kBAAA;EAmEY;;;;EA9DtB,KAAA,UAAe,YAAA;EA2EM;;;;EAtErB,OAAA;EA8DA;;;;EAzDA,MAAA;EAiEM;;AAAe;AAGvB;;;;EA5DE,SAAA;EA0EU;;;;EArEV,QAAA;EAyEa;;;;EApEb,UAAA;EA8EU;;;;EAzEV,kBAAA,GAAqB,0BAA0B;AAAA;;;;;;AA8F/B;KArFN,eAAA;EACV,MAAA;EACA,SAAA,GAAY,SAAA;EACZ,SAAA;EACA,OAAA;IACE,MAAA;IACA,OAAA;IACA,SAAA;IACA,QAAA;IACA,UAAA;IACA,kBAAA,GAAqB,0BAA0B;EAAA;AAAA;AAAA,KAIvC,kBAAA;EA4EO;AAAA;;;EAvEjB,QAAA;IAAA,CACG,QAAA,WAAmB,UAAA,CAAW,IAAA;EAAA;;;;EAKjC,WAAA,GAAc,iBAAA;;;;EAId,YAAA,GAAe,kBAAA;;;;EAIf,GAAA,GAAM,eAAA;AAAA;AAAA,KAGI,iBAAA;;;;EAIV,KAAA;;;;EAIA,SAAS;AAAA;;;;KAMC,SAAA;;;;EAIV,WAAA,EAAa,iBAAA;;;;EAIb,YAAA,GAAe,iBAAiB;AAAA;;;;KAMtB,UAAA;;;;EAIV,SAAA;;;;EAIA,EAAA;;;;EAIA,QAAA;;;;;EAKA,QAAA;;;;EAIA,OAAA,GAAU,MAAM;AAAA;AAAA,KAGN,WAAA,kBAA6B,IAAA;EACvC,IAAA,EAAM,QAAA;EACN,MAAA,EAAQ,SAAA;AAAA"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../auth/src/contracts/types.ts"],"mappings":";;;;;;;;;;;AAyBA;;;;AAA0B;AAO1B;;;;AAAsC;AAGtC;;;;AAAqB;cAVR,aAAA;;;;;;KAOD,0BAAA;;KAGA,SAAA;AAMZ;AAAA,KAHY,eAAA,IAAmB,IAAA,EAAM,IAAA,eAAmB,OAAO;;KAGnD,eAAA,GAAkB,MAAM;EAClC,QAAQ;AAAA;;;;;;;KASE,iBAAA;EAeE;;;AAUH;AAMX;EAzBE,KAAA,UAAe,WAAA;;;;EAIf,MAAA;EA+BA;;;;EA1BA,SAAA,GAAY,SAAS;EAsDrB;;;AAA+C;AASjD;;;;;EArDE,SAAA;AAAA;;;;KAMU,kBAAA;EAsDR;;;;EAjDF,KAAA,UAAe,YAAA;EAoDkC;AAAA;AAInD;;EAnDE,OAAA;EAyDiC;;;;EApDjC,MAAA;EAsEM;;;;;;;EA9DN,SAAA;EAkDkB;;;;EA7ClB,QAAA;EAyDA;;;AAAqB;EApDrB,UAAA;EAuD2B;;;AAQlB;EA1DT,kBAAA,GAAqB,0BAA0B;AAAA;;;;;;;KASrC,eAAA;EACV,MAAA;EACA,SAAA,GAAY,SAAA;EACZ,SAAA;EACA,OAAA;IACE,MAAA;IACA,OAAA;IACA,SAAA;IACA,QAAA;IACA,UAAA;IACA,kBAAA,GAAqB,0BAA0B;EAAA;AAAA;AAAA,KAIvC,kBAAA;EA+EA;;;;EA1EV,QAAA;IAAA,CACG,QAAA,WAAmB,UAAA,CAAW,IAAA;EAAA;EA2EhB;;;;EArEjB,eAAA,GAAkB,eAAA;EAqElB;;;EAjEA,WAAA,GAAc,iBAAA;;;;EAId,YAAA,GAAe,kBAAA;;;;EAIf,GAAA,GAAM,eAAA;AAAA;AAAA,KAGI,iBAAA;;;;EAIV,KAAA;;;;EAIA,SAAS;AAAA;;;;KAMC,SAAA;;;;EAIV,WAAA,EAAa,iBAAA;;;;EAIb,YAAA,GAAe,iBAAiB;AAAA;;;;KAMtB,UAAA;;;;EAIV,SAAA;;;;EAIA,EAAA;;;;EAIA,QAAA;;;;;EAKA,QAAA;;;;EAIA,OAAA,GAAU,MAAM;AAAA;AAAA,KAGN,WAAA,kBAA6B,IAAA;EACvC,IAAA,EAAM,QAAA;EACN,MAAA,EAAQ,SAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.mjs","names":[],"sources":["../../../../../../../auth/src/contracts/types.ts"],"sourcesContent":["import { type ChildModel } from \"@warlock.js/cascade\";\r\nimport { type Algorithm } from \"fast-jwt\";\r\nimport type { AccessToken } from \"../models/access-token\";\r\nimport type { Auth } from \"../models/auth.model\";\r\nimport type { RefreshToken } from \"../models/refresh-token\";\r\n\r\n/**\r\n * Symbol to indicate no expiration for tokens\r\n * Use this when you explicitly want tokens to never expire\r\n *\r\n * @example\r\n * ```typescript\r\n * // src/config/auth.ts\r\n * import { NO_EXPIRATION, type AuthConfigurations } from \"@warlock.js/auth\";\r\n *\r\n * const authConfigurations: AuthConfigurations = {\r\n * accessToken: {\r\n * secret: env(\"JWT_SECRET\"),\r\n * expiresIn: NO_EXPIRATION, // Token expires within 100 years\r\n * },\r\n * };\r\n *\r\n * export default authConfigurations;\r\n * ```\r\n */\r\nexport const NO_EXPIRATION = \"100y\";\r\n\r\n/**\r\n * Behavior when logout is called without a refresh token\r\n * - \"revoke-all\": Revoke all refresh tokens for the user (secure default)\r\n * - \"error\": Return an error requiring the refresh token\r\n */\r\nexport type LogoutWithoutTokenBehavior = \"revoke-all\" | \"error\";\r\n\r\n/**\r\n * Access-token configuration.\r\n *\r\n * `secret` may also be supplied via the deprecated `jwt.secret` (resolved by a\r\n * backward-compatible shim); one of the two is required at runtime.\r\n */\r\nexport type AccessTokenConfig = {\r\n /**\r\n * Override the persisted access-token model — extend {@link AccessToken} to\r\n * add columns (e.g. a tenant key) or rename them, then register the subclass\r\n * here. Defaults to the package's `AccessToken`.\r\n */\r\n model?: typeof AccessToken;\r\n /**\r\n * Secret used to sign access tokens.\r\n */\r\n secret?: string;\r\n /**\r\n * JWT algorithm.\r\n * @default \"HS256\"\r\n */\r\n algorithm?: Algorithm;\r\n /**\r\n * Access-token lifetime — a duration string `ms` parses to a **positive**\r\n * number of milliseconds. A value `ms` cannot parse (`\"30dayz\"`), a\r\n * non-positive one (`\"0d\"`), or a bare number throws naming this key on the\r\n * first token issue: each of them would otherwise sign a token with no `exp`\r\n * claim at all.\r\n * @example \"1h\" or NO_EXPIRATION\r\n * @default \"1h\"\r\n */\r\n expiresIn?: string;\r\n};\r\n\r\n/**\r\n * Refresh-token configuration.\r\n */\r\nexport type RefreshTokenConfig = {\r\n /**\r\n * Override the persisted refresh-token model — extend {@link RefreshToken}.\r\n * Defaults to the package's `RefreshToken`.\r\n */\r\n model?: typeof RefreshToken;\r\n /**\r\n * Enable refresh tokens.\r\n * @default true\r\n */\r\n enabled?: boolean;\r\n /**\r\n * Separate secret for refresh tokens (recommended for security). Falls back\r\n * to the access-token secret when omitted.\r\n */\r\n secret?: string;\r\n /**\r\n * Refresh-token lifetime — a duration string `ms` parses to a **positive**\r\n * number of milliseconds; an unusable value throws naming this key on issue\r\n * (see {@link AccessTokenConfig.expiresIn}).\r\n * @example \"7d\" or \"1w\"\r\n * @default \"7d\"\r\n */\r\n expiresIn?: string;\r\n /**\r\n * Rotate the refresh token on each use (old token revoked, replay detected).\r\n * @default true\r\n */\r\n rotation?: boolean;\r\n /**\r\n * Maximum active refresh tokens per user; the oldest are revoked past it.\r\n * @default 5\r\n */\r\n maxPerUser?: number;\r\n /**\r\n * Behavior when logout is called without a refresh token.\r\n * @default \"revoke-all\"\r\n */\r\n logoutWithoutToken?: LogoutWithoutTokenBehavior;\r\n};\r\n\r\n/**\r\n * Legacy JWT configuration shape. Still honored by the resolver shim with a\r\n * deprecation warning.\r\n *\r\n * @deprecated Use the top-level `accessToken` / `refreshToken` blocks.\r\n */\r\nexport type LegacyJwtConfig = {\r\n secret?: string;\r\n algorithm?: Algorithm;\r\n expiresIn?: string;\r\n refresh?: {\r\n secret?: string;\r\n enabled?: boolean;\r\n expiresIn?: string;\r\n rotation?: boolean;\r\n maxPerUser?: number;\r\n logoutWithoutToken?: LogoutWithoutTokenBehavior;\r\n };\r\n};\r\n\r\nexport type AuthConfigurations = {\r\n /**\r\n * Define all user types — maps a user-type slug to its `Auth` model class so\r\n * tokens and the middleware can resolve the right model.\r\n */\r\n userType: {\r\n [userType: string]: ChildModel<Auth>;\r\n };\r\n /**\r\n * Access-token configuration (secret, expiry, algorithm, model override).\r\n */\r\n accessToken?: AccessTokenConfig;\r\n /**\r\n * Refresh-token configuration (rotation, lifetime, cap, model override).\r\n */\r\n refreshToken?: RefreshTokenConfig;\r\n /**\r\n * @deprecated Use `accessToken` / `refreshToken`. Read via a backward-compatible shim.\r\n */\r\n jwt?: LegacyJwtConfig;\r\n};\r\n\r\nexport type AccessTokenOutput = {\r\n /**\r\n * JWT Token\r\n */\r\n token: string;\r\n /**\r\n * Exprie time in ISO format UTC time\r\n */\r\n expiresAt: string;\r\n};\r\n\r\n/**\r\n * Token pair returned after login or token refresh\r\n */\r\nexport type TokenPair = {\r\n /**\r\n * JWT access token (short-lived)\r\n */\r\n accessToken: AccessTokenOutput;\r\n /**\r\n * JWT refresh token (long-lived)\r\n */\r\n refreshToken?: AccessTokenOutput;\r\n};\r\n\r\n/**\r\n * Device information for session tracking\r\n */\r\nexport type DeviceInfo = {\r\n /**\r\n * User agent string from request\r\n */\r\n userAgent?: string;\r\n /**\r\n * Client IP address\r\n */\r\n ip?: string;\r\n /**\r\n * Optional device identifier\r\n */\r\n deviceId?: string;\r\n /**\r\n * Token family ID (for rotation tracking)\r\n * @internal\r\n */\r\n familyId?: string;\r\n /**\r\n * Access token payload\r\n */\r\n payload?: Record<string, any>;\r\n};\r\n\r\nexport type LoginResult<UserType extends Auth> = {\r\n user: UserType;\r\n tokens: TokenPair;\r\n};\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAyBA,MAAa,gBAAgB"}
1
+ {"version":3,"file":"types.mjs","names":[],"sources":["../../../../../../../auth/src/contracts/types.ts"],"sourcesContent":["import { type ChildModel } from \"@warlock.js/cascade\";\r\nimport { type Algorithm } from \"fast-jwt\";\r\nimport type { AccessToken } from \"../models/access-token\";\r\nimport type { Auth } from \"../models/auth.model\";\r\nimport type { RefreshToken } from \"../models/refresh-token\";\r\n\r\n/**\r\n * Symbol to indicate no expiration for tokens\r\n * Use this when you explicitly want tokens to never expire\r\n *\r\n * @example\r\n * ```typescript\r\n * // src/config/auth.ts\r\n * import { NO_EXPIRATION, type AuthConfigurations } from \"@warlock.js/auth\";\r\n *\r\n * const authConfigurations: AuthConfigurations = {\r\n * accessToken: {\r\n * secret: env(\"JWT_SECRET\"),\r\n * expiresIn: NO_EXPIRATION, // Token expires within 100 years\r\n * },\r\n * };\r\n *\r\n * export default authConfigurations;\r\n * ```\r\n */\r\nexport const NO_EXPIRATION = \"100y\";\r\n\r\n/**\r\n * Behavior when logout is called without a refresh token\r\n * - \"revoke-all\": Revoke all refresh tokens for the user (secure default)\r\n * - \"error\": Return an error requiring the refresh token\r\n */\r\nexport type LogoutWithoutTokenBehavior = \"revoke-all\" | \"error\";\n\n/** The single request credential source an auth middleware instance accepts. */\nexport type TokenFrom = \"header\" | `cookie:${string}`;\n\n/** Application policy that decides whether a resolved user may authenticate. */\nexport type CanAuthenticate = (user: Auth) => boolean | Promise<boolean>;\n\n/** Password credentials plus the application-defined identity fields. */\nexport type AuthCredentials = Record<string, unknown> & {\n password: string;\n};\n\r\n/**\r\n * Access-token configuration.\r\n *\r\n * `secret` may also be supplied via the deprecated `jwt.secret` (resolved by a\r\n * backward-compatible shim); one of the two is required at runtime.\r\n */\r\nexport type AccessTokenConfig = {\r\n /**\r\n * Override the persisted access-token model — extend {@link AccessToken} to\r\n * add columns (e.g. a tenant key) or rename them, then register the subclass\r\n * here. Defaults to the package's `AccessToken`.\r\n */\r\n model?: typeof AccessToken;\r\n /**\r\n * Secret used to sign access tokens.\r\n */\r\n secret?: string;\r\n /**\r\n * JWT algorithm.\r\n * @default \"HS256\"\r\n */\r\n algorithm?: Algorithm;\r\n /**\r\n * Access-token lifetime — a duration string `ms` parses to a **positive**\r\n * number of milliseconds. A value `ms` cannot parse (`\"30dayz\"`), a\r\n * non-positive one (`\"0d\"`), or a bare number throws naming this key on the\r\n * first token issue: each of them would otherwise sign a token with no `exp`\r\n * claim at all.\r\n * @example \"1h\" or NO_EXPIRATION\r\n * @default \"1h\"\r\n */\r\n expiresIn?: string;\r\n};\r\n\r\n/**\r\n * Refresh-token configuration.\r\n */\r\nexport type RefreshTokenConfig = {\r\n /**\r\n * Override the persisted refresh-token model — extend {@link RefreshToken}.\r\n * Defaults to the package's `RefreshToken`.\r\n */\r\n model?: typeof RefreshToken;\r\n /**\r\n * Enable refresh tokens.\r\n * @default true\r\n */\r\n enabled?: boolean;\r\n /**\r\n * Separate secret for refresh tokens (recommended for security). Falls back\r\n * to the access-token secret when omitted.\r\n */\r\n secret?: string;\r\n /**\r\n * Refresh-token lifetime — a duration string `ms` parses to a **positive**\r\n * number of milliseconds; an unusable value throws naming this key on issue\r\n * (see {@link AccessTokenConfig.expiresIn}).\r\n * @example \"7d\" or \"1w\"\r\n * @default \"7d\"\r\n */\r\n expiresIn?: string;\r\n /**\r\n * Rotate the refresh token on each use (old token revoked, replay detected).\r\n * @default true\r\n */\r\n rotation?: boolean;\r\n /**\r\n * Maximum active refresh tokens per user; the oldest are revoked past it.\r\n * @default 5\r\n */\r\n maxPerUser?: number;\r\n /**\r\n * Behavior when logout is called without a refresh token.\r\n * @default \"revoke-all\"\r\n */\r\n logoutWithoutToken?: LogoutWithoutTokenBehavior;\r\n};\r\n\r\n/**\r\n * Legacy JWT configuration shape. Still honored by the resolver shim with a\r\n * deprecation warning.\r\n *\r\n * @deprecated Use the top-level `accessToken` / `refreshToken` blocks.\r\n */\r\nexport type LegacyJwtConfig = {\r\n secret?: string;\r\n algorithm?: Algorithm;\r\n expiresIn?: string;\r\n refresh?: {\r\n secret?: string;\r\n enabled?: boolean;\r\n expiresIn?: string;\r\n rotation?: boolean;\r\n maxPerUser?: number;\r\n logoutWithoutToken?: LogoutWithoutTokenBehavior;\r\n };\r\n};\r\n\r\nexport type AuthConfigurations = {\r\n /**\r\n * Define all user types — maps a user-type slug to its `Auth` model class so\r\n * tokens and the middleware can resolve the right model.\r\n */\r\n userType: {\n [userType: string]: ChildModel<Auth>;\n };\n /**\n * Decide whether a resolved user may authenticate or receive new tokens.\n * @default () => true\n */\n canAuthenticate?: CanAuthenticate;\n /**\r\n * Access-token configuration (secret, expiry, algorithm, model override).\r\n */\r\n accessToken?: AccessTokenConfig;\r\n /**\r\n * Refresh-token configuration (rotation, lifetime, cap, model override).\r\n */\r\n refreshToken?: RefreshTokenConfig;\r\n /**\r\n * @deprecated Use `accessToken` / `refreshToken`. Read via a backward-compatible shim.\r\n */\r\n jwt?: LegacyJwtConfig;\r\n};\r\n\r\nexport type AccessTokenOutput = {\r\n /**\r\n * JWT Token\r\n */\r\n token: string;\r\n /**\r\n * Exprie time in ISO format UTC time\r\n */\r\n expiresAt: string;\r\n};\r\n\r\n/**\r\n * Token pair returned after login or token refresh\r\n */\r\nexport type TokenPair = {\r\n /**\r\n * JWT access token (short-lived)\r\n */\r\n accessToken: AccessTokenOutput;\r\n /**\r\n * JWT refresh token (long-lived)\r\n */\r\n refreshToken?: AccessTokenOutput;\r\n};\r\n\r\n/**\r\n * Device information for session tracking\r\n */\r\nexport type DeviceInfo = {\r\n /**\r\n * User agent string from request\r\n */\r\n userAgent?: string;\r\n /**\r\n * Client IP address\r\n */\r\n ip?: string;\r\n /**\r\n * Optional device identifier\r\n */\r\n deviceId?: string;\r\n /**\r\n * Token family ID (for rotation tracking)\r\n * @internal\r\n */\r\n familyId?: string;\r\n /**\r\n * Access token payload\r\n */\r\n payload?: Record<string, unknown>;\n};\n\r\nexport type LoginResult<UserType extends Auth> = {\r\n user: UserType;\r\n tokens: TokenPair;\r\n};\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAyBA,MAAa,gBAAgB"}
package/esm/index.d.mts CHANGED
@@ -3,7 +3,7 @@ import { registerAuthPurgeNeverExpiringCommand } from "./commands/auth-purge-nev
3
3
  import { registerJWTSecretGeneratorCommand } from "./commands/jwt-secret-generator-command.mjs";
4
4
  import { Auth } from "./models/auth.model.mjs";
5
5
  import { AccessToken, accessTokenSchema } from "./models/access-token/access-token.model.mjs";
6
- import { AccessTokenConfig, AccessTokenOutput, AuthConfigurations, DeviceInfo, LegacyJwtConfig, LoginResult, LogoutWithoutTokenBehavior, NO_EXPIRATION, RefreshTokenConfig, TokenPair } from "./contracts/types.mjs";
6
+ import { AccessTokenConfig, AccessTokenOutput, AuthConfigurations, AuthCredentials, CanAuthenticate, DeviceInfo, LegacyJwtConfig, LoginResult, LogoutWithoutTokenBehavior, NO_EXPIRATION, RefreshTokenConfig, TokenFrom, TokenPair } from "./contracts/types.mjs";
7
7
  import { RefreshToken, RefreshTokenIssueOptions, refreshTokenSchema } from "./models/refresh-token/refresh-token.model.mjs";
8
8
  import { Authenticable } from "./contracts/auth-contract.mjs";
9
9
  import { authMiddleware } from "./middleware/auth.middleware.mjs";
@@ -12,7 +12,7 @@ import { authMigrations } from "./models/index.mjs";
12
12
  import { AuthEventCallback, AuthEventName, AuthEventPayloads, authEvents } from "./services/auth-events.mjs";
13
13
  import { authService } from "./services/auth.service.mjs";
14
14
  import { generateJWTSecret } from "./services/generate-jwt-secret.mjs";
15
- import { TokenType, jwt } from "./services/jwt.mjs";
16
15
  import { AuthErrorCodes } from "./utils/auth-error-codes.mjs";
16
+ import { TokenType, TokenTypeError, isInvalidCredentialError, jwt } from "./services/jwt.mjs";
17
17
  import { isNeverExpiring, isUsableExpiry, tokenHasExpClaim } from "./utils/token-expiry.mjs";
18
- export { AccessToken, AccessTokenConfig, AccessTokenOutput, Auth, AuthConfigurations, AuthErrorCodes, AuthEventCallback, AuthEventName, AuthEventPayloads, Authenticable, DeviceInfo, LegacyJwtConfig, LoginResult, LoginThrottleIdentifier, LoginThrottleOptions, LogoutWithoutTokenBehavior, NO_EXPIRATION, RefreshToken, RefreshTokenConfig, RefreshTokenIssueOptions, TokenPair, TokenType, accessTokenSchema, authEvents, authMiddleware, authMigrations, authService, generateJWTSecret, isNeverExpiring, isUsableExpiry, jwt, loginThrottleMiddleware, refreshTokenSchema, registerAuthCleanupCommand, registerAuthPurgeNeverExpiringCommand, registerJWTSecretGeneratorCommand, tokenHasExpClaim };
18
+ export { AccessToken, AccessTokenConfig, AccessTokenOutput, Auth, AuthConfigurations, AuthCredentials, AuthErrorCodes, AuthEventCallback, AuthEventName, AuthEventPayloads, Authenticable, CanAuthenticate, DeviceInfo, LegacyJwtConfig, LoginResult, LoginThrottleIdentifier, LoginThrottleOptions, LogoutWithoutTokenBehavior, NO_EXPIRATION, RefreshToken, RefreshTokenConfig, RefreshTokenIssueOptions, TokenFrom, TokenPair, TokenType, TokenTypeError, accessTokenSchema, authEvents, authMiddleware, authMigrations, authService, generateJWTSecret, isInvalidCredentialError, isNeverExpiring, isUsableExpiry, jwt, loginThrottleMiddleware, refreshTokenSchema, registerAuthCleanupCommand, registerAuthPurgeNeverExpiringCommand, registerJWTSecretGeneratorCommand, tokenHasExpClaim };
package/esm/index.mjs CHANGED
@@ -2,14 +2,14 @@ import { isNeverExpiring, isUsableExpiry, tokenHasExpClaim } from "./utils/token
2
2
  import { AccessToken, accessTokenSchema } from "./models/access-token/access-token.model.mjs";
3
3
  import { RefreshToken, refreshTokenSchema } from "./models/refresh-token/refresh-token.model.mjs";
4
4
  import { authEvents } from "./services/auth-events.mjs";
5
- import { jwt } from "./services/jwt.mjs";
5
+ import { AuthErrorCodes } from "./utils/auth-error-codes.mjs";
6
+ import { TokenTypeError, isInvalidCredentialError, jwt } from "./services/jwt.mjs";
6
7
  import { authService } from "./services/auth.service.mjs";
7
8
  import { registerAuthCleanupCommand } from "./commands/auth-cleanup-command.mjs";
8
9
  import { registerAuthPurgeNeverExpiringCommand } from "./commands/auth-purge-never-expiring-command.mjs";
9
10
  import { generateJWTSecret } from "./services/generate-jwt-secret.mjs";
10
11
  import { registerJWTSecretGeneratorCommand } from "./commands/jwt-secret-generator-command.mjs";
11
12
  import { NO_EXPIRATION } from "./contracts/types.mjs";
12
- import { AuthErrorCodes } from "./utils/auth-error-codes.mjs";
13
13
  import { authMiddleware } from "./middleware/auth.middleware.mjs";
14
14
  import { loginThrottleMiddleware } from "./middleware/login-throttle.middleware.mjs";
15
15
  import "./middleware/index.mjs";
@@ -18,4 +18,4 @@ import { Auth } from "./models/auth.model.mjs";
18
18
  import { authMigrations } from "./models/index.mjs";
19
19
  import "./utils/index.mjs";
20
20
 
21
- export { AccessToken, Auth, AuthErrorCodes, NO_EXPIRATION, RefreshToken, accessTokenSchema, authEvents, authMiddleware, authMigrations, authService, generateJWTSecret, isNeverExpiring, isUsableExpiry, jwt, loginThrottleMiddleware, refreshTokenSchema, registerAuthCleanupCommand, registerAuthPurgeNeverExpiringCommand, registerJWTSecretGeneratorCommand, tokenHasExpClaim };
21
+ export { AccessToken, Auth, AuthErrorCodes, NO_EXPIRATION, RefreshToken, TokenTypeError, accessTokenSchema, authEvents, authMiddleware, authMigrations, authService, generateJWTSecret, isInvalidCredentialError, isNeverExpiring, isUsableExpiry, jwt, loginThrottleMiddleware, refreshTokenSchema, registerAuthCleanupCommand, registerAuthPurgeNeverExpiringCommand, registerJWTSecretGeneratorCommand, tokenHasExpClaim };
@@ -1,3 +1,6 @@
1
+ import { TokenFrom } from "../contracts/types.mjs";
2
+ import { Middleware } from "@warlock.js/core";
3
+
1
4
  //#region ../auth/src/middleware/auth.middleware.d.ts
2
5
  /**
3
6
  * Build a route gate that always requires an authenticated request.
@@ -11,12 +14,16 @@
11
14
  * token is always rejected with `401`. Routes that should be public
12
15
  * simply omit the middleware.
13
16
  *
17
+ * `tokenFrom` selects one credential source and defaults to `"header"`.
18
+ * A single source avoids making credential precedence depend on array order.
19
+ *
14
20
  * @example
15
21
  * router.get("/account", authMiddleware([]), accountController);
16
22
  * router.get("/admin", authMiddleware("admin"), adminController);
17
23
  * router.get("/back-office", authMiddleware(["admin", "staff"]), backOfficeController);
24
+ * router.get("/browser-account", authMiddleware([], "cookie:token"), accountController);
18
25
  */
19
- declare function authMiddleware(allowedUserType: string | string[]): Middleware;
26
+ declare function authMiddleware(allowedUserType: string | string[], tokenFrom?: TokenFrom): Middleware;
20
27
  //#endregion
21
28
  export { authMiddleware };
22
29
  //# sourceMappingURL=auth.middleware.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth.middleware.d.mts","names":[],"sources":["../../../../../../../auth/src/middleware/auth.middleware.ts"],"mappings":";;AAmDA;;;;AAAiE;;;;;;;;;;;;iBAAjD,cAAA,CAAe,eAAA,sBAAkC,UAAA"}
1
+ {"version":3,"file":"auth.middleware.d.mts","names":[],"sources":["../../../../../../../auth/src/middleware/auth.middleware.ts"],"mappings":";;;;;;AAmEA;;;;;;;;;AAGa;;;;;;;;;;iBAHG,cAAA,CACd,eAAA,qBACA,SAAA,GAAW,SAAA,GACV,UAAU"}
@@ -1,7 +1,8 @@
1
1
  import { AccessToken } from "../models/access-token/access-token.model.mjs";
2
2
  import "../models/access-token/index.mjs";
3
- import { jwt } from "../services/jwt.mjs";
4
3
  import { AuthErrorCodes } from "../utils/auth-error-codes.mjs";
4
+ import { isInvalidCredentialError, jwt } from "../services/jwt.mjs";
5
+ import { authService } from "../services/auth.service.mjs";
5
6
  import { config, t } from "@warlock.js/core";
6
7
  import { log } from "@warlock.js/logger";
7
8
 
@@ -24,6 +25,11 @@ import { log } from "@warlock.js/logger";
24
25
  function accessTokenRowIsExpired(accessToken) {
25
26
  return typeof accessToken.isExpired === "boolean" ? accessToken.isExpired : true;
26
27
  }
28
+ function readCredential(request, tokenFrom) {
29
+ if (tokenFrom === "header") return request.authorizationValue;
30
+ const value = request.cookie(tokenFrom.slice(7));
31
+ return value ? String(value) : "";
32
+ }
27
33
  /**
28
34
  * Build a route gate that always requires an authenticated request.
29
35
  *
@@ -36,58 +42,68 @@ function accessTokenRowIsExpired(accessToken) {
36
42
  * token is always rejected with `401`. Routes that should be public
37
43
  * simply omit the middleware.
38
44
  *
45
+ * `tokenFrom` selects one credential source and defaults to `"header"`.
46
+ * A single source avoids making credential precedence depend on array order.
47
+ *
39
48
  * @example
40
49
  * router.get("/account", authMiddleware([]), accountController);
41
50
  * router.get("/admin", authMiddleware("admin"), adminController);
42
51
  * router.get("/back-office", authMiddleware(["admin", "staff"]), backOfficeController);
52
+ * router.get("/browser-account", authMiddleware([], "cookie:token"), accountController);
43
53
  */
44
- function authMiddleware(allowedUserType) {
54
+ function authMiddleware(allowedUserType, tokenFrom = "header") {
45
55
  const allowedTypes = Array.isArray(allowedUserType) ? allowedUserType : [allowedUserType];
46
- const auth = async (request, response) => {
56
+ const auth = async ({ request, response }) => {
57
+ const authorizationValue = readCredential(request, tokenFrom);
58
+ if (!authorizationValue) return response.unauthorized({
59
+ error: t("auth.errors.missingAccessToken"),
60
+ errorCode: "EC001"
61
+ });
62
+ let decoded;
47
63
  try {
48
- const authorizationValue = request.authorizationValue;
49
- if (!authorizationValue) return response.unauthorized({
50
- error: t("auth.errors.missingAccessToken"),
51
- errorCode: "EC001"
52
- });
53
- const decoded = await jwt.verify(authorizationValue);
54
- request.decodedAccessToken = decoded;
55
- const accessToken = await config.key("auth.accessToken.model", AccessToken).findByToken(authorizationValue);
56
- if (!accessToken) return response.unauthorized({
64
+ decoded = await jwt.verify(authorizationValue);
65
+ } catch (error) {
66
+ if (!isInvalidCredentialError(error)) throw error;
67
+ log.error("http", "auth", error);
68
+ request.clearCurrentUser();
69
+ return response.unauthorized({
57
70
  error: t("auth.errors.invalidAccessToken"),
58
71
  errorCode: "EC002"
59
72
  });
60
- if (accessTokenRowIsExpired(accessToken)) {
61
- await accessToken.destroy();
62
- return response.unauthorized({
63
- error: t("auth.errors.invalidAccessToken"),
64
- errorCode: "EC002"
65
- });
66
- }
67
- const userType = decoded.userType ?? accessToken.userType;
68
- if (allowedTypes.length && !allowedTypes.includes(userType)) return response.unauthorized({
69
- error: t("auth.errors.unauthorized"),
70
- errorCode: "EC003"
73
+ }
74
+ request.decodedAccessToken = decoded;
75
+ const accessToken = await config.key("auth.accessToken.model", AccessToken).findByToken(authorizationValue);
76
+ if (!accessToken) return response.unauthorized({
77
+ error: t("auth.errors.invalidAccessToken"),
78
+ errorCode: "EC002"
79
+ });
80
+ if (accessTokenRowIsExpired(accessToken)) {
81
+ await accessToken.destroy();
82
+ return response.unauthorized({
83
+ error: t("auth.errors.invalidAccessToken"),
84
+ errorCode: "EC002"
71
85
  });
72
- const UserModel = config.key(`auth.userType.${userType}`);
73
- if (!UserModel) throw new Error(`User type ${userType} is unknown type.`);
74
- const currentUser = await UserModel.find(decoded.id);
75
- if (!currentUser) {
76
- await accessToken.destroy();
77
- return response.unauthorized({
78
- error: t("auth.errors.invalidAccessToken"),
79
- errorCode: "EC002"
80
- });
81
- }
82
- request.user = currentUser;
83
- } catch (error) {
84
- log.error("http", "auth", error);
85
- request.clearCurrentUser();
86
+ }
87
+ const userType = decoded.userType ?? accessToken.userType;
88
+ if (allowedTypes.length && !allowedTypes.includes(userType)) return response.unauthorized({
89
+ error: t("auth.errors.unauthorized"),
90
+ errorCode: "EC003"
91
+ });
92
+ const UserModel = config.key(`auth.userType.${userType}`);
93
+ if (!UserModel) throw new Error(`User type ${userType} is unknown type.`);
94
+ const currentUser = await UserModel.find(decoded.id);
95
+ if (!currentUser) {
96
+ await accessToken.destroy();
86
97
  return response.unauthorized({
87
98
  error: t("auth.errors.invalidAccessToken"),
88
99
  errorCode: "EC002"
89
100
  });
90
101
  }
102
+ if (!await authService.canAuthenticate(currentUser)) return response.unauthorized({
103
+ error: t("auth.errors.unauthorized"),
104
+ errorCode: "EC003"
105
+ });
106
+ request.user = currentUser;
91
107
  };
92
108
  return auth;
93
109
  }
@@ -1 +1 @@
1
- {"version":3,"file":"auth.middleware.mjs","names":[],"sources":["../../../../../../../auth/src/middleware/auth.middleware.ts"],"sourcesContent":["import { config, t, type Middleware, type Request, type Response } from \"@warlock.js/core\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport { AccessToken } from \"../models/access-token\";\r\nimport { jwt } from \"../services/jwt\";\r\nimport { AuthErrorCodes } from \"../utils/auth-error-codes\";\r\n\r\n/**\r\n * Decoded access-token claims the middleware reads. The full payload carries\r\n * more (`created_at`, `tokenType`, `iat`, `exp`) but only these drive routing.\r\n */\r\ntype DecodedAccessToken = {\r\n id: string | number;\r\n userType?: string;\r\n};\r\n\r\n/**\r\n * Whether the persisted row says the token is dead.\r\n *\r\n * The answer belongs to the model (`AccessToken.isExpired`), so a registered\r\n * override that renames or reshapes its expiry column stays authoritative and\r\n * the middleware never touches a column name. A row that cannot answer at all —\r\n * an override that dropped the getter — is treated as **expired**: the failure\r\n * mode of this whole defect class was a check that quietly answered \"fine\" when\r\n * it had nothing to check, and that is not repeated here.\r\n *\r\n * This is independent of the `exp` claim required by `jwt.verify`. That guard\r\n * catches a token whose *claims* carry no deadline; this one catches a token\r\n * whose *row* says the deadline has passed — a logged-out or expired session\r\n * whose JWT is still within its own lifetime. Neither subsumes the other.\r\n */\r\nfunction accessTokenRowIsExpired(accessToken: AccessToken): boolean {\r\n return typeof accessToken.isExpired === \"boolean\" ? accessToken.isExpired : true;\r\n}\r\n\r\n/**\r\n * Build a route gate that always requires an authenticated request.\r\n *\r\n * The argument is mandatory and selects which user types may pass:\r\n * - `[]` — any authenticated user (token required, type not checked).\r\n * - `\"admin\"` / `[\"admin\", \"staff\"]` — token required AND the user's\r\n * `userType` must be one of the listed types.\r\n *\r\n * There is no anonymous/optional mode: a request without a valid access\r\n * token is always rejected with `401`. Routes that should be public\r\n * simply omit the middleware.\r\n *\r\n * @example\r\n * router.get(\"/account\", authMiddleware([]), accountController);\r\n * router.get(\"/admin\", authMiddleware(\"admin\"), adminController);\r\n * router.get(\"/back-office\", authMiddleware([\"admin\", \"staff\"]), backOfficeController);\r\n */\r\nexport function authMiddleware(allowedUserType: string | string[]) {\r\n const allowedTypes = Array.isArray(allowedUserType) ? allowedUserType : [allowedUserType];\r\n\r\n const auth: Middleware = async (request: Request, response: Response) => {\r\n try {\r\n const authorizationValue = request.authorizationValue;\r\n\r\n if (!authorizationValue) {\r\n return response.unauthorized({\r\n error: t(\"auth.errors.missingAccessToken\"),\r\n errorCode: AuthErrorCodes.MissingAccessToken,\r\n });\r\n }\r\n\r\n const decoded = await jwt.verify<DecodedAccessToken>(authorizationValue);\r\n\r\n request.decodedAccessToken = decoded;\r\n\r\n // A valid signature is not enough — the token must still exist in storage,\r\n // so deleting the row (logout) invalidates it before its JWT expiry.\r\n const AccessTokenModel = config.key(\"auth.accessToken.model\", AccessToken);\r\n const accessToken = await AccessTokenModel.findByToken(authorizationValue);\r\n\r\n if (!accessToken) {\r\n return response.unauthorized({\r\n error: t(\"auth.errors.invalidAccessToken\"),\r\n errorCode: AuthErrorCodes.InvalidAccessToken,\r\n });\r\n }\r\n\r\n // ... and the row must still be live. Existence alone was the whole check\r\n // before 4.12.0, so a row whose own `expires_at` had passed still opened\r\n // the gate. The stored expiry is now enforced, and the dead row is\r\n // removed on the way out rather than left for the cleanup command.\r\n if (accessTokenRowIsExpired(accessToken)) {\r\n await accessToken.destroy();\r\n\r\n return response.unauthorized({\r\n error: t(\"auth.errors.invalidAccessToken\"),\r\n errorCode: AuthErrorCodes.InvalidAccessToken,\r\n });\r\n }\r\n\r\n const userType = decoded.userType ?? accessToken.userType;\r\n\r\n if (allowedTypes.length && !allowedTypes.includes(userType)) {\r\n return response.unauthorized({\r\n error: t(\"auth.errors.unauthorized\"),\r\n errorCode: AuthErrorCodes.Unauthorized,\r\n });\r\n }\r\n\r\n const UserModel = config.key(`auth.userType.${userType}`);\r\n\r\n if (!UserModel) {\r\n throw new Error(`User type ${userType} is unknown type.`);\r\n }\r\n\r\n const currentUser = await UserModel.find(decoded.id);\r\n\r\n if (!currentUser) {\r\n await accessToken.destroy();\r\n\r\n return response.unauthorized({\r\n error: t(\"auth.errors.invalidAccessToken\"),\r\n errorCode: AuthErrorCodes.InvalidAccessToken,\r\n });\r\n }\r\n\r\n request.user = currentUser;\r\n } catch (error: any) {\r\n log.error(\"http\", \"auth\", error);\r\n\r\n request.clearCurrentUser();\r\n\r\n return response.unauthorized({\r\n error: t(\"auth.errors.invalidAccessToken\"),\r\n errorCode: AuthErrorCodes.InvalidAccessToken,\r\n });\r\n }\r\n };\r\n\r\n return auth;\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA8BA,SAAS,wBAAwB,aAAmC;CAClE,OAAO,OAAO,YAAY,cAAc,YAAY,YAAY,YAAY;AAC9E;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,eAAe,iBAAoC;CACjE,MAAM,eAAe,MAAM,QAAQ,eAAe,IAAI,kBAAkB,CAAC,eAAe;CAExF,MAAM,OAAmB,OAAO,SAAkB,aAAuB;EACvE,IAAI;GACF,MAAM,qBAAqB,QAAQ;GAEnC,IAAI,CAAC,oBACH,OAAO,SAAS,aAAa;IAC3B,OAAO,EAAE,gCAAgC;IACzC;GACF,CAAC;GAGH,MAAM,UAAU,MAAM,IAAI,OAA2B,kBAAkB;GAEvE,QAAQ,qBAAqB;GAK7B,MAAM,cAAc,MADK,OAAO,IAAI,0BAA0B,WACrB,CAAC,CAAC,YAAY,kBAAkB;GAEzE,IAAI,CAAC,aACH,OAAO,SAAS,aAAa;IAC3B,OAAO,EAAE,gCAAgC;IACzC;GACF,CAAC;GAOH,IAAI,wBAAwB,WAAW,GAAG;IACxC,MAAM,YAAY,QAAQ;IAE1B,OAAO,SAAS,aAAa;KAC3B,OAAO,EAAE,gCAAgC;KACzC;IACF,CAAC;GACH;GAEA,MAAM,WAAW,QAAQ,YAAY,YAAY;GAEjD,IAAI,aAAa,UAAU,CAAC,aAAa,SAAS,QAAQ,GACxD,OAAO,SAAS,aAAa;IAC3B,OAAO,EAAE,0BAA0B;IACnC;GACF,CAAC;GAGH,MAAM,YAAY,OAAO,IAAI,iBAAiB,UAAU;GAExD,IAAI,CAAC,WACH,MAAM,IAAI,MAAM,aAAa,SAAS,kBAAkB;GAG1D,MAAM,cAAc,MAAM,UAAU,KAAK,QAAQ,EAAE;GAEnD,IAAI,CAAC,aAAa;IAChB,MAAM,YAAY,QAAQ;IAE1B,OAAO,SAAS,aAAa;KAC3B,OAAO,EAAE,gCAAgC;KACzC;IACF,CAAC;GACH;GAEA,QAAQ,OAAO;EACjB,SAAS,OAAY;GACnB,IAAI,MAAM,QAAQ,QAAQ,KAAK;GAE/B,QAAQ,iBAAiB;GAEzB,OAAO,SAAS,aAAa;IAC3B,OAAO,EAAE,gCAAgC;IACzC;GACF,CAAC;EACH;CACF;CAEA,OAAO;AACT"}
1
+ {"version":3,"file":"auth.middleware.mjs","names":[],"sources":["../../../../../../../auth/src/middleware/auth.middleware.ts"],"sourcesContent":["import { config, t, type Middleware, type Request } from \"@warlock.js/core\";\nimport { log } from \"@warlock.js/logger\";\nimport type { TokenFrom } from \"../contracts/types\";\nimport { AccessToken } from \"../models/access-token\";\nimport { authService } from \"../services/auth.service\";\nimport { isInvalidCredentialError, jwt } from \"../services/jwt\";\nimport { AuthErrorCodes } from \"../utils/auth-error-codes\";\n\r\n/**\r\n * Decoded access-token claims the middleware reads. The full payload carries\r\n * more (`created_at`, `tokenType`, `iat`, `exp`) but only these drive routing.\r\n */\r\ntype DecodedAccessToken = {\r\n id: string | number;\r\n userType?: string;\r\n};\r\n\r\n/**\n * Whether the persisted row says the token is dead.\n *\r\n * The answer belongs to the model (`AccessToken.isExpired`), so a registered\r\n * override that renames or reshapes its expiry column stays authoritative and\r\n * the middleware never touches a column name. A row that cannot answer at all —\r\n * an override that dropped the getter — is treated as **expired**: the failure\r\n * mode of this whole defect class was a check that quietly answered \"fine\" when\r\n * it had nothing to check, and that is not repeated here.\r\n *\r\n * This is independent of the `exp` claim required by `jwt.verify`. That guard\r\n * catches a token whose *claims* carry no deadline; this one catches a token\r\n * whose *row* says the deadline has passed — a logged-out or expired session\r\n * whose JWT is still within its own lifetime. Neither subsumes the other.\r\n */\r\nfunction accessTokenRowIsExpired(accessToken: AccessToken): boolean {\n return typeof accessToken.isExpired === \"boolean\" ? accessToken.isExpired : true;\n}\n\nfunction readCredential(request: Request, tokenFrom: TokenFrom): string {\n if (tokenFrom === \"header\") {\n return request.authorizationValue;\n }\n\n const value = request.cookie(tokenFrom.slice(\"cookie:\".length));\n\n return value ? String(value) : \"\";\n}\n\r\n/**\r\n * Build a route gate that always requires an authenticated request.\r\n *\r\n * The argument is mandatory and selects which user types may pass:\r\n * - `[]` — any authenticated user (token required, type not checked).\r\n * - `\"admin\"` / `[\"admin\", \"staff\"]` — token required AND the user's\r\n * `userType` must be one of the listed types.\r\n *\r\n * There is no anonymous/optional mode: a request without a valid access\n * token is always rejected with `401`. Routes that should be public\n * simply omit the middleware.\n *\n * `tokenFrom` selects one credential source and defaults to `\"header\"`.\n * A single source avoids making credential precedence depend on array order.\n *\r\n * @example\r\n * router.get(\"/account\", authMiddleware([]), accountController);\r\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(\n allowedUserType: string | string[],\n tokenFrom: TokenFrom = \"header\",\n): Middleware {\n const allowedTypes = Array.isArray(allowedUserType) ? allowedUserType : [allowedUserType];\r\n\r\n const auth: Middleware = async ({ request, response }) => {\r\n const authorizationValue = readCredential(request, tokenFrom);\n\r\n if (!authorizationValue) {\r\n return response.unauthorized({\r\n error: t(\"auth.errors.missingAccessToken\"),\r\n errorCode: AuthErrorCodes.MissingAccessToken,\r\n });\r\n }\r\n\r\n let decoded: DecodedAccessToken;\r\n\r\n // The ONLY try in this middleware, and it wraps a single call. Everything\r\n // after it is storage and configuration, where an exception means the\r\n // server is broken rather than the caller (D5).\r\n try {\r\n decoded = await jwt.verify<DecodedAccessToken>(authorizationValue);\r\n } catch (error) {\r\n if (!isInvalidCredentialError(error)) {\r\n // A DB/cache outage, a missing `JWT_SECRET`, or any other server-side\r\n // fault — not a verdict on this credential. Propagate to the request's\r\n // normal error path (a 500 monitoring can see) instead of answering\r\n // 401 and clearing the caller's session.\r\n throw error;\r\n }\r\n\r\n // A forged, malformed, expired token — or (D6) a `tokenType` mismatch —\r\n // is not an incident: it is a request carrying a credential the server\r\n // will never accept.\r\n log.error(\"http\", \"auth\", error);\r\n\r\n request.clearCurrentUser();\r\n\r\n return response.unauthorized({\r\n error: t(\"auth.errors.invalidAccessToken\"),\r\n errorCode: AuthErrorCodes.InvalidAccessToken,\r\n });\r\n }\r\n\r\n request.decodedAccessToken = decoded;\r\n\r\n // A valid signature is not enough — the token must still exist in storage,\r\n // so deleting the row (logout) invalidates it before its JWT expiry.\r\n const AccessTokenModel = config.key(\"auth.accessToken.model\", AccessToken);\r\n const accessToken = await AccessTokenModel.findByToken(authorizationValue);\r\n\r\n if (!accessToken) {\r\n return response.unauthorized({\r\n error: t(\"auth.errors.invalidAccessToken\"),\r\n errorCode: AuthErrorCodes.InvalidAccessToken,\r\n });\r\n }\r\n\r\n // ... and the row must still be live. Existence alone was the whole check\r\n // before 4.12.0, so a row whose own `expires_at` had passed still opened\r\n // the gate. The stored expiry is now enforced, and the dead row is\r\n // removed on the way out rather than left for the cleanup command.\r\n if (accessTokenRowIsExpired(accessToken)) {\r\n await accessToken.destroy();\r\n\r\n return response.unauthorized({\r\n error: t(\"auth.errors.invalidAccessToken\"),\r\n errorCode: AuthErrorCodes.InvalidAccessToken,\r\n });\r\n }\r\n\r\n const userType = decoded.userType ?? accessToken.userType;\r\n\r\n if (allowedTypes.length && !allowedTypes.includes(userType)) {\r\n return response.unauthorized({\r\n error: t(\"auth.errors.unauthorized\"),\r\n errorCode: AuthErrorCodes.Unauthorized,\r\n });\r\n }\r\n\r\n const UserModel = config.key(`auth.userType.${userType}`);\r\n\r\n if (!UserModel) {\r\n // Configuration, not credentials. Throwing keeps a mis-registered app\r\n // loudly broken instead of quietly rejecting every request of this type.\r\n throw new Error(`User type ${userType} is unknown type.`);\r\n }\r\n\r\n const currentUser = await UserModel.find(decoded.id);\r\n\r\n if (!currentUser) {\n await accessToken.destroy();\n\n return response.unauthorized({\n error: t(\"auth.errors.invalidAccessToken\"),\n errorCode: AuthErrorCodes.InvalidAccessToken,\n });\n }\n\n if (!(await authService.canAuthenticate(currentUser))) {\n return response.unauthorized({\n error: t(\"auth.errors.unauthorized\"),\n errorCode: AuthErrorCodes.Unauthorized,\n });\n }\n\n request.user = currentUser;\n };\r\n\r\n return auth;\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAgCA,SAAS,wBAAwB,aAAmC;CAClE,OAAO,OAAO,YAAY,cAAc,YAAY,YAAY,YAAY;AAC9E;AAEA,SAAS,eAAe,SAAkB,WAA8B;CACtE,IAAI,cAAc,UAChB,OAAO,QAAQ;CAGjB,MAAM,QAAQ,QAAQ,OAAO,UAAU,MAAM,CAAgB,CAAC;CAE9D,OAAO,QAAQ,OAAO,KAAK,IAAI;AACjC;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,eACd,iBACA,YAAuB,UACX;CACZ,MAAM,eAAe,MAAM,QAAQ,eAAe,IAAI,kBAAkB,CAAC,eAAe;CAExF,MAAM,OAAmB,OAAO,EAAE,SAAS,eAAe;EACxD,MAAM,qBAAqB,eAAe,SAAS,SAAS;EAE5D,IAAI,CAAC,oBACH,OAAO,SAAS,aAAa;GAC3B,OAAO,EAAE,gCAAgC;GACzC;EACF,CAAC;EAGH,IAAI;EAKJ,IAAI;GACF,UAAU,MAAM,IAAI,OAA2B,kBAAkB;EACnE,SAAS,OAAO;GACd,IAAI,CAAC,yBAAyB,KAAK,GAKjC,MAAM;GAMR,IAAI,MAAM,QAAQ,QAAQ,KAAK;GAE/B,QAAQ,iBAAiB;GAEzB,OAAO,SAAS,aAAa;IAC3B,OAAO,EAAE,gCAAgC;IACzC;GACF,CAAC;EACH;EAEA,QAAQ,qBAAqB;EAK7B,MAAM,cAAc,MADK,OAAO,IAAI,0BAA0B,WACrB,CAAC,CAAC,YAAY,kBAAkB;EAEzE,IAAI,CAAC,aACH,OAAO,SAAS,aAAa;GAC3B,OAAO,EAAE,gCAAgC;GACzC;EACF,CAAC;EAOH,IAAI,wBAAwB,WAAW,GAAG;GACxC,MAAM,YAAY,QAAQ;GAE1B,OAAO,SAAS,aAAa;IAC3B,OAAO,EAAE,gCAAgC;IACzC;GACF,CAAC;EACH;EAEA,MAAM,WAAW,QAAQ,YAAY,YAAY;EAEjD,IAAI,aAAa,UAAU,CAAC,aAAa,SAAS,QAAQ,GACxD,OAAO,SAAS,aAAa;GAC3B,OAAO,EAAE,0BAA0B;GACnC;EACF,CAAC;EAGH,MAAM,YAAY,OAAO,IAAI,iBAAiB,UAAU;EAExD,IAAI,CAAC,WAGH,MAAM,IAAI,MAAM,aAAa,SAAS,kBAAkB;EAG1D,MAAM,cAAc,MAAM,UAAU,KAAK,QAAQ,EAAE;EAEnD,IAAI,CAAC,aAAa;GAChB,MAAM,YAAY,QAAQ;GAE1B,OAAO,SAAS,aAAa;IAC3B,OAAO,EAAE,gCAAgC;IACzC;GACF,CAAC;EACH;EAEA,IAAI,CAAE,MAAM,YAAY,gBAAgB,WAAW,GACjD,OAAO,SAAS,aAAa;GAC3B,OAAO,EAAE,0BAA0B;GACnC;EACF,CAAC;EAGH,QAAQ,OAAO;CACjB;CAEA,OAAO;AACT"}
@@ -68,7 +68,7 @@ function loginThrottleMiddleware(options = {}) {
68
68
  const lockoutDuration = options.lockoutDuration ?? "15m";
69
69
  const tracked = options.by ?? ["email", "ip"];
70
70
  const credentialField = options.identifierKey ?? "email";
71
- return async (request, response) => {
71
+ return async ({ request, response }) => {
72
72
  const identifiers = options.identify ? options.identify(request) : buildDefaultIdentifiers(request, tracked, credentialField);
73
73
  if (identifiers.length === 0) return;
74
74
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"login-throttle.middleware.mjs","names":[],"sources":["../../../../../../../auth/src/middleware/login-throttle.middleware.ts"],"sourcesContent":["import { cache } from \"@warlock.js/cache\";\r\nimport { t, type Middleware, type Request, type Response } from \"@warlock.js/core\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport { AuthErrorCodes } from \"../utils/auth-error-codes\";\r\n\r\n/**\r\n * Built-in identifier kinds the throttle knows how to extract from a request.\r\n *\r\n * - `\"email\"` tracks per-account, so an attacker can't spray one user from\r\n * many IPs.\r\n * - `\"ip\"` tracks per-source, so an attacker can't stuff many accounts from\r\n * one IP.\r\n *\r\n * Lockout trips when ANY tracked identifier crosses `max` — defense-in-depth.\r\n */\r\nexport type LoginThrottleIdentifier = \"email\" | \"ip\";\r\n\r\n/**\r\n * Options for {@link loginThrottleMiddleware}.\r\n *\r\n * Failure-aware by design: only counts requests whose response is non-2xx\r\n * (or whatever `isFailure` says) and resets on success, so a user who types\r\n * a wrong password three times then the right one is not penalised.\r\n *\r\n * @example\r\n * loginThrottleMiddleware({ max: 5, window: \"15m\", lockoutDuration: \"15m\" });\r\n */\r\nexport type LoginThrottleOptions = {\r\n /**\r\n * Failures allowed within the window before the identifier is locked.\r\n *\r\n * @default 5\r\n */\r\n max?: number;\r\n\r\n /**\r\n * Counting window. `ms`-format string (`\"15m\"`, `\"1h\"`) or seconds as a number.\r\n *\r\n * @default \"15m\"\r\n */\r\n window?: string | number;\r\n\r\n /**\r\n * Lockout duration once the threshold is tripped. `ms`-format string or seconds.\r\n *\r\n * @default \"15m\"\r\n */\r\n lockoutDuration?: string | number;\r\n\r\n /**\r\n * Identifiers tracked. Each is counted and locked independently.\r\n *\r\n * @default [\"email\", \"ip\"]\r\n */\r\n by?: LoginThrottleIdentifier[];\r\n\r\n /**\r\n * Credential field consulted when `\"email\"` is tracked. Switch to\r\n * `\"username\"` (or any other input field) for systems that don't\r\n * authenticate by email.\r\n *\r\n * @default \"email\"\r\n */\r\n identifierKey?: string;\r\n\r\n /**\r\n * Override the default error message returned with the 429 response.\r\n */\r\n errorMessage?: string;\r\n\r\n /**\r\n * Decide whether the response represents a failed login. Defaults to\r\n * \"non-2xx is a failure\"; override if your controller signals failure\r\n * through a 200-style body.\r\n *\r\n * @default (response) => !response.isOk\r\n */\r\n isFailure?: (response: Response) => boolean;\r\n\r\n /**\r\n * Compute the identifiers tracked for this request. Override to derive the\r\n * account key from a header, a JWT subject, or any other shape that does\r\n * not match the built-in `email` / `ip` extraction.\r\n */\r\n identify?: (request: Request) => string[];\r\n};\r\n\r\nconst COUNTER_PREFIX = \"auth.throttle.count.\";\r\nconst LOCK_PREFIX = \"auth.throttle.lock.\";\r\n\r\nconst counterKey = (identifier: string): string => `${COUNTER_PREFIX}${identifier}`;\r\nconst lockKey = (identifier: string): string => `${LOCK_PREFIX}${identifier}`;\r\n\r\n/**\r\n * Built-in identifier extractor — reads the configured credential field from\r\n * the request body for the per-account key, and `request.detectIp()` for the\r\n * per-source key. Untruthy values are dropped; a request that yields zero\r\n * identifiers is left untracked so the middleware no-ops for it.\r\n */\r\nfunction buildDefaultIdentifiers(\r\n request: Request,\r\n tracked: LoginThrottleIdentifier[],\r\n credentialField: string,\r\n): string[] {\r\n const identifiers: string[] = [];\r\n\r\n if (tracked.includes(\"email\")) {\r\n const value = request.input(credentialField);\r\n\r\n if (typeof value === \"string\" && value.length > 0) {\r\n identifiers.push(`email.${value.toLowerCase()}`);\r\n }\r\n }\r\n\r\n if (tracked.includes(\"ip\")) {\r\n const ip = request.detectIp();\r\n\r\n if (ip) {\r\n identifiers.push(`ip.${ip}`);\r\n }\r\n }\r\n\r\n return identifiers;\r\n}\r\n\r\n/**\r\n * Failure-aware login throttle. Locks an identifier (account, IP, or both)\r\n * after too many failed login attempts within a window — without penalising\r\n * a user who eventually types the right password.\r\n *\r\n * Mechanism (per identifier):\r\n *\r\n * 1. **Before the controller** — reject with `429` if a lock key is set,\r\n * skipping the DB lookup AND the bcrypt verify (this also neutralises the\r\n * CPU-DoS angle of brute-forcing).\r\n * 2. **After the controller** — observe the outcome via `response.onSent`.\r\n * On failure (non-2xx by default), bump a fixed-window counter; once it\r\n * reaches `max`, set a lock key with `lockoutDuration` TTL. On success,\r\n * clear both the counter and the lock for that identifier.\r\n *\r\n * Storage is `@warlock.js/cache` (a peer dependency of `@warlock.js/auth`,\r\n * transitively guaranteed via `@warlock.js/core`). Lockout state is shared\r\n * across replicas — correct security model, because an attacker can't bypass\r\n * by round-robin to a different pod. Every cache call is guarded so a\r\n * misconfigured driver fails open (a throttle outage must never escalate\r\n * into a full auth outage).\r\n *\r\n * @example\r\n * import { loginThrottleMiddleware } from \"@warlock.js/auth\";\r\n * import { router } from \"@warlock.js/core\";\r\n *\r\n * router.post(\"/auth/login\", loginController, {\r\n * middleware: [loginThrottleMiddleware({ max: 5, window: \"15m\" })],\r\n * });\r\n *\r\n * @example\r\n * // Per-IP only — avoid account-lockout-as-DoS on anonymous-attack vectors\r\n * router.post(\"/auth/refresh-token\", refreshController, {\r\n * middleware: [loginThrottleMiddleware({ by: [\"ip\"], max: 10, window: \"1m\" })],\r\n * });\r\n */\r\nexport function loginThrottleMiddleware(options: LoginThrottleOptions = {}): Middleware {\r\n const max = options.max ?? 5;\r\n const window = options.window ?? \"15m\";\r\n const lockoutDuration = options.lockoutDuration ?? \"15m\";\r\n const tracked = options.by ?? [\"email\", \"ip\"];\r\n const credentialField = options.identifierKey ?? \"email\";\r\n\r\n return async (request: Request, response: Response) => {\r\n const identifiers = options.identify\r\n ? options.identify(request)\r\n : buildDefaultIdentifiers(request, tracked, credentialField);\r\n\r\n if (identifiers.length === 0) return;\r\n\r\n try {\r\n for (const identifier of identifiers) {\r\n const locked = await cache.get(lockKey(identifier));\r\n\r\n if (locked) {\r\n return response.tooManyRequests({\r\n error: options.errorMessage ?? t(\"auth.errors.tooManyAttempts\"),\r\n errorCode: AuthErrorCodes.TooManyAttempts,\r\n });\r\n }\r\n }\r\n } catch (error) {\r\n // Fail open — a cache outage (driver not initialised, network blip)\r\n // must never escalate into a full auth outage. Log and let the request\r\n // through; the onSent recorder below is independently guarded so an\r\n // outage there is also harmless.\r\n log.error(\"auth\", \"login-throttle\", error);\r\n\r\n return;\r\n }\r\n\r\n response.onSent(async (resolvedResponse: Response) => {\r\n try {\r\n const failed = options.isFailure\r\n ? options.isFailure(resolvedResponse)\r\n : !resolvedResponse.isOk;\r\n\r\n if (!failed) {\r\n for (const identifier of identifiers) {\r\n await cache.remove(counterKey(identifier));\r\n await cache.remove(lockKey(identifier));\r\n }\r\n\r\n return;\r\n }\r\n\r\n for (const identifier of identifiers) {\r\n const existing = await cache.get(counterKey(identifier));\r\n const ttlOption = existing === null ? { ttl: window } : undefined;\r\n const count = await cache.update<number>(\r\n counterKey(identifier),\r\n (current) => (current ?? 0) + 1,\r\n ttlOption,\r\n );\r\n\r\n if ((count ?? 0) >= max) {\r\n await cache.set(lockKey(identifier), true, { ttl: lockoutDuration });\r\n }\r\n }\r\n } catch (error) {\r\n log.error(\"auth\", \"login-throttle\", error);\r\n }\r\n });\r\n };\r\n}\r\n"],"mappings":";;;;;;AAuFA,MAAM,iBAAiB;AACvB,MAAM,cAAc;AAEpB,MAAM,cAAc,eAA+B,GAAG,iBAAiB;AACvE,MAAM,WAAW,eAA+B,GAAG,cAAc;;;;;;;AAQjE,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,SAAS,GAC9C,YAAY,KAAK,SAAS,MAAM,YAAY,GAAG;CAEnD;CAEA,IAAI,QAAQ,SAAS,IAAI,GAAG;EAC1B,MAAM,KAAK,QAAQ,SAAS;EAE5B,IAAI,IACF,YAAY,KAAK,MAAM,IAAI;CAE/B;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,SAAkB,aAAuB;EACrD,MAAM,cAAc,QAAQ,WACxB,QAAQ,SAAS,OAAO,IACxB,wBAAwB,SAAS,SAAS,eAAe;EAE7D,IAAI,YAAY,WAAW,GAAG;EAE9B,IAAI;GACF,KAAK,MAAM,cAAc,aAGvB,IAAI,MAFiB,MAAM,IAAI,QAAQ,UAAU,CAAC,GAGhD,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;KAEpC,MAAM,YAAY,MADK,MAAM,IAAI,WAAW,UAAU,CAAC,MACxB,OAAO,EAAE,KAAK,OAAO,IAAI;KAOxD,KAAK,MANe,MAAM,OACxB,WAAW,UAAU,IACpB,aAAa,WAAW,KAAK,GAC9B,SACF,KAEc,MAAM,KAClB,MAAM,MAAM,IAAI,QAAQ,UAAU,GAAG,MAAM,EAAE,KAAK,gBAAgB,CAAC;IAEvE;GACF,SAAS,OAAO;IACd,IAAI,MAAM,QAAQ,kBAAkB,KAAK;GAC3C;EACF,CAAC;CACH;AACF"}
1
+ {"version":3,"file":"login-throttle.middleware.mjs","names":[],"sources":["../../../../../../../auth/src/middleware/login-throttle.middleware.ts"],"sourcesContent":["import { cache } from \"@warlock.js/cache\";\r\nimport { t, type Middleware, type Request, type Response } from \"@warlock.js/core\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport { AuthErrorCodes } from \"../utils/auth-error-codes\";\r\n\r\n/**\r\n * Built-in identifier kinds the throttle knows how to extract from a request.\r\n *\r\n * - `\"email\"` tracks per-account, so an attacker can't spray one user from\r\n * many IPs.\r\n * - `\"ip\"` tracks per-source, so an attacker can't stuff many accounts from\r\n * one IP.\r\n *\r\n * Lockout trips when ANY tracked identifier crosses `max` — defense-in-depth.\r\n */\r\nexport type LoginThrottleIdentifier = \"email\" | \"ip\";\r\n\r\n/**\r\n * Options for {@link loginThrottleMiddleware}.\r\n *\r\n * Failure-aware by design: only counts requests whose response is non-2xx\r\n * (or whatever `isFailure` says) and resets on success, so a user who types\r\n * a wrong password three times then the right one is not penalised.\r\n *\r\n * @example\r\n * loginThrottleMiddleware({ max: 5, window: \"15m\", lockoutDuration: \"15m\" });\r\n */\r\nexport type LoginThrottleOptions = {\r\n /**\r\n * Failures allowed within the window before the identifier is locked.\r\n *\r\n * @default 5\r\n */\r\n max?: number;\r\n\r\n /**\r\n * Counting window. `ms`-format string (`\"15m\"`, `\"1h\"`) or seconds as a number.\r\n *\r\n * @default \"15m\"\r\n */\r\n window?: string | number;\r\n\r\n /**\r\n * Lockout duration once the threshold is tripped. `ms`-format string or seconds.\r\n *\r\n * @default \"15m\"\r\n */\r\n lockoutDuration?: string | number;\r\n\r\n /**\r\n * Identifiers tracked. Each is counted and locked independently.\r\n *\r\n * @default [\"email\", \"ip\"]\r\n */\r\n by?: LoginThrottleIdentifier[];\r\n\r\n /**\r\n * Credential field consulted when `\"email\"` is tracked. Switch to\r\n * `\"username\"` (or any other input field) for systems that don't\r\n * authenticate by email.\r\n *\r\n * @default \"email\"\r\n */\r\n identifierKey?: string;\r\n\r\n /**\r\n * Override the default error message returned with the 429 response.\r\n */\r\n errorMessage?: string;\r\n\r\n /**\r\n * Decide whether the response represents a failed login. Defaults to\r\n * \"non-2xx is a failure\"; override if your controller signals failure\r\n * through a 200-style body.\r\n *\r\n * @default (response) => !response.isOk\r\n */\r\n isFailure?: (response: Response) => boolean;\r\n\r\n /**\r\n * Compute the identifiers tracked for this request. Override to derive the\r\n * account key from a header, a JWT subject, or any other shape that does\r\n * not match the built-in `email` / `ip` extraction.\r\n */\r\n identify?: (request: Request) => string[];\r\n};\r\n\r\nconst COUNTER_PREFIX = \"auth.throttle.count.\";\r\nconst LOCK_PREFIX = \"auth.throttle.lock.\";\r\n\r\nconst counterKey = (identifier: string): string => `${COUNTER_PREFIX}${identifier}`;\r\nconst lockKey = (identifier: string): string => `${LOCK_PREFIX}${identifier}`;\r\n\r\n/**\r\n * Built-in identifier extractor — reads the configured credential field from\r\n * the request body for the per-account key, and `request.detectIp()` for the\r\n * per-source key. Untruthy values are dropped; a request that yields zero\r\n * identifiers is left untracked so the middleware no-ops for it.\r\n */\r\nfunction buildDefaultIdentifiers(\r\n request: Request,\r\n tracked: LoginThrottleIdentifier[],\r\n credentialField: string,\r\n): string[] {\r\n const identifiers: string[] = [];\r\n\r\n if (tracked.includes(\"email\")) {\r\n const value = request.input(credentialField);\r\n\r\n if (typeof value === \"string\" && value.length > 0) {\r\n identifiers.push(`email.${value.toLowerCase()}`);\r\n }\r\n }\r\n\r\n if (tracked.includes(\"ip\")) {\r\n const ip = request.detectIp();\r\n\r\n if (ip) {\r\n identifiers.push(`ip.${ip}`);\r\n }\r\n }\r\n\r\n return identifiers;\r\n}\r\n\r\n/**\r\n * Failure-aware login throttle. Locks an identifier (account, IP, or both)\r\n * after too many failed login attempts within a window — without penalising\r\n * a user who eventually types the right password.\r\n *\r\n * Mechanism (per identifier):\r\n *\r\n * 1. **Before the controller** — reject with `429` if a lock key is set,\r\n * skipping the DB lookup AND the bcrypt verify (this also neutralises the\r\n * CPU-DoS angle of brute-forcing).\r\n * 2. **After the controller** — observe the outcome via `response.onSent`.\r\n * On failure (non-2xx by default), bump a fixed-window counter; once it\r\n * reaches `max`, set a lock key with `lockoutDuration` TTL. On success,\r\n * clear both the counter and the lock for that identifier.\r\n *\r\n * Storage is `@warlock.js/cache` (a peer dependency of `@warlock.js/auth`,\r\n * transitively guaranteed via `@warlock.js/core`). Lockout state is shared\r\n * across replicas — correct security model, because an attacker can't bypass\r\n * by round-robin to a different pod. Every cache call is guarded so a\r\n * misconfigured driver fails open (a throttle outage must never escalate\r\n * into a full auth outage).\r\n *\r\n * @example\r\n * import { loginThrottleMiddleware } from \"@warlock.js/auth\";\r\n * import { router } from \"@warlock.js/core\";\r\n *\r\n * router.post(\"/auth/login\", loginController, {\r\n * middleware: [loginThrottleMiddleware({ max: 5, window: \"15m\" })],\r\n * });\r\n *\r\n * @example\r\n * // Per-IP only — avoid account-lockout-as-DoS on anonymous-attack vectors\r\n * router.post(\"/auth/refresh-token\", refreshController, {\r\n * middleware: [loginThrottleMiddleware({ by: [\"ip\"], max: 10, window: \"1m\" })],\r\n * });\r\n */\r\nexport function loginThrottleMiddleware(options: LoginThrottleOptions = {}): Middleware {\r\n const max = options.max ?? 5;\r\n const window = options.window ?? \"15m\";\r\n const lockoutDuration = options.lockoutDuration ?? \"15m\";\r\n const tracked = options.by ?? [\"email\", \"ip\"];\r\n const credentialField = options.identifierKey ?? \"email\";\r\n\r\n return async ({ request, response }) => {\r\n const identifiers = options.identify\r\n ? options.identify(request)\r\n : buildDefaultIdentifiers(request, tracked, credentialField);\r\n\r\n if (identifiers.length === 0) return;\r\n\r\n try {\r\n for (const identifier of identifiers) {\r\n const locked = await cache.get(lockKey(identifier));\r\n\r\n if (locked) {\r\n return response.tooManyRequests({\r\n error: options.errorMessage ?? t(\"auth.errors.tooManyAttempts\"),\r\n errorCode: AuthErrorCodes.TooManyAttempts,\r\n });\r\n }\r\n }\r\n } catch (error) {\r\n // Fail open — a cache outage (driver not initialised, network blip)\r\n // must never escalate into a full auth outage. Log and let the request\r\n // through; the onSent recorder below is independently guarded so an\r\n // outage there is also harmless.\r\n log.error(\"auth\", \"login-throttle\", error);\r\n\r\n return;\r\n }\r\n\r\n response.onSent(async (resolvedResponse: Response) => {\r\n try {\r\n const failed = options.isFailure\r\n ? options.isFailure(resolvedResponse)\r\n : !resolvedResponse.isOk;\r\n\r\n if (!failed) {\r\n for (const identifier of identifiers) {\r\n await cache.remove(counterKey(identifier));\r\n await cache.remove(lockKey(identifier));\r\n }\r\n\r\n return;\r\n }\r\n\r\n for (const identifier of identifiers) {\r\n const existing = await cache.get(counterKey(identifier));\r\n const ttlOption = existing === null ? { ttl: window } : undefined;\r\n const count = await cache.update<number>(\r\n counterKey(identifier),\r\n (current) => (current ?? 0) + 1,\r\n ttlOption,\r\n );\r\n\r\n if ((count ?? 0) >= max) {\r\n await cache.set(lockKey(identifier), true, { ttl: lockoutDuration });\r\n }\r\n }\r\n } catch (error) {\r\n log.error(\"auth\", \"login-throttle\", error);\r\n }\r\n });\r\n };\r\n}\r\n"],"mappings":";;;;;;AAuFA,MAAM,iBAAiB;AACvB,MAAM,cAAc;AAEpB,MAAM,cAAc,eAA+B,GAAG,iBAAiB;AACvE,MAAM,WAAW,eAA+B,GAAG,cAAc;;;;;;;AAQjE,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,SAAS,GAC9C,YAAY,KAAK,SAAS,MAAM,YAAY,GAAG;CAEnD;CAEA,IAAI,QAAQ,SAAS,IAAI,GAAG;EAC1B,MAAM,KAAK,QAAQ,SAAS;EAE5B,IAAI,IACF,YAAY,KAAK,MAAM,IAAI;CAE/B;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,IAAI;GACF,KAAK,MAAM,cAAc,aAGvB,IAAI,MAFiB,MAAM,IAAI,QAAQ,UAAU,CAAC,GAGhD,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;KAEpC,MAAM,YAAY,MADK,MAAM,IAAI,WAAW,UAAU,CAAC,MACxB,OAAO,EAAE,KAAK,OAAO,IAAI;KAOxD,KAAK,MANe,MAAM,OACxB,WAAW,UAAU,IACpB,aAAa,WAAW,KAAK,GAC9B,SACF,KAEc,MAAM,KAClB,MAAM,MAAM,IAAI,QAAQ,UAAU,GAAG,MAAM,EAAE,KAAK,gBAAgB,CAAC;IAEvE;GACF,SAAS,OAAO;IACd,IAAI,MAAM,QAAQ,kBAAkB,KAAK;GAC3C;EACF,CAAC;CACH;AACF"}
@@ -10,7 +10,22 @@ import { Model } from "@warlock.js/cascade";
10
10
  * `last_access` and the `is_active` soft-revoke flag were removed — neither was
11
11
  * ever read; access tokens are revoked by deleting the row.
12
12
  */
13
- declare const accessTokenSchema: any;
13
+ declare const accessTokenSchema: import("@warlock.js/seal").ObjectValidator<{
14
+ token: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
15
+ 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>;
17
+ expires_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date>;
18
+ }> & import("@warlock.js/seal").StandardSchemaV1<import("@warlock.js/seal").InferInputObjectShape<{
19
+ token: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
20
+ user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
21
+ user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
22
+ expires_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date>;
23
+ }>, import("@warlock.js/seal").InferInputObjectShape<{
24
+ token: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
25
+ user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
26
+ user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
27
+ expires_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date>;
28
+ }>>;
14
29
  /**
15
30
  * Persisted access-token record + the data layer for access tokens.
16
31
  *
@@ -23,7 +38,22 @@ declare const accessTokenSchema: any;
23
38
  */
24
39
  declare class AccessToken extends Model {
25
40
  static table: string;
26
- static schema: any;
41
+ static schema: import("@warlock.js/seal").ObjectValidator<{
42
+ token: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
43
+ user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
44
+ user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
45
+ expires_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date>;
46
+ }> & import("@warlock.js/seal").StandardSchemaV1<import("@warlock.js/seal").InferInputObjectShape<{
47
+ token: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
48
+ 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>;
50
+ expires_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date>;
51
+ }>, import("@warlock.js/seal").InferInputObjectShape<{
52
+ token: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
53
+ user_id: import("@warlock.js/seal").ScalarValidator & import("@warlock.js/seal").StandardSchemaV1<string | number | boolean, string | number | boolean>;
54
+ user_type: import("@warlock.js/seal").StringValidator & import("@warlock.js/seal").StandardSchemaV1<string, string>;
55
+ expires_at: import("@warlock.js/seal").DateValidator & import("@warlock.js/seal").StandardSchemaV1<Date, Date>;
56
+ }>>;
27
57
  /** The user this token was issued for. */
28
58
  get userId(): any;
29
59
  /** The user-type slug this token was issued for. */
@@ -54,7 +84,7 @@ declare class AccessToken extends Model {
54
84
  /**
55
85
  * Persist a freshly-signed access token for the user.
56
86
  */
57
- static issue(user: Auth, token: string, expiresAt: Date): any;
87
+ static issue(user: Auth, token: string, expiresAt: Date): Promise<AccessToken>;
58
88
  /**
59
89
  * Find an access-token row by its raw token string.
60
90
  */
@@ -62,11 +92,11 @@ declare class AccessToken extends Model {
62
92
  /**
63
93
  * Delete a specific token that belongs to the given user.
64
94
  */
65
- static deleteForUser(user: Auth, token: string): any;
95
+ static deleteForUser(user: Auth, token: string): Promise<number>;
66
96
  /**
67
97
  * Delete every access token belonging to the user.
68
98
  */
69
- static deleteAllForUser(user: Auth): any;
99
+ static deleteAllForUser(user: Auth): Promise<number>;
70
100
  /**
71
101
  * Hard-delete every expired access-token row. Returns the number removed.
72
102
  * Runs from the `auth.cleanup` CLI command (a cold batch path).
@@ -1 +1 @@
1
- {"version":3,"file":"access-token.model.d.mts","names":[],"sources":["../../../../../../../../auth/src/models/access-token/access-token.model.ts"],"mappings":";;;;;;AAaA;;;;AAKE;AAYF;cAjBa,iBAAA;;;;;;;;;;;cAiBA,WAAA,SAAoB,KAAA;EAAA,OACjB,KAAA;EAAA,OAEA,MAAA;EAHsB;EAAA,IAMzB,MAAA;EANoB;EAAA,IAWpB,QAAA;EARG;;;;;;;;;;;;;;;EAAA,IA2BH,SAAA;EAwCuB;;;;;;EAAA,IA1BvB,YAAA;EAyCyB;;;EAAA,OAlCtB,KAAA,CAAM,IAAA,EAAM,IAAA,EAAM,KAAA,UAAe,SAAA,EAAW,IAAA;EAkEtC;;;EAAA,OAtDN,WAAA,CAAY,KAAA,WAAgB,OAAA,CAAQ,WAAA;EAsDW;;;EAAA,OA/C/C,aAAA,CAAc,IAAA,EAAM,IAAA,EAAM,KAAA;;;;SAO1B,gBAAA,CAAiB,IAAA,EAAM,IAAA;;;;;SAQjB,YAAA,IAAgB,OAAA;;;;;;;;;;;;SAqBhB,iBAAA,IAAqB,OAAA,CAAQ,WAAA;;;;;;SAW7B,kBAAA,IAAsB,OAAA,CAAQ,WAAA;AAAA"}
1
+ {"version":3,"file":"access-token.model.d.mts","names":[],"sources":["../../../../../../../../auth/src/models/access-token/access-token.model.ts"],"mappings":";;;;;;AAaA;;;;;;cAAa,iBAAA,6BAAiB,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;cAiBjB,WAAA,SAAoB,KAAA;EAAA,OACjB,KAAA;EAAA,OAEA,MAAA,6BAAM,eAAA;;;;;;;;;;;;;;;;;MAGT,MAAA;;MAKA,QAAA;;;;;;AAXb;;;;;;;;;;MA8Ba,SAAA;EAqBmD;;;;;;EAAA,IAPnD,YAAA;EAiC8B;;;EAAA,OA1B3B,KAAA,CAAM,IAAA,EAAM,IAAA,EAAM,KAAA,UAAe,SAAA,EAAW,IAAA,GAAI,OAAA,CAAA,WAAA;EAkEZ;;;EAAA,OAtDpC,WAAA,CAAY,KAAA,WAAgB,OAAA,CAAQ,WAAA;EA/Dd;;;EAAA,OAsEtB,aAAA,CAAc,IAAA,EAAM,IAAA,EAAM,KAAA,WAAa,OAAA;EAnEjC;;;EAAA,OA0EN,gBAAA,CAAiB,IAAA,EAAM,IAAA,GAAI,OAAA;;;;;SAQrB,YAAA,IAAgB,OAAA;;;;;;;;;;;;SAqBhB,iBAAA,IAAqB,OAAA,CAAQ,WAAA;;;;;;SAW7B,kBAAA,IAAsB,OAAA,CAAQ,WAAA;AAAA"}
@@ -12,11 +12,7 @@ declare abstract class Auth<Schema extends ModelSchema = ModelSchema> extends Mo
12
12
  /**
13
13
  * Get access token payload
14
14
  */
15
- accessTokenPayload(): {
16
- id: any;
17
- userType: string;
18
- created_at: number;
19
- };
15
+ accessTokenPayload(): Record<string, unknown>;
20
16
  /**
21
17
  * Create both access and refresh tokens
22
18
  */
@@ -1 +1 @@
1
- {"version":3,"file":"auth.model.d.mts","names":[],"sources":["../../../../../../../auth/src/models/auth.model.ts"],"mappings":";;;;;;uBAMsB,IAAA,gBAAoB,WAAA,GAAc,WAAA,UAC9C,KAAA,CAAM,MAAA,aACH,aAAA;;AAFb;;eAOsB,QAAA;EAPoB;;;EAYjC,kBAAA;;;;;EAqBwC;;;EAdlC,eAAA,CAAgB,UAAA,GAAa,UAAA,GAAa,OAAA,CAAQ,SAAA;EA4Bf;;;EArBnC,mBAAA,CAAoB,IAAA,SAAa,OAAA,CAAQ,iBAAA;EA0CvB;;;EAnClB,oBAAA,CAAqB,UAAA,GAAa,UAAA,GAAa,OAAA,CAAQ,YAAA;EA0CJ;;;EAnCnD,iBAAA,CAAkB,KAAA,WAAgB,OAAA;EAtCvB;;;EA6CX,kBAAA,CAAmB,KAAA,WAAgB,OAAA;EA/CM;;;EAsDzC,qBAAA,IAAyB,OAAA;EA/ClB;;;EAsDP,eAAA,IAAmB,OAAA;;;;EAOnB,cAAA,IAAkB,OAAA,CAAQ,YAAA;EAjDgB;;;EAAA,OAwDnC,OAAA,CAAQ,IAAA,EAAM,UAAA,CAAW,IAAA,GAAO,IAAA,QAAY,OAAA,CAAQ,IAAA;EAjD1B;;;EAwDjC,eAAA,CAAgB,QAAA,WAAmB,OAAA;AAAA"}
1
+ {"version":3,"file":"auth.model.d.mts","names":[],"sources":["../../../../../../../auth/src/models/auth.model.ts"],"mappings":";;;;;;uBAMsB,IAAA,gBAAoB,WAAA,GAAc,WAAA,UAC9C,KAAA,CAAM,MAAA,aACH,aAAA;;AAFb;;eAOsB,QAAA;EAPoB;;;EAYjC,kBAAA,IAAkB,MAAA;EAOiB;;;EAA7B,eAAA,CAAgB,UAAA,GAAa,UAAA,GAAa,OAAA,CAAQ,SAAA;EAOjB;;;EAAjC,mBAAA,CAAoB,IAAA,SAAa,OAAA,CAAQ,iBAAA;EAcP;;;EAPlC,oBAAA,CAAqB,UAAA,GAAa,UAAA,GAAa,OAAA,CAAQ,YAAA;EAmC7B;;;EA5B1B,iBAAA,CAAkB,KAAA,WAAgB,OAAA;EAmCyB;;;EA5B3D,kBAAA,CAAmB,KAAA,WAAgB,OAAA;EA7CrC;;;EAoDE,qBAAA,IAAyB,OAAA;EAtDE;;;EA6D3B,eAAA,IAAmB,OAAA;EA3DrB;;;EAkEE,cAAA,IAAkB,OAAA,CAAQ,YAAA;EAjD1B;;;EAAA,OAwDO,OAAA,CAAQ,IAAA,EAAM,UAAA,CAAW,IAAA,GAAO,IAAA,QAAY,OAAA,CAAQ,IAAA;EAxDT;;;EA+DlD,eAAA,CAAgB,QAAA,WAAmB,OAAA;AAAA"}
@@ -3,7 +3,7 @@ import { AccessToken, accessTokenSchema } from "./access-token/access-token.mode
3
3
  import { RefreshToken, RefreshTokenIssueOptions, refreshTokenSchema } from "./refresh-token/refresh-token.model.mjs";
4
4
 
5
5
  //#region ../auth/src/models/index.d.ts
6
- declare const authMigrations: any[];
6
+ declare const authMigrations: import("@warlock.js/cascade").MigrationConstructor[];
7
7
  //#endregion
8
8
  export { authMigrations };
9
9
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../../../../../../auth/src/models/index.ts"],"mappings":";;;;;cAOa,cAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../../../../../auth/src/models/index.ts"],"mappings":";;;;;cAOa,cAAA,gCAAc,oBAAA"}