@warlock.js/auth 4.1.15 → 4.2.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 (91) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/LICENSE +21 -0
  3. package/README.md +78 -37
  4. package/esm/commands/auth-cleanup-command.d.mts +1 -1
  5. package/esm/commands/auth-cleanup-command.d.mts.map +1 -1
  6. package/esm/commands/auth-cleanup-command.mjs +1 -1
  7. package/esm/commands/auth-cleanup-command.mjs.map +1 -1
  8. package/esm/commands/jwt-secret-generator-command.d.mts +1 -1
  9. package/esm/commands/jwt-secret-generator-command.d.mts.map +1 -1
  10. package/esm/commands/jwt-secret-generator-command.mjs +1 -1
  11. package/esm/commands/jwt-secret-generator-command.mjs.map +1 -1
  12. package/esm/contracts/auth-contract.d.mts +2 -2
  13. package/esm/contracts/auth-contract.d.mts.map +1 -1
  14. package/esm/contracts/index.d.mts +1 -1
  15. package/esm/contracts/types.d.mts +105 -76
  16. package/esm/contracts/types.d.mts.map +1 -1
  17. package/esm/contracts/types.mjs +2 -2
  18. package/esm/contracts/types.mjs.map +1 -1
  19. package/esm/index.d.mts +6 -5
  20. package/esm/index.mjs +4 -3
  21. package/esm/middleware/auth.middleware.d.mts +1 -1
  22. package/esm/middleware/auth.middleware.d.mts.map +1 -1
  23. package/esm/middleware/auth.middleware.mjs +9 -9
  24. package/esm/middleware/auth.middleware.mjs.map +1 -1
  25. package/esm/middleware/index.d.mts +2 -0
  26. package/esm/middleware/index.mjs +1 -0
  27. package/esm/middleware/login-throttle.middleware.d.mts +116 -0
  28. package/esm/middleware/login-throttle.middleware.d.mts.map +1 -0
  29. package/esm/middleware/login-throttle.middleware.mjs +105 -0
  30. package/esm/middleware/login-throttle.middleware.mjs.map +1 -0
  31. package/esm/models/access-token/access-token.model.d.mts +47 -5
  32. package/esm/models/access-token/access-token.model.d.mts.map +1 -1
  33. package/esm/models/access-token/access-token.model.mjs +70 -4
  34. package/esm/models/access-token/access-token.model.mjs.map +1 -1
  35. package/esm/models/access-token/index.d.mts +1 -1
  36. package/esm/models/access-token/index.mjs +1 -1
  37. package/esm/models/access-token/migration.mjs +2 -3
  38. package/esm/models/access-token/migration.mjs.map +1 -1
  39. package/esm/models/auth.model.d.mts +2 -2
  40. package/esm/models/auth.model.d.mts.map +1 -1
  41. package/esm/models/auth.model.mjs +1 -1
  42. package/esm/models/auth.model.mjs.map +1 -1
  43. package/esm/models/index.d.mts +4 -3
  44. package/esm/models/index.d.mts.map +1 -1
  45. package/esm/models/index.mjs +3 -3
  46. package/esm/models/index.mjs.map +1 -1
  47. package/esm/models/refresh-token/index.d.mts +1 -1
  48. package/esm/models/refresh-token/index.mjs +1 -1
  49. package/esm/models/refresh-token/migration.mjs +2 -1
  50. package/esm/models/refresh-token/migration.mjs.map +1 -1
  51. package/esm/models/refresh-token/refresh-token.model.d.mts +94 -16
  52. package/esm/models/refresh-token/refresh-token.model.d.mts.map +1 -1
  53. package/esm/models/refresh-token/refresh-token.model.mjs +152 -13
  54. package/esm/models/refresh-token/refresh-token.model.mjs.map +1 -1
  55. package/esm/services/auth-config.mjs +63 -0
  56. package/esm/services/auth-config.mjs.map +1 -0
  57. package/esm/services/auth-events.d.mts +2 -2
  58. package/esm/services/auth-events.d.mts.map +1 -1
  59. package/esm/services/auth-events.mjs +7 -2
  60. package/esm/services/auth-events.mjs.map +1 -1
  61. package/esm/services/auth.service.d.mts +48 -30
  62. package/esm/services/auth.service.d.mts.map +1 -1
  63. package/esm/services/auth.service.mjs +88 -102
  64. package/esm/services/auth.service.mjs.map +1 -1
  65. package/esm/services/generate-jwt-secret.d.mts +1 -1
  66. package/esm/services/generate-jwt-secret.d.mts.map +1 -1
  67. package/esm/services/generate-jwt-secret.mjs +4 -4
  68. package/esm/services/generate-jwt-secret.mjs.map +1 -1
  69. package/esm/services/index.d.mts +1 -1
  70. package/esm/services/jwt.d.mts +13 -4
  71. package/esm/services/jwt.d.mts.map +1 -1
  72. package/esm/services/jwt.mjs +30 -11
  73. package/esm/services/jwt.mjs.map +1 -1
  74. package/esm/utils/auth-error-codes.d.mts +9 -2
  75. package/esm/utils/auth-error-codes.d.mts.map +1 -1
  76. package/esm/utils/auth-error-codes.mjs +7 -1
  77. package/esm/utils/auth-error-codes.mjs.map +1 -1
  78. package/llms-full.txt +269 -20
  79. package/llms.txt +2 -0
  80. package/package.json +9 -11
  81. package/skills/auth-basics/SKILL.md +8 -7
  82. package/skills/customize-token-storage/SKILL.md +112 -0
  83. package/skills/customize-user-type/SKILL.md +7 -2
  84. package/skills/handle-login-and-logout/SKILL.md +1 -1
  85. package/skills/manage-tokens/SKILL.md +9 -9
  86. package/skills/overview/SKILL.md +7 -1
  87. package/skills/protect-routes/SKILL.md +1 -0
  88. package/skills/run-auth-commands/SKILL.md +1 -1
  89. package/skills/throttle-login-attempts/SKILL.md +116 -0
  90. package/cjs/index.cjs +0 -807
  91. package/cjs/index.cjs.map +0 -1
@@ -1,12 +1,23 @@
1
- import { RefreshToken } from "../models/refresh-token/refresh-token.model.mjs";
2
1
  import { Auth } from "../models/auth.model.mjs";
3
2
  import { AccessTokenOutput, DeviceInfo, LoginResult, TokenPair } from "../contracts/types.mjs";
3
+ import { RefreshToken } from "../models/refresh-token/refresh-token.model.mjs";
4
4
  import { ChildModel } from "@warlock.js/cascade";
5
5
 
6
- //#region ../../@warlock.js/auth/src/services/auth.service.d.ts
6
+ //#region ../@warlock.js/auth/src/services/auth.service.d.ts
7
7
  declare class AuthService {
8
8
  /**
9
- * Build access token payload from user
9
+ * Resolve the active access-token model the package default, or a subclass
10
+ * an app registered under `config.auth.accessToken.model` (e.g. to add a
11
+ * tenant column). The service never references the concrete class directly so
12
+ * an override is a pure config change.
13
+ */
14
+ private get accessTokenModel();
15
+ /**
16
+ * Resolve the active refresh-token model (default or registered override).
17
+ */
18
+ private get refreshTokenModel();
19
+ /**
20
+ * Build the default access-token JWT payload from a user.
10
21
  */
11
22
  buildAccessTokenPayload(user: Auth): {
12
23
  id: any;
@@ -14,78 +25,85 @@ declare class AuthService {
14
25
  created_at: number;
15
26
  };
16
27
  /**
17
- * Generate access token for user
28
+ * Sign + persist an access token for the user and return the token with its
29
+ * expiry. The expiry is computed locally from `expiresIn` rather than by
30
+ * re-verifying the token we just signed.
18
31
  */
19
32
  generateAccessToken(user: Auth, payload?: any): Promise<AccessTokenOutput>;
20
33
  /**
21
- * Create refresh token for user
34
+ * Sign + persist a refresh token for the user (enforcing the per-user cap
35
+ * first). Resolves to `undefined` when refresh tokens are disabled in config.
22
36
  */
23
37
  createRefreshToken(user: Auth, deviceInfo?: DeviceInfo): Promise<RefreshToken | undefined>;
24
38
  /**
25
- * Create both access and refresh tokens
39
+ * Issue both an access and a refresh token, emitting the creation events.
26
40
  */
27
41
  createTokenPair(user: Auth, deviceInfo?: DeviceInfo): Promise<TokenPair>;
28
42
  /**
29
- * Refresh tokens using a refresh token
43
+ * Exchange a refresh token for a new token pair, with rotation + replay
44
+ * detection. A concurrent reuse of the same token loses the atomic revoke and
45
+ * is treated as a breach — the whole family is revoked and the request fails.
30
46
  */
31
47
  refreshTokens(refreshTokenString: string, deviceInfo?: DeviceInfo): Promise<TokenPair | null>;
32
48
  /**
33
- * Verify password
49
+ * Verify a plaintext password against a stored hash.
34
50
  */
35
51
  verifyPassword(plainPassword: string, hashedPassword: string): Promise<boolean>;
36
52
  /**
37
- * Hash password
53
+ * Hash a plaintext password.
38
54
  */
39
55
  hashPassword(password: string): Promise<string>;
40
56
  /**
41
- * Attempt to login user with given credentials
57
+ * Resolve a user by credentials, verifying the password. Returns `null` on a
58
+ * missing user or a wrong password, emitting `login.failed` either way.
42
59
  */
43
60
  attemptLogin<T extends Auth>(Model: ChildModel<T>, data: any): Promise<T | null>;
44
61
  /**
45
- * Full login flow: validate credentials, create tokens, emit events
46
- * Returns token pair on success, null on failure
62
+ * Full login flow: validate credentials, issue tokens, emit events. Returns
63
+ * the user + token pair on success, `null` on failure.
47
64
  */
48
65
  login<T extends Auth>(Model: ChildModel<T>, credentials: any, deviceInfo?: DeviceInfo): Promise<LoginResult<T> | null>;
49
66
  /**
50
- * Logout user
51
- * @param user - The authenticated user
52
- * @param accessToken - Optional access token string to revoke
53
- * @param refreshToken - Optional refresh token string to revoke
54
- * If refresh token is not provided, behavior is determined by config:
55
- * - "revoke-all" (default): Revoke ALL refresh tokens for security
56
- * - "error": Throw error requiring refresh token
67
+ * Log a user out.
68
+ *
69
+ * @param accessToken - access token string to revoke (optional)
70
+ * @param refreshToken - refresh token string to revoke (optional)
71
+ *
72
+ * When no refresh token is supplied, `config.auth.refreshToken.logoutWithoutToken`
73
+ * decides the behavior: `"revoke-all"` (default, fail-safe) revokes every
74
+ * refresh token; `"error"` requires the caller to pass one.
57
75
  */
58
76
  logout(user: Auth, accessToken?: string, refreshToken?: string): Promise<void>;
59
77
  /**
60
- * Remove specific access token
78
+ * Remove a specific access token belonging to the user.
61
79
  */
62
80
  removeAccessToken(user: Auth, token: string): Promise<void>;
63
81
  /**
64
- * Remove all access tokens for a user
82
+ * Remove every access token belonging to the user.
65
83
  */
66
84
  removeAllAccessTokens(user: Auth): Promise<void>;
67
85
  /**
68
- * Remove specific refresh token
86
+ * Remove a specific refresh token belonging to the user.
69
87
  */
70
88
  removeRefreshToken(user: Auth, token: string): Promise<void>;
71
89
  /**
72
- * Revoke all tokens for a user
90
+ * Revoke every active refresh token for the user and delete their access
91
+ * tokens — "log out of all devices". Revocation is a single bulk update; an
92
+ * event fires per revoked token.
73
93
  */
74
94
  revokeAllTokens(user: Auth): Promise<void>;
75
95
  /**
76
- * Revoke entire token family (for rotation breach detection)
96
+ * Revoke an entire token family rotation breach containment.
77
97
  */
78
98
  revokeTokenFamily(familyId: string): Promise<void>;
79
99
  /**
80
- * Cleanup expired tokens
100
+ * Delete expired tokens (refresh + access). Emits `token.expired` per refresh
101
+ * token and `cleanup.completed` with the refresh count. Drives the
102
+ * `auth.cleanup` CLI command.
81
103
  */
82
104
  cleanupExpiredTokens(): Promise<number>;
83
105
  /**
84
- * Enforce max refresh tokens per user
85
- */
86
- private enforceMaxRefreshTokens;
87
- /**
88
- * Get active sessions for user
106
+ * Active, unexpired sessions for the user, newest first.
89
107
  */
90
108
  getActiveSessions(user: Auth): Promise<RefreshToken[]>;
91
109
  }
@@ -1 +1 @@
1
- {"version":3,"file":"auth.service.d.mts","names":[],"sources":["../../../../../../@warlock.js/auth/src/services/auth.service.ts"],"mappings":";;;;;;cAWM,WAAA;;;AAJiD;EAQ9C,uBAAA,CAAwB,IAAA,EAAM,IAAA;;;;;EAWwB;;;EAAhD,mBAAA,CAAoB,IAAA,EAAM,IAAA,EAAM,OAAA,SAAgB,OAAA,CAAQ,iBAAA;EA0BlE;;;EAHU,kBAAA,CACX,IAAA,EAAM,IAAA,EACN,UAAA,GAAa,UAAA,GACZ,OAAA,CAAQ,YAAA;EA2CwD;;;EAAtD,eAAA,CAAgB,IAAA,EAAM,IAAA,EAAM,UAAA,GAAa,UAAA,GAAa,OAAA,CAAQ,SAAA;EAqFC;;;EA1D/D,aAAA,CACX,kBAAA,UACA,UAAA,GAAa,UAAA,GACZ,OAAA,CAAQ,SAAA;EAqEsC;;;EAdpC,cAAA,CAAe,aAAA,UAAuB,cAAA,WAAyB,OAAA;EAwCxD;;;EAjCP,YAAA,CAAa,QAAA,WAAmB,OAAA;EAoClC;;;EA7BE,YAAA,WAAuB,IAAA,CAAA,CAAM,KAAA,EAAO,UAAA,CAAW,CAAA,GAAI,IAAA,QAAY,OAAA,CAAQ,CAAA;EAkG/C;;;;EAzExB,KAAA,WAAgB,IAAA,CAAA,CAC3B,KAAA,EAAO,UAAA,CAAW,CAAA,GAClB,WAAA,OACA,UAAA,GAAa,UAAA,GACZ,OAAA,CAAQ,WAAA,CAAY,CAAA;EAyFqC;;;;;;;;;EA3D/C,MAAA,CAAO,IAAA,EAAM,IAAA,EAAM,WAAA,WAAsB,YAAA,YAAwB,OAAA;EA9OvE;;;EAqRM,iBAAA,CAAkB,IAAA,EAAM,IAAA,EAAM,KAAA,WAAgB,OAAA;;;;EAU9C,qBAAA,CAAsB,IAAA,EAAM,IAAA,GAAO,OAAA;EApRf;;;EA8RpB,kBAAA,CAAmB,IAAA,EAAM,IAAA,EAAM,KAAA,WAAgB,OAAA;EAvQ/C;;;EAiRA,eAAA,CAAgB,IAAA,EAAM,IAAA,GAAO,OAAA;EA/QxC;;;EAsSW,iBAAA,CAAkB,QAAA,WAAmB,OAAA;EA1Pf;;;EA2QtB,oBAAA,CAAA,GAAwB,OAAA;EA3Q8B;;;EAAA,QA4RrD,uBAAA;EA/PC;;;EAuRF,iBAAA,CAAkB,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,YAAA;AAAA;AAAA,cAazC,WAAA,EAAW,WAAoB"}
1
+ {"version":3,"file":"auth.service.d.mts","names":[],"sources":["../../../../../../../@warlock.js/auth/src/services/auth.service.ts"],"mappings":";;;;;;cAYM,WAAA;;;AALiD;;;;cAYzC,gBAAA;EA2ByD;;;EAAA,YApBzD,iBAAA;EA0CD;;;EAnCJ,uBAAA,CAAwB,IAAA,EAAM,IAAA;;;;;EA0FlC;;;;;EA7EU,mBAAA,CAAoB,IAAA,EAAM,IAAA,EAAM,OAAA,SAAgB,OAAA,CAAQ,iBAAA;EAwJe;;;;EArIvE,kBAAA,CACX,IAAA,EAAM,IAAA,EACN,UAAA,GAAa,UAAA,GACZ,OAAA,CAAQ,YAAA;EA+JI;;;EAvIF,eAAA,CAAgB,IAAA,EAAM,IAAA,EAAM,UAAA,GAAa,UAAA,GAAa,OAAA,CAAQ,SAAA;EAsKjD;;;;;EA1Ib,aAAA,CACX,kBAAA,UACA,UAAA,GAAa,UAAA,GACZ,OAAA,CAAQ,SAAA;EAkL2B;;;EAtHzB,cAAA,CAAe,aAAA,UAAuB,cAAA,WAAyB,OAAA;EA8I1B;;;EAvIrC,YAAA,CAAa,QAAA,WAAmB,OAAA;EAmKD;;;;EA3J/B,YAAA,WAAuB,IAAA,EAAM,KAAA,EAAO,UAAA,CAAW,CAAA,GAAI,IAAA,QAAY,OAAA,CAAQ,CAAA;EArK7E;;;;EA+LM,KAAA,WAAgB,IAAA,EAC3B,KAAA,EAAO,UAAA,CAAW,CAAA,GAClB,WAAA,OACA,UAAA,GAAa,UAAA,GACZ,OAAA,CAAQ,WAAA,CAAY,CAAA;;;;;;;;;;;EA8BV,MAAA,CAAO,IAAA,EAAM,IAAA,EAAM,WAAA,WAAsB,YAAA,YAAwB,OAAA;EA/L5E;;;EA4NW,iBAAA,CAAkB,IAAA,EAAM,IAAA,EAAM,KAAA,WAAgB,OAAA;EAnMxB;;;EA0MtB,qBAAA,CAAsB,IAAA,EAAM,IAAA,GAAO,OAAA;EA1MmB;;;EAiNtD,kBAAA,CAAmB,IAAA,EAAM,IAAA,EAAM,KAAA,WAAgB,OAAA;EAnL7C;;;;;EA4LF,eAAA,CAAgB,IAAA,EAAM,IAAA,GAAO,OAAA;EA/HS;;;EA8ItC,iBAAA,CAAkB,QAAA,WAAmB,OAAA;EAvIL;;;;;EAkJhC,oBAAA,IAAwB,OAAA;EA1IK;;;EA2J7B,iBAAA,CAAkB,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,YAAA;AAAA;AAAA,cAKzC,WAAA,EAAW,WAAoB"}
@@ -2,16 +2,32 @@ import { AccessToken } from "../models/access-token/access-token.model.mjs";
2
2
  import "../models/access-token/index.mjs";
3
3
  import { RefreshToken } from "../models/refresh-token/refresh-token.model.mjs";
4
4
  import "../models/refresh-token/index.mjs";
5
+ import { authConfig } from "./auth-config.mjs";
5
6
  import { authEvents } from "./auth-events.mjs";
6
7
  import { jwt } from "./jwt.mjs";
7
8
  import { config, hashPassword, verifyPassword } from "@warlock.js/core";
8
9
  import { Random } from "@mongez/reinforcements";
9
10
  import ms from "ms";
10
11
 
11
- //#region ../../@warlock.js/auth/src/services/auth.service.ts
12
+ //#region ../@warlock.js/auth/src/services/auth.service.ts
12
13
  var AuthService = class {
13
14
  /**
14
- * Build access token payload from user
15
+ * Resolve the active access-token model the package default, or a subclass
16
+ * an app registered under `config.auth.accessToken.model` (e.g. to add a
17
+ * tenant column). The service never references the concrete class directly so
18
+ * an override is a pure config change.
19
+ */
20
+ get accessTokenModel() {
21
+ return config.key("auth.accessToken.model", AccessToken);
22
+ }
23
+ /**
24
+ * Resolve the active refresh-token model (default or registered override).
25
+ */
26
+ get refreshTokenModel() {
27
+ return config.key("auth.refreshToken.model", RefreshToken);
28
+ }
29
+ /**
30
+ * Build the default access-token JWT payload from a user.
15
31
  */
16
32
  buildAccessTokenPayload(user) {
17
33
  return {
@@ -21,54 +37,46 @@ var AuthService = class {
21
37
  };
22
38
  }
23
39
  /**
24
- * Generate access token for user
40
+ * Sign + persist an access token for the user and return the token with its
41
+ * expiry. The expiry is computed locally from `expiresIn` rather than by
42
+ * re-verifying the token we just signed.
25
43
  */
26
44
  async generateAccessToken(user, payload) {
27
45
  const data = payload || this.buildAccessTokenPayload(user);
28
- const expiresInConfig = config.key("auth.jwt.expiresIn");
29
- const expiresIn = expiresInConfig ? ms(expiresInConfig) : 3600;
46
+ const expiresInConfig = authConfig.accessToken.expiresIn();
47
+ const expiresIn = expiresInConfig ? ms(expiresInConfig) : ms("1h");
30
48
  const token = await jwt.generate(data, { expiresIn });
31
- const decoed = await jwt.verify(token);
32
- await AccessToken.create({
33
- token,
34
- user_id: user.id,
35
- user_type: user.userType
36
- });
49
+ const expiresAt = new Date(Date.now() + expiresIn);
50
+ await this.accessTokenModel.issue(user, token, expiresAt);
37
51
  return {
38
52
  token,
39
- expiresAt: (/* @__PURE__ */ new Date(decoed.exp * 1e3)).toISOString()
53
+ expiresAt: expiresAt.toISOString()
40
54
  };
41
55
  }
42
56
  /**
43
- * Create refresh token for user
57
+ * Sign + persist a refresh token for the user (enforcing the per-user cap
58
+ * first). Resolves to `undefined` when refresh tokens are disabled in config.
44
59
  */
45
60
  async createRefreshToken(user, deviceInfo) {
46
- if (config.get("auth.jwt.refresh.enabled") === false) return;
61
+ if (!authConfig.refreshToken.enabled()) return;
47
62
  const familyId = deviceInfo?.familyId || Random.string(32);
48
63
  const payload = {
49
64
  userId: user.id,
50
65
  userType: user.userType,
51
66
  familyId
52
67
  };
53
- const expiresIn = ms(config.get("auth.jwt.refresh.expiresIn", "7d"));
54
- const token = await jwt.generateRefreshToken(payload, { expiresIn });
68
+ const expiresIn = ms(authConfig.refreshToken.expiresIn());
55
69
  const expiresAt = new Date(Date.now() + expiresIn).toISOString();
56
- await this.enforceMaxRefreshTokens(user);
57
- return RefreshToken.create({
58
- token,
59
- user_id: user.id,
60
- user_type: user.userType,
61
- family_id: familyId,
62
- expires_at: expiresAt,
63
- device_info: deviceInfo ? {
64
- userAgent: deviceInfo.userAgent,
65
- ip: deviceInfo.ip,
66
- deviceId: deviceInfo.deviceId
67
- } : void 0
70
+ await this.refreshTokenModel.enforceMax(user, authConfig.refreshToken.maxPerUser());
71
+ const token = await jwt.generateRefreshToken(payload, { expiresIn });
72
+ return this.refreshTokenModel.issue(user, token, {
73
+ familyId,
74
+ expiresAt,
75
+ deviceInfo
68
76
  });
69
77
  }
70
78
  /**
71
- * Create both access and refresh tokens
79
+ * Issue both an access and a refresh token, emitting the creation events.
72
80
  */
73
81
  async createTokenPair(user, deviceInfo) {
74
82
  const accessToken = await this.generateAccessToken(user, deviceInfo?.payload);
@@ -85,26 +93,32 @@ var AuthService = class {
85
93
  return tokenPair;
86
94
  }
87
95
  /**
88
- * Refresh tokens using a refresh token
96
+ * Exchange a refresh token for a new token pair, with rotation + replay
97
+ * detection. A concurrent reuse of the same token loses the atomic revoke and
98
+ * is treated as a breach — the whole family is revoked and the request fails.
89
99
  */
90
100
  async refreshTokens(refreshTokenString, deviceInfo) {
91
101
  try {
92
102
  const decoded = await jwt.verifyRefreshToken(refreshTokenString);
93
103
  if (!decoded) return null;
94
- const refreshToken = await RefreshToken.first({ token: refreshTokenString });
104
+ const refreshToken = await this.refreshTokenModel.findByToken(refreshTokenString);
95
105
  if (!refreshToken?.isValid) {
96
- if (refreshToken) await this.revokeTokenFamily(refreshToken.get("family_id"));
106
+ if (refreshToken) await this.revokeTokenFamily(refreshToken.familyId);
97
107
  return null;
98
108
  }
99
109
  const UserModel = config.key(`auth.userType.${decoded.userType}`);
100
110
  if (!UserModel) return null;
101
111
  const user = await UserModel.find(decoded.userId);
102
112
  if (!user) return null;
103
- if (config.key("auth.jwt.refresh.rotation", true)) await refreshToken.revoke();
104
- else await refreshToken.markAsUsed();
113
+ if (authConfig.refreshToken.rotation()) {
114
+ if (!await refreshToken.revokeIfActive()) {
115
+ await this.revokeTokenFamily(refreshToken.familyId);
116
+ return null;
117
+ }
118
+ } else await refreshToken.markAsUsed();
105
119
  const newTokenPair = await this.createTokenPair(user, {
106
120
  ...deviceInfo,
107
- familyId: refreshToken.get("family_id")
121
+ familyId: refreshToken.familyId
108
122
  });
109
123
  authEvents.emit("token.refreshed", user, newTokenPair, refreshToken);
110
124
  return newTokenPair;
@@ -113,19 +127,20 @@ var AuthService = class {
113
127
  }
114
128
  }
115
129
  /**
116
- * Verify password
130
+ * Verify a plaintext password against a stored hash.
117
131
  */
118
132
  async verifyPassword(plainPassword, hashedPassword) {
119
133
  return verifyPassword(plainPassword, hashedPassword);
120
134
  }
121
135
  /**
122
- * Hash password
136
+ * Hash a plaintext password.
123
137
  */
124
138
  async hashPassword(password) {
125
139
  return hashPassword(password);
126
140
  }
127
141
  /**
128
- * Attempt to login user with given credentials
142
+ * Resolve a user by credentials, verifying the password. Returns `null` on a
143
+ * missing user or a wrong password, emitting `login.failed` either way.
129
144
  */
130
145
  async attemptLogin(Model, data) {
131
146
  const { password, ...otherData } = data;
@@ -142,13 +157,13 @@ var AuthService = class {
142
157
  return user;
143
158
  }
144
159
  /**
145
- * Full login flow: validate credentials, create tokens, emit events
146
- * Returns token pair on success, null on failure
160
+ * Full login flow: validate credentials, issue tokens, emit events. Returns
161
+ * the user + token pair on success, `null` on failure.
147
162
  */
148
163
  async login(Model, credentials, deviceInfo) {
149
164
  const user = await this.attemptLogin(Model, credentials);
150
165
  if (!user) return null;
151
- if (!config.key("auth.jwt.refresh.enabled", true)) return {
166
+ if (!authConfig.refreshToken.enabled()) return {
152
167
  user,
153
168
  tokens: { accessToken: await this.generateAccessToken(user, deviceInfo?.payload) }
154
169
  };
@@ -160,112 +175,83 @@ var AuthService = class {
160
175
  };
161
176
  }
162
177
  /**
163
- * Logout user
164
- * @param user - The authenticated user
165
- * @param accessToken - Optional access token string to revoke
166
- * @param refreshToken - Optional refresh token string to revoke
167
- * If refresh token is not provided, behavior is determined by config:
168
- * - "revoke-all" (default): Revoke ALL refresh tokens for security
169
- * - "error": Throw error requiring refresh token
178
+ * Log a user out.
179
+ *
180
+ * @param accessToken - access token string to revoke (optional)
181
+ * @param refreshToken - refresh token string to revoke (optional)
182
+ *
183
+ * When no refresh token is supplied, `config.auth.refreshToken.logoutWithoutToken`
184
+ * decides the behavior: `"revoke-all"` (default, fail-safe) revokes every
185
+ * refresh token; `"error"` requires the caller to pass one.
170
186
  */
171
187
  async logout(user, accessToken, refreshToken) {
172
188
  if (accessToken) await this.removeAccessToken(user, accessToken);
173
189
  if (refreshToken) {
174
- const token = await RefreshToken.first({
175
- token: refreshToken,
176
- userId: user.id
177
- });
190
+ const token = await this.refreshTokenModel.findForUser(user, refreshToken);
178
191
  if (token) {
179
192
  await token.revoke();
180
193
  authEvents.emit("session.destroyed", user, token);
181
194
  }
182
195
  } else {
183
- if (config.key("auth.jwt.refresh.logoutWithoutToken", "revoke-all") === "error") throw new Error("Refresh token required for logout");
196
+ if (authConfig.refreshToken.logoutWithoutToken() === "error") throw new Error("Refresh token required for logout");
184
197
  await this.revokeAllTokens(user);
185
198
  authEvents.emit("logout.failsafe", user);
186
199
  }
187
200
  authEvents.emit("logout", user);
188
201
  }
189
202
  /**
190
- * Remove specific access token
203
+ * Remove a specific access token belonging to the user.
191
204
  */
192
205
  async removeAccessToken(user, token) {
193
- AccessToken.delete({
194
- token,
195
- userId: user.id
196
- });
206
+ await this.accessTokenModel.deleteForUser(user, token);
197
207
  }
198
208
  /**
199
- * Remove all access tokens for a user
209
+ * Remove every access token belonging to the user.
200
210
  */
201
211
  async removeAllAccessTokens(user) {
202
- AccessToken.delete({ user_id: user.id });
212
+ await this.accessTokenModel.deleteAllForUser(user);
203
213
  }
204
214
  /**
205
- * Remove specific refresh token
215
+ * Remove a specific refresh token belonging to the user.
206
216
  */
207
217
  async removeRefreshToken(user, token) {
208
- RefreshToken.delete({
209
- token,
210
- userId: user.id
211
- });
218
+ await this.refreshTokenModel.deleteForUser(user, token);
212
219
  }
213
220
  /**
214
- * Revoke all tokens for a user
221
+ * Revoke every active refresh token for the user and delete their access
222
+ * tokens — "log out of all devices". Revocation is a single bulk update; an
223
+ * event fires per revoked token.
215
224
  */
216
225
  async revokeAllTokens(user) {
217
- const refreshTokens = await RefreshToken.query().where("user_id", user.id).where("user_type", user.userType).where("revoked_at", null).get();
218
- for (const token of refreshTokens) {
219
- await token.revoke();
220
- authEvents.emit("token.revoked", user, token);
221
- }
226
+ const revokedTokens = await this.refreshTokenModel.revokeAllFor(user);
227
+ for (const token of revokedTokens) authEvents.emit("token.revoked", user, token);
222
228
  await this.removeAllAccessTokens(user);
223
229
  authEvents.emit("logout.all", user);
224
230
  }
225
231
  /**
226
- * Revoke entire token family (for rotation breach detection)
232
+ * Revoke an entire token family rotation breach containment.
227
233
  */
228
234
  async revokeTokenFamily(familyId) {
229
- const tokens = await RefreshToken.query().where("family_id", familyId).where("revoked_at", null).get();
230
- for (const token of tokens) await token.revoke();
231
- authEvents.emit("token.familyRevoked", familyId, tokens);
235
+ const revokedTokens = await this.refreshTokenModel.revokeFamily(familyId);
236
+ authEvents.emit("token.familyRevoked", familyId, revokedTokens);
232
237
  }
233
238
  /**
234
- * Cleanup expired tokens
239
+ * Delete expired tokens (refresh + access). Emits `token.expired` per refresh
240
+ * token and `cleanup.completed` with the refresh count. Drives the
241
+ * `auth.cleanup` CLI command.
235
242
  */
236
243
  async cleanupExpiredTokens() {
237
- const expiredTokens = await RefreshToken.query().where("expires_at", "<", /* @__PURE__ */ new Date()).get();
238
- for (const token of expiredTokens) {
239
- authEvents.emit("token.expired", token);
240
- await token.destroy();
241
- }
244
+ const expiredTokens = await this.refreshTokenModel.purgeExpired();
245
+ for (const token of expiredTokens) authEvents.emit("token.expired", token);
246
+ await this.accessTokenModel.purgeExpired();
242
247
  authEvents.emit("cleanup.completed", expiredTokens.length);
243
248
  return expiredTokens.length;
244
249
  }
245
250
  /**
246
- * Enforce max refresh tokens per user
247
- */
248
- async enforceMaxRefreshTokens(user) {
249
- const maxPerUser = config.key("auth.jwt.refresh.maxPerUser", 5);
250
- const activeTokens = await RefreshToken.query().where({
251
- user_id: user.id,
252
- user_type: user.userType,
253
- revoked_at: null
254
- }).orderBy("created_at", "asc").get();
255
- if (activeTokens.length >= maxPerUser) {
256
- const tokensToRevoke = activeTokens.slice(0, activeTokens.length - maxPerUser + 1);
257
- for (const token of tokensToRevoke) await token.revoke();
258
- }
259
- }
260
- /**
261
- * Get active sessions for user
251
+ * Active, unexpired sessions for the user, newest first.
262
252
  */
263
253
  async getActiveSessions(user) {
264
- return RefreshToken.query().where({
265
- user_id: user.id,
266
- user_type: user.userType,
267
- revoked_at: null
268
- }).where("expires_at", ">", /* @__PURE__ */ new Date()).orderBy("created_at", "desc").get();
254
+ return this.refreshTokenModel.activeFor(user);
269
255
  }
270
256
  };
271
257
  const authService = new AuthService();
@@ -1 +1 @@
1
- {"version":3,"file":"auth.service.mjs","names":[],"sources":["../../../../../../@warlock.js/auth/src/services/auth.service.ts"],"sourcesContent":["import { Random } from \"@mongez/reinforcements\";\nimport type { ChildModel } from \"@warlock.js/cascade\";\nimport { config, hashPassword, verifyPassword } from \"@warlock.js/core\";\nimport ms from \"ms\";\nimport type { AccessTokenOutput, DeviceInfo, LoginResult, TokenPair } from \"../contracts/types\";\nimport { AccessToken } from \"../models/access-token\";\nimport type { Auth } from \"../models/auth.model\";\nimport { RefreshToken } from \"../models/refresh-token\";\nimport { authEvents } from \"./auth-events\";\nimport { jwt } from \"./jwt\";\n\nclass AuthService {\n /**\n * Build access token payload from user\n */\n public buildAccessTokenPayload(user: Auth) {\n return {\n id: user.id,\n userType: user.userType,\n created_at: Date.now(),\n };\n }\n\n /**\n * Generate access token for user\n */\n public async generateAccessToken(user: Auth, payload?: any): Promise<AccessTokenOutput> {\n const data = payload || this.buildAccessTokenPayload(user);\n const expiresInConfig = config.key(\"auth.jwt.expiresIn\");\n const expiresIn = expiresInConfig ? ms(expiresInConfig) : 3_600; // default 1 hour\n\n // If expiresIn is undefined, token never expires\n const token = await jwt.generate(data, { expiresIn });\n\n const decoed = await jwt.verify(token);\n\n // Store in database\n await AccessToken.create({\n token,\n user_id: user.id,\n user_type: user.userType,\n });\n\n return { token, expiresAt: new Date(decoed.exp * 1_000).toISOString() };\n }\n\n /**\n * Create refresh token for user\n */\n public async createRefreshToken(\n user: Auth,\n deviceInfo?: DeviceInfo,\n ): Promise<RefreshToken | undefined> {\n if (config.get(\"auth.jwt.refresh.enabled\") === false) return;\n\n const familyId = deviceInfo?.familyId || Random.string(32);\n\n const payload = {\n userId: user.id,\n userType: user.userType,\n familyId,\n };\n\n const epireTime = config.get(\"auth.jwt.refresh.expiresIn\", \"7d\");\n\n const expiresIn = ms(epireTime);\n\n const token = await jwt.generateRefreshToken(payload, { expiresIn });\n\n // Calculate expiration date (undefined means never expires, but we still set a far future date)\n const expiresAt = new Date(Date.now() + expiresIn).toISOString();\n\n // Enforce max tokens per user\n await this.enforceMaxRefreshTokens(user);\n\n // Store in database\n return RefreshToken.create({\n token,\n user_id: user.id,\n user_type: user.userType,\n family_id: familyId,\n expires_at: expiresAt,\n device_info: deviceInfo\n ? {\n userAgent: deviceInfo.userAgent,\n ip: deviceInfo.ip,\n deviceId: deviceInfo.deviceId,\n }\n : undefined,\n });\n }\n\n /**\n * Create both access and refresh tokens\n */\n public async createTokenPair(user: Auth, deviceInfo?: DeviceInfo): Promise<TokenPair> {\n const accessToken = await this.generateAccessToken(user, deviceInfo?.payload);\n const refreshToken = await this.createRefreshToken(user, deviceInfo);\n\n const tokenPair: TokenPair = {\n accessToken,\n refreshToken: refreshToken\n ? {\n token: refreshToken.get(\"token\"),\n expiresAt: refreshToken.get(\"expires_at\"),\n }\n : undefined,\n };\n\n // Emit events\n authEvents.emit(\"token.created\", user, tokenPair);\n\n if (refreshToken) {\n authEvents.emit(\"session.created\", user, refreshToken, deviceInfo);\n }\n\n return tokenPair;\n }\n\n /**\n * Refresh tokens using a refresh token\n */\n public async refreshTokens(\n refreshTokenString: string,\n deviceInfo?: DeviceInfo,\n ): Promise<TokenPair | null> {\n try {\n // 1. Verify JWT signature\n const decoded = await jwt.verifyRefreshToken<{\n userId: number;\n userType: string;\n familyId: string;\n }>(refreshTokenString);\n\n if (!decoded) return null;\n\n // 2. Find token in database\n const refreshToken = await RefreshToken.first({ token: refreshTokenString });\n\n if (!refreshToken?.isValid) {\n // If token was already used (rotation detection), revoke entire family\n if (refreshToken) {\n await this.revokeTokenFamily(refreshToken.get(\"family_id\"));\n }\n return null;\n }\n\n // 3. Get user model and find user\n const UserModel = config.key(`auth.userType.${decoded.userType}`);\n if (!UserModel) return null;\n\n const user = (await UserModel.find(decoded.userId)) as Auth | null;\n if (!user) return null;\n\n // 4. Rotate token if enabled (revoke old token)\n const rotationEnabled = config.key(\"auth.jwt.refresh.rotation\", true);\n if (rotationEnabled) {\n await refreshToken.revoke();\n } else {\n await refreshToken.markAsUsed();\n }\n\n // 5. Generate new token pair (keep same family)\n const newTokenPair = await this.createTokenPair(user, {\n ...deviceInfo,\n familyId: refreshToken.get(\"family_id\"),\n });\n\n // Emit token refreshed event\n authEvents.emit(\"token.refreshed\", user, newTokenPair, refreshToken);\n\n return newTokenPair;\n } catch {\n return null;\n }\n }\n\n /**\n * Verify password\n */\n public async verifyPassword(plainPassword: string, hashedPassword: string): Promise<boolean> {\n return verifyPassword(plainPassword, hashedPassword);\n }\n\n /**\n * Hash password\n */\n public async hashPassword(password: string): Promise<string> {\n return hashPassword(password);\n }\n\n /**\n * Attempt to login user with given credentials\n */\n public async attemptLogin<T extends Auth>(Model: ChildModel<T>, data: any): Promise<T | null> {\n const { password, ...otherData } = data;\n\n // Emit login attempt event\n authEvents.emit(\"login.attempt\", otherData);\n\n const user = (await Model.first(otherData)) as T | null;\n\n if (!user) {\n authEvents.emit(\"login.failed\", otherData, \"User not found\");\n return null;\n }\n\n if (!(await this.verifyPassword(password, user.string(\"password\")!))) {\n authEvents.emit(\"login.failed\", otherData, \"Invalid password\");\n return null;\n }\n\n return user;\n }\n\n /**\n * Full login flow: validate credentials, create tokens, emit events\n * Returns token pair on success, null on failure\n */\n public async login<T extends Auth>(\n Model: ChildModel<T>,\n credentials: any,\n deviceInfo?: DeviceInfo,\n ): Promise<LoginResult<T> | null> {\n const user = await this.attemptLogin(Model, credentials);\n\n if (!user) {\n return null;\n }\n\n // if no refresh token in config, then return user and access token only\n if (!config.key(\"auth.jwt.refresh.enabled\", true)) {\n const accessToken = await this.generateAccessToken(user, deviceInfo?.payload);\n return { user, tokens: { accessToken } };\n }\n\n const tokens = await this.createTokenPair(user, deviceInfo);\n\n // Emit login success event\n authEvents.emit(\"login.success\", user, tokens, deviceInfo);\n\n return { user, tokens } as LoginResult<T>;\n }\n\n /**\n * Logout user\n * @param user - The authenticated user\n * @param accessToken - Optional access token string to revoke\n * @param refreshToken - Optional refresh token string to revoke\n * If refresh token is not provided, behavior is determined by config:\n * - \"revoke-all\" (default): Revoke ALL refresh tokens for security\n * - \"error\": Throw error requiring refresh token\n */\n public async logout(user: Auth, accessToken?: string, refreshToken?: string): Promise<void> {\n // Remove access token if provided\n if (accessToken) {\n await this.removeAccessToken(user, accessToken);\n }\n\n if (refreshToken) {\n // Revoke specific refresh token\n const token = await RefreshToken.first({\n token: refreshToken,\n userId: user.id, // Security: ensure token belongs to this user\n });\n\n if (token) {\n await token.revoke();\n authEvents.emit(\"session.destroyed\", user, token);\n }\n } else {\n // No refresh token provided - check configured behavior\n const behavior = config.key(\"auth.jwt.refresh.logoutWithoutToken\", \"revoke-all\") as\n | \"revoke-all\"\n | \"error\";\n\n if (behavior === \"error\") {\n throw new Error(\"Refresh token required for logout\");\n }\n\n // Default: revoke-all (fail-safe)\n await this.revokeAllTokens(user);\n authEvents.emit(\"logout.failsafe\", user);\n }\n\n // Emit logout event\n authEvents.emit(\"logout\", user);\n }\n\n /**\n * Remove specific access token\n */\n public async removeAccessToken(user: Auth, token: string): Promise<void> {\n AccessToken.delete({\n token,\n userId: user.id,\n });\n }\n\n /**\n * Remove all access tokens for a user\n */\n public async removeAllAccessTokens(user: Auth): Promise<void> {\n // Delete access token\n AccessToken.delete({\n user_id: user.id,\n });\n }\n\n /**\n * Remove specific refresh token\n */\n public async removeRefreshToken(user: Auth, token: string): Promise<void> {\n RefreshToken.delete({\n token,\n userId: user.id,\n });\n }\n\n /**\n * Revoke all tokens for a user\n */\n public async revokeAllTokens(user: Auth): Promise<void> {\n // Revoke all refresh tokens\n const refreshTokens = await RefreshToken.query()\n .where(\"user_id\", user.id)\n .where(\"user_type\", user.userType)\n .where(\"revoked_at\", null)\n .get();\n\n for (const token of refreshTokens) {\n await token.revoke();\n authEvents.emit(\"token.revoked\", user, token);\n }\n\n // Delete all access tokens\n await this.removeAllAccessTokens(user);\n\n // Emit logout all event\n authEvents.emit(\"logout.all\", user);\n }\n\n /**\n * Revoke entire token family (for rotation breach detection)\n */\n public async revokeTokenFamily(familyId: string): Promise<void> {\n const tokens = await RefreshToken.query()\n .where(\"family_id\", familyId)\n .where(\"revoked_at\", null)\n .get();\n\n for (const token of tokens) {\n await token.revoke();\n }\n\n // Emit family revoked event\n authEvents.emit(\"token.familyRevoked\", familyId, tokens);\n }\n\n /**\n * Cleanup expired tokens\n */\n public async cleanupExpiredTokens(): Promise<number> {\n const expiredTokens = await RefreshToken.query().where(\"expires_at\", \"<\", new Date()).get();\n\n for (const token of expiredTokens) {\n authEvents.emit(\"token.expired\", token);\n await token.destroy();\n }\n\n // Emit cleanup completed event\n authEvents.emit(\"cleanup.completed\", expiredTokens.length);\n\n return expiredTokens.length;\n }\n\n /**\n * Enforce max refresh tokens per user\n */\n private async enforceMaxRefreshTokens(user: Auth): Promise<void> {\n const maxPerUser = config.key(\"auth.jwt.refresh.maxPerUser\", 5);\n\n const activeTokens = await RefreshToken.query()\n .where({\n user_id: user.id,\n user_type: user.userType,\n revoked_at: null,\n })\n .orderBy(\"created_at\", \"asc\")\n .get();\n\n // Revoke oldest tokens if exceeding limit\n if (activeTokens.length >= maxPerUser) {\n const tokensToRevoke = activeTokens.slice(0, activeTokens.length - maxPerUser + 1);\n for (const token of tokensToRevoke) {\n await token.revoke();\n }\n }\n }\n\n /**\n * Get active sessions for user\n */\n public async getActiveSessions(user: Auth): Promise<RefreshToken[]> {\n return RefreshToken.query()\n .where({\n user_id: user.id,\n user_type: user.userType,\n revoked_at: null,\n })\n .where(\"expires_at\", \">\", new Date())\n .orderBy(\"created_at\", \"desc\")\n .get();\n }\n}\n\nexport const authService = new AuthService();\n"],"mappings":"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"}
1
+ {"version":3,"file":"auth.service.mjs","names":[],"sources":["../../../../../../../@warlock.js/auth/src/services/auth.service.ts"],"sourcesContent":["import { Random } from \"@mongez/reinforcements\";\nimport type { ChildModel } from \"@warlock.js/cascade\";\nimport { config, hashPassword, verifyPassword } from \"@warlock.js/core\";\nimport ms from \"ms\";\nimport type { AccessTokenOutput, DeviceInfo, LoginResult, TokenPair } from \"../contracts/types\";\nimport { AccessToken } from \"../models/access-token\";\nimport type { Auth } from \"../models/auth.model\";\nimport { RefreshToken } from \"../models/refresh-token\";\nimport { authConfig } from \"./auth-config\";\nimport { authEvents } from \"./auth-events\";\nimport { jwt } from \"./jwt\";\n\nclass AuthService {\n /**\n * Resolve the active access-token model — the package default, or a subclass\n * an app registered under `config.auth.accessToken.model` (e.g. to add a\n * tenant column). The service never references the concrete class directly so\n * an override is a pure config change.\n */\n private get accessTokenModel(): typeof AccessToken {\n return config.key(\"auth.accessToken.model\", AccessToken);\n }\n\n /**\n * Resolve the active refresh-token model (default or registered override).\n */\n private get refreshTokenModel(): typeof RefreshToken {\n return config.key(\"auth.refreshToken.model\", RefreshToken);\n }\n\n /**\n * Build the default access-token JWT payload from a user.\n */\n public buildAccessTokenPayload(user: Auth) {\n return {\n id: user.id,\n userType: user.userType,\n created_at: Date.now(),\n };\n }\n\n /**\n * Sign + persist an access token for the user and return the token with its\n * expiry. The expiry is computed locally from `expiresIn` rather than by\n * re-verifying the token we just signed.\n */\n public async generateAccessToken(user: Auth, payload?: any): Promise<AccessTokenOutput> {\n const data = payload || this.buildAccessTokenPayload(user);\n const expiresInConfig = authConfig.accessToken.expiresIn();\n const expiresIn = expiresInConfig\n ? ms(expiresInConfig as ms.StringValue)\n : ms(\"1h\"); // default 1 hour\n\n const token = await jwt.generate(data, { expiresIn });\n const expiresAt = new Date(Date.now() + expiresIn);\n\n await this.accessTokenModel.issue(user, token, expiresAt);\n\n return { token, expiresAt: expiresAt.toISOString() };\n }\n\n /**\n * Sign + persist a refresh token for the user (enforcing the per-user cap\n * first). Resolves to `undefined` when refresh tokens are disabled in config.\n */\n public async createRefreshToken(\n user: Auth,\n deviceInfo?: DeviceInfo,\n ): Promise<RefreshToken | undefined> {\n if (!authConfig.refreshToken.enabled()) return;\n\n const familyId = deviceInfo?.familyId || Random.string(32);\n\n const payload = {\n userId: user.id,\n userType: user.userType,\n familyId,\n };\n\n const expiresIn = ms(authConfig.refreshToken.expiresIn() as ms.StringValue);\n const expiresAt = new Date(Date.now() + expiresIn).toISOString();\n\n await this.refreshTokenModel.enforceMax(user, authConfig.refreshToken.maxPerUser());\n\n const token = await jwt.generateRefreshToken(payload, { expiresIn });\n\n return this.refreshTokenModel.issue(user, token, { familyId, expiresAt, deviceInfo });\n }\n\n /**\n * Issue both an access and a refresh token, emitting the creation events.\n */\n public async createTokenPair(user: Auth, deviceInfo?: DeviceInfo): Promise<TokenPair> {\n const accessToken = await this.generateAccessToken(user, deviceInfo?.payload);\n const refreshToken = await this.createRefreshToken(user, deviceInfo);\n\n const tokenPair: TokenPair = {\n accessToken,\n refreshToken: refreshToken\n ? {\n token: refreshToken.get(\"token\"),\n expiresAt: refreshToken.get(\"expires_at\"),\n }\n : undefined,\n };\n\n authEvents.emit(\"token.created\", user, tokenPair);\n\n if (refreshToken) {\n authEvents.emit(\"session.created\", user, refreshToken, deviceInfo);\n }\n\n return tokenPair;\n }\n\n /**\n * Exchange a refresh token for a new token pair, with rotation + replay\n * detection. A concurrent reuse of the same token loses the atomic revoke and\n * is treated as a breach — the whole family is revoked and the request fails.\n */\n public async refreshTokens(\n refreshTokenString: string,\n deviceInfo?: DeviceInfo,\n ): Promise<TokenPair | null> {\n try {\n const decoded = await jwt.verifyRefreshToken<{\n userId: number;\n userType: string;\n familyId: string;\n }>(refreshTokenString);\n\n if (!decoded) return null;\n\n const refreshToken = await this.refreshTokenModel.findByToken(refreshTokenString);\n\n if (!refreshToken?.isValid) {\n // Already-invalid token presented → likely a replayed (rotated) token.\n if (refreshToken) {\n await this.revokeTokenFamily(refreshToken.familyId);\n }\n\n return null;\n }\n\n const UserModel = config.key(`auth.userType.${decoded.userType}`);\n\n if (!UserModel) return null;\n\n const user = (await UserModel.find(decoded.userId)) as Auth | null;\n\n if (!user) return null;\n\n const rotationEnabled = authConfig.refreshToken.rotation();\n\n if (rotationEnabled) {\n const won = await refreshToken.revokeIfActive();\n\n if (!won) {\n // A concurrent request already rotated this token (reuse / replay).\n await this.revokeTokenFamily(refreshToken.familyId);\n\n return null;\n }\n } else {\n await refreshToken.markAsUsed();\n }\n\n const newTokenPair = await this.createTokenPair(user, {\n ...deviceInfo,\n familyId: refreshToken.familyId,\n });\n\n authEvents.emit(\"token.refreshed\", user, newTokenPair, refreshToken);\n\n return newTokenPair;\n } catch {\n return null;\n }\n }\n\n /**\n * Verify a plaintext password against a stored hash.\n */\n public async verifyPassword(plainPassword: string, hashedPassword: string): Promise<boolean> {\n return verifyPassword(plainPassword, hashedPassword);\n }\n\n /**\n * Hash a plaintext password.\n */\n public async hashPassword(password: string): Promise<string> {\n return hashPassword(password);\n }\n\n /**\n * Resolve a user by credentials, verifying the password. Returns `null` on a\n * missing user or a wrong password, emitting `login.failed` either way.\n */\n public async attemptLogin<T extends Auth>(Model: ChildModel<T>, data: any): Promise<T | null> {\n const { password, ...otherData } = data;\n\n authEvents.emit(\"login.attempt\", otherData);\n\n const user = (await Model.first(otherData)) as T | null;\n\n if (!user) {\n authEvents.emit(\"login.failed\", otherData, \"User not found\");\n\n return null;\n }\n\n if (!(await this.verifyPassword(password, user.string(\"password\")!))) {\n authEvents.emit(\"login.failed\", otherData, \"Invalid password\");\n\n return null;\n }\n\n return user;\n }\n\n /**\n * Full login flow: validate credentials, issue tokens, emit events. Returns\n * the user + token pair on success, `null` on failure.\n */\n public async login<T extends Auth>(\n Model: ChildModel<T>,\n credentials: any,\n deviceInfo?: DeviceInfo,\n ): Promise<LoginResult<T> | null> {\n const user = await this.attemptLogin(Model, credentials);\n\n if (!user) {\n return null;\n }\n\n if (!authConfig.refreshToken.enabled()) {\n const accessToken = await this.generateAccessToken(user, deviceInfo?.payload);\n\n return { user, tokens: { accessToken } };\n }\n\n const tokens = await this.createTokenPair(user, deviceInfo);\n\n authEvents.emit(\"login.success\", user, tokens, deviceInfo);\n\n return { user, tokens } as LoginResult<T>;\n }\n\n /**\n * Log a user out.\n *\n * @param accessToken - access token string to revoke (optional)\n * @param refreshToken - refresh token string to revoke (optional)\n *\n * When no refresh token is supplied, `config.auth.refreshToken.logoutWithoutToken`\n * decides the behavior: `\"revoke-all\"` (default, fail-safe) revokes every\n * refresh token; `\"error\"` requires the caller to pass one.\n */\n public async logout(user: Auth, accessToken?: string, refreshToken?: string): Promise<void> {\n if (accessToken) {\n await this.removeAccessToken(user, accessToken);\n }\n\n if (refreshToken) {\n const token = await this.refreshTokenModel.findForUser(user, refreshToken);\n\n if (token) {\n await token.revoke();\n authEvents.emit(\"session.destroyed\", user, token);\n }\n } else {\n const behavior = authConfig.refreshToken.logoutWithoutToken();\n\n if (behavior === \"error\") {\n throw new Error(\"Refresh token required for logout\");\n }\n\n await this.revokeAllTokens(user);\n authEvents.emit(\"logout.failsafe\", user);\n }\n\n authEvents.emit(\"logout\", user);\n }\n\n /**\n * Remove a specific access token belonging to the user.\n */\n public async removeAccessToken(user: Auth, token: string): Promise<void> {\n await this.accessTokenModel.deleteForUser(user, token);\n }\n\n /**\n * Remove every access token belonging to the user.\n */\n public async removeAllAccessTokens(user: Auth): Promise<void> {\n await this.accessTokenModel.deleteAllForUser(user);\n }\n\n /**\n * Remove a specific refresh token belonging to the user.\n */\n public async removeRefreshToken(user: Auth, token: string): Promise<void> {\n await this.refreshTokenModel.deleteForUser(user, token);\n }\n\n /**\n * Revoke every active refresh token for the user and delete their access\n * tokens — \"log out of all devices\". Revocation is a single bulk update; an\n * event fires per revoked token.\n */\n public async revokeAllTokens(user: Auth): Promise<void> {\n const revokedTokens = await this.refreshTokenModel.revokeAllFor(user);\n\n for (const token of revokedTokens) {\n authEvents.emit(\"token.revoked\", user, token);\n }\n\n await this.removeAllAccessTokens(user);\n\n authEvents.emit(\"logout.all\", user);\n }\n\n /**\n * Revoke an entire token family — rotation breach containment.\n */\n public async revokeTokenFamily(familyId: string): Promise<void> {\n const revokedTokens = await this.refreshTokenModel.revokeFamily(familyId);\n\n authEvents.emit(\"token.familyRevoked\", familyId, revokedTokens);\n }\n\n /**\n * Delete expired tokens (refresh + access). Emits `token.expired` per refresh\n * token and `cleanup.completed` with the refresh count. Drives the\n * `auth.cleanup` CLI command.\n */\n public async cleanupExpiredTokens(): Promise<number> {\n const expiredTokens = await this.refreshTokenModel.purgeExpired();\n\n for (const token of expiredTokens) {\n authEvents.emit(\"token.expired\", token);\n }\n\n await this.accessTokenModel.purgeExpired();\n\n authEvents.emit(\"cleanup.completed\", expiredTokens.length);\n\n return expiredTokens.length;\n }\n\n /**\n * Active, unexpired sessions for the user, newest first.\n */\n public async getActiveSessions(user: Auth): Promise<RefreshToken[]> {\n return this.refreshTokenModel.activeFor(user);\n }\n}\n\nexport const authService = new AuthService();\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- //#region ../../@warlock.js/auth/src/services/generate-jwt-secret.d.ts
1
+ //#region ../@warlock.js/auth/src/services/generate-jwt-secret.d.ts
2
2
  declare function generateJWTSecret(): Promise<void>;
3
3
  //#endregion
4
4
  export { generateJWTSecret };
@@ -1 +1 @@
1
- {"version":3,"file":"generate-jwt-secret.d.mts","names":[],"sources":["../../../../../../@warlock.js/auth/src/services/generate-jwt-secret.ts"],"mappings":";iBAKsB,iBAAA,CAAA,GAAiB,OAAA"}
1
+ {"version":3,"file":"generate-jwt-secret.d.mts","names":[],"sources":["../../../../../../../@warlock.js/auth/src/services/generate-jwt-secret.ts"],"mappings":";iBAKsB,iBAAA,IAAiB,OAAA"}
@@ -1,9 +1,9 @@
1
1
  import { environment, rootPath } from "@warlock.js/core";
2
2
  import { Random } from "@mongez/reinforcements";
3
- import { fileExistsAsync, getFileAsync, putFileAsync } from "@warlock.js/fs";
4
3
  import { log } from "@warlock.js/logger";
4
+ import { fileExistsAsync, getFileAsync, putFileAsync } from "@warlock.js/fs";
5
5
 
6
- //#region ../../@warlock.js/auth/src/services/generate-jwt-secret.ts
6
+ //#region ../@warlock.js/auth/src/services/generate-jwt-secret.ts
7
7
  async function generateJWTSecret() {
8
8
  let envFile = rootPath(".env");
9
9
  log.info("jwt", "generating", "Generating JWT secrets");
@@ -22,7 +22,7 @@ async function generateJWTSecret() {
22
22
  }
23
23
  let secretsToAdd = "";
24
24
  if (!hasJwtSecret) {
25
- const jwtSecret = Random.string(32);
25
+ const jwtSecret = Random.token(32);
26
26
  secretsToAdd += `
27
27
  # JWT Secret
28
28
  JWT_SECRET=${jwtSecret}
@@ -30,7 +30,7 @@ JWT_SECRET=${jwtSecret}
30
30
  log.success("jwt", "generated", "JWT_SECRET generated and added to the .env file.");
31
31
  } else log.info("jwt", "exists", "JWT_SECRET already exists in the .env file.");
32
32
  if (!hasJwtRefreshSecret) {
33
- const jwtRefreshSecret = Random.string(32);
33
+ const jwtRefreshSecret = Random.token(32);
34
34
  secretsToAdd += `
35
35
  # JWT Refresh Secret
36
36
  JWT_REFRESH_SECRET=${jwtRefreshSecret}
@@ -1 +1 @@
1
- {"version":3,"file":"generate-jwt-secret.mjs","names":[],"sources":["../../../../../../@warlock.js/auth/src/services/generate-jwt-secret.ts"],"sourcesContent":["import { fileExistsAsync, getFileAsync, putFileAsync } from \"@warlock.js/fs\";\r\nimport { Random } from \"@mongez/reinforcements\";\r\nimport { environment, rootPath } from \"@warlock.js/core\";\r\nimport { log } from \"@warlock.js/logger\";\r\n\r\nexport async function generateJWTSecret() {\r\n let envFile = rootPath(\".env\");\r\n\r\n log.info(\"jwt\", \"generating\", \"Generating JWT secrets\");\r\n\r\n const environmentMode = environment();\r\n\r\n if (!(await fileExistsAsync(envFile))) {\r\n const envFileType = environmentMode === \"production\" ? \".env.production\" : \".env.development\";\r\n envFile = rootPath(envFileType);\r\n }\r\n\r\n if (!(await fileExistsAsync(envFile))) {\r\n log.error(\"jwt\", \"error\", \".env file not found\");\r\n return;\r\n }\r\n\r\n let contents = await getFileAsync(envFile);\r\n\r\n const hasJwtSecret = contents.includes(\"JWT_SECRET\");\r\n const hasJwtRefreshSecret = contents.includes(\"JWT_REFRESH_SECRET\");\r\n\r\n if (hasJwtSecret && hasJwtRefreshSecret) {\r\n log.warn(\"jwt\", \"exists\", \"JWT secrets already exist in the .env file.\");\r\n return;\r\n }\r\n\r\n let secretsToAdd = \"\";\r\n\r\n if (!hasJwtSecret) {\r\n const jwtSecret = Random.string(32);\r\n secretsToAdd += `\r\n# JWT Secret\r\nJWT_SECRET=${jwtSecret}\r\n`;\r\n log.success(\"jwt\", \"generated\", \"JWT_SECRET generated and added to the .env file.\");\r\n } else {\r\n log.info(\"jwt\", \"exists\", \"JWT_SECRET already exists in the .env file.\");\r\n }\r\n\r\n if (!hasJwtRefreshSecret) {\r\n const jwtRefreshSecret = Random.string(32);\r\n secretsToAdd += `\r\n# JWT Refresh Secret\r\nJWT_REFRESH_SECRET=${jwtRefreshSecret}\r\n`;\r\n log.success(\"jwt\", \"generated\", \"JWT_REFRESH_SECRET generated and added to the .env file.\");\r\n } else {\r\n log.info(\"jwt\", \"exists\", \"JWT_REFRESH_SECRET already exists in the .env file.\");\r\n }\r\n\r\n if (secretsToAdd) {\r\n contents += secretsToAdd;\r\n await putFileAsync(envFile, contents);\r\n }\r\n}\r\n"],"mappings":";;;;;;AAKA,eAAsB,oBAAoB;CACxC,IAAI,UAAU,SAAS,MAAM;CAE7B,IAAI,KAAK,OAAO,cAAc,wBAAwB;CAEtD,MAAM,kBAAkB,YAAY;CAEpC,IAAI,CAAE,MAAM,gBAAgB,OAAO,GAEjC,UAAU,SADU,oBAAoB,eAAe,oBAAoB,kBAC7C;CAGhC,IAAI,CAAE,MAAM,gBAAgB,OAAO,GAAI;EACrC,IAAI,MAAM,OAAO,SAAS,qBAAqB;EAC/C;CACF;CAEA,IAAI,WAAW,MAAM,aAAa,OAAO;CAEzC,MAAM,eAAe,SAAS,SAAS,YAAY;CACnD,MAAM,sBAAsB,SAAS,SAAS,oBAAoB;CAElE,IAAI,gBAAgB,qBAAqB;EACvC,IAAI,KAAK,OAAO,UAAU,6CAA6C;EACvE;CACF;CAEA,IAAI,eAAe;CAEnB,IAAI,CAAC,cAAc;EACjB,MAAM,YAAY,OAAO,OAAO,EAAE;EAClC,gBAAgB;;aAEP,UAAU;;EAEnB,IAAI,QAAQ,OAAO,aAAa,kDAAkD;CACpF,OACE,IAAI,KAAK,OAAO,UAAU,6CAA6C;CAGzE,IAAI,CAAC,qBAAqB;EACxB,MAAM,mBAAmB,OAAO,OAAO,EAAE;EACzC,gBAAgB;;qBAEC,iBAAiB;;EAElC,IAAI,QAAQ,OAAO,aAAa,0DAA0D;CAC5F,OACE,IAAI,KAAK,OAAO,UAAU,qDAAqD;CAGjF,IAAI,cAAc;EAChB,YAAY;EACZ,MAAM,aAAa,SAAS,QAAQ;CACtC;AACF"}
1
+ {"version":3,"file":"generate-jwt-secret.mjs","names":[],"sources":["../../../../../../../@warlock.js/auth/src/services/generate-jwt-secret.ts"],"sourcesContent":["import { fileExistsAsync, getFileAsync, putFileAsync } from \"@warlock.js/fs\";\r\nimport { Random } from \"@mongez/reinforcements\";\r\nimport { environment, rootPath } from \"@warlock.js/core\";\r\nimport { log } from \"@warlock.js/logger\";\r\n\r\nexport async function generateJWTSecret() {\r\n let envFile = rootPath(\".env\");\r\n\r\n log.info(\"jwt\", \"generating\", \"Generating JWT secrets\");\r\n\r\n const environmentMode = environment();\r\n\r\n if (!(await fileExistsAsync(envFile))) {\r\n const envFileType = environmentMode === \"production\" ? \".env.production\" : \".env.development\";\r\n envFile = rootPath(envFileType);\r\n }\r\n\r\n if (!(await fileExistsAsync(envFile))) {\r\n log.error(\"jwt\", \"error\", \".env file not found\");\r\n return;\r\n }\r\n\r\n let contents = await getFileAsync(envFile);\r\n\r\n const hasJwtSecret = contents.includes(\"JWT_SECRET\");\r\n const hasJwtRefreshSecret = contents.includes(\"JWT_REFRESH_SECRET\");\r\n\r\n if (hasJwtSecret && hasJwtRefreshSecret) {\r\n log.warn(\"jwt\", \"exists\", \"JWT secrets already exist in the .env file.\");\r\n return;\r\n }\r\n\r\n let secretsToAdd = \"\";\r\n\r\n if (!hasJwtSecret) {\r\n const jwtSecret = Random.token(32);\r\n secretsToAdd += `\r\n# JWT Secret\r\nJWT_SECRET=${jwtSecret}\r\n`;\r\n log.success(\"jwt\", \"generated\", \"JWT_SECRET generated and added to the .env file.\");\r\n } else {\r\n log.info(\"jwt\", \"exists\", \"JWT_SECRET already exists in the .env file.\");\r\n }\r\n\r\n if (!hasJwtRefreshSecret) {\r\n const jwtRefreshSecret = Random.token(32);\r\n secretsToAdd += `\r\n# JWT Refresh Secret\r\nJWT_REFRESH_SECRET=${jwtRefreshSecret}\r\n`;\r\n log.success(\"jwt\", \"generated\", \"JWT_REFRESH_SECRET generated and added to the .env file.\");\r\n } else {\r\n log.info(\"jwt\", \"exists\", \"JWT_REFRESH_SECRET already exists in the .env file.\");\r\n }\r\n\r\n if (secretsToAdd) {\r\n contents += secretsToAdd;\r\n await putFileAsync(envFile, contents);\r\n }\r\n}\r\n"],"mappings":";;;;;;AAKA,eAAsB,oBAAoB;CACxC,IAAI,UAAU,SAAS,MAAM;CAE7B,IAAI,KAAK,OAAO,cAAc,wBAAwB;CAEtD,MAAM,kBAAkB,YAAY;CAEpC,IAAI,CAAE,MAAM,gBAAgB,OAAO,GAEjC,UAAU,SADU,oBAAoB,eAAe,oBAAoB,kBAC7C;CAGhC,IAAI,CAAE,MAAM,gBAAgB,OAAO,GAAI;EACrC,IAAI,MAAM,OAAO,SAAS,qBAAqB;EAC/C;CACF;CAEA,IAAI,WAAW,MAAM,aAAa,OAAO;CAEzC,MAAM,eAAe,SAAS,SAAS,YAAY;CACnD,MAAM,sBAAsB,SAAS,SAAS,oBAAoB;CAElE,IAAI,gBAAgB,qBAAqB;EACvC,IAAI,KAAK,OAAO,UAAU,6CAA6C;EACvE;CACF;CAEA,IAAI,eAAe;CAEnB,IAAI,CAAC,cAAc;EACjB,MAAM,YAAY,OAAO,MAAM,EAAE;EACjC,gBAAgB;;aAEP,UAAU;;EAEnB,IAAI,QAAQ,OAAO,aAAa,kDAAkD;CACpF,OACE,IAAI,KAAK,OAAO,UAAU,6CAA6C;CAGzE,IAAI,CAAC,qBAAqB;EACxB,MAAM,mBAAmB,OAAO,MAAM,EAAE;EACxC,gBAAgB;;qBAEC,iBAAiB;;EAElC,IAAI,QAAQ,OAAO,aAAa,0DAA0D;CAC5F,OACE,IAAI,KAAK,OAAO,UAAU,qDAAqD;CAGjF,IAAI,cAAc;EAChB,YAAY;EACZ,MAAM,aAAa,SAAS,QAAQ;CACtC;AACF"}
@@ -1,4 +1,4 @@
1
1
  import { AuthEventCallback, AuthEventName, AuthEventPayloads, authEvents } from "./auth-events.mjs";
2
2
  import { authService } from "./auth.service.mjs";
3
3
  import { generateJWTSecret } from "./generate-jwt-secret.mjs";
4
- import { jwt } from "./jwt.mjs";
4
+ import { TokenType, jwt } from "./jwt.mjs";