@soapjs/soap-auth 0.4.4 → 1.0.1

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 (32) hide show
  1. package/README.md +286 -125
  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/services/pkce.service.js +8 -6
  13. package/build/soap-auth.js +62 -0
  14. package/build/strategies/jwt/jwt.strategy.d.ts +2 -2
  15. package/build/strategies/jwt/jwt.strategy.js +6 -6
  16. package/build/strategies/oauth2/hybrid.oauth2.strategy.js +2 -2
  17. package/build/strategies/oauth2/oauth2.strategy.d.ts +1 -1
  18. package/build/strategies/oauth2/oauth2.strategy.js +12 -8
  19. package/build/strategies/oauth2/oauth2.types.d.ts +8 -3
  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/strategies/token-auth.strategy.d.ts +2 -2
  28. package/build/strategies/token-auth.strategy.js +2 -2
  29. package/build/types.d.ts +22 -12
  30. package/package.json +137 -13
  31. package/.claude/settings.local.json +0 -20
  32. 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
+ };
@@ -24,8 +24,9 @@ class PKCEService {
24
24
  const cv = this.config.verifier.generate?.() || (0, tools_1.generateRandomString)();
25
25
  this.config.verifier.embed(context, cv);
26
26
  const expirationTime = Date.now() + (this.config.verifier.expiresIn ?? 300) * 1000;
27
- await this.config.verifier.persistence?.store(cv, {
27
+ await this.config.verifier.persistence?.store(cv, context, {
28
28
  expiration: expirationTime,
29
+ key: "code_verifier",
29
30
  });
30
31
  return cv;
31
32
  }
@@ -37,8 +38,9 @@ class PKCEService {
37
38
  this.defaultGenerateCodeChallenge(codeVerifier);
38
39
  this.config.challenge.embed(context, challenge);
39
40
  const expirationTime = Date.now() + (this.config.challenge.expiresIn ?? 300) * 1000;
40
- await this.config.challenge.persistence?.store?.(challenge, {
41
+ await this.config.challenge.persistence?.store?.(challenge, context, {
41
42
  expiration: expirationTime,
43
+ key: "code_challenge",
42
44
  });
43
45
  return challenge;
44
46
  }
@@ -49,7 +51,7 @@ class PKCEService {
49
51
  const codeVerifier = this.extractCodeVerifier(context);
50
52
  if (!codeVerifier)
51
53
  return true;
52
- const stored = await this.config.verifier?.persistence?.read?.(codeVerifier);
54
+ const stored = await this.config.verifier?.persistence?.read?.(context, codeVerifier);
53
55
  if (!stored || !stored.expiration)
54
56
  return true;
55
57
  return Date.now() > stored.expiration;
@@ -58,7 +60,7 @@ class PKCEService {
58
60
  const challenge = this.extractCodeChallenge(context);
59
61
  if (!challenge)
60
62
  return true;
61
- const stored = await this.config.challenge?.persistence?.read?.(challenge);
63
+ const stored = await this.config.challenge?.persistence?.read?.(context, challenge);
62
64
  if (!stored || !stored.expiration)
63
65
  return true;
64
66
  return Date.now() > stored.expiration;
@@ -66,14 +68,14 @@ class PKCEService {
66
68
  async clearCodeVerifier(context) {
67
69
  const cv = this.config.verifier.extract(context);
68
70
  if (cv) {
69
- await this.config.verifier.persistence?.remove?.(cv);
71
+ await this.config.verifier.persistence?.remove?.(context, cv);
70
72
  this.config.verifier.embed(context, "");
71
73
  }
72
74
  }
73
75
  async clearCodeChallenge(context) {
74
76
  const challenge = this.config.challenge.extract(context);
75
77
  if (challenge) {
76
- await this.config.challenge.persistence?.remove?.(challenge);
78
+ await this.config.challenge.persistence?.remove?.(context, challenge);
77
79
  this.config.challenge.embed(context, "");
78
80
  }
79
81
  }
@@ -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");
@@ -13,8 +13,8 @@ export declare class JwtStrategy<TContext = Soap.HttpContext, TUser extends Soap
13
13
  protected verifyRefreshToken(token: string): Promise<any>;
14
14
  protected generateAccessToken(user: TUser, context: TContext): Promise<string>;
15
15
  protected generateRefreshToken(user: TUser, context: TContext): Promise<string>;
16
- protected storeAccessToken(token: string): Promise<void>;
17
- protected storeRefreshToken(token: string): Promise<void>;
16
+ protected storeAccessToken(token: string, context?: TContext): Promise<void>;
17
+ protected storeRefreshToken(token: string, context?: TContext): Promise<void>;
18
18
  protected embedAccessToken(token: string, context: TContext): void;
19
19
  protected embedRefreshToken(token: string, context: TContext): void;
20
20
  protected extractAccessToken(context: TContext): string | undefined;
@@ -104,14 +104,14 @@ class JwtStrategy extends token_auth_strategy_1.TokenAuthStrategy {
104
104
  const payload = this.buildAccessTokenPayload(user, context);
105
105
  return Promise.resolve(jwt_tools_1.JwtTools.generateRefreshToken(payload, this.refreshTokenConfig));
106
106
  }
107
- async storeAccessToken(token) {
107
+ async storeAccessToken(token, context) {
108
108
  if (this.accessTokenConfig.persistence?.store) {
109
- await this.accessTokenConfig.persistence.store(token, null, this.accessTokenConfig.issuer.options.expiresIn);
109
+ await this.accessTokenConfig.persistence.store(token, context ?? null, { expiresIn: this.accessTokenConfig.issuer.options.expiresIn });
110
110
  }
111
111
  }
112
- async storeRefreshToken(token) {
112
+ async storeRefreshToken(token, context) {
113
113
  if (this.refreshTokenConfig?.persistence?.store) {
114
- await this.refreshTokenConfig.persistence.store(token, null, this.refreshTokenConfig.issuer.options.expiresIn);
114
+ await this.refreshTokenConfig.persistence.store(token, context ?? null, { expiresIn: this.refreshTokenConfig.issuer.options.expiresIn });
115
115
  }
116
116
  }
117
117
  embedAccessToken(token, context) {
@@ -164,7 +164,7 @@ class JwtStrategy extends token_auth_strategy_1.TokenAuthStrategy {
164
164
  async invalidateRefreshToken(token, context) {
165
165
  const refreshToken = token || (await this.extractRefreshToken(context));
166
166
  if (refreshToken) {
167
- await this.refreshTokenConfig?.persistence?.remove?.(refreshToken);
167
+ await this.refreshTokenConfig?.persistence?.remove?.(context ?? null, refreshToken);
168
168
  if (context) {
169
169
  jwt_tools_1.JwtTools.clearDefaultJwtCookie(context);
170
170
  jwt_tools_1.JwtTools.clearDefaultJwtHeader(context);
@@ -175,7 +175,7 @@ class JwtStrategy extends token_auth_strategy_1.TokenAuthStrategy {
175
175
  async invalidateAccessToken(token, context) {
176
176
  const accessToken = token || (await this.extractAccessToken(context));
177
177
  if (accessToken) {
178
- await this.accessTokenConfig.persistence?.remove?.(accessToken);
178
+ await this.accessTokenConfig.persistence?.remove?.(context ?? null, accessToken);
179
179
  if (context) {
180
180
  jwt_tools_1.JwtTools.clearDefaultJwtCookie(context);
181
181
  jwt_tools_1.JwtTools.clearDefaultJwtHeader(context);
@@ -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);
@@ -59,7 +59,7 @@ class HybridOAuth2Strategy extends oauth2_strategy_1.OAuth2Strategy {
59
59
  idToken = exchangedTokens.idToken;
60
60
  }
61
61
  const user = idToken
62
- ? await this.verifyIdToken(idToken)
62
+ ? await this.verifyIdToken(idToken, context)
63
63
  : await this.fetchUser(accessToken);
64
64
  if (!user)
65
65
  throw new errors_1.UserNotFoundError();
@@ -55,7 +55,7 @@ export declare abstract class OAuth2Strategy<TContext = Soap.HttpContext, TUser
55
55
  idToken?: string;
56
56
  }>;
57
57
  protected handleTokenRefreshFailure(context: TContext): Promise<void>;
58
- protected verifyIdToken(idToken: string): Promise<TUser | null>;
58
+ protected verifyIdToken(idToken: string, context?: TContext): Promise<TUser | null>;
59
59
  revokeToken(token: string): Promise<void>;
60
60
  protected isTokenExpired(token: string): boolean;
61
61
  }
@@ -161,11 +161,15 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
161
161
  let nonce;
162
162
  if (this.config.state) {
163
163
  state = await oauth2_tools_1.OAuth2Tools.generateState(this.config);
164
- await this.config.state.persistence?.store?.(state);
164
+ await this.config.state.persistence?.store?.(state, context, {
165
+ key: "state",
166
+ });
165
167
  }
166
168
  if (this.config.nonce) {
167
169
  nonce = await oauth2_tools_1.OAuth2Tools.generateNonce(this.config);
168
- await this.config.nonce.persistence?.store?.(nonce);
170
+ await this.config.nonce.persistence?.store?.(nonce, context, {
171
+ key: "nonce",
172
+ });
169
173
  }
170
174
  const authUrl = await this.buildAuthorizationUrl(context, state, nonce);
171
175
  this.redirectUser(context, authUrl);
@@ -175,14 +179,14 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
175
179
  return;
176
180
  }
177
181
  const returnedState = oauth2_tools_1.OAuth2Tools.extractState(context);
178
- const storedState = (await this.config.state.persistence?.read?.());
182
+ const storedState = (await this.config.state.persistence?.read?.(context, "state"));
179
183
  const valid = this.config.state.validateState
180
184
  ? await this.config.state.validateState(storedState, returnedState)
181
185
  : !!storedState && storedState === returnedState;
182
186
  if (!valid) {
183
187
  throw new oauth2_errors_1.InvalidStateError();
184
188
  }
185
- await this.config.state.persistence?.remove?.();
189
+ await this.config.state.persistence?.remove?.(context, "state");
186
190
  }
187
191
  async buildAuthorizationUrl(context, state, nonce) {
188
192
  const params = new URLSearchParams({
@@ -202,7 +206,7 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
202
206
  let authorizationUrl = `${this.config.endpoints.authorizationUrl}?${params.toString()}`;
203
207
  if (this.pkce) {
204
208
  const codeVerifier = await this.pkce.generateCodeVerifier(context);
205
- const codeChallenge = this.pkce.generateCodeChallenge(codeVerifier, context);
209
+ const codeChallenge = await this.pkce.generateCodeChallenge(codeVerifier, context);
206
210
  authorizationUrl += `&code_challenge=${codeChallenge}&code_challenge_method=S256`;
207
211
  }
208
212
  return authorizationUrl;
@@ -212,7 +216,7 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
212
216
  grant_type: "authorization_code",
213
217
  client_id: this.config.clientId,
214
218
  code,
215
- redirectUri: this.config.redirectUri,
219
+ redirect_uri: this.config.redirectUri,
216
220
  };
217
221
  if (this.pkce) {
218
222
  const codeVerifier = this.pkce.extractCodeVerifier(context);
@@ -363,8 +367,8 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
363
367
  this.logger?.error(e);
364
368
  }
365
369
  }
366
- async verifyIdToken(idToken) {
367
- const storedNonce = await this.config?.nonce?.persistence?.read?.();
370
+ async verifyIdToken(idToken, context) {
371
+ const storedNonce = await this.config?.nonce?.persistence?.read?.(context, "nonce");
368
372
  if (storedNonce) {
369
373
  const decodedToken = await this.jwks.verify(idToken);
370
374
  if (decodedToken.nonce) {
@@ -9,13 +9,13 @@ export interface OAuth2Endpoints {
9
9
  logoutUrl?: string;
10
10
  }
11
11
  export interface OAuth2StateConfig<TContext = unknown, TData = any> {
12
- persistence?: PersistenceConfig;
12
+ persistence?: PersistenceConfig<TData, TContext>;
13
13
  context?: ContextOperationConfig<TContext, TData>;
14
14
  generateState?: () => string | Promise<string>;
15
15
  validateState?: (storedState: TContext, returnedState: string) => boolean | Promise<boolean>;
16
16
  }
17
17
  export interface OAuth2NonceConfig<TContext = unknown, TData = any> {
18
- persistence?: PersistenceConfig;
18
+ persistence?: PersistenceConfig<TData, TContext>;
19
19
  context?: ContextOperationConfig<TContext, TData>;
20
20
  generateNonce?: () => string | Promise<string>;
21
21
  validateNonce?: (storedNonce: string | null, returnedNonce: string) => boolean | Promise<boolean>;
@@ -47,6 +47,11 @@ export interface OAuth2StrategyConfig<TContext = unknown, TUser = unknown> exten
47
47
  [key: string]: AuthRouteConfig;
48
48
  };
49
49
  state?: OAuth2StateConfig<TContext>;
50
- nonce?: OAuth2NonceConfig;
50
+ nonce?: OAuth2NonceConfig<TContext>;
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;