@soapjs/soap-auth 0.4.4 → 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.
- package/README.md +261 -128
- package/build/index.d.ts +1 -0
- package/build/index.js +1 -0
- package/build/recipes/auth-config.recipes.d.ts +40 -0
- package/build/recipes/auth-config.recipes.js +135 -0
- package/build/recipes/http-context.helpers.d.ts +13 -0
- package/build/recipes/http-context.helpers.js +64 -0
- package/build/recipes/index.d.ts +3 -0
- package/build/recipes/index.js +19 -0
- package/build/recipes/oauth2-presets.d.ts +20 -0
- package/build/recipes/oauth2-presets.js +74 -0
- package/build/soap-auth.js +62 -0
- package/build/strategies/oauth2/hybrid.oauth2.strategy.js +1 -1
- package/build/strategies/oauth2/oauth2.strategy.js +2 -2
- package/build/strategies/oauth2/oauth2.types.d.ts +5 -0
- package/build/strategies/oauth2/providers/configurable-hybrid-oauth2.strategy.d.ts +19 -0
- package/build/strategies/oauth2/providers/configurable-hybrid-oauth2.strategy.js +85 -0
- package/build/strategies/oauth2/providers/configurable-oauth2.strategy.d.ts +11 -0
- package/build/strategies/oauth2/providers/configurable-oauth2.strategy.js +46 -0
- package/build/strategies/oauth2/providers/index.d.ts +2 -0
- package/build/strategies/oauth2/providers/index.js +2 -0
- package/build/strategies/oauth2/providers/provider.types.d.ts +3 -0
- package/build/types.d.ts +5 -2
- package/package.json +90 -12
- package/.claude/settings.local.json +0 -20
- 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,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
|
+
};
|
package/build/soap-auth.js
CHANGED
|
@@ -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");
|
|
@@ -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
|
-
|
|
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;
|
|
@@ -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 {
|
|
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]:
|
|
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>;
|
package/package.json
CHANGED
|
@@ -1,35 +1,113 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soapjs/soap-auth",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Authentication strategies, sessions, MFA, and token helpers for the SoapJS ecosystem.",
|
|
5
5
|
"homepage": "https://docs.soapjs.com",
|
|
6
|
-
"repository":
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/soapjs/soap-auth.git"
|
|
9
|
+
},
|
|
7
10
|
"main": "build/index.js",
|
|
8
11
|
"types": "build/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./build/index.d.ts",
|
|
15
|
+
"require": "./build/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./session": {
|
|
18
|
+
"types": "./build/session/index.d.ts",
|
|
19
|
+
"require": "./build/session/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./session/*": {
|
|
22
|
+
"types": "./build/session/*.d.ts",
|
|
23
|
+
"require": "./build/session/*.js"
|
|
24
|
+
},
|
|
25
|
+
"./services": {
|
|
26
|
+
"types": "./build/services/index.d.ts",
|
|
27
|
+
"require": "./build/services/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./services/*": {
|
|
30
|
+
"types": "./build/services/*.d.ts",
|
|
31
|
+
"require": "./build/services/*.js"
|
|
32
|
+
},
|
|
33
|
+
"./strategies": {
|
|
34
|
+
"types": "./build/strategies/index.d.ts",
|
|
35
|
+
"require": "./build/strategies/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./strategies/*": {
|
|
38
|
+
"types": "./build/strategies/*.d.ts",
|
|
39
|
+
"require": "./build/strategies/*.js"
|
|
40
|
+
},
|
|
41
|
+
"./tools": {
|
|
42
|
+
"types": "./build/tools/index.d.ts",
|
|
43
|
+
"require": "./build/tools/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./tools/*": {
|
|
46
|
+
"types": "./build/tools/*.d.ts",
|
|
47
|
+
"require": "./build/tools/*.js"
|
|
48
|
+
},
|
|
49
|
+
"./recipes": {
|
|
50
|
+
"types": "./build/recipes/index.d.ts",
|
|
51
|
+
"require": "./build/recipes/index.js"
|
|
52
|
+
},
|
|
53
|
+
"./recipes/*": {
|
|
54
|
+
"types": "./build/recipes/*.d.ts",
|
|
55
|
+
"require": "./build/recipes/*.js"
|
|
56
|
+
},
|
|
57
|
+
"./errors": {
|
|
58
|
+
"types": "./build/errors.d.ts",
|
|
59
|
+
"require": "./build/errors.js"
|
|
60
|
+
},
|
|
61
|
+
"./types": {
|
|
62
|
+
"types": "./build/types.d.ts",
|
|
63
|
+
"require": "./build/types.js"
|
|
64
|
+
},
|
|
65
|
+
"./soap-auth": {
|
|
66
|
+
"types": "./build/soap-auth.d.ts",
|
|
67
|
+
"require": "./build/soap-auth.js"
|
|
68
|
+
},
|
|
69
|
+
"./utils/validation": {
|
|
70
|
+
"types": "./build/utils/validation.d.ts",
|
|
71
|
+
"require": "./build/utils/validation.js"
|
|
72
|
+
},
|
|
73
|
+
"./package.json": "./package.json"
|
|
74
|
+
},
|
|
75
|
+
"files": [
|
|
76
|
+
"build",
|
|
77
|
+
"README.md",
|
|
78
|
+
"LICENSE",
|
|
79
|
+
"ldap.md",
|
|
80
|
+
"saml.md"
|
|
81
|
+
],
|
|
82
|
+
"sideEffects": false,
|
|
9
83
|
"license": "MIT",
|
|
10
84
|
"author": "Radoslaw Kamysz",
|
|
11
85
|
"scripts": {
|
|
12
86
|
"test:unit": "jest --config=jest.config.unit.json",
|
|
13
87
|
"clean": "rm -rf ./build",
|
|
14
|
-
"build": "npm run clean && tsc
|
|
15
|
-
"
|
|
88
|
+
"build": "npm run clean && tsc --project tsconfig.build.json",
|
|
89
|
+
"prepack": "npm run build",
|
|
90
|
+
"prepublishOnly": "npm run test:unit && npm run build"
|
|
91
|
+
},
|
|
92
|
+
"publishConfig": {
|
|
93
|
+
"access": "public"
|
|
16
94
|
},
|
|
17
95
|
"devDependencies": {
|
|
18
|
-
"@soapjs/soap": "^0.12.
|
|
19
|
-
"@types/jest": "^
|
|
20
|
-
"jest": "^
|
|
21
|
-
"ts-jest": "^
|
|
96
|
+
"@soapjs/soap": "^0.12.1",
|
|
97
|
+
"@types/jest": "^29.5.14",
|
|
98
|
+
"jest": "^29.7.0",
|
|
99
|
+
"ts-jest": "^29.4.11",
|
|
22
100
|
"typescript": "^4.8.2"
|
|
23
101
|
},
|
|
24
102
|
"peerDependencies": {
|
|
25
103
|
"@soapjs/soap": ">=0.12.0"
|
|
26
104
|
},
|
|
27
105
|
"engines": {
|
|
28
|
-
"node": ">=
|
|
106
|
+
"node": ">=24.17.0"
|
|
29
107
|
},
|
|
30
108
|
"dependencies": {
|
|
31
109
|
"bcrypt": "^6.0.0",
|
|
32
|
-
"jsonwebtoken": "^9.0.
|
|
33
|
-
"jwks-rsa": "^3.
|
|
110
|
+
"jsonwebtoken": "^9.0.3",
|
|
111
|
+
"jwks-rsa": "^3.2.2"
|
|
34
112
|
}
|
|
35
113
|
}
|