@soapjs/soap-auth 0.4.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +261 -128
  2. package/build/index.d.ts +1 -0
  3. package/build/index.js +1 -0
  4. package/build/recipes/auth-config.recipes.d.ts +40 -0
  5. package/build/recipes/auth-config.recipes.js +135 -0
  6. package/build/recipes/http-context.helpers.d.ts +13 -0
  7. package/build/recipes/http-context.helpers.js +64 -0
  8. package/build/recipes/index.d.ts +3 -0
  9. package/build/recipes/index.js +19 -0
  10. package/build/recipes/oauth2-presets.d.ts +20 -0
  11. package/build/recipes/oauth2-presets.js +74 -0
  12. package/build/soap-auth.js +62 -0
  13. package/build/strategies/jwt/jwt.strategy.js +6 -3
  14. package/build/strategies/jwt/jwt.tools.js +8 -6
  15. package/build/strategies/local/local.strategy.d.ts +2 -2
  16. package/build/strategies/local/local.strategy.js +7 -7
  17. package/build/strategies/oauth2/hybrid.oauth2.strategy.js +1 -1
  18. package/build/strategies/oauth2/oauth2.strategy.js +2 -2
  19. package/build/strategies/oauth2/oauth2.types.d.ts +5 -0
  20. package/build/strategies/oauth2/providers/configurable-hybrid-oauth2.strategy.d.ts +19 -0
  21. package/build/strategies/oauth2/providers/configurable-hybrid-oauth2.strategy.js +85 -0
  22. package/build/strategies/oauth2/providers/configurable-oauth2.strategy.d.ts +11 -0
  23. package/build/strategies/oauth2/providers/configurable-oauth2.strategy.js +46 -0
  24. package/build/strategies/oauth2/providers/index.d.ts +2 -0
  25. package/build/strategies/oauth2/providers/index.js +2 -0
  26. package/build/strategies/oauth2/providers/provider.types.d.ts +3 -0
  27. package/build/types.d.ts +5 -2
  28. package/package.json +91 -13
  29. package/.claude/settings.local.json +0 -20
  30. package/build/__tests__/soap-auth.test.d.ts +0 -1
  31. package/build/__tests__/soap-auth.test.js +0 -136
  32. package/build/services/__tests__/account-lock.service.test.d.ts +0 -1
  33. package/build/services/__tests__/account-lock.service.test.js +0 -55
  34. package/build/services/__tests__/auth-throttle.service.test.d.ts +0 -1
  35. package/build/services/__tests__/auth-throttle.service.test.js +0 -48
  36. package/build/services/__tests__/jwks.service.test.d.ts +0 -1
  37. package/build/services/__tests__/jwks.service.test.js +0 -39
  38. package/build/services/__tests__/mfa.service.test.d.ts +0 -1
  39. package/build/services/__tests__/mfa.service.test.js +0 -66
  40. package/build/services/__tests__/password.service.test.d.ts +0 -1
  41. package/build/services/__tests__/password.service.test.js +0 -73
  42. package/build/services/__tests__/pkce.service.test.d.ts +0 -1
  43. package/build/services/__tests__/pkce.service.test.js +0 -77
  44. package/build/services/__tests__/rate-limit.service.test.d.ts +0 -1
  45. package/build/services/__tests__/rate-limit.service.test.js +0 -37
  46. package/build/services/__tests__/role.service.test.d.ts +0 -1
  47. package/build/services/__tests__/role.service.test.js +0 -31
  48. package/build/services/__tests__/totp.service.test.d.ts +0 -1
  49. package/build/services/__tests__/totp.service.test.js +0 -120
  50. package/build/session/__tests__/file.session-store.test.d.ts +0 -1
  51. package/build/session/__tests__/file.session-store.test.js +0 -117
  52. package/build/session/__tests__/memory.session-store.test.d.ts +0 -1
  53. package/build/session/__tests__/memory.session-store.test.js +0 -77
  54. package/build/session/__tests__/session-handler.test.d.ts +0 -1
  55. package/build/session/__tests__/session-handler.test.js +0 -345
  56. package/build/strategies/__tests__/base-auth.strategy.test.d.ts +0 -15
  57. package/build/strategies/__tests__/base-auth.strategy.test.js +0 -138
  58. package/build/strategies/__tests__/credential-auth.strategy.test.d.ts +0 -15
  59. package/build/strategies/__tests__/credential-auth.strategy.test.js +0 -266
  60. package/build/strategies/__tests__/token-auth.strategy.test.d.ts +0 -29
  61. package/build/strategies/__tests__/token-auth.strategy.test.js +0 -299
  62. package/build/strategies/api-key/__tests__/api-key.strategy.test.d.ts +0 -1
  63. package/build/strategies/api-key/__tests__/api-key.strategy.test.js +0 -103
  64. package/build/strategies/basic/__tests__/basic.strategy.test.d.ts +0 -1
  65. package/build/strategies/basic/__tests__/basic.strategy.test.js +0 -104
  66. package/build/strategies/jwt/__tests__/jwt.strategy.test.d.ts +0 -1
  67. package/build/strategies/jwt/__tests__/jwt.strategy.test.js +0 -156
  68. package/build/strategies/jwt/__tests__/jwt.tools.test.d.ts +0 -1
  69. package/build/strategies/jwt/__tests__/jwt.tools.test.js +0 -98
  70. package/build/strategies/local/__tests__/local.strategy.test.d.ts +0 -1
  71. package/build/strategies/local/__tests__/local.strategy.test.js +0 -121
  72. package/build/strategies/oauth2/__tests__/oauth2.strategy.test.d.ts +0 -1
  73. package/build/strategies/oauth2/__tests__/oauth2.strategy.test.js +0 -239
  74. package/build/strategies/oauth2/providers/__tests__/social-providers.test.d.ts +0 -1
  75. package/build/strategies/oauth2/providers/__tests__/social-providers.test.js +0 -201
  76. package/build/utils/__tests__/validation.test.d.ts +0 -1
  77. package/build/utils/__tests__/validation.test.js +0 -181
  78. package/jest.config.unit.json +0 -10
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.redirect = exports.setCookie = exports.getCookie = exports.setBearerToken = exports.extractBearerToken = void 0;
4
+ function extractBearerToken(context) {
5
+ const header = context?.req?.headers?.authorization ??
6
+ context?.request?.headers?.authorization ??
7
+ context?.headers?.authorization;
8
+ if (typeof header === "string" && header.toLowerCase().startsWith("bearer ")) {
9
+ return header.slice(7);
10
+ }
11
+ return undefined;
12
+ }
13
+ exports.extractBearerToken = extractBearerToken;
14
+ function setBearerToken(context, token) {
15
+ const value = `Bearer ${token}`;
16
+ if (typeof context?.res?.setHeader === "function") {
17
+ context.res.setHeader("Authorization", value);
18
+ }
19
+ else if (typeof context?.response?.setHeader === "function") {
20
+ context.response.setHeader("Authorization", value);
21
+ }
22
+ else if (typeof context?.setHeader === "function") {
23
+ context.setHeader("Authorization", value);
24
+ }
25
+ }
26
+ exports.setBearerToken = setBearerToken;
27
+ function getCookie(context, name) {
28
+ return (context?.req?.cookies?.[name] ??
29
+ context?.request?.cookies?.[name] ??
30
+ context?.cookies?.[name]);
31
+ }
32
+ exports.getCookie = getCookie;
33
+ function setCookie(context, name, value, options = {}) {
34
+ if (typeof context?.res?.cookie === "function") {
35
+ context.res.cookie(name, value, options);
36
+ }
37
+ else if (typeof context?.response?.cookie === "function") {
38
+ context.response.cookie(name, value, options);
39
+ }
40
+ else if (typeof context?.cookie === "function") {
41
+ context.cookie(name, value, options);
42
+ }
43
+ }
44
+ exports.setCookie = setCookie;
45
+ function redirect(context, url, status = 302) {
46
+ if (typeof context?.res?.redirect === "function") {
47
+ context.res.redirect(url);
48
+ }
49
+ else if (typeof context?.response?.redirect === "function") {
50
+ context.response.redirect(url);
51
+ }
52
+ else if (typeof context?.redirect === "function") {
53
+ context.redirect(url);
54
+ }
55
+ else if (typeof context?.res?.setHeader === "function") {
56
+ context.res.setHeader("Location", url);
57
+ context.res.status?.(status);
58
+ }
59
+ else if (typeof context?.setHeader === "function") {
60
+ context.setHeader("Location", url);
61
+ context.status?.(status);
62
+ }
63
+ }
64
+ exports.redirect = redirect;
@@ -0,0 +1,3 @@
1
+ export * from "./auth-config.recipes";
2
+ export * from "./http-context.helpers";
3
+ export * from "./oauth2-presets";
@@ -0,0 +1,19 @@
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./auth-config.recipes"), exports);
18
+ __exportStar(require("./http-context.helpers"), exports);
19
+ __exportStar(require("./oauth2-presets"), exports);
@@ -0,0 +1,20 @@
1
+ import { OAuth2Endpoints } from "../strategies/oauth2/oauth2.types";
2
+ export type OAuth2ProviderPreset = "auth0" | "keycloak" | "discord" | "google" | "github" | "facebook";
3
+ export interface Auth0PresetOptions {
4
+ domain: string;
5
+ }
6
+ export interface KeycloakPresetOptions {
7
+ baseUrl: string;
8
+ realm: string;
9
+ }
10
+ export interface FacebookPresetOptions {
11
+ version?: string;
12
+ }
13
+ export declare const oauth2ProviderEndpoints: {
14
+ auth0(options: Auth0PresetOptions): OAuth2Endpoints;
15
+ keycloak(options: KeycloakPresetOptions): OAuth2Endpoints;
16
+ discord(): OAuth2Endpoints;
17
+ google(): OAuth2Endpoints;
18
+ github(): OAuth2Endpoints;
19
+ facebook(options?: FacebookPresetOptions): OAuth2Endpoints;
20
+ };
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.oauth2ProviderEndpoints = void 0;
4
+ function trimTrailingSlash(value) {
5
+ return value.replace(/\/+$/, "");
6
+ }
7
+ function ensureHttpsUrl(value) {
8
+ if (/^https?:\/\//i.test(value)) {
9
+ return trimTrailingSlash(value);
10
+ }
11
+ return `https://${trimTrailingSlash(value)}`;
12
+ }
13
+ exports.oauth2ProviderEndpoints = {
14
+ auth0(options) {
15
+ if (!options?.domain) {
16
+ throw new Error("Auth0 OAuth2 preset requires domain.");
17
+ }
18
+ const baseUrl = ensureHttpsUrl(options.domain);
19
+ return {
20
+ authorizationUrl: `${baseUrl}/authorize`,
21
+ tokenUrl: `${baseUrl}/oauth/token`,
22
+ userInfoUrl: `${baseUrl}/userinfo`,
23
+ revocationUrl: `${baseUrl}/oauth/revoke`,
24
+ logoutUrl: `${baseUrl}/v2/logout`,
25
+ };
26
+ },
27
+ keycloak(options) {
28
+ if (!options?.baseUrl || !options?.realm) {
29
+ throw new Error("Keycloak OAuth2 preset requires baseUrl and realm.");
30
+ }
31
+ const baseUrl = ensureHttpsUrl(options.baseUrl);
32
+ const realmUrl = `${baseUrl}/realms/${encodeURIComponent(options.realm)}`;
33
+ return {
34
+ authorizationUrl: `${realmUrl}/protocol/openid-connect/auth`,
35
+ tokenUrl: `${realmUrl}/protocol/openid-connect/token`,
36
+ userInfoUrl: `${realmUrl}/protocol/openid-connect/userinfo`,
37
+ revocationUrl: `${realmUrl}/protocol/openid-connect/revoke`,
38
+ logoutUrl: `${realmUrl}/protocol/openid-connect/logout`,
39
+ };
40
+ },
41
+ discord() {
42
+ return {
43
+ authorizationUrl: "https://discord.com/oauth2/authorize",
44
+ tokenUrl: "https://discord.com/api/oauth2/token",
45
+ userInfoUrl: "https://discord.com/api/users/@me",
46
+ revocationUrl: "https://discord.com/api/oauth2/token/revoke",
47
+ };
48
+ },
49
+ google() {
50
+ return {
51
+ authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth",
52
+ tokenUrl: "https://oauth2.googleapis.com/token",
53
+ userInfoUrl: "https://openidconnect.googleapis.com/v1/userinfo",
54
+ revocationUrl: "https://oauth2.googleapis.com/revoke",
55
+ };
56
+ },
57
+ github() {
58
+ return {
59
+ authorizationUrl: "https://github.com/login/oauth/authorize",
60
+ tokenUrl: "https://github.com/login/oauth/access_token",
61
+ userInfoUrl: "https://api.github.com/user",
62
+ revocationUrl: "https://api.github.com/applications/{client_id}/token",
63
+ };
64
+ },
65
+ facebook(options = {}) {
66
+ const version = options.version ?? "v19.0";
67
+ return {
68
+ authorizationUrl: "https://www.facebook.com/dialog/oauth",
69
+ tokenUrl: `https://graph.facebook.com/${version}/oauth/access_token`,
70
+ userInfoUrl: `https://graph.facebook.com/${version}/me`,
71
+ revocationUrl: `https://graph.facebook.com/${version}/me/permissions`,
72
+ };
73
+ },
74
+ };
@@ -7,6 +7,7 @@ const jwt_strategy_1 = require("./strategies/jwt/jwt.strategy");
7
7
  const local_strategy_1 = require("./strategies/local/local.strategy");
8
8
  const basic_strategy_1 = require("./strategies/basic/basic.strategy");
9
9
  const api_key_strategy_1 = require("./strategies/api-key/api-key.strategy");
10
+ const providers_1 = require("./strategies/oauth2/providers");
10
11
  class SoapAuth {
11
12
  requiredStrategyMethods = ["authenticate"];
12
13
  strategies = new Map();
@@ -219,6 +220,67 @@ class SoapAuth {
219
220
  if (sharedJwt) {
220
221
  auth.addStrategy(sharedJwt, "jwt", "http");
221
222
  }
223
+ if (config.http.oauth2) {
224
+ for (const [provider, providerConfig] of Object.entries(config.http.oauth2)) {
225
+ switch (provider) {
226
+ case "google":
227
+ auth.addStrategy(new providers_1.GoogleStrategy(providerConfig, sessionHandler, sharedJwt, logger), "google", "http");
228
+ break;
229
+ case "github":
230
+ auth.addStrategy(new providers_1.GitHubStrategy(providerConfig, sessionHandler, sharedJwt, logger), "github", "http");
231
+ break;
232
+ case "facebook":
233
+ auth.addStrategy(new providers_1.FacebookStrategy(providerConfig, sessionHandler, sharedJwt, logger), "facebook", "http");
234
+ break;
235
+ default:
236
+ if (providerConfig.endpoints?.authorizationUrl &&
237
+ providerConfig.endpoints?.tokenUrl) {
238
+ auth.addStrategy(new providers_1.ConfigurableOAuth2Strategy({
239
+ ...providerConfig,
240
+ name: provider,
241
+ grantType: providerConfig.grantType ?? "authorization_code",
242
+ routes: {
243
+ login: {
244
+ path: `/auth/${provider}`,
245
+ method: "GET",
246
+ },
247
+ callback: {
248
+ path: `/auth/${provider}/callback`,
249
+ method: "GET",
250
+ },
251
+ ...providerConfig.routes,
252
+ },
253
+ }, sessionHandler, sharedJwt, logger), provider, "http");
254
+ break;
255
+ }
256
+ throw new Error(`OAuth2 provider "${provider}" requires endpoints.authorizationUrl and endpoints.tokenUrl, or a custom strategy via http.custom.`);
257
+ }
258
+ }
259
+ }
260
+ if (config.http.hybridOAuth2) {
261
+ for (const [provider, providerConfig] of Object.entries(config.http.hybridOAuth2)) {
262
+ if (!providerConfig.endpoints?.authorizationUrl ||
263
+ !providerConfig.endpoints?.tokenUrl) {
264
+ throw new Error(`Hybrid OAuth2 provider "${provider}" requires endpoints.authorizationUrl and endpoints.tokenUrl, or a custom strategy via http.custom.`);
265
+ }
266
+ auth.addStrategy(new providers_1.ConfigurableHybridOAuth2Strategy({
267
+ ...providerConfig,
268
+ name: provider,
269
+ grantType: providerConfig.grantType ?? "authorization_code",
270
+ routes: {
271
+ login: {
272
+ path: `/auth/${provider}`,
273
+ method: "GET",
274
+ },
275
+ callback: {
276
+ path: `/auth/${provider}/callback`,
277
+ method: "GET",
278
+ },
279
+ ...providerConfig.routes,
280
+ },
281
+ }, sessionHandler, sharedJwt, logger), provider, "http");
282
+ }
283
+ }
222
284
  if (config.http.custom) {
223
285
  for (const [name, strategy] of Object.entries(config.http.custom)) {
224
286
  auth.addStrategy(strategy, name, "http");
@@ -105,12 +105,12 @@ class JwtStrategy extends token_auth_strategy_1.TokenAuthStrategy {
105
105
  return Promise.resolve(jwt_tools_1.JwtTools.generateRefreshToken(payload, this.refreshTokenConfig));
106
106
  }
107
107
  async storeAccessToken(token) {
108
- if (this.accessTokenConfig.persistence.store) {
108
+ if (this.accessTokenConfig.persistence?.store) {
109
109
  await this.accessTokenConfig.persistence.store(token, null, this.accessTokenConfig.issuer.options.expiresIn);
110
110
  }
111
111
  }
112
112
  async storeRefreshToken(token) {
113
- if (this.refreshTokenConfig.persistence.store) {
113
+ if (this.refreshTokenConfig?.persistence?.store) {
114
114
  await this.refreshTokenConfig.persistence.store(token, null, this.refreshTokenConfig.issuer.options.expiresIn);
115
115
  }
116
116
  }
@@ -141,6 +141,9 @@ class JwtStrategy extends token_auth_strategy_1.TokenAuthStrategy {
141
141
  }
142
142
  }
143
143
  extractRefreshToken(context) {
144
+ if (!this.refreshTokenConfig) {
145
+ return undefined;
146
+ }
144
147
  if (this.refreshTokenConfig.extract) {
145
148
  return this.refreshTokenConfig.extract(context);
146
149
  }
@@ -161,7 +164,7 @@ class JwtStrategy extends token_auth_strategy_1.TokenAuthStrategy {
161
164
  async invalidateRefreshToken(token, context) {
162
165
  const refreshToken = token || (await this.extractRefreshToken(context));
163
166
  if (refreshToken) {
164
- await this.refreshTokenConfig.persistence?.remove?.(refreshToken);
167
+ await this.refreshTokenConfig?.persistence?.remove?.(refreshToken);
165
168
  if (context) {
166
169
  jwt_tools_1.JwtTools.clearDefaultJwtCookie(context);
167
170
  jwt_tools_1.JwtTools.clearDefaultJwtHeader(context);
@@ -88,6 +88,7 @@ class JwtTools {
88
88
  return context.req.cookies?.refreshToken;
89
89
  }
90
90
  static prepareAccessTokenConfig = (config) => {
91
+ const verifierOptions = config.verifier?.options ?? {};
91
92
  return Soap.removeUndefinedProperties({
92
93
  ...config,
93
94
  issuer: {
@@ -101,14 +102,15 @@ class JwtTools {
101
102
  verifier: {
102
103
  ...config.verifier,
103
104
  options: {
104
- ...config.verifier.options,
105
- algorithms: config.verifier.options.algorithms || ["HS256"],
106
- expiresIn: config.verifier.options.expiresIn || "1h",
105
+ ...verifierOptions,
106
+ algorithms: verifierOptions.algorithms || ["HS256"],
107
+ expiresIn: verifierOptions.expiresIn || "1h",
107
108
  },
108
109
  },
109
110
  });
110
111
  };
111
112
  static prepareRefreshTokenConfig = (config) => {
113
+ const verifierOptions = config.verifier?.options ?? {};
112
114
  return Soap.removeUndefinedProperties({
113
115
  ...config,
114
116
  issuer: {
@@ -122,9 +124,9 @@ class JwtTools {
122
124
  verifier: {
123
125
  ...config.verifier,
124
126
  options: {
125
- ...config.verifier.options,
126
- algorithms: config.verifier.options.algorithms || ["HS256"],
127
- expiresIn: config.verifier.options.expiresIn || "7d",
127
+ ...verifierOptions,
128
+ algorithms: verifierOptions.algorithms || ["HS256"],
129
+ expiresIn: verifierOptions.expiresIn || "7d",
128
130
  },
129
131
  },
130
132
  });
@@ -15,9 +15,9 @@ export declare class LocalStrategy<TContext = Soap.HttpContext, TUser extends So
15
15
  password: string;
16
16
  }>;
17
17
  protected verifyCredentials(identifier: string, password: string): Promise<boolean>;
18
- protected fetchUser(credentials: {
18
+ protected fetchUser(identifierOrCredentials: string | {
19
19
  identifier: string;
20
- password: string;
20
+ password?: string;
21
21
  }): Promise<TUser | null>;
22
22
  changePassword(context: TContext): Promise<void>;
23
23
  }
@@ -61,13 +61,13 @@ class LocalStrategy extends credential_auth_strategy_1.CredentialAuthStrategy {
61
61
  validation_1.ValidationUtils.nonEmptyString(password, "password");
62
62
  return this.config.credentials.verifyCredentials(identifier, password);
63
63
  }
64
- async fetchUser(credentials) {
65
- validation_1.ValidationUtils.required(credentials, "credentials");
66
- if (credentials && typeof credentials === "object" && !Array.isArray(credentials)) {
67
- validation_1.ValidationUtils.nonEmptyString(credentials.identifier, "credentials.identifier");
68
- validation_1.ValidationUtils.nonEmptyString(credentials.password, "credentials.password");
69
- }
70
- 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
71
  }
72
72
  async changePassword(context) {
73
73
  try {
@@ -49,7 +49,7 @@ class HybridOAuth2Strategy extends oauth2_strategy_1.OAuth2Strategy {
49
49
  if (!accessToken) {
50
50
  const code = this.extractAuthorizationCode(context);
51
51
  if (!code) {
52
- this.login(context);
52
+ await this.login(context);
53
53
  throw new errors_1.MissingAuthorizationCodeError();
54
54
  }
55
55
  await this.validateState(context);
@@ -202,7 +202,7 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
202
202
  let authorizationUrl = `${this.config.endpoints.authorizationUrl}?${params.toString()}`;
203
203
  if (this.pkce) {
204
204
  const codeVerifier = await this.pkce.generateCodeVerifier(context);
205
- const codeChallenge = this.pkce.generateCodeChallenge(codeVerifier, context);
205
+ const codeChallenge = await this.pkce.generateCodeChallenge(codeVerifier, context);
206
206
  authorizationUrl += `&code_challenge=${codeChallenge}&code_challenge_method=S256`;
207
207
  }
208
208
  return authorizationUrl;
@@ -212,7 +212,7 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
212
212
  grant_type: "authorization_code",
213
213
  client_id: this.config.clientId,
214
214
  code,
215
- redirectUri: this.config.redirectUri,
215
+ redirect_uri: this.config.redirectUri,
216
216
  };
217
217
  if (this.pkce) {
218
218
  const codeVerifier = this.pkce.extractCodeVerifier(context);
@@ -50,3 +50,8 @@ export interface OAuth2StrategyConfig<TContext = unknown, TUser = unknown> exten
50
50
  nonce?: OAuth2NonceConfig;
51
51
  jwks?: JwksConfig;
52
52
  }
53
+ export interface OAuth2ProviderConfig<TContext = unknown, TUser = unknown> extends Omit<OAuth2StrategyConfig<TContext, TUser>, "endpoints" | "grantType" | "routes"> {
54
+ grantType?: "authorization_code";
55
+ endpoints?: Partial<OAuth2Endpoints>;
56
+ routes?: OAuth2StrategyConfig<TContext, TUser>["routes"];
57
+ }
@@ -0,0 +1,19 @@
1
+ import * as Soap from "@soapjs/soap";
2
+ import { SessionHandler } from "../../../session/session-handler";
3
+ import { JwtStrategy } from "../../jwt/jwt.strategy";
4
+ import { HybridOAuth2Strategy } from "../hybrid.oauth2.strategy";
5
+ import { ConfigurableOAuth2StrategyConfig } from "./provider.types";
6
+ export declare class ConfigurableHybridOAuth2Strategy<TUser extends Soap.AuthUser = Soap.AuthUser> extends HybridOAuth2Strategy<Soap.HttpContext, TUser> {
7
+ protected config: ConfigurableOAuth2StrategyConfig<TUser>;
8
+ readonly name: string;
9
+ constructor(config: ConfigurableOAuth2StrategyConfig<TUser>, session?: SessionHandler, jwt?: JwtStrategy<Soap.HttpContext, TUser>, logger?: Soap.Logger);
10
+ protected extractAccessToken(ctx: Soap.HttpContext): Promise<string | undefined>;
11
+ protected extractRefreshToken(ctx: Soap.HttpContext): Promise<string | undefined>;
12
+ protected storeAccessToken(_token: string, _ctx: Soap.HttpContext): Promise<void>;
13
+ protected storeRefreshToken(_token: string, _ctx: Soap.HttpContext): Promise<void>;
14
+ protected embedAccessToken(token: string, ctx: Soap.HttpContext): void;
15
+ protected embedRefreshToken(token: string, ctx: Soap.HttpContext): void;
16
+ protected extractAuthorizationCode(ctx: Soap.HttpContext): string | null;
17
+ protected redirectUser(ctx: Soap.HttpContext, authUrl: string): void;
18
+ protected fetchUser(accessToken: string): Promise<TUser | null>;
19
+ }
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConfigurableHybridOAuth2Strategy = void 0;
4
+ const errors_1 = require("../../../errors");
5
+ const hybrid_oauth2_strategy_1 = require("../hybrid.oauth2.strategy");
6
+ class ConfigurableHybridOAuth2Strategy extends hybrid_oauth2_strategy_1.HybridOAuth2Strategy {
7
+ config;
8
+ name;
9
+ constructor(config, session, jwt, logger) {
10
+ super(config, session, jwt, logger);
11
+ this.config = config;
12
+ this.name = config.name;
13
+ }
14
+ extractAccessToken(ctx) {
15
+ const auth = ctx.req.headers?.authorization;
16
+ if (typeof auth === "string" && auth.startsWith("Bearer ")) {
17
+ return Promise.resolve(auth.slice(7));
18
+ }
19
+ return Promise.resolve(ctx.req.cookies?.access_token);
20
+ }
21
+ extractRefreshToken(ctx) {
22
+ return Promise.resolve(ctx.req.cookies?.refresh_token);
23
+ }
24
+ storeAccessToken(_token, _ctx) {
25
+ return Promise.resolve();
26
+ }
27
+ storeRefreshToken(_token, _ctx) {
28
+ return Promise.resolve();
29
+ }
30
+ embedAccessToken(token, ctx) {
31
+ ctx.res.setHeader("Authorization", `Bearer ${token}`);
32
+ }
33
+ embedRefreshToken(token, ctx) {
34
+ ctx.res.cookie("refresh_token", token, {
35
+ httpOnly: true,
36
+ secure: true,
37
+ sameSite: "lax",
38
+ maxAge: 30 * 24 * 60 * 60 * 1000,
39
+ });
40
+ }
41
+ extractAuthorizationCode(ctx) {
42
+ return ctx.req.query?.code ?? null;
43
+ }
44
+ redirectUser(ctx, authUrl) {
45
+ if (typeof ctx.res.redirect === "function") {
46
+ ctx.res.redirect(authUrl);
47
+ }
48
+ else {
49
+ ctx.res.setHeader("Location", authUrl);
50
+ ctx.res.status(302).json({ message: "Redirecting", location: authUrl });
51
+ }
52
+ }
53
+ async fetchUser(accessToken) {
54
+ try {
55
+ if (!this.config.endpoints.userInfoUrl) {
56
+ if (this.config.user?.fetchUser) {
57
+ return this.config.user.fetchUser(accessToken);
58
+ }
59
+ throw new errors_1.MissingConfigError("userInfoUrl");
60
+ }
61
+ const response = await fetch(this.config.endpoints.userInfoUrl, {
62
+ headers: { Authorization: `Bearer ${accessToken}` },
63
+ });
64
+ if (!response.ok) {
65
+ return null;
66
+ }
67
+ const profile = await response.json();
68
+ if (this.config.user?.validateUser) {
69
+ return this.config.user.validateUser(profile);
70
+ }
71
+ return {
72
+ id: profile.sub ?? profile.id,
73
+ email: profile.email,
74
+ username: profile.preferred_username ?? profile.username ?? profile.name,
75
+ name: profile.name,
76
+ picture: profile.picture ?? profile.avatar_url,
77
+ };
78
+ }
79
+ catch (error) {
80
+ this.logger?.error("ConfigurableHybridOAuth2Strategy.fetchUser failed:", error);
81
+ return null;
82
+ }
83
+ }
84
+ }
85
+ exports.ConfigurableHybridOAuth2Strategy = ConfigurableHybridOAuth2Strategy;
@@ -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 { ConfigurableOAuth2StrategyConfig } from "./provider.types";
6
+ export declare class ConfigurableOAuth2Strategy<TUser extends Soap.AuthUser = Soap.AuthUser> extends HttpOAuth2Strategy<TUser> {
7
+ protected config: ConfigurableOAuth2StrategyConfig<TUser>;
8
+ readonly name: string;
9
+ constructor(config: ConfigurableOAuth2StrategyConfig<TUser>, session?: SessionHandler, jwt?: JwtStrategy<Soap.HttpContext, TUser>, logger?: Soap.Logger);
10
+ protected fetchUser(accessToken: string): Promise<TUser | null>;
11
+ }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConfigurableOAuth2Strategy = void 0;
4
+ const http_oauth2_strategy_1 = require("./http-oauth2.strategy");
5
+ const errors_1 = require("../../../errors");
6
+ class ConfigurableOAuth2Strategy extends http_oauth2_strategy_1.HttpOAuth2Strategy {
7
+ config;
8
+ name;
9
+ constructor(config, session, jwt, logger) {
10
+ super(config, session, jwt, logger);
11
+ this.config = config;
12
+ this.name = config.name;
13
+ }
14
+ async fetchUser(accessToken) {
15
+ try {
16
+ if (!this.config.endpoints.userInfoUrl) {
17
+ if (this.config.user?.fetchUser) {
18
+ return this.config.user.fetchUser(accessToken);
19
+ }
20
+ throw new errors_1.MissingConfigError("userInfoUrl");
21
+ }
22
+ const response = await fetch(this.config.endpoints.userInfoUrl, {
23
+ headers: { Authorization: `Bearer ${accessToken}` },
24
+ });
25
+ if (!response.ok) {
26
+ return null;
27
+ }
28
+ const profile = await response.json();
29
+ if (this.config.user?.validateUser) {
30
+ return this.config.user.validateUser(profile);
31
+ }
32
+ return {
33
+ id: profile.sub ?? profile.id,
34
+ email: profile.email,
35
+ username: profile.preferred_username ?? profile.username ?? profile.name,
36
+ name: profile.name,
37
+ picture: profile.picture ?? profile.avatar_url,
38
+ };
39
+ }
40
+ catch (error) {
41
+ this.logger?.error("ConfigurableOAuth2Strategy.fetchUser failed:", error);
42
+ return null;
43
+ }
44
+ }
45
+ }
46
+ exports.ConfigurableOAuth2Strategy = ConfigurableOAuth2Strategy;
@@ -1,4 +1,6 @@
1
1
  export * from "./http-oauth2.strategy";
2
+ export * from "./configurable-oauth2.strategy";
3
+ export * from "./configurable-hybrid-oauth2.strategy";
2
4
  export * from "./google.strategy";
3
5
  export * from "./github.strategy";
4
6
  export * from "./facebook.strategy";
@@ -15,6 +15,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./http-oauth2.strategy"), exports);
18
+ __exportStar(require("./configurable-oauth2.strategy"), exports);
19
+ __exportStar(require("./configurable-hybrid-oauth2.strategy"), exports);
18
20
  __exportStar(require("./google.strategy"), exports);
19
21
  __exportStar(require("./github.strategy"), exports);
20
22
  __exportStar(require("./facebook.strategy"), exports);
@@ -5,3 +5,6 @@ export interface SocialProviderConfig<TUser extends Soap.AuthUser = Soap.AuthUse
5
5
  endpoints?: Partial<OAuth2Endpoints>;
6
6
  routes?: OAuth2StrategyConfig<Soap.HttpContext, TUser>["routes"];
7
7
  }
8
+ export interface ConfigurableOAuth2StrategyConfig<TUser extends Soap.AuthUser = Soap.AuthUser> extends OAuth2StrategyConfig<Soap.HttpContext, TUser> {
9
+ name: string;
10
+ }
package/build/types.d.ts CHANGED
@@ -2,7 +2,7 @@ import * as Soap from "@soapjs/soap";
2
2
  export type AuthResult<TUser extends Soap.AuthUser = Soap.AuthUser> = Soap.AuthResult<TUser>;
3
3
  export type AuthStrategy<TUser extends Soap.AuthUser = Soap.AuthUser> = Soap.AuthStrategy<TUser>;
4
4
  import { LocalStrategyConfig } from "./strategies/local/local.types";
5
- import { OAuth2StrategyConfig } from "./strategies/oauth2/oauth2.types";
5
+ import { OAuth2ProviderConfig } from "./strategies/oauth2/oauth2.types";
6
6
  import { ApiKeyStrategyConfig } from "./strategies/api-key/api-key.types";
7
7
  import { BasicStrategyConfig } from "./strategies/basic/basic.types";
8
8
  import { JwtConfig } from "./strategies/jwt/jwt.types";
@@ -175,7 +175,10 @@ export interface SoapHttpAuthConfig<TContext = unknown, TUser extends Soap.AuthU
175
175
  local?: LocalStrategyConfig<TContext, TUser>;
176
176
  jwt?: JwtConfig<TContext, TUser>;
177
177
  oauth2?: {
178
- [provider: string]: OAuth2StrategyConfig<TContext, TUser>;
178
+ [provider: string]: OAuth2ProviderConfig<TContext, TUser>;
179
+ };
180
+ hybridOAuth2?: {
181
+ [provider: string]: OAuth2ProviderConfig<TContext, TUser>;
179
182
  };
180
183
  apiKey?: ApiKeyStrategyConfig<TContext, TUser>;
181
184
  basic?: BasicStrategyConfig<TContext, TUser>;