@soapjs/soap-auth 0.3.3 → 0.4.4

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 (98) hide show
  1. package/.claude/settings.local.json +20 -0
  2. package/build/errors.d.ts +1 -1
  3. package/build/errors.js +2 -2
  4. package/build/index.d.ts +1 -0
  5. package/build/index.js +1 -0
  6. package/build/services/auth-throttle.service.d.ts +2 -2
  7. package/build/services/auth-throttle.service.js +2 -2
  8. package/build/services/index.d.ts +1 -0
  9. package/build/services/index.js +1 -0
  10. package/build/services/password.service.d.ts +7 -5
  11. package/build/services/password.service.js +76 -18
  12. package/build/services/totp.service.d.ts +16 -0
  13. package/build/services/totp.service.js +96 -0
  14. package/build/session/session-handler.d.ts +1 -0
  15. package/build/session/session-handler.js +39 -6
  16. package/build/soap-auth.d.ts +13 -6
  17. package/build/soap-auth.js +132 -5
  18. package/build/strategies/api-key/api-key.strategy.d.ts +4 -4
  19. package/build/strategies/api-key/api-key.strategy.js +3 -2
  20. package/build/strategies/base-auth.strategy.d.ts +5 -4
  21. package/build/strategies/basic/basic.strategy.d.ts +2 -1
  22. package/build/strategies/basic/basic.strategy.js +1 -0
  23. package/build/strategies/credential-auth.strategy.d.ts +7 -7
  24. package/build/strategies/credential-auth.strategy.js +9 -14
  25. package/build/strategies/index.d.ts +1 -0
  26. package/build/strategies/index.js +1 -0
  27. package/build/strategies/jwt/jwt.strategy.d.ts +3 -1
  28. package/build/strategies/jwt/jwt.strategy.js +41 -9
  29. package/build/strategies/jwt/jwt.tools.js +16 -14
  30. package/build/strategies/local/local.strategy.d.ts +6 -3
  31. package/build/strategies/local/local.strategy.js +83 -2
  32. package/build/strategies/oauth2/hybrid.oauth2.strategy.d.ts +3 -3
  33. package/build/strategies/oauth2/hybrid.oauth2.strategy.js +1 -6
  34. package/build/strategies/oauth2/oauth2.errors.d.ts +1 -0
  35. package/build/strategies/oauth2/oauth2.errors.js +4 -0
  36. package/build/strategies/oauth2/oauth2.strategy.d.ts +6 -4
  37. package/build/strategies/oauth2/oauth2.strategy.js +114 -46
  38. package/build/strategies/oauth2/oauth2.tools.js +2 -2
  39. package/build/strategies/oauth2/oauth2.types.d.ts +2 -2
  40. package/build/strategies/oauth2/providers/facebook.strategy.d.ts +11 -0
  41. package/build/strategies/oauth2/providers/facebook.strategy.js +58 -0
  42. package/build/strategies/oauth2/providers/github.strategy.d.ts +11 -0
  43. package/build/strategies/oauth2/providers/github.strategy.js +56 -0
  44. package/build/strategies/oauth2/providers/google.strategy.d.ts +11 -0
  45. package/build/strategies/oauth2/providers/google.strategy.js +52 -0
  46. package/build/strategies/oauth2/providers/http-oauth2.strategy.d.ts +16 -0
  47. package/build/strategies/oauth2/providers/http-oauth2.strategy.js +49 -0
  48. package/build/strategies/oauth2/providers/index.d.ts +5 -0
  49. package/build/strategies/oauth2/providers/index.js +21 -0
  50. package/build/strategies/oauth2/providers/provider.types.d.ts +7 -0
  51. package/build/strategies/oauth2/providers/provider.types.js +2 -0
  52. package/build/strategies/token-auth.strategy.d.ts +4 -4
  53. package/build/strategies/token-auth.strategy.js +2 -3
  54. package/build/tools/tools.js +1 -2
  55. package/build/types.d.ts +31 -32
  56. package/build/utils/validation.d.ts +23 -0
  57. package/build/utils/validation.js +139 -0
  58. package/package.json +8 -7
  59. package/build/__tests__/soap-auth.test.d.ts +0 -1
  60. package/build/__tests__/soap-auth.test.js +0 -42
  61. package/build/services/__tests__/account-lock.service.test.d.ts +0 -1
  62. package/build/services/__tests__/account-lock.service.test.js +0 -55
  63. package/build/services/__tests__/auth-throttle.service.test.d.ts +0 -1
  64. package/build/services/__tests__/auth-throttle.service.test.js +0 -48
  65. package/build/services/__tests__/jwks.service.test.d.ts +0 -1
  66. package/build/services/__tests__/jwks.service.test.js +0 -39
  67. package/build/services/__tests__/mfa.service.test.d.ts +0 -1
  68. package/build/services/__tests__/mfa.service.test.js +0 -66
  69. package/build/services/__tests__/password.service.test.d.ts +0 -1
  70. package/build/services/__tests__/password.service.test.js +0 -66
  71. package/build/services/__tests__/pkce.service.test.d.ts +0 -1
  72. package/build/services/__tests__/pkce.service.test.js +0 -77
  73. package/build/services/__tests__/rate-limit.service.test.d.ts +0 -1
  74. package/build/services/__tests__/rate-limit.service.test.js +0 -37
  75. package/build/services/__tests__/role.service.test.d.ts +0 -1
  76. package/build/services/__tests__/role.service.test.js +0 -31
  77. package/build/session/__tests__/file.session-store.test.d.ts +0 -1
  78. package/build/session/__tests__/file.session-store.test.js +0 -117
  79. package/build/session/__tests__/memory.session-store.test.d.ts +0 -1
  80. package/build/session/__tests__/memory.session-store.test.js +0 -77
  81. package/build/session/__tests__/session-handler.test.d.ts +0 -1
  82. package/build/session/__tests__/session-handler.test.js +0 -337
  83. package/build/strategies/__tests__/base-auth.strategy.test.d.ts +0 -14
  84. package/build/strategies/__tests__/base-auth.strategy.test.js +0 -137
  85. package/build/strategies/__tests__/credential-auth.strategy.test.d.ts +0 -14
  86. package/build/strategies/__tests__/credential-auth.strategy.test.js +0 -265
  87. package/build/strategies/__tests__/token-auth.strategy.test.d.ts +0 -28
  88. package/build/strategies/__tests__/token-auth.strategy.test.js +0 -298
  89. package/build/strategies/api-key/__tests__/api-key.strategy.test.d.ts +0 -1
  90. package/build/strategies/api-key/__tests__/api-key.strategy.test.js +0 -103
  91. package/build/strategies/basic/__tests__/basic.strategy.test.d.ts +0 -1
  92. package/build/strategies/basic/__tests__/basic.strategy.test.js +0 -104
  93. package/build/strategies/jwt/__tests__/jwt.strategy.test.d.ts +0 -1
  94. package/build/strategies/jwt/__tests__/jwt.strategy.test.js +0 -156
  95. package/build/strategies/jwt/__tests__/jwt.tools.test.d.ts +0 -1
  96. package/build/strategies/jwt/__tests__/jwt.tools.test.js +0 -98
  97. package/build/strategies/local/__tests__/local.strategy.test.d.ts +0 -1
  98. package/build/strategies/local/__tests__/local.strategy.test.js +0 -115
@@ -3,24 +3,105 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LocalStrategy = void 0;
4
4
  const credential_auth_strategy_1 = require("../credential-auth.strategy");
5
5
  const local_tools_1 = require("./local.tools");
6
+ const errors_1 = require("../../errors");
7
+ const validation_1 = require("../../utils/validation");
6
8
  class LocalStrategy extends credential_auth_strategy_1.CredentialAuthStrategy {
7
9
  session;
8
10
  jwt;
9
11
  logger;
12
+ name = "local";
10
13
  constructor(config, session, jwt, logger) {
14
+ LocalStrategy.validateConfig(config);
11
15
  super((0, local_tools_1.prepareLocalConfig)(config), session, jwt, logger);
12
16
  this.session = session;
13
17
  this.jwt = jwt;
14
18
  this.logger = logger;
15
19
  }
20
+ static validateConfig(config) {
21
+ try {
22
+ validation_1.ValidationUtils.required(config, "config");
23
+ if (config.credentials) {
24
+ validation_1.ValidationUtils.required(config.credentials.extractCredentials, "config.credentials.extractCredentials");
25
+ validation_1.ValidationUtils.function(config.credentials.extractCredentials, "config.credentials.extractCredentials");
26
+ validation_1.ValidationUtils.required(config.credentials.verifyCredentials, "config.credentials.verifyCredentials");
27
+ validation_1.ValidationUtils.function(config.credentials.verifyCredentials, "config.credentials.verifyCredentials");
28
+ }
29
+ if (config.user) {
30
+ validation_1.ValidationUtils.required(config.user.fetchUser, "config.user.fetchUser");
31
+ validation_1.ValidationUtils.function(config.user.fetchUser, "config.user.fetchUser");
32
+ }
33
+ if (config.routes) {
34
+ validation_1.ValidationUtils.required(config.routes.login, "config.routes.login");
35
+ validation_1.ValidationUtils.required(config.routes.logout, "config.routes.logout");
36
+ validation_1.ValidationUtils.object(config.routes.login, "config.routes.login");
37
+ validation_1.ValidationUtils.object(config.routes.logout, "config.routes.logout");
38
+ if (config.routes.login) {
39
+ validation_1.ValidationUtils.nonEmptyString(config.routes.login.path, "config.routes.login.path");
40
+ validation_1.ValidationUtils.nonEmptyString(config.routes.login.method, "config.routes.login.method");
41
+ }
42
+ if (config.routes.logout) {
43
+ validation_1.ValidationUtils.nonEmptyString(config.routes.logout.path, "config.routes.logout.path");
44
+ validation_1.ValidationUtils.nonEmptyString(config.routes.logout.method, "config.routes.logout.method");
45
+ }
46
+ }
47
+ }
48
+ catch (error) {
49
+ if (error instanceof validation_1.ValidationError) {
50
+ throw error;
51
+ }
52
+ throw new validation_1.ValidationError(`Invalid Local strategy configuration: ${error.message}`);
53
+ }
54
+ }
16
55
  extractCredentials(context) {
56
+ validation_1.ValidationUtils.required(context, "context");
17
57
  return this.config.credentials.extractCredentials(context);
18
58
  }
19
59
  async verifyCredentials(identifier, password) {
60
+ validation_1.ValidationUtils.nonEmptyString(identifier, "identifier");
61
+ validation_1.ValidationUtils.nonEmptyString(password, "password");
20
62
  return this.config.credentials.verifyCredentials(identifier, password);
21
63
  }
22
- async fetchUser(credentials) {
23
- return this.config.user.fetchUser(credentials.identifier);
64
+ async fetchUser(identifierOrCredentials) {
65
+ validation_1.ValidationUtils.required(identifierOrCredentials, "identifier");
66
+ const identifier = typeof identifierOrCredentials === "string"
67
+ ? identifierOrCredentials
68
+ : identifierOrCredentials?.identifier;
69
+ validation_1.ValidationUtils.nonEmptyString(identifier, "identifier");
70
+ return this.config.user.fetchUser(identifier);
71
+ }
72
+ async changePassword(context) {
73
+ try {
74
+ const credentials = await this.config.credentials.extractCredentials(context);
75
+ if (!credentials) {
76
+ throw new errors_1.MissingCredentialsError();
77
+ }
78
+ if (!credentials.identifier ||
79
+ !credentials.password ||
80
+ !credentials.newPassword) {
81
+ throw new errors_1.InvalidCredentialsError();
82
+ }
83
+ await this.accountLock?.isAccountLocked(credentials.identifier);
84
+ await this.throttle?.checkFailedAttempts(credentials.identifier);
85
+ const user = await this.fetchUser(credentials);
86
+ if (!user) {
87
+ throw new errors_1.UserNotFoundError();
88
+ }
89
+ if ((await this.verifyCredentials(credentials.identifier, credentials.password)) === false) {
90
+ await this.throttle?.incrementFailedAttempts(credentials.identifier);
91
+ throw new errors_1.InvalidCredentialsError();
92
+ }
93
+ await this.password.validatePassword(credentials.newPassword, credentials.password);
94
+ await this.password.updatePassword(credentials.identifier, credentials.newPassword);
95
+ await this.throttle?.resetFailedAttempts(credentials.identifier);
96
+ await this.onSuccess("change_password", {
97
+ identifier: credentials.identifier,
98
+ });
99
+ this.logger?.info(`User password ${credentials.identifier} changed.`);
100
+ }
101
+ catch (error) {
102
+ await this.onFailure("change_password", { context, error });
103
+ throw error;
104
+ }
24
105
  }
25
106
  }
26
107
  exports.LocalStrategy = LocalStrategy;
@@ -1,5 +1,5 @@
1
- import { AuthResult } from "../../types";
1
+ import * as Soap from "@soapjs/soap";
2
2
  import { OAuth2Strategy } from "./oauth2.strategy";
3
- export declare abstract class HybridOAuth2Strategy<TContext = unknown, TUser = unknown> extends OAuth2Strategy<TContext, TUser> {
4
- authenticate(context: TContext): Promise<AuthResult<TUser>>;
3
+ export declare abstract class HybridOAuth2Strategy<TContext = Soap.HttpContext, TUser extends Soap.AuthUser = Soap.AuthUser> extends OAuth2Strategy<TContext, TUser> {
4
+ authenticate(context: TContext): Promise<Soap.AuthResult<TUser> | null>;
5
5
  }
@@ -3,8 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.HybridOAuth2Strategy = void 0;
4
4
  const errors_1 = require("../../errors");
5
5
  const oauth2_strategy_1 = require("./oauth2.strategy");
6
- const oauth2_errors_1 = require("./oauth2.errors");
7
- const oauth2_tools_1 = require("./oauth2.tools");
8
6
  class HybridOAuth2Strategy extends oauth2_strategy_1.OAuth2Strategy {
9
7
  async authenticate(context) {
10
8
  try {
@@ -54,10 +52,7 @@ class HybridOAuth2Strategy extends oauth2_strategy_1.OAuth2Strategy {
54
52
  this.login(context);
55
53
  throw new errors_1.MissingAuthorizationCodeError();
56
54
  }
57
- const returnedState = oauth2_tools_1.OAuth2Tools.extractState(context);
58
- if (!(await this.config.state?.validateState(context, returnedState))) {
59
- throw new oauth2_errors_1.InvalidStateError();
60
- }
55
+ await this.validateState(context);
61
56
  const exchangedTokens = await this.exchangeCodeForToken(context, code);
62
57
  accessToken = exchangedTokens.accessToken;
63
58
  refreshToken = exchangedTokens.refreshToken;
@@ -1,6 +1,7 @@
1
1
  export declare class InvalidNonceError extends Error {
2
2
  }
3
3
  export declare class InvalidStateError extends Error {
4
+ constructor(message?: string);
4
5
  }
5
6
  export declare class InvalidIdTokenError extends Error {
6
7
  }
@@ -5,6 +5,10 @@ class InvalidNonceError extends Error {
5
5
  }
6
6
  exports.InvalidNonceError = InvalidNonceError;
7
7
  class InvalidStateError extends Error {
8
+ constructor(message = "OAuth2 state mismatch (possible CSRF).") {
9
+ super(message);
10
+ this.name = "InvalidStateError";
11
+ }
8
12
  }
9
13
  exports.InvalidStateError = InvalidStateError;
10
14
  class InvalidIdTokenError extends Error {
@@ -1,16 +1,16 @@
1
1
  import * as Soap from "@soapjs/soap";
2
- import { AuthResult } from "../../types";
3
2
  import { OAuth2StrategyConfig } from "./oauth2.types";
4
3
  import { SessionHandler } from "../../session/session-handler";
5
4
  import { BaseAuthStrategy } from "../base-auth.strategy";
6
5
  import { JwtStrategy } from "../jwt/jwt.strategy";
7
6
  import { JwtService } from "../../services/jwks.service";
8
7
  import { PKCEService } from "../../services/pkce.service";
9
- export declare abstract class OAuth2Strategy<TContext = unknown, TUser = unknown> extends BaseAuthStrategy<TContext, TUser> {
8
+ export declare abstract class OAuth2Strategy<TContext = Soap.HttpContext, TUser extends Soap.AuthUser = Soap.AuthUser> extends BaseAuthStrategy<TContext, TUser> {
10
9
  protected config: OAuth2StrategyConfig<TContext, TUser>;
11
10
  protected session?: SessionHandler;
12
11
  protected jwt?: JwtStrategy<TContext, TUser>;
13
12
  protected logger?: Soap.Logger;
13
+ abstract readonly name: string;
14
14
  protected jwks: JwtService;
15
15
  protected pkce: PKCEService<TContext>;
16
16
  protected abstract extractAccessToken(context: TContext): Promise<string | undefined>;
@@ -27,13 +27,15 @@ export declare abstract class OAuth2Strategy<TContext = unknown, TUser = unknown
27
27
  identifier: string;
28
28
  password: string;
29
29
  }>;
30
- authenticate(context: TContext): Promise<AuthResult<TUser>>;
30
+ authenticate(context: TContext): Promise<Soap.AuthResult<TUser> | null>;
31
31
  protected processOAuthFlow(context: TContext): Promise<{
32
32
  accessToken: string;
33
33
  refreshToken?: string;
34
34
  }>;
35
35
  protected verifyAuthorizationCode(context: TContext, code: string): Promise<void>;
36
- protected buildAuthorizationUrl(context: TContext): Promise<string>;
36
+ protected startAuthorizationFlow(context: TContext): Promise<void>;
37
+ protected validateState(context: TContext): Promise<void>;
38
+ protected buildAuthorizationUrl(context: TContext, state?: string, nonce?: string): Promise<string>;
37
39
  protected exchangeCodeForToken(context: TContext, code: string): Promise<{
38
40
  accessToken: string;
39
41
  idToken?: string;
@@ -1,10 +1,6 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.OAuth2Strategy = void 0;
7
- const axios_1 = __importDefault(require("axios"));
8
4
  const errors_1 = require("../../errors");
9
5
  const oauth2_tools_1 = require("./oauth2.tools");
10
6
  const base_auth_strategy_1 = require("../base-auth.strategy");
@@ -118,7 +114,7 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
118
114
  if (this.session) {
119
115
  session = await this.session.issueSession(user, context);
120
116
  }
121
- await this.role.isAuthorized(user);
117
+ await this.role?.isAuthorized(user);
122
118
  return { user: user, tokens: { accessToken, refreshToken }, session };
123
119
  }
124
120
  catch (error) {
@@ -133,6 +129,7 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
133
129
  if (this.config.grantType === "authorization_code") {
134
130
  const authorizationCode = this.extractAuthorizationCode(context);
135
131
  await this.verifyAuthorizationCode(context, authorizationCode);
132
+ await this.validateState(context);
136
133
  const tokenResult = await this.exchangeCodeForToken(context, authorizationCode);
137
134
  return tokenResult;
138
135
  }
@@ -155,13 +152,54 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
155
152
  async verifyAuthorizationCode(context, code) {
156
153
  if (!code) {
157
154
  this.logger?.warn("Authorization code missing, redirecting user.");
158
- const authUrl = await this.buildAuthorizationUrl(context);
159
- this.redirectUser(context, authUrl);
155
+ await this.startAuthorizationFlow(context);
160
156
  throw new errors_1.MissingAuthorizationCodeError();
161
157
  }
162
158
  }
163
- async buildAuthorizationUrl(context) {
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 ?? ""}`;
159
+ async startAuthorizationFlow(context) {
160
+ let state;
161
+ let nonce;
162
+ if (this.config.state) {
163
+ state = await oauth2_tools_1.OAuth2Tools.generateState(this.config);
164
+ await this.config.state.persistence?.store?.(state);
165
+ }
166
+ if (this.config.nonce) {
167
+ nonce = await oauth2_tools_1.OAuth2Tools.generateNonce(this.config);
168
+ await this.config.nonce.persistence?.store?.(nonce);
169
+ }
170
+ const authUrl = await this.buildAuthorizationUrl(context, state, nonce);
171
+ this.redirectUser(context, authUrl);
172
+ }
173
+ async validateState(context) {
174
+ if (!this.config.state) {
175
+ return;
176
+ }
177
+ const returnedState = oauth2_tools_1.OAuth2Tools.extractState(context);
178
+ const storedState = (await this.config.state.persistence?.read?.());
179
+ const valid = this.config.state.validateState
180
+ ? await this.config.state.validateState(storedState, returnedState)
181
+ : !!storedState && storedState === returnedState;
182
+ if (!valid) {
183
+ throw new oauth2_errors_1.InvalidStateError();
184
+ }
185
+ await this.config.state.persistence?.remove?.();
186
+ }
187
+ async buildAuthorizationUrl(context, state, nonce) {
188
+ const params = new URLSearchParams({
189
+ client_id: this.config.clientId,
190
+ redirect_uri: this.config.redirectUri,
191
+ response_type: "code",
192
+ scope: Array.isArray(this.config.scope)
193
+ ? this.config.scope.join(" ")
194
+ : this.config.scope ?? "",
195
+ });
196
+ if (state) {
197
+ params.set("state", state);
198
+ }
199
+ if (nonce) {
200
+ params.set("nonce", nonce);
201
+ }
202
+ let authorizationUrl = `${this.config.endpoints.authorizationUrl}?${params.toString()}`;
165
203
  if (this.pkce) {
166
204
  const codeVerifier = await this.pkce.generateCodeVerifier(context);
167
205
  const codeChallenge = this.pkce.generateCodeChallenge(codeVerifier, context);
@@ -186,11 +224,15 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
186
224
  else if (this.config.clientSecret) {
187
225
  data.client_secret = this.config.clientSecret;
188
226
  }
189
- const response = await axios_1.default.post(this.config.endpoints.tokenUrl, {
227
+ const response = await fetch(this.config.endpoints.tokenUrl, {
228
+ method: "POST",
190
229
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
191
230
  body: new URLSearchParams(data).toString(),
192
231
  });
193
- const tokenData = await response.data();
232
+ if (!response.ok) {
233
+ throw new Error(`Token exchange failed: ${response.status} ${response.statusText}`);
234
+ }
235
+ const tokenData = await response.json();
194
236
  return {
195
237
  accessToken: tokenData.access_token,
196
238
  refreshToken: tokenData.refresh_token,
@@ -199,12 +241,7 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
199
241
  }
200
242
  async login(context) {
201
243
  try {
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);
207
- this.redirectUser(context, authUrl);
244
+ await this.startAuthorizationFlow(context);
208
245
  }
209
246
  catch (error) {
210
247
  await this.onFailure("login", {
@@ -219,10 +256,13 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
219
256
  if (!this.config.endpoints.userInfoUrl) {
220
257
  throw new errors_1.MissingConfigError("userInfoUrl");
221
258
  }
222
- const response = await axios_1.default.get(this.config.endpoints.userInfoUrl, {
259
+ const response = await fetch(this.config.endpoints.userInfoUrl, {
223
260
  headers: { Authorization: `Bearer ${accessToken}` },
224
261
  });
225
- return (await this.config.user.validateUser(response.data)) || null;
262
+ if (!response.ok) {
263
+ throw new Error(`User info request failed: ${response.status}`);
264
+ }
265
+ return (await this.config.user.validateUser(await response.json())) || null;
226
266
  }
227
267
  catch (error) {
228
268
  this.logger?.error("Failed to fetch user information:", error);
@@ -230,25 +270,41 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
230
270
  }
231
271
  }
232
272
  async exchangeClientCredentials() {
233
- const response = await axios_1.default.post(this.config.endpoints.tokenUrl, {
234
- grant_type: "client_credentials",
235
- client_id: this.config.clientId,
236
- client_secret: this.config.clientSecret,
273
+ const response = await fetch(this.config.endpoints.tokenUrl, {
274
+ method: "POST",
275
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
276
+ body: new URLSearchParams({
277
+ grant_type: "client_credentials",
278
+ client_id: this.config.clientId,
279
+ client_secret: this.config.clientSecret,
280
+ }).toString(),
237
281
  });
238
- return { accessToken: response.data.access_token };
282
+ if (!response.ok) {
283
+ throw new Error(`Client credentials exchange failed: ${response.status}`);
284
+ }
285
+ const data = await response.json();
286
+ return { accessToken: data.access_token };
239
287
  }
240
288
  async exchangePasswordGrant(username, password) {
241
289
  if (!username || !password) {
242
290
  throw new errors_1.MissingCredentialsError();
243
291
  }
244
- const response = await axios_1.default.post(this.config.endpoints.tokenUrl, {
245
- grant_type: "password",
246
- client_id: this.config.clientId,
247
- client_secret: this.config.clientSecret,
248
- username,
249
- password,
292
+ const response = await fetch(this.config.endpoints.tokenUrl, {
293
+ method: "POST",
294
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
295
+ body: new URLSearchParams({
296
+ grant_type: "password",
297
+ client_id: this.config.clientId,
298
+ client_secret: this.config.clientSecret,
299
+ username,
300
+ password,
301
+ }).toString(),
250
302
  });
251
- return { accessToken: response.data.access_token };
303
+ if (!response.ok) {
304
+ throw new Error(`Password grant failed: ${response.status}`);
305
+ }
306
+ const data = await response.json();
307
+ return { accessToken: data.access_token };
252
308
  }
253
309
  async refreshAccessToken(context) {
254
310
  try {
@@ -256,22 +312,27 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
256
312
  if (!refreshToken) {
257
313
  throw new errors_1.MissingTokenError("Refresh");
258
314
  }
259
- const response = await axios_1.default.post(this.config.endpoints.tokenUrl, {
260
- grant_type: "refresh_token",
261
- client_id: this.config.clientId,
262
- client_secret: this.config.clientSecret,
263
- refresh_token: refreshToken,
315
+ const response = await fetch(this.config.endpoints.tokenUrl, {
316
+ method: "POST",
317
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
318
+ body: new URLSearchParams({
319
+ grant_type: "refresh_token",
320
+ client_id: this.config.clientId,
321
+ client_secret: this.config.clientSecret,
322
+ refresh_token: refreshToken,
323
+ }).toString(),
264
324
  });
265
- if (response.data.error) {
266
- this.logger?.warn(`OAuth2 error: ${response.data.error_description || response.data.error}`);
325
+ const responseData = await response.json();
326
+ if (responseData.error) {
327
+ this.logger?.warn(`OAuth2 error: ${responseData.error_description || responseData.error}`);
267
328
  }
268
- if (response.status !== 200 || !response.data.access_token) {
329
+ if (!response.ok || !responseData.access_token) {
269
330
  throw new Error("Failed to refresh token");
270
331
  }
271
332
  const refreshedTokens = {
272
- accessToken: response.data.access_token,
273
- refreshToken: response.data.refresh_token,
274
- idToken: response.data.id_token,
333
+ accessToken: responseData.access_token,
334
+ refreshToken: responseData.refresh_token,
335
+ idToken: responseData.id_token,
275
336
  };
276
337
  await this.storeAccessToken(refreshedTokens.accessToken, context);
277
338
  await this.embedAccessToken(refreshedTokens.accessToken, context);
@@ -322,11 +383,18 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
322
383
  throw new errors_1.MissingConfigError("revocationUrl");
323
384
  }
324
385
  try {
325
- await axios_1.default.post(this.config.endpoints.revocationUrl, {
326
- token,
327
- client_id: this.config.clientId,
328
- client_secret: this.config.clientSecret,
386
+ const response = await fetch(this.config.endpoints.revocationUrl, {
387
+ method: "POST",
388
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
389
+ body: new URLSearchParams({
390
+ token,
391
+ client_id: this.config.clientId,
392
+ client_secret: this.config.clientSecret,
393
+ }).toString(),
329
394
  });
395
+ if (!response.ok) {
396
+ throw new Error(`Token revocation failed: ${response.status}`);
397
+ }
330
398
  this.logger?.info("Token revoked successfully.");
331
399
  }
332
400
  catch (error) {
@@ -104,14 +104,14 @@ const prepareOAuth2Config = (config) => {
104
104
  : undefined,
105
105
  state: config.state
106
106
  ? {
107
- generateState: () => Math.random().toString(36).substring(2, 15),
107
+ generateState: () => (0, tools_1.generateRandomString)(),
108
108
  validateState: (storedState, returnedState) => storedState === returnedState,
109
109
  ...config.state,
110
110
  }
111
111
  : undefined,
112
112
  nonce: config.nonce
113
113
  ? {
114
- generateNonce: () => Math.random().toString(36).substring(2, 15),
114
+ generateNonce: () => (0, tools_1.generateRandomString)(),
115
115
  validateNonce: (storedNonce, returnedNonce) => storedNonce === returnedNonce,
116
116
  ...config.nonce,
117
117
  }
@@ -1,5 +1,5 @@
1
1
  import { Algorithm } from "jsonwebtoken";
2
- import { CredentailsConfig, PKCEConfig, AuthRouteConfig, TokenAuthStrategyConfig, PersistenceConfig, ContextOperationConfig } from "../../types";
2
+ import { CredentialsConfig, PKCEConfig, AuthRouteConfig, TokenAuthStrategyConfig, PersistenceConfig, ContextOperationConfig } from "../../types";
3
3
  export interface OAuth2Endpoints {
4
4
  authorizationUrl: string;
5
5
  tokenUrl: string;
@@ -36,7 +36,7 @@ export interface OAuth2StrategyConfig<TContext = unknown, TUser = unknown> exten
36
36
  responseType?: "code" | "token" | "id_token" | string;
37
37
  grantType: "authorization_code" | "client_credentials" | "password" | "refresh_token" | string;
38
38
  endpoints: OAuth2Endpoints;
39
- credentials?: CredentailsConfig<TContext>;
39
+ credentials?: CredentialsConfig<TContext>;
40
40
  pkce?: PKCEConfig<TContext>;
41
41
  routes: {
42
42
  login: AuthRouteConfig;
@@ -0,0 +1,11 @@
1
+ import * as Soap from "@soapjs/soap";
2
+ import { SessionHandler } from "../../../session/session-handler";
3
+ import { JwtStrategy } from "../../jwt/jwt.strategy";
4
+ import { HttpOAuth2Strategy } from "./http-oauth2.strategy";
5
+ import { SocialProviderConfig } from "./provider.types";
6
+ export type FacebookStrategyConfig<TUser extends Soap.AuthUser = Soap.AuthUser> = SocialProviderConfig<TUser>;
7
+ export declare class FacebookStrategy<TUser extends Soap.AuthUser = Soap.AuthUser> extends HttpOAuth2Strategy<TUser> {
8
+ readonly name = "facebook";
9
+ constructor(config: FacebookStrategyConfig<TUser>, session?: SessionHandler, jwt?: JwtStrategy<Soap.HttpContext, TUser>, logger?: Soap.Logger);
10
+ protected fetchUser(accessToken: string): Promise<TUser | null>;
11
+ }
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FacebookStrategy = void 0;
4
+ const http_oauth2_strategy_1 = require("./http-oauth2.strategy");
5
+ const FACEBOOK_API_VERSION = "v19.0";
6
+ const FACEBOOK_ENDPOINTS = {
7
+ authorizationUrl: "https://www.facebook.com/dialog/oauth",
8
+ tokenUrl: `https://graph.facebook.com/${FACEBOOK_API_VERSION}/oauth/access_token`,
9
+ userInfoUrl: `https://graph.facebook.com/${FACEBOOK_API_VERSION}/me`,
10
+ revocationUrl: `https://graph.facebook.com/${FACEBOOK_API_VERSION}/me/permissions`,
11
+ };
12
+ const FACEBOOK_FIELDS = "id,name,email,picture";
13
+ class FacebookStrategy extends http_oauth2_strategy_1.HttpOAuth2Strategy {
14
+ name = "facebook";
15
+ constructor(config, session, jwt, logger) {
16
+ super({
17
+ grantType: "authorization_code",
18
+ scope: config.scope ?? ["email", "public_profile"],
19
+ routes: {
20
+ login: { path: "/auth/facebook", method: "GET" },
21
+ callback: { path: "/auth/facebook/callback", method: "GET" },
22
+ logout: { path: "/auth/facebook/logout", method: "POST" },
23
+ ...config.routes,
24
+ },
25
+ ...config,
26
+ endpoints: { ...FACEBOOK_ENDPOINTS, ...config.endpoints },
27
+ }, session, jwt, logger);
28
+ }
29
+ async fetchUser(accessToken) {
30
+ try {
31
+ const url = new URL(FACEBOOK_ENDPOINTS.userInfoUrl);
32
+ url.searchParams.set("fields", FACEBOOK_FIELDS);
33
+ url.searchParams.set("access_token", accessToken);
34
+ const response = await fetch(url.toString());
35
+ if (!response.ok)
36
+ return null;
37
+ const profile = await response.json();
38
+ if (profile.error) {
39
+ this.logger?.error("Facebook API error:", profile.error);
40
+ return null;
41
+ }
42
+ if (this.config.user?.validateUser) {
43
+ return this.config.user.validateUser(profile);
44
+ }
45
+ return {
46
+ id: profile.id,
47
+ email: profile.email,
48
+ username: profile.name,
49
+ picture: profile.picture?.data?.url,
50
+ };
51
+ }
52
+ catch (error) {
53
+ this.logger?.error("FacebookStrategy.fetchUser failed:", error);
54
+ return null;
55
+ }
56
+ }
57
+ }
58
+ exports.FacebookStrategy = FacebookStrategy;
@@ -0,0 +1,11 @@
1
+ import * as Soap from "@soapjs/soap";
2
+ import { SessionHandler } from "../../../session/session-handler";
3
+ import { JwtStrategy } from "../../jwt/jwt.strategy";
4
+ import { HttpOAuth2Strategy } from "./http-oauth2.strategy";
5
+ import { SocialProviderConfig } from "./provider.types";
6
+ export type GitHubStrategyConfig<TUser extends Soap.AuthUser = Soap.AuthUser> = SocialProviderConfig<TUser>;
7
+ export declare class GitHubStrategy<TUser extends Soap.AuthUser = Soap.AuthUser> extends HttpOAuth2Strategy<TUser> {
8
+ readonly name = "github";
9
+ constructor(config: GitHubStrategyConfig<TUser>, session?: SessionHandler, jwt?: JwtStrategy<Soap.HttpContext, TUser>, logger?: Soap.Logger);
10
+ protected fetchUser(accessToken: string): Promise<TUser | null>;
11
+ }
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GitHubStrategy = void 0;
4
+ const http_oauth2_strategy_1 = require("./http-oauth2.strategy");
5
+ const GITHUB_ENDPOINTS = {
6
+ authorizationUrl: "https://github.com/login/oauth/authorize",
7
+ tokenUrl: "https://github.com/login/oauth/access_token",
8
+ userInfoUrl: "https://api.github.com/user",
9
+ revocationUrl: "https://api.github.com/applications/{client_id}/token",
10
+ };
11
+ class GitHubStrategy extends http_oauth2_strategy_1.HttpOAuth2Strategy {
12
+ name = "github";
13
+ constructor(config, session, jwt, logger) {
14
+ super({
15
+ grantType: "authorization_code",
16
+ scope: config.scope ?? ["read:user", "user:email"],
17
+ routes: {
18
+ login: { path: "/auth/github", method: "GET" },
19
+ callback: { path: "/auth/github/callback", method: "GET" },
20
+ logout: { path: "/auth/github/logout", method: "POST" },
21
+ ...config.routes,
22
+ },
23
+ ...config,
24
+ endpoints: { ...GITHUB_ENDPOINTS, ...config.endpoints },
25
+ }, session, jwt, logger);
26
+ }
27
+ async fetchUser(accessToken) {
28
+ try {
29
+ const response = await fetch(GITHUB_ENDPOINTS.userInfoUrl, {
30
+ headers: {
31
+ Authorization: `Bearer ${accessToken}`,
32
+ Accept: "application/vnd.github+json",
33
+ "X-GitHub-Api-Version": "2022-11-28",
34
+ },
35
+ });
36
+ if (!response.ok)
37
+ return null;
38
+ const profile = await response.json();
39
+ if (this.config.user?.validateUser) {
40
+ return this.config.user.validateUser(profile);
41
+ }
42
+ return {
43
+ id: profile.id,
44
+ email: profile.email,
45
+ username: profile.login,
46
+ name: profile.name,
47
+ avatarUrl: profile.avatar_url,
48
+ };
49
+ }
50
+ catch (error) {
51
+ this.logger?.error("GitHubStrategy.fetchUser failed:", error);
52
+ return null;
53
+ }
54
+ }
55
+ }
56
+ exports.GitHubStrategy = GitHubStrategy;
@@ -0,0 +1,11 @@
1
+ import * as Soap from "@soapjs/soap";
2
+ import { SessionHandler } from "../../../session/session-handler";
3
+ import { JwtStrategy } from "../../jwt/jwt.strategy";
4
+ import { HttpOAuth2Strategy } from "./http-oauth2.strategy";
5
+ import { SocialProviderConfig } from "./provider.types";
6
+ export type GoogleStrategyConfig<TUser extends Soap.AuthUser = Soap.AuthUser> = SocialProviderConfig<TUser>;
7
+ export declare class GoogleStrategy<TUser extends Soap.AuthUser = Soap.AuthUser> extends HttpOAuth2Strategy<TUser> {
8
+ readonly name = "google";
9
+ constructor(config: GoogleStrategyConfig<TUser>, session?: SessionHandler, jwt?: JwtStrategy<Soap.HttpContext, TUser>, logger?: Soap.Logger);
10
+ protected fetchUser(accessToken: string): Promise<TUser | null>;
11
+ }