@soapjs/soap-auth 0.3.1 → 0.3.3

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 (121) hide show
  1. package/build/__tests__/soap-auth.test.d.ts +1 -0
  2. package/build/__tests__/soap-auth.test.js +42 -0
  3. package/build/errors.d.ts +14 -3
  4. package/build/errors.js +29 -8
  5. package/build/index.d.ts +1 -1
  6. package/build/index.js +1 -1
  7. package/build/services/__tests__/account-lock.service.test.d.ts +1 -0
  8. package/build/services/__tests__/account-lock.service.test.js +55 -0
  9. package/build/services/__tests__/auth-throttle.service.test.d.ts +1 -0
  10. package/build/services/__tests__/auth-throttle.service.test.js +48 -0
  11. package/build/services/__tests__/jwks.service.test.d.ts +1 -0
  12. package/build/services/__tests__/jwks.service.test.js +39 -0
  13. package/build/services/__tests__/mfa.service.test.d.ts +1 -0
  14. package/build/services/__tests__/mfa.service.test.js +66 -0
  15. package/build/services/__tests__/password.service.test.d.ts +1 -0
  16. package/build/services/__tests__/password.service.test.js +66 -0
  17. package/build/services/__tests__/pkce.service.test.d.ts +1 -0
  18. package/build/services/__tests__/pkce.service.test.js +77 -0
  19. package/build/services/__tests__/rate-limit.service.test.d.ts +1 -0
  20. package/build/services/__tests__/rate-limit.service.test.js +37 -0
  21. package/build/services/__tests__/role.service.test.d.ts +1 -0
  22. package/build/services/__tests__/role.service.test.js +31 -0
  23. package/build/services/account-lock.service.d.ts +12 -0
  24. package/build/services/account-lock.service.js +39 -0
  25. package/build/services/auth-throttle.service.d.ts +10 -0
  26. package/build/services/auth-throttle.service.js +43 -0
  27. package/build/services/index.d.ts +8 -0
  28. package/build/{factories → services}/index.js +8 -3
  29. package/build/services/jwks.service.d.ts +7 -0
  30. package/build/services/jwks.service.js +41 -0
  31. package/build/services/mfa.service.d.ts +12 -0
  32. package/build/services/mfa.service.js +74 -0
  33. package/build/services/password.service.d.ts +14 -0
  34. package/build/services/password.service.js +78 -0
  35. package/build/services/pkce.service.d.ts +14 -0
  36. package/build/services/pkce.service.js +81 -0
  37. package/build/services/rate-limit.service.d.ts +9 -0
  38. package/build/services/rate-limit.service.js +26 -0
  39. package/build/services/role.service.d.ts +9 -0
  40. package/build/services/role.service.js +26 -0
  41. package/build/session/__tests__/file.session-store.test.d.ts +1 -0
  42. package/build/session/__tests__/file.session-store.test.js +117 -0
  43. package/build/session/__tests__/memory.session-store.test.d.ts +1 -0
  44. package/build/session/__tests__/memory.session-store.test.js +77 -0
  45. package/build/session/__tests__/session-handler.test.d.ts +1 -0
  46. package/build/session/__tests__/session-handler.test.js +337 -0
  47. package/build/session/file.session-store.d.ts +1 -0
  48. package/build/session/file.session-store.js +7 -0
  49. package/build/session/memory.session-store.d.ts +4 -1
  50. package/build/session/memory.session-store.js +11 -5
  51. package/build/session/session-handler.d.ts +12 -7
  52. package/build/session/session-handler.js +46 -13
  53. package/build/session/session.errors.d.ts +6 -0
  54. package/build/session/session.errors.js +15 -0
  55. package/build/soap-auth.d.ts +9 -8
  56. package/build/soap-auth.js +42 -29
  57. package/build/strategies/__tests__/base-auth.strategy.test.d.ts +14 -0
  58. package/build/strategies/__tests__/base-auth.strategy.test.js +137 -0
  59. package/build/strategies/__tests__/credential-auth.strategy.test.d.ts +14 -0
  60. package/build/strategies/__tests__/credential-auth.strategy.test.js +265 -0
  61. package/build/strategies/__tests__/token-auth.strategy.test.d.ts +28 -0
  62. package/build/strategies/__tests__/token-auth.strategy.test.js +298 -0
  63. package/build/strategies/api-key/__tests__/api-key.strategy.test.d.ts +1 -0
  64. package/build/strategies/api-key/__tests__/api-key.strategy.test.js +103 -0
  65. package/build/strategies/api-key/api-key.strategy.d.ts +5 -2
  66. package/build/strategies/api-key/api-key.strategy.js +43 -35
  67. package/build/strategies/api-key/api-key.tools.d.ts +2 -0
  68. package/build/strategies/api-key/api-key.tools.js +39 -0
  69. package/build/strategies/api-key/api-key.types.d.ts +10 -2
  70. package/build/strategies/base-auth.strategy.d.ts +11 -5
  71. package/build/strategies/base-auth.strategy.js +45 -52
  72. package/build/strategies/basic/__tests__/basic.strategy.test.d.ts +1 -0
  73. package/build/strategies/basic/__tests__/basic.strategy.test.js +104 -0
  74. package/build/strategies/basic/basic.strategy.d.ts +5 -7
  75. package/build/strategies/basic/basic.strategy.js +6 -6
  76. package/build/strategies/basic/basic.tools.d.ts +2 -0
  77. package/build/strategies/basic/basic.tools.js +44 -0
  78. package/build/strategies/credential-auth.strategy.d.ts +7 -17
  79. package/build/strategies/credential-auth.strategy.js +116 -181
  80. package/build/strategies/jwt/__tests__/jwt.strategy.test.d.ts +1 -0
  81. package/build/strategies/jwt/__tests__/jwt.strategy.test.js +156 -0
  82. package/build/strategies/jwt/__tests__/jwt.tools.test.d.ts +1 -0
  83. package/build/strategies/jwt/__tests__/jwt.tools.test.js +98 -0
  84. package/build/strategies/jwt/jwt.strategy.d.ts +13 -14
  85. package/build/strategies/jwt/jwt.strategy.js +57 -44
  86. package/build/strategies/jwt/jwt.tools.d.ts +20 -7
  87. package/build/strategies/jwt/jwt.tools.js +180 -81
  88. package/build/strategies/local/__tests__/local.strategy.test.d.ts +1 -0
  89. package/build/strategies/local/__tests__/local.strategy.test.js +115 -0
  90. package/build/strategies/local/local.strategy.d.ts +4 -3
  91. package/build/strategies/local/local.strategy.js +7 -6
  92. package/build/strategies/local/local.tools.d.ts +2 -0
  93. package/build/strategies/local/local.tools.js +44 -0
  94. package/build/strategies/oauth2/hybrid.oauth2.strategy.d.ts +5 -0
  95. package/build/strategies/oauth2/hybrid.oauth2.strategy.js +92 -0
  96. package/build/strategies/oauth2/oauth2.errors.d.ts +12 -0
  97. package/build/strategies/oauth2/oauth2.errors.js +24 -0
  98. package/build/strategies/oauth2/oauth2.strategy.d.ts +25 -15
  99. package/build/strategies/oauth2/oauth2.strategy.js +131 -141
  100. package/build/strategies/oauth2/oauth2.tools.d.ts +7 -2
  101. package/build/strategies/oauth2/oauth2.tools.js +119 -14
  102. package/build/strategies/oauth2/oauth2.types.d.ts +32 -1
  103. package/build/strategies/token-auth.strategy.d.ts +14 -8
  104. package/build/strategies/token-auth.strategy.js +162 -38
  105. package/build/tools/index.d.ts +0 -2
  106. package/build/tools/index.js +0 -2
  107. package/build/tools/tools.d.ts +2 -1
  108. package/build/tools/tools.js +9 -12
  109. package/build/types.d.ts +88 -57
  110. package/package.json +1 -1
  111. package/build/factories/auth-strategy.factory.d.ts +0 -9
  112. package/build/factories/auth-strategy.factory.js +0 -16
  113. package/build/factories/http-auth-strategy.factory.d.ts +0 -5
  114. package/build/factories/http-auth-strategy.factory.js +0 -41
  115. package/build/factories/index.d.ts +0 -3
  116. package/build/factories/socket-auth-strategy.factory.d.ts +0 -5
  117. package/build/factories/socket-auth-strategy.factory.js +0 -27
  118. package/build/tools/session.tools.d.ts +0 -6
  119. package/build/tools/session.tools.js +0 -15
  120. package/build/tools/token.tools.d.ts +0 -7
  121. package/build/tools/token.tools.js +0 -32
@@ -1,254 +1,189 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
2
25
  Object.defineProperty(exports, "__esModule", { value: true });
3
26
  exports.CredentialAuthStrategy = void 0;
27
+ const Soap = __importStar(require("@soapjs/soap"));
4
28
  const errors_1 = require("../errors");
5
29
  const base_auth_strategy_1 = require("./base-auth.strategy");
30
+ const password_service_1 = require("../services/password.service");
6
31
  class CredentialAuthStrategy extends base_auth_strategy_1.BaseAuthStrategy {
7
32
  config;
8
33
  session;
34
+ jwt;
9
35
  logger;
10
- constructor(config, session, logger) {
36
+ password;
37
+ constructor(config, session, jwt, logger) {
11
38
  super(config, session, logger);
12
39
  this.config = config;
13
40
  this.session = session;
41
+ this.jwt = jwt;
14
42
  this.logger = logger;
15
- }
16
- async storeUserSession(user, context) {
17
- if (!this.session || !this.config.session) {
18
- this.logger?.info("Session management is not configured. Skipping session storage.");
19
- return;
43
+ if (config.passwordPolicy) {
44
+ this.password = new password_service_1.PasswordService(config.passwordPolicy, logger);
20
45
  }
21
- let sessionId = this.config.session.getSessionId?.(context);
22
- if (!sessionId) {
23
- sessionId = this.config.session.generateSessionId
24
- ? this.config.session.generateSessionId(user, context)
25
- : `sid-${Date.now()}-${Math.random().toString(36).substring(7)}`;
46
+ }
47
+ async fetchUser(payload) {
48
+ if (this.config?.user?.fetchUser) {
49
+ return this.config.user.fetchUser(payload);
26
50
  }
27
- const sessionData = this.config.session.createSessionData
28
- ? this.config.session.createSessionData(user, context)
29
- : { user };
30
- if (this.config.session.store) {
31
- await this.config.session.store.setSession(sessionId, sessionData);
51
+ throw new Soap.NotImplementedError("fetchUser");
52
+ }
53
+ async authenticate(context) {
54
+ try {
55
+ await this.rateLimit?.checkRateLimit(context);
56
+ if (this.jwt) {
57
+ try {
58
+ return await this.jwt.authenticate(context);
59
+ }
60
+ catch (e) {
61
+ this.logger?.warn("JWT authentication failed, falling back to session.");
62
+ }
63
+ }
64
+ if (this.session) {
65
+ return await this.authenticateWithSession(context);
66
+ }
67
+ this.logger?.warn("No authentication method found. Proceeding as guest.");
68
+ if (this.config.allowGuest) {
69
+ return { user: null };
70
+ }
71
+ throw new errors_1.UserNotFoundError();
32
72
  }
33
- else {
34
- await this.session.set(sessionId, sessionData);
73
+ catch (error) {
74
+ await this.onFailure("authenticate", {
75
+ context,
76
+ error,
77
+ });
78
+ throw error;
35
79
  }
36
- this.config.session.embedSessionId?.(context, sessionId);
37
- this.logger?.info(`Stored user session with ID: ${sessionId}`);
38
- }
39
- async handleAuthenticationError(error, context) {
40
- this.logger?.error("Authentication failed:", error);
41
- await this.onFailure("login", { context, error });
42
- throw new errors_1.AuthError(error, "Authentication failed.");
43
- }
44
- async preAuthChecks(identifier) {
45
- await this.isAccountLocked(identifier);
46
- await this.checkFailedAttempts(identifier);
47
- await this.checkRateLimit(identifier);
48
- await this.checkPasswordExpiry(identifier);
49
80
  }
50
- async handleFailedLogin(identifier) {
51
- await this.config.failedAttempts.incrementFailedAttempts?.(identifier);
52
- }
53
- async handleSuccessfulLogin(identifier) {
54
- await this.config.failedAttempts.resetFailedAttempts?.(identifier);
55
- }
56
- async finalizeAuthentication(user, context) {
57
- await this.checkMfa(user, context);
58
- await this.isAuthorized(user);
59
- await this.storeUserSession(user, context);
60
- }
61
- async authenticate(context) {
81
+ async login(context) {
62
82
  try {
83
+ await this.rateLimit?.checkRateLimit(context);
63
84
  const credentials = await this.extractCredentials(context);
64
- if (!credentials)
85
+ if (!credentials) {
65
86
  throw new errors_1.MissingCredentialsError();
66
- await this.preAuthChecks(credentials.identifier);
67
- const valid = await this.verifyCredentials(credentials.identifier, credentials.password);
68
- if (!valid) {
69
- await this.handleFailedLogin(credentials.identifier);
87
+ }
88
+ await this.accountLock?.isAccountLocked(credentials.identifier);
89
+ await this.throttle?.checkFailedAttempts(credentials.identifier);
90
+ if ((await this.verifyCredentials(credentials.identifier, credentials.password)) === false) {
91
+ await this.throttle?.incrementFailedAttempts(credentials.identifier);
70
92
  throw new errors_1.InvalidCredentialsError();
71
93
  }
72
- await this.handleSuccessfulLogin(credentials.identifier);
73
- const user = await this.retrieveUser(credentials.identifier);
74
- if (!user)
94
+ const isPasswordChangeRequired = await this.password?.isPasswordChangeRequired(credentials.identifier);
95
+ if (isPasswordChangeRequired) {
96
+ throw new errors_1.ExpiredPasswordError();
97
+ }
98
+ await this.throttle?.resetFailedAttempts(credentials.identifier);
99
+ const user = await this.fetchUser(credentials.identifier);
100
+ if (!user) {
75
101
  throw new errors_1.UserNotFoundError();
76
- await this.finalizeAuthentication(user, context);
77
- this.auditLoginAttempt(credentials.identifier, true, context);
78
- return { user };
79
- }
80
- catch (e) {
81
- this.auditLoginAttempt(null, false, context);
82
- return this.handleAuthenticationError(e, context);
83
- }
84
- }
85
- async handleSession(user, context) {
86
- if (this.session) {
87
- const sessionId = this.session.generateSessionId();
88
- await this.session.set(sessionId, user);
102
+ }
103
+ await this.mfa?.checkMfa(user, context);
104
+ await this.role?.isAuthorized(user);
105
+ const tokens = await this.jwt?.issueTokens(user, context);
106
+ const session = await this.session?.issueSession(user, context);
107
+ return { user, session, tokens };
108
+ }
109
+ catch (error) {
110
+ await this.onFailure("login", {
111
+ context,
112
+ error,
113
+ });
114
+ throw error;
89
115
  }
90
116
  }
91
117
  async logout(context) {
92
118
  try {
93
- if (this.session) {
94
- const sessionId = this.session.getSessionId?.(context);
95
- if (!sessionId)
96
- throw new Error("Session ID is missing in the context.");
97
- await this.session.destroy(sessionId);
98
- this.logger?.info(`Session destroyed: ${sessionId}`);
99
- }
119
+ await this.session?.logoutSession(context);
100
120
  await this.onSuccess("logout", context);
101
121
  }
102
- catch (e) {
103
- const error = new errors_1.AuthError(e, "Logout process failed.");
104
- this.logger?.error("Error during logout:", error);
105
- await this.onFailure("logout", { context, error });
122
+ catch (error) {
123
+ await this.onFailure("logout", {
124
+ error,
125
+ });
106
126
  throw error;
107
127
  }
108
128
  }
109
129
  async requestPasswordReset(identifier, email) {
110
130
  try {
111
- if (!this.config?.passwordPolicy.generateResetToken) {
112
- throw new Error("Password reset token generation is not configured.");
113
- }
114
- const token = await this.config.passwordPolicy.generateResetToken(identifier);
131
+ const token = await this.password.generateResetToken(identifier);
115
132
  if (email) {
116
- await this.config.passwordPolicy.sendResetEmail?.(email, token);
133
+ await this.password.sendResetEmail(email, token);
117
134
  }
118
- await this.onSuccess("request_password_reset", { identifier });
135
+ await this.onSuccess("request_password_reset", {
136
+ identifier,
137
+ tokens: { reset: token },
138
+ });
119
139
  this.logger.info(`Password reset requested for identifier: ${identifier}`);
120
140
  }
121
- catch (e) {
122
- const error = new errors_1.AuthError(e, "Password reset request error.");
123
- this.logger.error("Password reset request error:", e);
141
+ catch (error) {
124
142
  await this.onFailure("request_password_reset", {
125
143
  identifier,
126
- error: e,
144
+ email,
145
+ error,
127
146
  });
128
147
  throw error;
129
148
  }
130
149
  }
131
150
  async resetPassword(identifier, token, newPassword) {
132
151
  try {
133
- if (!this.config.passwordPolicy?.validateResetToken) {
134
- throw new Error("Password reset token validation is not configured.");
152
+ if (!this.password?.validateResetToken) {
153
+ throw new Soap.NotImplementedError("validateResetToken");
135
154
  }
136
- const isValid = await this.config.passwordPolicy.validateResetToken(token);
137
- if (!isValid) {
138
- throw new Error("Invalid or expired reset token.");
155
+ if ((await this.password.validateResetToken(token)) === false) {
156
+ throw new errors_1.ExpiredResetTokenError();
139
157
  }
140
- await this.config.passwordPolicy.updatePassword(identifier, newPassword);
158
+ await this.password.updatePassword(identifier, newPassword);
141
159
  await this.onSuccess("password_reset", { identifier });
142
160
  this.logger.info(`Password successfully reset for identifier: ${identifier}`);
143
- this.auditPasswordChange(identifier);
144
161
  }
145
- catch (e) {
146
- const error = new errors_1.AuthError(e, "Password reset error");
147
- this.logger.error("Password reset error:", e);
162
+ catch (error) {
148
163
  await this.onFailure("password_reset", {
149
164
  identifier,
150
- error: e,
165
+ additional: { token },
166
+ error,
151
167
  });
152
168
  throw error;
153
169
  }
154
170
  }
155
171
  async changePassword(identifier, oldPassword, newPassword) {
156
172
  try {
157
- if (!this.config.credentials.verifyCredentials) {
158
- throw new Error("Credential verification is not configured.");
159
- }
160
- const isAuthenticated = await this.config.credentials.verifyCredentials(identifier, oldPassword);
161
- if (!isAuthenticated) {
173
+ if (!(await this.verifyCredentials(identifier, oldPassword))) {
162
174
  throw new errors_1.InvalidCredentialsError();
163
175
  }
164
- await this.config.passwordPolicy?.updatePassword?.(identifier, newPassword);
176
+ await this.password.updatePassword(identifier, newPassword);
165
177
  await this.onSuccess("change_password", { identifier });
166
178
  this.logger.info(`Password changed successfully for identifier: ${identifier}`);
167
- this.auditPasswordChange(identifier);
168
179
  }
169
- catch (e) {
170
- const error = new errors_1.AuthError(e, "Change password error");
171
- this.logger.error("Change password error:", e);
180
+ catch (error) {
172
181
  await this.onFailure("change_password", {
173
182
  identifier,
174
- error: e,
183
+ error,
175
184
  });
176
185
  throw error;
177
186
  }
178
187
  }
179
- async auditLoginAttempt(identifier, success, context) {
180
- await this.config.audit?.logAttempt?.(identifier, success, context);
181
- }
182
- async auditPasswordChange(identifier, context) {
183
- await this.config.audit?.logPasswordChange?.(identifier, context);
184
- }
185
- validatePasswordPolicy(password) {
186
- return this.config.passwordPolicy.validatePassword?.(password) ?? true;
187
- }
188
- async checkFailedAttempts(identifier) {
189
- try {
190
- if (this.config.security?.maxFailedLoginAttempts) {
191
- const failedAttempts = (await this.config.failedAttempts.getFailedAttempts?.(identifier)) ||
192
- 0;
193
- if (failedAttempts >= this.config.security.maxFailedLoginAttempts) {
194
- this.logger.warn(`User ${identifier} is temporarily locked out.`);
195
- throw new errors_1.AccountLockedError();
196
- }
197
- }
198
- }
199
- catch (e) {
200
- this.logger.error("Check failed attempts:", e);
201
- }
202
- }
203
- async isAccountLocked(account) {
204
- if (await this.config.lock.isAccountLocked?.(account)) {
205
- throw new errors_1.AccountLockedError();
206
- }
207
- if (typeof account === "string" && this.config.security?.lockoutDuration) {
208
- const lockoutKey = `lockout:${account}`;
209
- const lockoutSession = await this.session?.get(lockoutKey);
210
- if (lockoutSession) {
211
- const elapsed = Date.now() - Number(lockoutSession.date);
212
- if (elapsed < this.config.security.lockoutDuration * 60 * 1000) {
213
- this.logger.warn(`Account ${account} is temporarily locked out.`);
214
- return true;
215
- }
216
- else {
217
- await this.session?.destroy(lockoutKey);
218
- }
219
- }
220
- }
221
- return false;
222
- }
223
- async incrementFailedAttempts(account) {
224
- if (this.config.failedAttempts.incrementFailedAttempts) {
225
- await this.config.failedAttempts.incrementFailedAttempts(account);
226
- const failedAttempts = (await this.config.failedAttempts.getFailedAttempts?.(account)) || 0;
227
- if (this.config.security?.maxFailedLoginAttempts &&
228
- failedAttempts >= this.config.security.maxFailedLoginAttempts) {
229
- const lockoutKey = `lockout:${account}`;
230
- await this.session?.set(lockoutKey, {
231
- date: Date.now(),
232
- });
233
- this.logger.warn(`Account ${account} has been locked due to failed attempts.`);
234
- this.notifyAccountLocked(account);
235
- }
236
- }
237
- }
238
- async notifyAccountLocked(identifier) {
239
- if (this.config.security?.notifyOnLockout) {
240
- await this.config.security.notifyOnLockout(identifier);
241
- }
242
- }
243
- async checkPasswordExpiry(identifier) {
244
- if (this.config.passwordPolicy?.passwordExpirationDays) {
245
- const lastChanged = await this.config.passwordPolicy.getLastPasswordChange?.(identifier);
246
- if (lastChanged &&
247
- Date.now() - Number(lastChanged) >
248
- this.config.passwordPolicy.passwordExpirationDays * 86400000) {
249
- throw new Error("Password expired, please reset your password.");
250
- }
251
- }
252
- }
253
188
  }
254
189
  exports.CredentialAuthStrategy = CredentialAuthStrategy;
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const jsonwebtoken_1 = require("jsonwebtoken");
4
+ const jwt_strategy_1 = require("../jwt.strategy");
5
+ const errors_1 = require("../../../errors");
6
+ describe("JWTStrategy", () => {
7
+ let strategy;
8
+ let mockLogger;
9
+ let mockConfig;
10
+ let mockUser;
11
+ let mockContext;
12
+ beforeEach(() => {
13
+ mockLogger = { error: jest.fn(), warn: jest.fn(), info: jest.fn() };
14
+ mockUser = { id: "123", email: "test@example.com" };
15
+ mockContext = {
16
+ req: {
17
+ headers: { authorization: "Bearer mock-access-token" },
18
+ cookies: { refreshToken: "mock-refresh-token" },
19
+ },
20
+ res: { setHeader: jest.fn(), cookie: jest.fn(), clearCookie: jest.fn() },
21
+ };
22
+ mockConfig = {
23
+ accessToken: {
24
+ issuer: { secretKey: "access-secret", options: { expiresIn: "1h" } },
25
+ verifier: { options: {} },
26
+ persistence: { store: jest.fn() },
27
+ extract: jest.fn(),
28
+ embed: jest.fn(),
29
+ },
30
+ refreshToken: {
31
+ issuer: { secretKey: "refresh-secret", options: { expiresIn: "7d" } },
32
+ verifier: { options: {} },
33
+ persistence: { store: jest.fn(), remove: jest.fn() },
34
+ extract: jest.fn(),
35
+ embed: jest.fn(),
36
+ },
37
+ user: { fetchUser: jest.fn().mockResolvedValue(mockUser) },
38
+ routes: {},
39
+ };
40
+ strategy = new jwt_strategy_1.JwtStrategy(mockConfig, mockLogger);
41
+ });
42
+ it("should authenticate user with valid access token", async () => {
43
+ const mockAccessToken = "mock-access-token";
44
+ jest
45
+ .spyOn(mockConfig.accessToken, "extract")
46
+ .mockReturnValue(mockAccessToken);
47
+ jest.spyOn(strategy, "verifyAccessToken").mockResolvedValue(mockUser);
48
+ const result = await strategy.authenticate(mockContext);
49
+ expect(result.user).toEqual(mockUser);
50
+ expect(result.tokens.accessToken).toEqual("mock-access-token");
51
+ });
52
+ it("should refresh tokens when access token is invalid", async () => {
53
+ const mockAccessToken = "mock-access-token";
54
+ const mockRefreshToken = "mock-refresh-token";
55
+ jest
56
+ .spyOn(mockConfig.accessToken, "extract")
57
+ .mockReturnValue(mockAccessToken);
58
+ jest
59
+ .spyOn(mockConfig.refreshToken, "extract")
60
+ .mockReturnValue(mockRefreshToken);
61
+ jest
62
+ .spyOn(strategy, "verifyAccessToken")
63
+ .mockRejectedValue(new jsonwebtoken_1.TokenExpiredError("Access", new Date()));
64
+ jest.spyOn(strategy, "verifyRefreshToken").mockResolvedValue(mockUser);
65
+ jest
66
+ .spyOn(strategy, "generateAccessToken")
67
+ .mockResolvedValue("new-access-token");
68
+ jest
69
+ .spyOn(strategy, "generateRefreshToken")
70
+ .mockResolvedValue("new-refresh-token");
71
+ jest.spyOn(strategy, "storeAccessToken").mockResolvedValue(null);
72
+ jest.spyOn(strategy, "storeRefreshToken").mockResolvedValue(null);
73
+ const result = await strategy.authenticate(mockContext);
74
+ expect(result.user).toEqual(mockUser);
75
+ expect(result.tokens.accessToken).toEqual("new-access-token");
76
+ expect(result.tokens.refreshToken).toEqual("new-refresh-token");
77
+ });
78
+ it("should throw MissingTokenError when no tokens are provided", async () => {
79
+ jest.spyOn(mockConfig.accessToken, "extract").mockReturnValue(undefined);
80
+ jest.spyOn(mockConfig.refreshToken, "extract").mockReturnValue(undefined);
81
+ await expect(strategy.authenticate(mockContext)).rejects.toThrow(errors_1.MissingTokenError);
82
+ });
83
+ it("should throw InvalidTokenError if user does not exist", async () => {
84
+ const mockAccessToken = "mock-access-token";
85
+ const mockRefreshToken = null;
86
+ jest
87
+ .spyOn(mockConfig.accessToken, "extract")
88
+ .mockReturnValue(mockAccessToken);
89
+ jest
90
+ .spyOn(mockConfig.refreshToken, "extract")
91
+ .mockReturnValue(mockRefreshToken);
92
+ jest.spyOn(strategy, "verifyAccessToken").mockResolvedValue(mockUser);
93
+ mockConfig.user.fetchUser = jest.fn().mockResolvedValue(null);
94
+ await expect(strategy.authenticate(mockContext)).rejects.toThrow(errors_1.InvalidTokenError);
95
+ });
96
+ it("should throw InvalidTokenError when refresh token is invalid", async () => {
97
+ const mockAccessToken = "mock-access-token";
98
+ const mockRefreshToken = "mock-refresh-token";
99
+ jest
100
+ .spyOn(mockConfig.accessToken, "extract")
101
+ .mockReturnValue(mockAccessToken);
102
+ jest
103
+ .spyOn(mockConfig.refreshToken, "extract")
104
+ .mockReturnValue(mockRefreshToken);
105
+ jest
106
+ .spyOn(strategy, "verifyAccessToken")
107
+ .mockRejectedValue(new errors_1.InvalidTokenError("Access"));
108
+ jest
109
+ .spyOn(strategy, "verifyRefreshToken")
110
+ .mockRejectedValue(new errors_1.InvalidTokenError("Refresh"));
111
+ await expect(strategy.authenticate(mockContext)).rejects.toThrow(errors_1.InvalidTokenError);
112
+ });
113
+ it("should generate and store access and refresh tokens", async () => {
114
+ jest
115
+ .spyOn(strategy, "generateAccessToken")
116
+ .mockResolvedValue("new-access-token");
117
+ jest
118
+ .spyOn(strategy, "generateRefreshToken")
119
+ .mockResolvedValue("new-refresh-token");
120
+ jest.spyOn(strategy, "storeAccessToken").mockResolvedValue(null);
121
+ jest.spyOn(strategy, "storeRefreshToken").mockResolvedValue(null);
122
+ const tokens = await strategy.issueTokens(mockUser, mockContext);
123
+ expect(tokens).toHaveProperty("accessToken", "new-access-token");
124
+ expect(tokens).toHaveProperty("refreshToken", "new-refresh-token");
125
+ expect(strategy.storeAccessToken).toHaveBeenCalledWith("new-access-token");
126
+ expect(strategy.storeRefreshToken).toHaveBeenCalledWith("new-refresh-token");
127
+ });
128
+ it("should invalidate refresh token", async () => {
129
+ jest
130
+ .spyOn(strategy, "extractRefreshToken")
131
+ .mockResolvedValue("mock-refresh-token");
132
+ jest.spyOn(strategy, "invalidateRefreshToken").mockResolvedValue(null);
133
+ await strategy.invalidateRefreshToken(mockContext);
134
+ expect(strategy.invalidateRefreshToken).toHaveBeenCalledWith(mockContext);
135
+ });
136
+ it("should extract access token from context", async () => {
137
+ const mockAccessToken = "mock-access-token";
138
+ jest
139
+ .spyOn(mockConfig.accessToken, "extract")
140
+ .mockReturnValue(mockAccessToken);
141
+ const token = await strategy.extractAccessToken(mockContext);
142
+ expect(token).toBe("mock-access-token");
143
+ });
144
+ it("should extract refresh token from context", async () => {
145
+ const mockRefreshToken = "mock-refresh-token";
146
+ jest
147
+ .spyOn(mockConfig.refreshToken, "extract")
148
+ .mockReturnValueOnce(mockRefreshToken);
149
+ const token = await strategy.extractRefreshToken(mockContext);
150
+ expect(token).toBe("mock-refresh-token");
151
+ });
152
+ it("should throw UndefinedTokenSecretError if access secret key is missing", async () => {
153
+ mockConfig.accessToken.issuer.secretKey = undefined;
154
+ expect(() => new jwt_strategy_1.JwtStrategy(mockConfig, mockLogger)).toThrow(errors_1.UndefinedTokenSecretError);
155
+ });
156
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const globals_1 = require("@jest/globals");
4
+ const jwt_tools_1 = require("../jwt.tools");
5
+ const errors_1 = require("../../../errors");
6
+ (0, globals_1.describe)("JwtTools", () => {
7
+ const secretKey = "test-secret";
8
+ const payload = { id: "user123", email: "test@example.com" };
9
+ const config = {
10
+ accessToken: {
11
+ issuer: { secretKey, options: { expiresIn: "1h" } },
12
+ verifier: { options: {} },
13
+ },
14
+ refreshToken: {
15
+ issuer: { secretKey, options: { expiresIn: "7d" } },
16
+ verifier: { options: {} },
17
+ },
18
+ routes: {},
19
+ };
20
+ (0, globals_1.it)("should generate an access token", () => {
21
+ const token = jwt_tools_1.JwtTools.generateAccessToken(payload, config.accessToken);
22
+ (0, globals_1.expect)(typeof token).toBe("string");
23
+ });
24
+ (0, globals_1.it)("should throw error when generating access token without secret key", () => {
25
+ const invalidConfig = {
26
+ ...config,
27
+ accessToken: { issuer: { secretKey: "" } },
28
+ };
29
+ (0, globals_1.expect)(() => jwt_tools_1.JwtTools.generateAccessToken(payload, invalidConfig.accessToken)).toThrow(errors_1.UndefinedTokenSecretError);
30
+ });
31
+ (0, globals_1.it)("should generate a refresh token", () => {
32
+ const token = jwt_tools_1.JwtTools.generateRefreshToken(payload, config.refreshToken);
33
+ (0, globals_1.expect)(typeof token).toBe("string");
34
+ });
35
+ (0, globals_1.it)("should throw error when generating refresh token without secret key", () => {
36
+ const invalidConfig = {
37
+ ...config,
38
+ refreshToken: { issuer: { secretKey: "" } },
39
+ };
40
+ (0, globals_1.expect)(() => jwt_tools_1.JwtTools.generateRefreshToken(payload, invalidConfig.refreshToken)).toThrow(errors_1.UndefinedTokenSecretError);
41
+ });
42
+ (0, globals_1.it)("should verify a valid access token", () => {
43
+ const token = jwt_tools_1.JwtTools.generateAccessToken(payload, config.accessToken);
44
+ const decoded = jwt_tools_1.JwtTools.verifyAccessToken(token, config.accessToken);
45
+ (0, globals_1.expect)(decoded.id).toBe(payload.id);
46
+ });
47
+ (0, globals_1.it)("should throw error for undefined access token", () => {
48
+ (0, globals_1.expect)(() => jwt_tools_1.JwtTools.verifyAccessToken("", config.accessToken)).toThrow(errors_1.UndefinedTokenError);
49
+ });
50
+ (0, globals_1.it)("should throw error for invalid access token", () => {
51
+ (0, globals_1.expect)(() => jwt_tools_1.JwtTools.verifyAccessToken("invalid-token", config.accessToken)).toThrow(errors_1.InvalidTokenError);
52
+ });
53
+ (0, globals_1.it)("should verify a valid refresh token", () => {
54
+ const token = jwt_tools_1.JwtTools.generateRefreshToken(payload, config.refreshToken);
55
+ const decoded = jwt_tools_1.JwtTools.verifyRefreshToken(token, config.refreshToken);
56
+ (0, globals_1.expect)(decoded.id).toBe(payload.id);
57
+ });
58
+ (0, globals_1.it)("should throw error for undefined refresh token", () => {
59
+ (0, globals_1.expect)(() => jwt_tools_1.JwtTools.verifyRefreshToken("", config.refreshToken)).toThrow(errors_1.UndefinedTokenError);
60
+ });
61
+ (0, globals_1.it)("should throw error for invalid refresh token", () => {
62
+ (0, globals_1.expect)(() => jwt_tools_1.JwtTools.verifyRefreshToken("invalid-token", config.refreshToken)).toThrow(errors_1.InvalidTokenError);
63
+ });
64
+ (0, globals_1.it)("should set the access token in the response header", () => {
65
+ const token = jwt_tools_1.JwtTools.generateAccessToken(payload, config.accessToken);
66
+ const context = { res: { setHeader: globals_1.jest.fn() } };
67
+ jwt_tools_1.JwtTools.setAccessTokenHeader(token, context);
68
+ (0, globals_1.expect)(context.res.setHeader).toHaveBeenCalledWith("Authorization", `Bearer ${token}`);
69
+ });
70
+ (0, globals_1.it)("should set the refresh token in cookies", () => {
71
+ const token = jwt_tools_1.JwtTools.generateRefreshToken(payload, config.refreshToken);
72
+ const context = { res: { cookie: globals_1.jest.fn() } };
73
+ jwt_tools_1.JwtTools.setRefreshTokenCookie(token, context);
74
+ (0, globals_1.expect)(context.res.cookie).toHaveBeenCalledWith("refreshToken", token, globals_1.expect.any(Object));
75
+ });
76
+ (0, globals_1.it)("should clear tokens from headers and cookies", () => {
77
+ const context = {
78
+ res: { clearCookie: globals_1.jest.fn(), setHeader: globals_1.jest.fn() },
79
+ };
80
+ jwt_tools_1.JwtTools.clearTokens(context);
81
+ (0, globals_1.expect)(context.res.clearCookie).toHaveBeenCalledWith("refreshToken");
82
+ (0, globals_1.expect)(context.res.setHeader).toHaveBeenCalledWith("Authorization", "");
83
+ });
84
+ (0, globals_1.it)("should retrieve an access token from request headers", () => {
85
+ const token = jwt_tools_1.JwtTools.generateAccessToken(payload, config.accessToken);
86
+ const context = {
87
+ req: { headers: { authorization: `Bearer ${token}` } },
88
+ };
89
+ const extractedToken = jwt_tools_1.JwtTools.getAccessToken(context);
90
+ (0, globals_1.expect)(extractedToken).toBe(token);
91
+ });
92
+ (0, globals_1.it)("should retrieve a refresh token from request cookies", () => {
93
+ const token = jwt_tools_1.JwtTools.generateRefreshToken(payload, config.refreshToken);
94
+ const context = { req: { cookies: { refreshToken: token } } };
95
+ const extractedToken = jwt_tools_1.JwtTools.getRefreshToken(context);
96
+ (0, globals_1.expect)(extractedToken).toBe(token);
97
+ });
98
+ });