@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.
- 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/jwt/jwt.strategy.js +6 -3
- package/build/strategies/jwt/jwt.tools.js +8 -6
- package/build/strategies/local/local.strategy.d.ts +2 -2
- package/build/strategies/local/local.strategy.js +7 -7
- 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 +91 -13
- package/.claude/settings.local.json +0 -20
- package/build/__tests__/soap-auth.test.d.ts +0 -1
- package/build/__tests__/soap-auth.test.js +0 -136
- package/build/services/__tests__/account-lock.service.test.d.ts +0 -1
- package/build/services/__tests__/account-lock.service.test.js +0 -55
- package/build/services/__tests__/auth-throttle.service.test.d.ts +0 -1
- package/build/services/__tests__/auth-throttle.service.test.js +0 -48
- package/build/services/__tests__/jwks.service.test.d.ts +0 -1
- package/build/services/__tests__/jwks.service.test.js +0 -39
- package/build/services/__tests__/mfa.service.test.d.ts +0 -1
- package/build/services/__tests__/mfa.service.test.js +0 -66
- package/build/services/__tests__/password.service.test.d.ts +0 -1
- package/build/services/__tests__/password.service.test.js +0 -73
- package/build/services/__tests__/pkce.service.test.d.ts +0 -1
- package/build/services/__tests__/pkce.service.test.js +0 -77
- package/build/services/__tests__/rate-limit.service.test.d.ts +0 -1
- package/build/services/__tests__/rate-limit.service.test.js +0 -37
- package/build/services/__tests__/role.service.test.d.ts +0 -1
- package/build/services/__tests__/role.service.test.js +0 -31
- package/build/services/__tests__/totp.service.test.d.ts +0 -1
- package/build/services/__tests__/totp.service.test.js +0 -120
- package/build/session/__tests__/file.session-store.test.d.ts +0 -1
- package/build/session/__tests__/file.session-store.test.js +0 -117
- package/build/session/__tests__/memory.session-store.test.d.ts +0 -1
- package/build/session/__tests__/memory.session-store.test.js +0 -77
- package/build/session/__tests__/session-handler.test.d.ts +0 -1
- package/build/session/__tests__/session-handler.test.js +0 -345
- package/build/strategies/__tests__/base-auth.strategy.test.d.ts +0 -15
- package/build/strategies/__tests__/base-auth.strategy.test.js +0 -138
- package/build/strategies/__tests__/credential-auth.strategy.test.d.ts +0 -15
- package/build/strategies/__tests__/credential-auth.strategy.test.js +0 -266
- package/build/strategies/__tests__/token-auth.strategy.test.d.ts +0 -29
- package/build/strategies/__tests__/token-auth.strategy.test.js +0 -299
- package/build/strategies/api-key/__tests__/api-key.strategy.test.d.ts +0 -1
- package/build/strategies/api-key/__tests__/api-key.strategy.test.js +0 -103
- package/build/strategies/basic/__tests__/basic.strategy.test.d.ts +0 -1
- package/build/strategies/basic/__tests__/basic.strategy.test.js +0 -104
- package/build/strategies/jwt/__tests__/jwt.strategy.test.d.ts +0 -1
- package/build/strategies/jwt/__tests__/jwt.strategy.test.js +0 -156
- package/build/strategies/jwt/__tests__/jwt.tools.test.d.ts +0 -1
- package/build/strategies/jwt/__tests__/jwt.tools.test.js +0 -98
- package/build/strategies/local/__tests__/local.strategy.test.d.ts +0 -1
- package/build/strategies/local/__tests__/local.strategy.test.js +0 -121
- package/build/strategies/oauth2/__tests__/oauth2.strategy.test.d.ts +0 -1
- package/build/strategies/oauth2/__tests__/oauth2.strategy.test.js +0 -239
- package/build/strategies/oauth2/providers/__tests__/social-providers.test.d.ts +0 -1
- package/build/strategies/oauth2/providers/__tests__/social-providers.test.js +0 -201
- package/build/utils/__tests__/validation.test.d.ts +0 -1
- package/build/utils/__tests__/validation.test.js +0 -181
- 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");
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
...
|
|
105
|
-
algorithms:
|
|
106
|
-
expiresIn:
|
|
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
|
-
...
|
|
126
|
-
algorithms:
|
|
127
|
-
expiresIn:
|
|
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(
|
|
18
|
+
protected fetchUser(identifierOrCredentials: string | {
|
|
19
19
|
identifier: string;
|
|
20
|
-
password
|
|
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(
|
|
65
|
-
validation_1.ValidationUtils.required(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
return this.config.user.fetchUser(
|
|
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
|
-
|
|
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>;
|