@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
@@ -8,16 +8,28 @@ const axios_1 = __importDefault(require("axios"));
8
8
  const errors_1 = require("../../errors");
9
9
  const oauth2_tools_1 = require("./oauth2.tools");
10
10
  const base_auth_strategy_1 = require("../base-auth.strategy");
11
+ const jwks_service_1 = require("../../services/jwks.service");
12
+ const pkce_service_1 = require("../../services/pkce.service");
13
+ const oauth2_errors_1 = require("./oauth2.errors");
11
14
  class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
12
15
  config;
13
16
  session;
17
+ jwt;
14
18
  logger;
15
- constructor(config, session, logger) {
16
- super(config, session, logger);
19
+ jwks;
20
+ pkce;
21
+ constructor(config, session, jwt, logger) {
22
+ super((0, oauth2_tools_1.prepareOAuth2Config)(config), session, logger);
17
23
  this.config = config;
18
24
  this.session = session;
25
+ this.jwt = jwt;
19
26
  this.logger = logger;
20
- this.config.scope = this.config.scope ?? "email";
27
+ if (config.jwks) {
28
+ this.jwks = new jwks_service_1.JwtService({ ...config.jwks, audience: config.clientId });
29
+ }
30
+ if (config.pkce) {
31
+ this.pkce = new pkce_service_1.PKCEService(config.pkce);
32
+ }
21
33
  }
22
34
  async logout(context) {
23
35
  try {
@@ -29,8 +41,11 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
29
41
  }
30
42
  }
31
43
  catch (error) {
32
- this.logger?.error("Logout failed:", error);
33
- throw new Error("Logout failed.");
44
+ await this.onFailure("logout", {
45
+ context,
46
+ error,
47
+ });
48
+ throw error;
34
49
  }
35
50
  }
36
51
  async getCredentialsForPasswordGrant(context) {
@@ -46,90 +61,17 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
46
61
  };
47
62
  }
48
63
  }
49
- throw new Error("Missing credentials for password grant.");
50
- }
51
- retrieveAccessToken(context) {
52
- if (this.config.accessToken?.retrieve) {
53
- return this.config.accessToken.retrieve(context);
54
- }
55
- if (typeof context === "object" && "headers" in context) {
56
- const authHeader = context.headers?.authorization;
57
- if (authHeader?.startsWith("Bearer ")) {
58
- return authHeader.split(" ")[1];
59
- }
60
- }
61
- if (typeof context === "object" && "cookies" in context) {
62
- return context.cookies?.access_token;
63
- }
64
- return undefined;
65
- }
66
- retrieveRefreshToken(context) {
67
- if (this.config.refreshToken?.retrieve) {
68
- return this.config.refreshToken.retrieve(context);
69
- }
70
- if (typeof context === "object" && "cookies" in context) {
71
- return context.cookies?.refresh_token;
72
- }
73
- return undefined;
74
- }
75
- storeAccessToken(token, context) {
76
- if (this.config.accessToken?.persistence?.store) {
77
- return this.config.accessToken.persistence.store(token, context, this.config.accessToken.issuer.options.expiresIn);
78
- }
79
- if (typeof context === "object" && "cookies" in context) {
80
- context.cookies.set("access_token", token, { httpOnly: true });
81
- }
82
- }
83
- storeRefreshToken(token, context) {
84
- if (this.config.refreshToken?.persistence?.store) {
85
- return this.config.refreshToken.persistence.store(token, context, this.config.refreshToken.issuer.options.expiresIn);
86
- }
87
- if (typeof context === "object" && "cookies" in context) {
88
- context.cookies.set("refresh_token", token, { httpOnly: true });
89
- }
90
- }
91
- embedAccessToken(token, context) {
92
- if (this.config.accessToken?.embed) {
93
- this.config.accessToken.embed(context, token);
94
- }
95
- else if (typeof context === "object" && "response" in context) {
96
- context.response.setHeader("Authorization", `Bearer ${token}`);
97
- }
98
- }
99
- embedRefreshToken(token, context) {
100
- if (this.config.refreshToken?.embed) {
101
- return this.config.refreshToken.embed(context, token);
102
- }
103
- else if (typeof context === "object" && "cookies" in context) {
104
- context.cookies.set("refresh_token", token, { httpOnly: true });
105
- }
106
- }
107
- async isTokenExpired(token) {
108
- try {
109
- const parts = token.split(".");
110
- if (parts.length !== 3) {
111
- this.logger?.warn("Non-JWT token provided, cannot determine expiration.");
112
- return false;
113
- }
114
- const decoded = JSON.parse(Buffer.from(parts[1], "base64").toString());
115
- if (!decoded.exp)
116
- return false;
117
- const currentTime = Math.floor(Date.now() / 1000);
118
- return decoded.exp < currentTime;
119
- }
120
- catch (error) {
121
- this.logger?.warn("Failed to decode token:", error);
122
- return false;
123
- }
64
+ throw new errors_1.MissingCredentialsError();
124
65
  }
125
66
  async authenticate(context) {
126
67
  try {
127
68
  let user;
69
+ let session;
128
70
  let refreshToken;
129
- let accessToken = await this.retrieveAccessToken(context);
71
+ let accessToken = await this.extractAccessToken(context);
130
72
  if (!accessToken) {
131
73
  this.logger?.info("No access token found, checking for refresh token.");
132
- refreshToken = await this.retrieveRefreshToken(context);
74
+ refreshToken = await this.extractRefreshToken(context);
133
75
  if (refreshToken) {
134
76
  this.logger?.info("Found refresh token, attempting to refresh access token.");
135
77
  const newTokens = await this.refreshAccessToken(context);
@@ -151,9 +93,9 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
151
93
  }
152
94
  }
153
95
  }
154
- else if (accessToken && (await this.isTokenExpired(accessToken))) {
96
+ else if (accessToken && this.isTokenExpired(accessToken)) {
155
97
  this.logger?.info("Access token expired, attempting refresh.");
156
- refreshToken = await this.retrieveRefreshToken(context);
98
+ refreshToken = await this.extractRefreshToken(context);
157
99
  if (!refreshToken) {
158
100
  throw new errors_1.MissingTokenError("Refresh");
159
101
  }
@@ -168,23 +110,29 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
168
110
  if (!accessToken) {
169
111
  throw new errors_1.MissingTokenError("Access");
170
112
  }
171
- user = await this.retrieveUser(accessToken);
113
+ user = await this.fetchUser(accessToken);
172
114
  if (!user) {
173
115
  this.logger?.error("User retrieval failed: No user found for access token.");
174
116
  throw new errors_1.UserNotFoundError();
175
117
  }
176
- await this.isAuthorized(user);
177
- return { user, tokens: { accessToken, refreshToken } };
118
+ if (this.session) {
119
+ session = await this.session.issueSession(user, context);
120
+ }
121
+ await this.role.isAuthorized(user);
122
+ return { user: user, tokens: { accessToken, refreshToken }, session };
178
123
  }
179
124
  catch (error) {
180
- this.logger?.error("OAuth2 authentication failed:", error);
181
- throw new errors_1.AuthError(error, "OAuth2 authentication failed.");
125
+ await this.onFailure("authenticate", {
126
+ context,
127
+ error,
128
+ });
129
+ throw error;
182
130
  }
183
131
  }
184
132
  async processOAuthFlow(context) {
185
133
  if (this.config.grantType === "authorization_code") {
186
134
  const authorizationCode = this.extractAuthorizationCode(context);
187
- this.verifyAuthorizationCode(context, authorizationCode);
135
+ await this.verifyAuthorizationCode(context, authorizationCode);
188
136
  const tokenResult = await this.exchangeCodeForToken(context, authorizationCode);
189
137
  return tokenResult;
190
138
  }
@@ -197,43 +145,26 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
197
145
  this.logger?.info("Using password grant.");
198
146
  const credentials = await this.getCredentialsForPasswordGrant(context);
199
147
  if (!credentials) {
200
- throw new Error("Missing credentials for password grant.");
148
+ throw new errors_1.MissingCredentialsError();
201
149
  }
202
150
  const passwordResult = await this.exchangePasswordGrant(credentials.identifier, credentials.password);
203
151
  return { accessToken: passwordResult.accessToken };
204
152
  }
205
- throw new Error(`Unsupported grant type: ${this.config.grantType}`);
153
+ throw new oauth2_errors_1.UnsupportedGrantTypeError(this.config.grantType);
206
154
  }
207
- verifyAuthorizationCode(context, code) {
155
+ async verifyAuthorizationCode(context, code) {
208
156
  if (!code) {
209
157
  this.logger?.warn("Authorization code missing, redirecting user.");
210
- const authUrl = this.buildAuthorizationUrl(context);
158
+ const authUrl = await this.buildAuthorizationUrl(context);
211
159
  this.redirectUser(context, authUrl);
212
160
  throw new errors_1.MissingAuthorizationCodeError();
213
161
  }
214
162
  }
215
- extractAuthorizationCode(context) {
216
- if (typeof context === "object" && "query" in context) {
217
- return context.query.code || null;
218
- }
219
- return null;
220
- }
221
- redirectUser(context, authUrl) {
222
- if (typeof context === "object" && "response" in context) {
223
- context.response.redirect(authUrl);
224
- }
225
- else {
226
- this.logger?.warn("Redirect attempted in unsupported context.");
227
- }
228
- }
229
- buildAuthorizationUrl(context) {
163
+ async buildAuthorizationUrl(context) {
230
164
  let authorizationUrl = `${this.config.endpoints.authorizationUrl}?client_id=${this.config.clientId}&redirect_uri=${encodeURIComponent(this.config.redirectUri)}&response_type=code&scope=${this.config.scope ?? ""}`;
231
- if (this.config.pkce) {
232
- const codeVerifier = this.config.pkce.generateCodeVerifier
233
- ? this.config.pkce.generateCodeVerifier()
234
- : oauth2_tools_1.OAuth2Tools.generateCodeVerifier();
235
- const codeChallenge = oauth2_tools_1.OAuth2Tools.generateCodeChallenge(codeVerifier);
236
- this.config.pkce.storeCodeVerifier?.(context, codeVerifier);
165
+ if (this.pkce) {
166
+ const codeVerifier = await this.pkce.generateCodeVerifier(context);
167
+ const codeChallenge = this.pkce.generateCodeChallenge(codeVerifier, context);
237
168
  authorizationUrl += `&code_challenge=${codeChallenge}&code_challenge_method=S256`;
238
169
  }
239
170
  return authorizationUrl;
@@ -243,46 +174,50 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
243
174
  grant_type: "authorization_code",
244
175
  client_id: this.config.clientId,
245
176
  code,
246
- redirect_uri: this.config.redirectUri,
177
+ redirectUri: this.config.redirectUri,
247
178
  };
248
- if (this.config.pkce) {
249
- const codeVerifier = this.config.pkce.retrieveCodeVerifier?.(context);
179
+ if (this.pkce) {
180
+ const codeVerifier = this.pkce.extractCodeVerifier(context);
250
181
  if (!codeVerifier) {
251
- throw new Error("Missing PKCE code verifier in context.");
182
+ throw new oauth2_errors_1.MissingCodeVerifierError();
252
183
  }
253
184
  data.code_verifier = codeVerifier;
254
185
  }
255
186
  else if (this.config.clientSecret) {
256
187
  data.client_secret = this.config.clientSecret;
257
188
  }
258
- const response = await fetch(this.config.endpoints.tokenUrl, {
259
- method: "POST",
189
+ const response = await axios_1.default.post(this.config.endpoints.tokenUrl, {
260
190
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
261
191
  body: new URLSearchParams(data).toString(),
262
192
  });
263
- if (!response.ok) {
264
- throw new Error(`Token exchange failed with status: ${response.status}`);
265
- }
266
- const tokenData = await response.json();
193
+ const tokenData = await response.data();
267
194
  return {
268
195
  accessToken: tokenData.access_token,
269
196
  refreshToken: tokenData.refresh_token,
197
+ idToken: tokenData.id_token,
270
198
  };
271
199
  }
272
- handleAuthorizationRedirect(context) {
200
+ async login(context) {
273
201
  try {
274
- const authUrl = this.buildAuthorizationUrl(context);
202
+ const state = await oauth2_tools_1.OAuth2Tools.generateState(this.config);
203
+ const nonce = await oauth2_tools_1.OAuth2Tools.generateNonce(this.config);
204
+ await this.config.state?.persistence?.store?.(state);
205
+ await this.config.nonce?.persistence?.store?.(nonce);
206
+ const authUrl = await this.buildAuthorizationUrl(context);
275
207
  this.redirectUser(context, authUrl);
276
208
  }
277
209
  catch (error) {
278
- this.logger?.error("Authorization redirect failed:", error);
279
- throw new errors_1.AuthError(error, "Authorization redirect failed.");
210
+ await this.onFailure("login", {
211
+ context,
212
+ error,
213
+ });
214
+ throw error;
280
215
  }
281
216
  }
282
- async retrieveUser(accessToken) {
217
+ async fetchUser(accessToken) {
283
218
  try {
284
219
  if (!this.config.endpoints.userInfoUrl) {
285
- throw new Error("User info endpoint not configured.");
220
+ throw new errors_1.MissingConfigError("userInfoUrl");
286
221
  }
287
222
  const response = await axios_1.default.get(this.config.endpoints.userInfoUrl, {
288
223
  headers: { Authorization: `Bearer ${accessToken}` },
@@ -304,7 +239,7 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
304
239
  }
305
240
  async exchangePasswordGrant(username, password) {
306
241
  if (!username || !password) {
307
- throw new Error("Missing credentials for password grant.");
242
+ throw new errors_1.MissingCredentialsError();
308
243
  }
309
244
  const response = await axios_1.default.post(this.config.endpoints.tokenUrl, {
310
245
  grant_type: "password",
@@ -317,7 +252,7 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
317
252
  }
318
253
  async refreshAccessToken(context) {
319
254
  try {
320
- const refreshToken = await this.retrieveRefreshToken(context);
255
+ const refreshToken = await this.extractRefreshToken(context);
321
256
  if (!refreshToken) {
322
257
  throw new errors_1.MissingTokenError("Refresh");
323
258
  }
@@ -327,31 +262,65 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
327
262
  client_secret: this.config.clientSecret,
328
263
  refresh_token: refreshToken,
329
264
  });
265
+ if (response.data.error) {
266
+ this.logger?.warn(`OAuth2 error: ${response.data.error_description || response.data.error}`);
267
+ }
330
268
  if (response.status !== 200 || !response.data.access_token) {
331
269
  throw new Error("Failed to refresh token");
332
270
  }
333
271
  const refreshedTokens = {
334
272
  accessToken: response.data.access_token,
335
273
  refreshToken: response.data.refresh_token,
274
+ idToken: response.data.id_token,
336
275
  };
337
276
  await this.storeAccessToken(refreshedTokens.accessToken, context);
338
- if (refreshedTokens.refreshToken) {
339
- await this.storeRefreshToken(refreshedTokens.refreshToken, context);
340
- }
341
277
  await this.embedAccessToken(refreshedTokens.accessToken, context);
342
278
  if (refreshedTokens.refreshToken) {
279
+ await this.storeRefreshToken(refreshedTokens.refreshToken, context);
343
280
  await this.embedRefreshToken(refreshedTokens.refreshToken, context);
344
281
  }
345
282
  return refreshedTokens;
346
283
  }
347
284
  catch (error) {
348
- this.logger?.error("Token refresh failed:", error);
349
- throw new errors_1.AuthError(error, "Token refresh failed.");
285
+ await this.handleTokenRefreshFailure(context);
286
+ await this.onFailure("refresh_access_token", {
287
+ error,
288
+ });
289
+ throw error;
290
+ }
291
+ }
292
+ async handleTokenRefreshFailure(context) {
293
+ try {
294
+ if (this.config.refreshToken) {
295
+ await this.storeRefreshToken("", context);
296
+ }
297
+ if (this.config.autoLogoutOnRefreshFailure) {
298
+ await this.logout(context);
299
+ }
300
+ }
301
+ catch (e) {
302
+ this.logger?.error(e);
303
+ }
304
+ }
305
+ async verifyIdToken(idToken) {
306
+ const storedNonce = await this.config?.nonce?.persistence?.read?.();
307
+ if (storedNonce) {
308
+ const decodedToken = await this.jwks.verify(idToken);
309
+ if (decodedToken.nonce) {
310
+ if ((await oauth2_tools_1.OAuth2Tools.validateNonce(storedNonce, decodedToken.nonce, this.config.nonce)) === false) {
311
+ throw new oauth2_errors_1.InvalidNonceError("Invalid nonce value");
312
+ }
313
+ }
314
+ if (decodedToken) {
315
+ return this.config.user?.validateUser?.(decodedToken);
316
+ }
350
317
  }
318
+ return null;
351
319
  }
352
320
  async revokeToken(token) {
353
- if (!this.config.endpoints.revocationUrl)
354
- return;
321
+ if (!this.config.endpoints?.revocationUrl) {
322
+ throw new errors_1.MissingConfigError("revocationUrl");
323
+ }
355
324
  try {
356
325
  await axios_1.default.post(this.config.endpoints.revocationUrl, {
357
326
  token,
@@ -361,7 +330,28 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
361
330
  this.logger?.info("Token revoked successfully.");
362
331
  }
363
332
  catch (error) {
364
- this.logger?.error("Failed to revoke token:", error);
333
+ await this.onFailure("revoke_token", {
334
+ error,
335
+ });
336
+ throw error;
337
+ }
338
+ }
339
+ isTokenExpired(token) {
340
+ try {
341
+ const parts = token.split(".");
342
+ if (parts.length !== 3) {
343
+ this.logger?.warn("Non-JWT token provided, cannot determine expiration.");
344
+ return false;
345
+ }
346
+ const decoded = JSON.parse(Buffer.from(parts[1], "base64").toString());
347
+ if (!decoded.exp)
348
+ return false;
349
+ const currentTime = Math.floor(Date.now() / 1000);
350
+ return decoded.exp < currentTime;
351
+ }
352
+ catch (error) {
353
+ this.logger?.warn("Failed to decode token:", error);
354
+ return false;
365
355
  }
366
356
  }
367
357
  }
@@ -1,4 +1,9 @@
1
+ import { OAuth2NonceConfig, OAuth2StrategyConfig } from "./oauth2.types";
1
2
  export declare class OAuth2Tools {
2
- static generateCodeVerifier(): string;
3
- static generateCodeChallenge(codeVerifier: string): string;
3
+ static generateState(config: OAuth2StrategyConfig): Promise<string>;
4
+ static generateNonce(config: OAuth2StrategyConfig): Promise<string>;
5
+ static validateNonce(expectedNonce: string, nonce: string, config: OAuth2NonceConfig): Promise<boolean>;
6
+ static extractState<TContext>(context: TContext): string | null;
7
+ static extractNonce(idToken: string): string | null;
4
8
  }
9
+ export declare const prepareOAuth2Config: <TContext = any, TUser = any>(config: Partial<OAuth2StrategyConfig<TContext, TUser>>) => OAuth2StrategyConfig<TContext, TUser>;
@@ -1,22 +1,127 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
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;
4
24
  };
5
25
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.OAuth2Tools = void 0;
7
- const crypto_1 = __importDefault(require("crypto"));
26
+ exports.prepareOAuth2Config = exports.OAuth2Tools = void 0;
27
+ const Soap = __importStar(require("@soapjs/soap"));
28
+ const tools_1 = require("../../tools");
8
29
  class OAuth2Tools {
9
- static generateCodeVerifier() {
10
- return crypto_1.default.randomBytes(32).toString("hex");
30
+ static async generateState(config) {
31
+ return (await config.state?.generateState?.()) || (0, tools_1.generateRandomString)();
32
+ }
33
+ static async generateNonce(config) {
34
+ return (await config.nonce?.generateNonce?.()) || (0, tools_1.generateRandomString)();
35
+ }
36
+ static async validateNonce(expectedNonce, nonce, config) {
37
+ if (config.validateNonce) {
38
+ return config.validateNonce(expectedNonce, nonce);
39
+ }
40
+ return expectedNonce === nonce;
11
41
  }
12
- static generateCodeChallenge(codeVerifier) {
13
- return crypto_1.default
14
- .createHash("sha256")
15
- .update(codeVerifier)
16
- .digest("base64")
17
- .replace(/\+/g, "-")
18
- .replace(/\//g, "_")
19
- .replace(/=+$/, "");
42
+ static extractState(context) {
43
+ return context.query?.state || null;
44
+ }
45
+ static extractNonce(idToken) {
46
+ const decoded = JSON.parse(Buffer.from(idToken.split(".")[1], "base64").toString());
47
+ return decoded.nonce || null;
20
48
  }
21
49
  }
22
50
  exports.OAuth2Tools = OAuth2Tools;
51
+ const prepareOAuth2Config = (config) => {
52
+ return Soap.removeUndefinedProperties({
53
+ ...config,
54
+ grantType: config.grantType ?? "authorization_code",
55
+ responseType: config.responseType ?? "code",
56
+ scope: config.scope ?? "openid profile email",
57
+ autoLogoutOnRefreshFailure: config.autoLogoutOnRefreshFailure ?? false,
58
+ routes: {
59
+ login: {
60
+ path: "/auth/oauth2/login",
61
+ method: "GET",
62
+ ...config.routes.login,
63
+ },
64
+ callback: {
65
+ path: "/auth/oauth2/callback",
66
+ method: "GET",
67
+ ...config.routes.callback,
68
+ },
69
+ logout: config.routes?.logout
70
+ ? {
71
+ path: "/auth/oauth2/logout",
72
+ method: "POST",
73
+ ...config.routes.logout,
74
+ }
75
+ : undefined,
76
+ refresh: config.routes?.refresh
77
+ ? {
78
+ path: "/auth/oauth2/refresh",
79
+ method: "POST",
80
+ ...config.routes.refresh,
81
+ }
82
+ : undefined,
83
+ revoke: config.routes?.revoke
84
+ ? {
85
+ path: "/auth/oauth2/revoke",
86
+ method: "POST",
87
+ ...config.routes.revoke,
88
+ }
89
+ : undefined,
90
+ ...config.routes,
91
+ },
92
+ pkce: config.pkce
93
+ ? {
94
+ challenge: {
95
+ expiresIn: 300,
96
+ ...config.pkce.challenge,
97
+ },
98
+ verifier: {
99
+ expiresIn: 300,
100
+ ...config.pkce.verifier,
101
+ },
102
+ ...config.pkce,
103
+ }
104
+ : undefined,
105
+ state: config.state
106
+ ? {
107
+ generateState: () => Math.random().toString(36).substring(2, 15),
108
+ validateState: (storedState, returnedState) => storedState === returnedState,
109
+ ...config.state,
110
+ }
111
+ : undefined,
112
+ nonce: config.nonce
113
+ ? {
114
+ generateNonce: () => Math.random().toString(36).substring(2, 15),
115
+ validateNonce: (storedNonce, returnedNonce) => storedNonce === returnedNonce,
116
+ ...config.nonce,
117
+ }
118
+ : undefined,
119
+ jwks: config.jwks
120
+ ? {
121
+ algorithms: ["RS256"],
122
+ ...config.jwks,
123
+ }
124
+ : undefined,
125
+ });
126
+ };
127
+ exports.prepareOAuth2Config = prepareOAuth2Config;
@@ -1,4 +1,5 @@
1
- import { CredentailsConfig, PKCEConfig, TokenAuthStrategyConfig } from "../../types";
1
+ import { Algorithm } from "jsonwebtoken";
2
+ import { CredentailsConfig, PKCEConfig, AuthRouteConfig, TokenAuthStrategyConfig, PersistenceConfig, ContextOperationConfig } from "../../types";
2
3
  export interface OAuth2Endpoints {
3
4
  authorizationUrl: string;
4
5
  tokenUrl: string;
@@ -7,7 +8,26 @@ export interface OAuth2Endpoints {
7
8
  revocationUrl?: string;
8
9
  logoutUrl?: string;
9
10
  }
11
+ export interface OAuth2StateConfig<TContext = unknown, TData = any> {
12
+ persistence?: PersistenceConfig;
13
+ context?: ContextOperationConfig<TContext, TData>;
14
+ generateState?: () => string | Promise<string>;
15
+ validateState?: (storedState: TContext, returnedState: string) => boolean | Promise<boolean>;
16
+ }
17
+ export interface OAuth2NonceConfig<TContext = unknown, TData = any> {
18
+ persistence?: PersistenceConfig;
19
+ context?: ContextOperationConfig<TContext, TData>;
20
+ generateNonce?: () => string | Promise<string>;
21
+ validateNonce?: (storedNonce: string | null, returnedNonce: string) => boolean | Promise<boolean>;
22
+ }
23
+ export type JwksConfig = {
24
+ jwksUri: string;
25
+ issuer: string;
26
+ algorithms?: Algorithm[];
27
+ audience?: string;
28
+ };
10
29
  export interface OAuth2StrategyConfig<TContext = unknown, TUser = unknown> extends TokenAuthStrategyConfig<TContext, TUser> {
30
+ autoLogoutOnRefreshFailure?: boolean;
11
31
  clientId: string;
12
32
  clientSecret: string;
13
33
  redirectUri: string;
@@ -18,4 +38,15 @@ export interface OAuth2StrategyConfig<TContext = unknown, TUser = unknown> exten
18
38
  endpoints: OAuth2Endpoints;
19
39
  credentials?: CredentailsConfig<TContext>;
20
40
  pkce?: PKCEConfig<TContext>;
41
+ routes: {
42
+ login: AuthRouteConfig;
43
+ callback: AuthRouteConfig;
44
+ logout?: AuthRouteConfig;
45
+ refresh?: AuthRouteConfig;
46
+ revoke?: AuthRouteConfig;
47
+ [key: string]: AuthRouteConfig;
48
+ };
49
+ state?: OAuth2StateConfig<TContext>;
50
+ nonce?: OAuth2NonceConfig;
51
+ jwks?: JwksConfig;
21
52
  }
@@ -7,19 +7,25 @@ export declare abstract class TokenAuthStrategy<TContext = unknown, TUser = unkn
7
7
  protected session?: SessionHandler;
8
8
  protected logger?: Soap.Logger;
9
9
  constructor(config: TokenAuthStrategyConfig<TContext, TUser>, session?: SessionHandler, logger?: Soap.Logger);
10
- protected abstract retrieveAccessToken(context: TContext): Promise<string | undefined>;
11
- protected abstract retrieveRefreshToken(context: TContext): Promise<string | undefined>;
10
+ protected abstract extractAccessToken(context: TContext): string | undefined;
11
+ protected abstract extractRefreshToken(context: TContext): string | undefined;
12
12
  protected abstract verifyAccessToken(token: string): Promise<any>;
13
13
  protected abstract verifyRefreshToken(token: string): Promise<any>;
14
- protected abstract generateAccessToken(payload: any): Promise<string>;
15
- protected abstract generateRefreshToken(payload: any): Promise<string>;
16
- protected abstract storeAccessToken(token: string, context: TContext): Promise<void>;
17
- protected abstract storeRefreshToken(token: string, context: TContext): Promise<void>;
14
+ protected abstract generateAccessToken(data: TUser, context: TContext): Promise<string>;
15
+ protected abstract generateRefreshToken(data: TUser, context: TContext): Promise<string>;
16
+ protected abstract storeAccessToken(token: string): Promise<void>;
17
+ protected abstract storeRefreshToken(token: string): Promise<void>;
18
+ protected abstract invalidateAccessToken(token: string, context?: TContext): Promise<void>;
19
+ protected abstract invalidateRefreshToken(token: string, context?: TContext): Promise<void>;
18
20
  protected abstract embedAccessToken(token: string, context: TContext): void;
19
21
  protected abstract embedRefreshToken(token: string, context: TContext): void;
22
+ protected fetchUser(payload: unknown): Promise<TUser | null>;
20
23
  authenticate(context: TContext): Promise<AuthResult<TUser>>;
21
- rotateTokens(context: TContext): Promise<{
24
+ private verifyAndFetchUser;
25
+ refreshTokens(context: TContext, existingUser?: TUser): Promise<AuthResult<TUser>>;
26
+ issueTokens(user: TUser, context: TContext, rotate?: boolean): Promise<{
22
27
  accessToken: string;
23
- refreshToken?: string;
28
+ refreshToken: any;
24
29
  }>;
30
+ protected rotateToken(refreshToken: string, user: TUser, context: TContext): Promise<any>;
25
31
  }