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