@soapjs/soap-auth 0.3.1 → 0.3.2
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/build/__tests__/soap-auth.test.d.ts +1 -0
- package/build/__tests__/soap-auth.test.js +42 -0
- package/build/errors.d.ts +14 -3
- package/build/errors.js +29 -8
- package/build/index.d.ts +1 -1
- package/build/index.js +1 -1
- package/build/services/__tests__/account-lock.service.test.d.ts +1 -0
- package/build/services/__tests__/account-lock.service.test.js +55 -0
- package/build/services/__tests__/auth-throttle.service.test.d.ts +1 -0
- package/build/services/__tests__/auth-throttle.service.test.js +48 -0
- package/build/services/__tests__/jwks.service.test.d.ts +1 -0
- package/build/services/__tests__/jwks.service.test.js +39 -0
- package/build/services/__tests__/mfa.service.test.d.ts +1 -0
- package/build/services/__tests__/mfa.service.test.js +66 -0
- package/build/services/__tests__/password.service.test.d.ts +1 -0
- package/build/services/__tests__/password.service.test.js +66 -0
- package/build/services/__tests__/pkce.service.test.d.ts +1 -0
- package/build/services/__tests__/pkce.service.test.js +77 -0
- package/build/services/__tests__/rate-limit.service.test.d.ts +1 -0
- package/build/services/__tests__/rate-limit.service.test.js +37 -0
- package/build/services/__tests__/role.service.test.d.ts +1 -0
- package/build/services/__tests__/role.service.test.js +31 -0
- package/build/services/account-lock.service.d.ts +12 -0
- package/build/services/account-lock.service.js +39 -0
- package/build/services/auth-throttle.service.d.ts +10 -0
- package/build/services/auth-throttle.service.js +43 -0
- package/build/services/index.d.ts +8 -0
- package/build/{factories → services}/index.js +8 -3
- package/build/services/jwks.service.d.ts +7 -0
- package/build/services/jwks.service.js +41 -0
- package/build/services/mfa.service.d.ts +12 -0
- package/build/services/mfa.service.js +74 -0
- package/build/services/password.service.d.ts +14 -0
- package/build/services/password.service.js +78 -0
- package/build/services/pkce.service.d.ts +14 -0
- package/build/services/pkce.service.js +81 -0
- package/build/services/rate-limit.service.d.ts +9 -0
- package/build/services/rate-limit.service.js +26 -0
- package/build/services/role.service.d.ts +9 -0
- package/build/services/role.service.js +26 -0
- package/build/session/__tests__/file.session-store.test.d.ts +1 -0
- package/build/session/__tests__/file.session-store.test.js +117 -0
- package/build/session/__tests__/memory.session-store.test.d.ts +1 -0
- package/build/session/__tests__/memory.session-store.test.js +77 -0
- package/build/session/__tests__/session-handler.test.d.ts +1 -0
- package/build/session/__tests__/session-handler.test.js +337 -0
- package/build/session/file.session-store.d.ts +1 -0
- package/build/session/file.session-store.js +7 -0
- package/build/session/memory.session-store.d.ts +4 -1
- package/build/session/memory.session-store.js +11 -5
- package/build/session/session-handler.d.ts +12 -7
- package/build/session/session-handler.js +46 -13
- package/build/session/session.errors.d.ts +6 -0
- package/build/session/session.errors.js +15 -0
- package/build/soap-auth.d.ts +9 -8
- package/build/soap-auth.js +42 -29
- package/build/strategies/__tests__/base-auth.strategy.test.d.ts +14 -0
- package/build/strategies/__tests__/base-auth.strategy.test.js +137 -0
- package/build/strategies/__tests__/credential-auth.strategy.test.d.ts +14 -0
- package/build/strategies/__tests__/credential-auth.strategy.test.js +265 -0
- package/build/strategies/__tests__/token-auth.strategy.test.d.ts +28 -0
- package/build/strategies/__tests__/token-auth.strategy.test.js +298 -0
- package/build/strategies/api-key/__tests__/api-key.strategy.test.d.ts +1 -0
- package/build/strategies/api-key/__tests__/api-key.strategy.test.js +103 -0
- package/build/strategies/api-key/api-key.strategy.d.ts +5 -2
- package/build/strategies/api-key/api-key.strategy.js +43 -35
- package/build/strategies/api-key/api-key.tools.d.ts +2 -0
- package/build/strategies/api-key/api-key.tools.js +39 -0
- package/build/strategies/api-key/api-key.types.d.ts +10 -2
- package/build/strategies/base-auth.strategy.d.ts +11 -5
- package/build/strategies/base-auth.strategy.js +45 -52
- package/build/strategies/basic/__tests__/basic.strategy.test.d.ts +1 -0
- package/build/strategies/basic/__tests__/basic.strategy.test.js +104 -0
- package/build/strategies/basic/basic.strategy.d.ts +5 -7
- package/build/strategies/basic/basic.strategy.js +6 -6
- package/build/strategies/basic/basic.tools.d.ts +2 -0
- package/build/strategies/basic/basic.tools.js +44 -0
- package/build/strategies/credential-auth.strategy.d.ts +7 -17
- package/build/strategies/credential-auth.strategy.js +116 -181
- package/build/strategies/jwt/__tests__/jwt.strategy.test.d.ts +1 -0
- package/build/strategies/jwt/__tests__/jwt.strategy.test.js +156 -0
- package/build/strategies/jwt/__tests__/jwt.tools.test.d.ts +1 -0
- package/build/strategies/jwt/__tests__/jwt.tools.test.js +98 -0
- package/build/strategies/jwt/jwt.strategy.d.ts +13 -14
- package/build/strategies/jwt/jwt.strategy.js +57 -44
- package/build/strategies/jwt/jwt.tools.d.ts +20 -7
- package/build/strategies/jwt/jwt.tools.js +180 -81
- package/build/strategies/local/__tests__/local.strategy.test.d.ts +1 -0
- package/build/strategies/local/__tests__/local.strategy.test.js +115 -0
- package/build/strategies/local/local.strategy.d.ts +4 -3
- package/build/strategies/local/local.strategy.js +7 -6
- package/build/strategies/local/local.tools.d.ts +2 -0
- package/build/strategies/local/local.tools.js +44 -0
- package/build/strategies/oauth2/hybrid.oauth2.strategy.d.ts +5 -0
- package/build/strategies/oauth2/hybrid.oauth2.strategy.js +92 -0
- package/build/strategies/oauth2/oauth2.errors.d.ts +12 -0
- package/build/strategies/oauth2/oauth2.errors.js +24 -0
- package/build/strategies/oauth2/oauth2.strategy.d.ts +25 -15
- package/build/strategies/oauth2/oauth2.strategy.js +131 -141
- package/build/strategies/oauth2/oauth2.tools.d.ts +7 -2
- package/build/strategies/oauth2/oauth2.tools.js +119 -14
- package/build/strategies/oauth2/oauth2.types.d.ts +32 -1
- package/build/strategies/token-auth.strategy.d.ts +14 -8
- package/build/strategies/token-auth.strategy.js +162 -38
- package/build/tools/index.d.ts +0 -2
- package/build/tools/index.js +0 -2
- package/build/tools/tools.d.ts +2 -1
- package/build/tools/tools.js +9 -12
- package/build/types.d.ts +88 -57
- package/package.json +1 -1
- package/build/factories/auth-strategy.factory.d.ts +0 -9
- package/build/factories/auth-strategy.factory.js +0 -16
- package/build/factories/http-auth-strategy.factory.d.ts +0 -5
- package/build/factories/http-auth-strategy.factory.js +0 -41
- package/build/factories/index.d.ts +0 -3
- package/build/factories/socket-auth-strategy.factory.d.ts +0 -5
- package/build/factories/socket-auth-strategy.factory.js +0 -27
- package/build/tools/session.tools.d.ts +0 -6
- package/build/tools/session.tools.js +0 -15
- package/build/tools/token.tools.d.ts +0 -7
- package/build/tools/token.tools.js +0 -32
|
@@ -8,16 +8,28 @@ const axios_1 = __importDefault(require("axios"));
|
|
|
8
8
|
const errors_1 = require("../../errors");
|
|
9
9
|
const oauth2_tools_1 = require("./oauth2.tools");
|
|
10
10
|
const base_auth_strategy_1 = require("../base-auth.strategy");
|
|
11
|
+
const jwks_service_1 = require("../../services/jwks.service");
|
|
12
|
+
const pkce_service_1 = require("../../services/pkce.service");
|
|
13
|
+
const oauth2_errors_1 = require("./oauth2.errors");
|
|
11
14
|
class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
|
|
12
15
|
config;
|
|
13
16
|
session;
|
|
17
|
+
jwt;
|
|
14
18
|
logger;
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
jwks;
|
|
20
|
+
pkce;
|
|
21
|
+
constructor(config, session, jwt, logger) {
|
|
22
|
+
super((0, oauth2_tools_1.prepareOAuth2Config)(config), session, logger);
|
|
17
23
|
this.config = config;
|
|
18
24
|
this.session = session;
|
|
25
|
+
this.jwt = jwt;
|
|
19
26
|
this.logger = logger;
|
|
20
|
-
|
|
27
|
+
if (config.jwks) {
|
|
28
|
+
this.jwks = new jwks_service_1.JwtService({ ...config.jwks, audience: config.clientId });
|
|
29
|
+
}
|
|
30
|
+
if (config.pkce) {
|
|
31
|
+
this.pkce = new pkce_service_1.PKCEService(config.pkce);
|
|
32
|
+
}
|
|
21
33
|
}
|
|
22
34
|
async logout(context) {
|
|
23
35
|
try {
|
|
@@ -29,8 +41,11 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
|
|
|
29
41
|
}
|
|
30
42
|
}
|
|
31
43
|
catch (error) {
|
|
32
|
-
this.
|
|
33
|
-
|
|
44
|
+
await this.onFailure("logout", {
|
|
45
|
+
context,
|
|
46
|
+
error,
|
|
47
|
+
});
|
|
48
|
+
throw error;
|
|
34
49
|
}
|
|
35
50
|
}
|
|
36
51
|
async getCredentialsForPasswordGrant(context) {
|
|
@@ -46,90 +61,17 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
|
|
|
46
61
|
};
|
|
47
62
|
}
|
|
48
63
|
}
|
|
49
|
-
throw new
|
|
50
|
-
}
|
|
51
|
-
retrieveAccessToken(context) {
|
|
52
|
-
if (this.config.accessToken?.retrieve) {
|
|
53
|
-
return this.config.accessToken.retrieve(context);
|
|
54
|
-
}
|
|
55
|
-
if (typeof context === "object" && "headers" in context) {
|
|
56
|
-
const authHeader = context.headers?.authorization;
|
|
57
|
-
if (authHeader?.startsWith("Bearer ")) {
|
|
58
|
-
return authHeader.split(" ")[1];
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
if (typeof context === "object" && "cookies" in context) {
|
|
62
|
-
return context.cookies?.access_token;
|
|
63
|
-
}
|
|
64
|
-
return undefined;
|
|
65
|
-
}
|
|
66
|
-
retrieveRefreshToken(context) {
|
|
67
|
-
if (this.config.refreshToken?.retrieve) {
|
|
68
|
-
return this.config.refreshToken.retrieve(context);
|
|
69
|
-
}
|
|
70
|
-
if (typeof context === "object" && "cookies" in context) {
|
|
71
|
-
return context.cookies?.refresh_token;
|
|
72
|
-
}
|
|
73
|
-
return undefined;
|
|
74
|
-
}
|
|
75
|
-
storeAccessToken(token, context) {
|
|
76
|
-
if (this.config.accessToken?.persistence?.store) {
|
|
77
|
-
return this.config.accessToken.persistence.store(token, context, this.config.accessToken.issuer.options.expiresIn);
|
|
78
|
-
}
|
|
79
|
-
if (typeof context === "object" && "cookies" in context) {
|
|
80
|
-
context.cookies.set("access_token", token, { httpOnly: true });
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
storeRefreshToken(token, context) {
|
|
84
|
-
if (this.config.refreshToken?.persistence?.store) {
|
|
85
|
-
return this.config.refreshToken.persistence.store(token, context, this.config.refreshToken.issuer.options.expiresIn);
|
|
86
|
-
}
|
|
87
|
-
if (typeof context === "object" && "cookies" in context) {
|
|
88
|
-
context.cookies.set("refresh_token", token, { httpOnly: true });
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
embedAccessToken(token, context) {
|
|
92
|
-
if (this.config.accessToken?.embed) {
|
|
93
|
-
this.config.accessToken.embed(context, token);
|
|
94
|
-
}
|
|
95
|
-
else if (typeof context === "object" && "response" in context) {
|
|
96
|
-
context.response.setHeader("Authorization", `Bearer ${token}`);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
embedRefreshToken(token, context) {
|
|
100
|
-
if (this.config.refreshToken?.embed) {
|
|
101
|
-
return this.config.refreshToken.embed(context, token);
|
|
102
|
-
}
|
|
103
|
-
else if (typeof context === "object" && "cookies" in context) {
|
|
104
|
-
context.cookies.set("refresh_token", token, { httpOnly: true });
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
async isTokenExpired(token) {
|
|
108
|
-
try {
|
|
109
|
-
const parts = token.split(".");
|
|
110
|
-
if (parts.length !== 3) {
|
|
111
|
-
this.logger?.warn("Non-JWT token provided, cannot determine expiration.");
|
|
112
|
-
return false;
|
|
113
|
-
}
|
|
114
|
-
const decoded = JSON.parse(Buffer.from(parts[1], "base64").toString());
|
|
115
|
-
if (!decoded.exp)
|
|
116
|
-
return false;
|
|
117
|
-
const currentTime = Math.floor(Date.now() / 1000);
|
|
118
|
-
return decoded.exp < currentTime;
|
|
119
|
-
}
|
|
120
|
-
catch (error) {
|
|
121
|
-
this.logger?.warn("Failed to decode token:", error);
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
64
|
+
throw new errors_1.MissingCredentialsError();
|
|
124
65
|
}
|
|
125
66
|
async authenticate(context) {
|
|
126
67
|
try {
|
|
127
68
|
let user;
|
|
69
|
+
let session;
|
|
128
70
|
let refreshToken;
|
|
129
|
-
let accessToken = await this.
|
|
71
|
+
let accessToken = await this.extractAccessToken(context);
|
|
130
72
|
if (!accessToken) {
|
|
131
73
|
this.logger?.info("No access token found, checking for refresh token.");
|
|
132
|
-
refreshToken = await this.
|
|
74
|
+
refreshToken = await this.extractRefreshToken(context);
|
|
133
75
|
if (refreshToken) {
|
|
134
76
|
this.logger?.info("Found refresh token, attempting to refresh access token.");
|
|
135
77
|
const newTokens = await this.refreshAccessToken(context);
|
|
@@ -151,9 +93,9 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
|
|
|
151
93
|
}
|
|
152
94
|
}
|
|
153
95
|
}
|
|
154
|
-
else if (accessToken &&
|
|
96
|
+
else if (accessToken && this.isTokenExpired(accessToken)) {
|
|
155
97
|
this.logger?.info("Access token expired, attempting refresh.");
|
|
156
|
-
refreshToken = await this.
|
|
98
|
+
refreshToken = await this.extractRefreshToken(context);
|
|
157
99
|
if (!refreshToken) {
|
|
158
100
|
throw new errors_1.MissingTokenError("Refresh");
|
|
159
101
|
}
|
|
@@ -168,23 +110,29 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
|
|
|
168
110
|
if (!accessToken) {
|
|
169
111
|
throw new errors_1.MissingTokenError("Access");
|
|
170
112
|
}
|
|
171
|
-
user = await this.
|
|
113
|
+
user = await this.fetchUser(accessToken);
|
|
172
114
|
if (!user) {
|
|
173
115
|
this.logger?.error("User retrieval failed: No user found for access token.");
|
|
174
116
|
throw new errors_1.UserNotFoundError();
|
|
175
117
|
}
|
|
176
|
-
|
|
177
|
-
|
|
118
|
+
if (this.session) {
|
|
119
|
+
session = await this.session.issueSession(user, context);
|
|
120
|
+
}
|
|
121
|
+
await this.role.isAuthorized(user);
|
|
122
|
+
return { user: user, tokens: { accessToken, refreshToken }, session };
|
|
178
123
|
}
|
|
179
124
|
catch (error) {
|
|
180
|
-
this.
|
|
181
|
-
|
|
125
|
+
await this.onFailure("authenticate", {
|
|
126
|
+
context,
|
|
127
|
+
error,
|
|
128
|
+
});
|
|
129
|
+
throw error;
|
|
182
130
|
}
|
|
183
131
|
}
|
|
184
132
|
async processOAuthFlow(context) {
|
|
185
133
|
if (this.config.grantType === "authorization_code") {
|
|
186
134
|
const authorizationCode = this.extractAuthorizationCode(context);
|
|
187
|
-
this.verifyAuthorizationCode(context, authorizationCode);
|
|
135
|
+
await this.verifyAuthorizationCode(context, authorizationCode);
|
|
188
136
|
const tokenResult = await this.exchangeCodeForToken(context, authorizationCode);
|
|
189
137
|
return tokenResult;
|
|
190
138
|
}
|
|
@@ -197,43 +145,26 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
|
|
|
197
145
|
this.logger?.info("Using password grant.");
|
|
198
146
|
const credentials = await this.getCredentialsForPasswordGrant(context);
|
|
199
147
|
if (!credentials) {
|
|
200
|
-
throw new
|
|
148
|
+
throw new errors_1.MissingCredentialsError();
|
|
201
149
|
}
|
|
202
150
|
const passwordResult = await this.exchangePasswordGrant(credentials.identifier, credentials.password);
|
|
203
151
|
return { accessToken: passwordResult.accessToken };
|
|
204
152
|
}
|
|
205
|
-
throw new
|
|
153
|
+
throw new oauth2_errors_1.UnsupportedGrantTypeError(this.config.grantType);
|
|
206
154
|
}
|
|
207
|
-
verifyAuthorizationCode(context, code) {
|
|
155
|
+
async verifyAuthorizationCode(context, code) {
|
|
208
156
|
if (!code) {
|
|
209
157
|
this.logger?.warn("Authorization code missing, redirecting user.");
|
|
210
|
-
const authUrl = this.buildAuthorizationUrl(context);
|
|
158
|
+
const authUrl = await this.buildAuthorizationUrl(context);
|
|
211
159
|
this.redirectUser(context, authUrl);
|
|
212
160
|
throw new errors_1.MissingAuthorizationCodeError();
|
|
213
161
|
}
|
|
214
162
|
}
|
|
215
|
-
|
|
216
|
-
if (typeof context === "object" && "query" in context) {
|
|
217
|
-
return context.query.code || null;
|
|
218
|
-
}
|
|
219
|
-
return null;
|
|
220
|
-
}
|
|
221
|
-
redirectUser(context, authUrl) {
|
|
222
|
-
if (typeof context === "object" && "response" in context) {
|
|
223
|
-
context.response.redirect(authUrl);
|
|
224
|
-
}
|
|
225
|
-
else {
|
|
226
|
-
this.logger?.warn("Redirect attempted in unsupported context.");
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
buildAuthorizationUrl(context) {
|
|
163
|
+
async buildAuthorizationUrl(context) {
|
|
230
164
|
let authorizationUrl = `${this.config.endpoints.authorizationUrl}?client_id=${this.config.clientId}&redirect_uri=${encodeURIComponent(this.config.redirectUri)}&response_type=code&scope=${this.config.scope ?? ""}`;
|
|
231
|
-
if (this.
|
|
232
|
-
const codeVerifier = this.
|
|
233
|
-
|
|
234
|
-
: oauth2_tools_1.OAuth2Tools.generateCodeVerifier();
|
|
235
|
-
const codeChallenge = oauth2_tools_1.OAuth2Tools.generateCodeChallenge(codeVerifier);
|
|
236
|
-
this.config.pkce.storeCodeVerifier?.(context, codeVerifier);
|
|
165
|
+
if (this.pkce) {
|
|
166
|
+
const codeVerifier = await this.pkce.generateCodeVerifier(context);
|
|
167
|
+
const codeChallenge = this.pkce.generateCodeChallenge(codeVerifier, context);
|
|
237
168
|
authorizationUrl += `&code_challenge=${codeChallenge}&code_challenge_method=S256`;
|
|
238
169
|
}
|
|
239
170
|
return authorizationUrl;
|
|
@@ -243,46 +174,50 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
|
|
|
243
174
|
grant_type: "authorization_code",
|
|
244
175
|
client_id: this.config.clientId,
|
|
245
176
|
code,
|
|
246
|
-
|
|
177
|
+
redirectUri: this.config.redirectUri,
|
|
247
178
|
};
|
|
248
|
-
if (this.
|
|
249
|
-
const codeVerifier = this.
|
|
179
|
+
if (this.pkce) {
|
|
180
|
+
const codeVerifier = this.pkce.extractCodeVerifier(context);
|
|
250
181
|
if (!codeVerifier) {
|
|
251
|
-
throw new
|
|
182
|
+
throw new oauth2_errors_1.MissingCodeVerifierError();
|
|
252
183
|
}
|
|
253
184
|
data.code_verifier = codeVerifier;
|
|
254
185
|
}
|
|
255
186
|
else if (this.config.clientSecret) {
|
|
256
187
|
data.client_secret = this.config.clientSecret;
|
|
257
188
|
}
|
|
258
|
-
const response = await
|
|
259
|
-
method: "POST",
|
|
189
|
+
const response = await axios_1.default.post(this.config.endpoints.tokenUrl, {
|
|
260
190
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
261
191
|
body: new URLSearchParams(data).toString(),
|
|
262
192
|
});
|
|
263
|
-
|
|
264
|
-
throw new Error(`Token exchange failed with status: ${response.status}`);
|
|
265
|
-
}
|
|
266
|
-
const tokenData = await response.json();
|
|
193
|
+
const tokenData = await response.data();
|
|
267
194
|
return {
|
|
268
195
|
accessToken: tokenData.access_token,
|
|
269
196
|
refreshToken: tokenData.refresh_token,
|
|
197
|
+
idToken: tokenData.id_token,
|
|
270
198
|
};
|
|
271
199
|
}
|
|
272
|
-
|
|
200
|
+
async login(context) {
|
|
273
201
|
try {
|
|
274
|
-
const
|
|
202
|
+
const state = await oauth2_tools_1.OAuth2Tools.generateState(this.config);
|
|
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);
|
|
275
207
|
this.redirectUser(context, authUrl);
|
|
276
208
|
}
|
|
277
209
|
catch (error) {
|
|
278
|
-
this.
|
|
279
|
-
|
|
210
|
+
await this.onFailure("login", {
|
|
211
|
+
context,
|
|
212
|
+
error,
|
|
213
|
+
});
|
|
214
|
+
throw error;
|
|
280
215
|
}
|
|
281
216
|
}
|
|
282
|
-
async
|
|
217
|
+
async fetchUser(accessToken) {
|
|
283
218
|
try {
|
|
284
219
|
if (!this.config.endpoints.userInfoUrl) {
|
|
285
|
-
throw new
|
|
220
|
+
throw new errors_1.MissingConfigError("userInfoUrl");
|
|
286
221
|
}
|
|
287
222
|
const response = await axios_1.default.get(this.config.endpoints.userInfoUrl, {
|
|
288
223
|
headers: { Authorization: `Bearer ${accessToken}` },
|
|
@@ -304,7 +239,7 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
|
|
|
304
239
|
}
|
|
305
240
|
async exchangePasswordGrant(username, password) {
|
|
306
241
|
if (!username || !password) {
|
|
307
|
-
throw new
|
|
242
|
+
throw new errors_1.MissingCredentialsError();
|
|
308
243
|
}
|
|
309
244
|
const response = await axios_1.default.post(this.config.endpoints.tokenUrl, {
|
|
310
245
|
grant_type: "password",
|
|
@@ -317,7 +252,7 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
|
|
|
317
252
|
}
|
|
318
253
|
async refreshAccessToken(context) {
|
|
319
254
|
try {
|
|
320
|
-
const refreshToken = await this.
|
|
255
|
+
const refreshToken = await this.extractRefreshToken(context);
|
|
321
256
|
if (!refreshToken) {
|
|
322
257
|
throw new errors_1.MissingTokenError("Refresh");
|
|
323
258
|
}
|
|
@@ -327,31 +262,65 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
|
|
|
327
262
|
client_secret: this.config.clientSecret,
|
|
328
263
|
refresh_token: refreshToken,
|
|
329
264
|
});
|
|
265
|
+
if (response.data.error) {
|
|
266
|
+
this.logger?.warn(`OAuth2 error: ${response.data.error_description || response.data.error}`);
|
|
267
|
+
}
|
|
330
268
|
if (response.status !== 200 || !response.data.access_token) {
|
|
331
269
|
throw new Error("Failed to refresh token");
|
|
332
270
|
}
|
|
333
271
|
const refreshedTokens = {
|
|
334
272
|
accessToken: response.data.access_token,
|
|
335
273
|
refreshToken: response.data.refresh_token,
|
|
274
|
+
idToken: response.data.id_token,
|
|
336
275
|
};
|
|
337
276
|
await this.storeAccessToken(refreshedTokens.accessToken, context);
|
|
338
|
-
if (refreshedTokens.refreshToken) {
|
|
339
|
-
await this.storeRefreshToken(refreshedTokens.refreshToken, context);
|
|
340
|
-
}
|
|
341
277
|
await this.embedAccessToken(refreshedTokens.accessToken, context);
|
|
342
278
|
if (refreshedTokens.refreshToken) {
|
|
279
|
+
await this.storeRefreshToken(refreshedTokens.refreshToken, context);
|
|
343
280
|
await this.embedRefreshToken(refreshedTokens.refreshToken, context);
|
|
344
281
|
}
|
|
345
282
|
return refreshedTokens;
|
|
346
283
|
}
|
|
347
284
|
catch (error) {
|
|
348
|
-
this.
|
|
349
|
-
|
|
285
|
+
await this.handleTokenRefreshFailure(context);
|
|
286
|
+
await this.onFailure("refresh_access_token", {
|
|
287
|
+
error,
|
|
288
|
+
});
|
|
289
|
+
throw error;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
async handleTokenRefreshFailure(context) {
|
|
293
|
+
try {
|
|
294
|
+
if (this.config.refreshToken) {
|
|
295
|
+
await this.storeRefreshToken("", context);
|
|
296
|
+
}
|
|
297
|
+
if (this.config.autoLogoutOnRefreshFailure) {
|
|
298
|
+
await this.logout(context);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
catch (e) {
|
|
302
|
+
this.logger?.error(e);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
async verifyIdToken(idToken) {
|
|
306
|
+
const storedNonce = await this.config?.nonce?.persistence?.read?.();
|
|
307
|
+
if (storedNonce) {
|
|
308
|
+
const decodedToken = await this.jwks.verify(idToken);
|
|
309
|
+
if (decodedToken.nonce) {
|
|
310
|
+
if ((await oauth2_tools_1.OAuth2Tools.validateNonce(storedNonce, decodedToken.nonce, this.config.nonce)) === false) {
|
|
311
|
+
throw new oauth2_errors_1.InvalidNonceError("Invalid nonce value");
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (decodedToken) {
|
|
315
|
+
return this.config.user?.validateUser?.(decodedToken);
|
|
316
|
+
}
|
|
350
317
|
}
|
|
318
|
+
return null;
|
|
351
319
|
}
|
|
352
320
|
async revokeToken(token) {
|
|
353
|
-
if (!this.config.endpoints
|
|
354
|
-
|
|
321
|
+
if (!this.config.endpoints?.revocationUrl) {
|
|
322
|
+
throw new errors_1.MissingConfigError("revocationUrl");
|
|
323
|
+
}
|
|
355
324
|
try {
|
|
356
325
|
await axios_1.default.post(this.config.endpoints.revocationUrl, {
|
|
357
326
|
token,
|
|
@@ -361,7 +330,28 @@ class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
|
|
|
361
330
|
this.logger?.info("Token revoked successfully.");
|
|
362
331
|
}
|
|
363
332
|
catch (error) {
|
|
364
|
-
this.
|
|
333
|
+
await this.onFailure("revoke_token", {
|
|
334
|
+
error,
|
|
335
|
+
});
|
|
336
|
+
throw error;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
isTokenExpired(token) {
|
|
340
|
+
try {
|
|
341
|
+
const parts = token.split(".");
|
|
342
|
+
if (parts.length !== 3) {
|
|
343
|
+
this.logger?.warn("Non-JWT token provided, cannot determine expiration.");
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
const decoded = JSON.parse(Buffer.from(parts[1], "base64").toString());
|
|
347
|
+
if (!decoded.exp)
|
|
348
|
+
return false;
|
|
349
|
+
const currentTime = Math.floor(Date.now() / 1000);
|
|
350
|
+
return decoded.exp < currentTime;
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
this.logger?.warn("Failed to decode token:", error);
|
|
354
|
+
return false;
|
|
365
355
|
}
|
|
366
356
|
}
|
|
367
357
|
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
import { OAuth2NonceConfig, OAuth2StrategyConfig } from "./oauth2.types";
|
|
1
2
|
export declare class OAuth2Tools {
|
|
2
|
-
static
|
|
3
|
-
static
|
|
3
|
+
static generateState(config: OAuth2StrategyConfig): Promise<string>;
|
|
4
|
+
static generateNonce(config: OAuth2StrategyConfig): Promise<string>;
|
|
5
|
+
static validateNonce(expectedNonce: string, nonce: string, config: OAuth2NonceConfig): Promise<boolean>;
|
|
6
|
+
static extractState<TContext>(context: TContext): string | null;
|
|
7
|
+
static extractNonce(idToken: string): string | null;
|
|
4
8
|
}
|
|
9
|
+
export declare const prepareOAuth2Config: <TContext = any, TUser = any>(config: Partial<OAuth2StrategyConfig<TContext, TUser>>) => OAuth2StrategyConfig<TContext, TUser>;
|
|
@@ -1,22 +1,127 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
4
24
|
};
|
|
5
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.OAuth2Tools = void 0;
|
|
7
|
-
const
|
|
26
|
+
exports.prepareOAuth2Config = exports.OAuth2Tools = void 0;
|
|
27
|
+
const Soap = __importStar(require("@soapjs/soap"));
|
|
28
|
+
const tools_1 = require("../../tools");
|
|
8
29
|
class OAuth2Tools {
|
|
9
|
-
static
|
|
10
|
-
return
|
|
30
|
+
static async generateState(config) {
|
|
31
|
+
return (await config.state?.generateState?.()) || (0, tools_1.generateRandomString)();
|
|
32
|
+
}
|
|
33
|
+
static async generateNonce(config) {
|
|
34
|
+
return (await config.nonce?.generateNonce?.()) || (0, tools_1.generateRandomString)();
|
|
35
|
+
}
|
|
36
|
+
static async validateNonce(expectedNonce, nonce, config) {
|
|
37
|
+
if (config.validateNonce) {
|
|
38
|
+
return config.validateNonce(expectedNonce, nonce);
|
|
39
|
+
}
|
|
40
|
+
return expectedNonce === nonce;
|
|
11
41
|
}
|
|
12
|
-
static
|
|
13
|
-
return
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
.replace(/\//g, "_")
|
|
19
|
-
.replace(/=+$/, "");
|
|
42
|
+
static extractState(context) {
|
|
43
|
+
return context.query?.state || null;
|
|
44
|
+
}
|
|
45
|
+
static extractNonce(idToken) {
|
|
46
|
+
const decoded = JSON.parse(Buffer.from(idToken.split(".")[1], "base64").toString());
|
|
47
|
+
return decoded.nonce || null;
|
|
20
48
|
}
|
|
21
49
|
}
|
|
22
50
|
exports.OAuth2Tools = OAuth2Tools;
|
|
51
|
+
const prepareOAuth2Config = (config) => {
|
|
52
|
+
return Soap.removeUndefinedProperties({
|
|
53
|
+
...config,
|
|
54
|
+
grantType: config.grantType ?? "authorization_code",
|
|
55
|
+
responseType: config.responseType ?? "code",
|
|
56
|
+
scope: config.scope ?? "openid profile email",
|
|
57
|
+
autoLogoutOnRefreshFailure: config.autoLogoutOnRefreshFailure ?? false,
|
|
58
|
+
routes: {
|
|
59
|
+
login: {
|
|
60
|
+
path: "/auth/oauth2/login",
|
|
61
|
+
method: "GET",
|
|
62
|
+
...config.routes.login,
|
|
63
|
+
},
|
|
64
|
+
callback: {
|
|
65
|
+
path: "/auth/oauth2/callback",
|
|
66
|
+
method: "GET",
|
|
67
|
+
...config.routes.callback,
|
|
68
|
+
},
|
|
69
|
+
logout: config.routes?.logout
|
|
70
|
+
? {
|
|
71
|
+
path: "/auth/oauth2/logout",
|
|
72
|
+
method: "POST",
|
|
73
|
+
...config.routes.logout,
|
|
74
|
+
}
|
|
75
|
+
: undefined,
|
|
76
|
+
refresh: config.routes?.refresh
|
|
77
|
+
? {
|
|
78
|
+
path: "/auth/oauth2/refresh",
|
|
79
|
+
method: "POST",
|
|
80
|
+
...config.routes.refresh,
|
|
81
|
+
}
|
|
82
|
+
: undefined,
|
|
83
|
+
revoke: config.routes?.revoke
|
|
84
|
+
? {
|
|
85
|
+
path: "/auth/oauth2/revoke",
|
|
86
|
+
method: "POST",
|
|
87
|
+
...config.routes.revoke,
|
|
88
|
+
}
|
|
89
|
+
: undefined,
|
|
90
|
+
...config.routes,
|
|
91
|
+
},
|
|
92
|
+
pkce: config.pkce
|
|
93
|
+
? {
|
|
94
|
+
challenge: {
|
|
95
|
+
expiresIn: 300,
|
|
96
|
+
...config.pkce.challenge,
|
|
97
|
+
},
|
|
98
|
+
verifier: {
|
|
99
|
+
expiresIn: 300,
|
|
100
|
+
...config.pkce.verifier,
|
|
101
|
+
},
|
|
102
|
+
...config.pkce,
|
|
103
|
+
}
|
|
104
|
+
: undefined,
|
|
105
|
+
state: config.state
|
|
106
|
+
? {
|
|
107
|
+
generateState: () => Math.random().toString(36).substring(2, 15),
|
|
108
|
+
validateState: (storedState, returnedState) => storedState === returnedState,
|
|
109
|
+
...config.state,
|
|
110
|
+
}
|
|
111
|
+
: undefined,
|
|
112
|
+
nonce: config.nonce
|
|
113
|
+
? {
|
|
114
|
+
generateNonce: () => Math.random().toString(36).substring(2, 15),
|
|
115
|
+
validateNonce: (storedNonce, returnedNonce) => storedNonce === returnedNonce,
|
|
116
|
+
...config.nonce,
|
|
117
|
+
}
|
|
118
|
+
: undefined,
|
|
119
|
+
jwks: config.jwks
|
|
120
|
+
? {
|
|
121
|
+
algorithms: ["RS256"],
|
|
122
|
+
...config.jwks,
|
|
123
|
+
}
|
|
124
|
+
: undefined,
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
exports.prepareOAuth2Config = prepareOAuth2Config;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Algorithm } from "jsonwebtoken";
|
|
2
|
+
import { CredentailsConfig, PKCEConfig, AuthRouteConfig, TokenAuthStrategyConfig, PersistenceConfig, ContextOperationConfig } from "../../types";
|
|
2
3
|
export interface OAuth2Endpoints {
|
|
3
4
|
authorizationUrl: string;
|
|
4
5
|
tokenUrl: string;
|
|
@@ -7,7 +8,26 @@ export interface OAuth2Endpoints {
|
|
|
7
8
|
revocationUrl?: string;
|
|
8
9
|
logoutUrl?: string;
|
|
9
10
|
}
|
|
11
|
+
export interface OAuth2StateConfig<TContext = unknown, TData = any> {
|
|
12
|
+
persistence?: PersistenceConfig;
|
|
13
|
+
context?: ContextOperationConfig<TContext, TData>;
|
|
14
|
+
generateState?: () => string | Promise<string>;
|
|
15
|
+
validateState?: (storedState: TContext, returnedState: string) => boolean | Promise<boolean>;
|
|
16
|
+
}
|
|
17
|
+
export interface OAuth2NonceConfig<TContext = unknown, TData = any> {
|
|
18
|
+
persistence?: PersistenceConfig;
|
|
19
|
+
context?: ContextOperationConfig<TContext, TData>;
|
|
20
|
+
generateNonce?: () => string | Promise<string>;
|
|
21
|
+
validateNonce?: (storedNonce: string | null, returnedNonce: string) => boolean | Promise<boolean>;
|
|
22
|
+
}
|
|
23
|
+
export type JwksConfig = {
|
|
24
|
+
jwksUri: string;
|
|
25
|
+
issuer: string;
|
|
26
|
+
algorithms?: Algorithm[];
|
|
27
|
+
audience?: string;
|
|
28
|
+
};
|
|
10
29
|
export interface OAuth2StrategyConfig<TContext = unknown, TUser = unknown> extends TokenAuthStrategyConfig<TContext, TUser> {
|
|
30
|
+
autoLogoutOnRefreshFailure?: boolean;
|
|
11
31
|
clientId: string;
|
|
12
32
|
clientSecret: string;
|
|
13
33
|
redirectUri: string;
|
|
@@ -18,4 +38,15 @@ export interface OAuth2StrategyConfig<TContext = unknown, TUser = unknown> exten
|
|
|
18
38
|
endpoints: OAuth2Endpoints;
|
|
19
39
|
credentials?: CredentailsConfig<TContext>;
|
|
20
40
|
pkce?: PKCEConfig<TContext>;
|
|
41
|
+
routes: {
|
|
42
|
+
login: AuthRouteConfig;
|
|
43
|
+
callback: AuthRouteConfig;
|
|
44
|
+
logout?: AuthRouteConfig;
|
|
45
|
+
refresh?: AuthRouteConfig;
|
|
46
|
+
revoke?: AuthRouteConfig;
|
|
47
|
+
[key: string]: AuthRouteConfig;
|
|
48
|
+
};
|
|
49
|
+
state?: OAuth2StateConfig<TContext>;
|
|
50
|
+
nonce?: OAuth2NonceConfig;
|
|
51
|
+
jwks?: JwksConfig;
|
|
21
52
|
}
|
|
@@ -7,19 +7,25 @@ export declare abstract class TokenAuthStrategy<TContext = unknown, TUser = unkn
|
|
|
7
7
|
protected session?: SessionHandler;
|
|
8
8
|
protected logger?: Soap.Logger;
|
|
9
9
|
constructor(config: TokenAuthStrategyConfig<TContext, TUser>, session?: SessionHandler, logger?: Soap.Logger);
|
|
10
|
-
protected abstract
|
|
11
|
-
protected abstract
|
|
10
|
+
protected abstract extractAccessToken(context: TContext): string | undefined;
|
|
11
|
+
protected abstract extractRefreshToken(context: TContext): string | undefined;
|
|
12
12
|
protected abstract verifyAccessToken(token: string): Promise<any>;
|
|
13
13
|
protected abstract verifyRefreshToken(token: string): Promise<any>;
|
|
14
|
-
protected abstract generateAccessToken(
|
|
15
|
-
protected abstract generateRefreshToken(
|
|
16
|
-
protected abstract storeAccessToken(token: string
|
|
17
|
-
protected abstract storeRefreshToken(token: string
|
|
14
|
+
protected abstract generateAccessToken(data: TUser, context: TContext): Promise<string>;
|
|
15
|
+
protected abstract generateRefreshToken(data: TUser, context: TContext): Promise<string>;
|
|
16
|
+
protected abstract storeAccessToken(token: string): Promise<void>;
|
|
17
|
+
protected abstract storeRefreshToken(token: string): Promise<void>;
|
|
18
|
+
protected abstract invalidateAccessToken(token: string, context?: TContext): Promise<void>;
|
|
19
|
+
protected abstract invalidateRefreshToken(token: string, context?: TContext): Promise<void>;
|
|
18
20
|
protected abstract embedAccessToken(token: string, context: TContext): void;
|
|
19
21
|
protected abstract embedRefreshToken(token: string, context: TContext): void;
|
|
22
|
+
protected fetchUser(payload: unknown): Promise<TUser | null>;
|
|
20
23
|
authenticate(context: TContext): Promise<AuthResult<TUser>>;
|
|
21
|
-
|
|
24
|
+
private verifyAndFetchUser;
|
|
25
|
+
refreshTokens(context: TContext, existingUser?: TUser): Promise<AuthResult<TUser>>;
|
|
26
|
+
issueTokens(user: TUser, context: TContext, rotate?: boolean): Promise<{
|
|
22
27
|
accessToken: string;
|
|
23
|
-
refreshToken
|
|
28
|
+
refreshToken: any;
|
|
24
29
|
}>;
|
|
30
|
+
protected rotateToken(refreshToken: string, user: TUser, context: TContext): Promise<any>;
|
|
25
31
|
}
|