@soapjs/soap-auth 0.1.1 → 0.3.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 +120 -169
- package/build/factories/http-auth-strategy.factory.js +1 -1
- package/build/factories/index.d.ts +3 -0
- package/build/factories/index.js +19 -0
- package/build/index.d.ts +4 -25
- package/build/index.js +4 -25
- package/build/session/index.d.ts +3 -0
- package/build/session/index.js +19 -0
- package/build/soap-auth.d.ts +9 -9
- package/build/soap-auth.js +64 -34
- package/build/strategies/api-key/api-key.strategy.d.ts +4 -3
- package/build/strategies/api-key/api-key.strategy.js +9 -6
- package/build/strategies/api-key/api-key.types.d.ts +2 -4
- package/build/strategies/base-auth.strategy.d.ts +4 -3
- package/build/strategies/base-auth.strategy.js +18 -2
- package/build/strategies/basic/basic.strategy.d.ts +5 -11
- package/build/strategies/basic/basic.strategy.js +14 -19
- package/build/strategies/basic/basic.types.d.ts +2 -2
- package/build/strategies/{credential-based-auth.strategy.d.ts → credential-auth.strategy.d.ts} +15 -12
- package/build/strategies/{credential-based-auth.strategy.js → credential-auth.strategy.js} +95 -46
- package/build/strategies/index.d.ts +16 -0
- package/build/strategies/index.js +32 -0
- package/build/strategies/jwt/jwt.strategy.d.ts +17 -2
- package/build/strategies/jwt/jwt.strategy.js +118 -57
- package/build/strategies/jwt/jwt.tools.d.ts +7 -3
- package/build/strategies/jwt/jwt.tools.js +80 -41
- package/build/strategies/jwt/jwt.types.d.ts +3 -27
- package/build/strategies/local/local.strategy.d.ts +3 -9
- package/build/strategies/local/local.strategy.js +7 -58
- package/build/strategies/local/local.types.d.ts +2 -2
- package/build/strategies/oauth2/oauth2.strategy.d.ts +21 -7
- package/build/strategies/oauth2/oauth2.strategy.js +158 -49
- package/build/strategies/oauth2/oauth2.types.d.ts +8 -16
- package/build/strategies/token-auth.strategy.d.ts +25 -0
- package/build/strategies/token-auth.strategy.js +78 -0
- package/build/tools/index.d.ts +3 -0
- package/build/tools/index.js +19 -0
- package/build/types.d.ts +87 -57
- package/package.json +2 -1
- package/build/strategies/token-based-auth.strategy.d.ts +0 -25
- package/build/strategies/token-based-auth.strategy.js +0 -130
|
@@ -6,85 +6,163 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.OAuth2Strategy = void 0;
|
|
7
7
|
const axios_1 = __importDefault(require("axios"));
|
|
8
8
|
const errors_1 = require("../../errors");
|
|
9
|
-
const token_based_auth_strategy_1 = require("../token-based-auth.strategy");
|
|
10
9
|
const oauth2_tools_1 = require("./oauth2.tools");
|
|
11
|
-
|
|
10
|
+
const base_auth_strategy_1 = require("../base-auth.strategy");
|
|
11
|
+
class OAuth2Strategy extends base_auth_strategy_1.BaseAuthStrategy {
|
|
12
12
|
config;
|
|
13
|
-
accessTokenConfig;
|
|
14
|
-
refreshTokenConfig;
|
|
15
13
|
session;
|
|
16
14
|
logger;
|
|
17
|
-
constructor(config,
|
|
18
|
-
super(config,
|
|
15
|
+
constructor(config, session, logger) {
|
|
16
|
+
super(config, session, logger);
|
|
19
17
|
this.config = config;
|
|
20
|
-
this.accessTokenConfig = accessTokenConfig;
|
|
21
|
-
this.refreshTokenConfig = refreshTokenConfig;
|
|
22
18
|
this.session = session;
|
|
23
19
|
this.logger = logger;
|
|
24
20
|
this.config.scope = this.config.scope ?? "email";
|
|
25
21
|
}
|
|
22
|
+
async logout(context) {
|
|
23
|
+
try {
|
|
24
|
+
await this.storeAccessToken("", context);
|
|
25
|
+
await this.storeRefreshToken("", context);
|
|
26
|
+
if (this.config.endpoints.logoutUrl) {
|
|
27
|
+
this.logger?.info("Redirecting to OAuth2 logout endpoint.");
|
|
28
|
+
this.redirectUser(context, this.config.endpoints.logoutUrl);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
this.logger?.error("Logout failed:", error);
|
|
33
|
+
throw new Error("Logout failed.");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async getCredentialsForPasswordGrant(context) {
|
|
37
|
+
if (this.config.credentials?.extractCredentials) {
|
|
38
|
+
return this.config.credentials.extractCredentials(context);
|
|
39
|
+
}
|
|
40
|
+
else if (typeof context === "object" && "body" in context) {
|
|
41
|
+
const body = context.body;
|
|
42
|
+
if (body.username && body.password) {
|
|
43
|
+
return {
|
|
44
|
+
identifier: body.username,
|
|
45
|
+
password: body.password,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
throw new Error("Missing credentials for password grant.");
|
|
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
|
+
}
|
|
124
|
+
}
|
|
26
125
|
async authenticate(context) {
|
|
27
126
|
try {
|
|
28
127
|
let user;
|
|
29
128
|
let refreshToken;
|
|
30
|
-
let accessToken = await this.
|
|
129
|
+
let accessToken = await this.retrieveAccessToken(context);
|
|
31
130
|
if (!accessToken) {
|
|
32
131
|
this.logger?.info("No access token found, checking for refresh token.");
|
|
33
|
-
refreshToken = await this.
|
|
132
|
+
refreshToken = await this.retrieveRefreshToken(context);
|
|
34
133
|
if (refreshToken) {
|
|
35
134
|
this.logger?.info("Found refresh token, attempting to refresh access token.");
|
|
36
135
|
const newTokens = await this.refreshAccessToken(context);
|
|
37
136
|
accessToken = newTokens.accessToken;
|
|
38
|
-
this.
|
|
137
|
+
this.embedAccessToken(accessToken, context);
|
|
39
138
|
if (newTokens.refreshToken) {
|
|
40
139
|
refreshToken = newTokens.refreshToken;
|
|
41
|
-
this.
|
|
140
|
+
this.embedRefreshToken(newTokens.refreshToken, context);
|
|
42
141
|
}
|
|
43
142
|
}
|
|
44
143
|
else {
|
|
45
144
|
this.logger?.info("No refresh token found, attempting authentication using grant type.");
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const tokenResult = await this.exchangeCodeForToken(context, authorizationCode);
|
|
51
|
-
accessToken = tokenResult.accessToken;
|
|
52
|
-
refreshToken = tokenResult.refreshToken;
|
|
53
|
-
break;
|
|
54
|
-
case "client_credentials":
|
|
55
|
-
this.logger?.info("Using client credentials grant.");
|
|
56
|
-
const clientCredResult = await this.exchangeClientCredentials();
|
|
57
|
-
accessToken = clientCredResult.accessToken;
|
|
58
|
-
break;
|
|
59
|
-
case "password":
|
|
60
|
-
this.logger?.info("Using password grant.");
|
|
61
|
-
if (!this.config.credentials) {
|
|
62
|
-
throw new Error("Missing credentials for password grant.");
|
|
63
|
-
}
|
|
64
|
-
const passwordResult = await this.exchangePasswordGrant();
|
|
65
|
-
accessToken = passwordResult.accessToken;
|
|
66
|
-
break;
|
|
67
|
-
default:
|
|
68
|
-
throw new Error(`Unsupported grant type: ${this.config.grantType}`);
|
|
69
|
-
}
|
|
70
|
-
this.accessTokenConfig.embed?.(context, accessToken);
|
|
145
|
+
const tokens = await this.processOAuthFlow(context);
|
|
146
|
+
accessToken = tokens.accessToken;
|
|
147
|
+
refreshToken = tokens.refreshToken;
|
|
148
|
+
this.embedAccessToken(accessToken, context);
|
|
71
149
|
if (refreshToken) {
|
|
72
|
-
this.
|
|
150
|
+
this.embedRefreshToken(refreshToken, context);
|
|
73
151
|
}
|
|
74
152
|
}
|
|
75
153
|
}
|
|
76
154
|
else if (accessToken && (await this.isTokenExpired(accessToken))) {
|
|
77
155
|
this.logger?.info("Access token expired, attempting refresh.");
|
|
78
|
-
refreshToken = await this.
|
|
156
|
+
refreshToken = await this.retrieveRefreshToken(context);
|
|
79
157
|
if (!refreshToken) {
|
|
80
158
|
throw new errors_1.MissingTokenError("Refresh");
|
|
81
159
|
}
|
|
82
160
|
const newTokens = await this.refreshAccessToken(context);
|
|
83
161
|
accessToken = newTokens.accessToken;
|
|
84
|
-
this.
|
|
162
|
+
this.embedAccessToken(accessToken, context);
|
|
85
163
|
if (newTokens.refreshToken && newTokens.refreshToken !== refreshToken) {
|
|
86
164
|
refreshToken = newTokens.refreshToken;
|
|
87
|
-
this.
|
|
165
|
+
this.embedRefreshToken(newTokens.refreshToken, context);
|
|
88
166
|
}
|
|
89
167
|
}
|
|
90
168
|
if (!accessToken) {
|
|
@@ -92,6 +170,7 @@ class OAuth2Strategy extends token_based_auth_strategy_1.TokenBasedAuthStrategy
|
|
|
92
170
|
}
|
|
93
171
|
user = await this.retrieveUser(accessToken);
|
|
94
172
|
if (!user) {
|
|
173
|
+
this.logger?.error("User retrieval failed: No user found for access token.");
|
|
95
174
|
throw new errors_1.UserNotFoundError();
|
|
96
175
|
}
|
|
97
176
|
await this.isAuthorized(user);
|
|
@@ -102,6 +181,29 @@ class OAuth2Strategy extends token_based_auth_strategy_1.TokenBasedAuthStrategy
|
|
|
102
181
|
throw new errors_1.AuthError(error, "OAuth2 authentication failed.");
|
|
103
182
|
}
|
|
104
183
|
}
|
|
184
|
+
async processOAuthFlow(context) {
|
|
185
|
+
if (this.config.grantType === "authorization_code") {
|
|
186
|
+
const authorizationCode = this.extractAuthorizationCode(context);
|
|
187
|
+
this.verifyAuthorizationCode(context, authorizationCode);
|
|
188
|
+
const tokenResult = await this.exchangeCodeForToken(context, authorizationCode);
|
|
189
|
+
return tokenResult;
|
|
190
|
+
}
|
|
191
|
+
if (this.config.grantType === "client_credentials") {
|
|
192
|
+
this.logger?.info("Using client credentials grant.");
|
|
193
|
+
const clientCredResult = await this.exchangeClientCredentials();
|
|
194
|
+
return { accessToken: clientCredResult.accessToken };
|
|
195
|
+
}
|
|
196
|
+
if (this.config.grantType === "password") {
|
|
197
|
+
this.logger?.info("Using password grant.");
|
|
198
|
+
const credentials = await this.getCredentialsForPasswordGrant(context);
|
|
199
|
+
if (!credentials) {
|
|
200
|
+
throw new Error("Missing credentials for password grant.");
|
|
201
|
+
}
|
|
202
|
+
const passwordResult = await this.exchangePasswordGrant(credentials.identifier, credentials.password);
|
|
203
|
+
return { accessToken: passwordResult.accessToken };
|
|
204
|
+
}
|
|
205
|
+
throw new Error(`Unsupported grant type: ${this.config.grantType}`);
|
|
206
|
+
}
|
|
105
207
|
verifyAuthorizationCode(context, code) {
|
|
106
208
|
if (!code) {
|
|
107
209
|
this.logger?.warn("Authorization code missing, redirecting user.");
|
|
@@ -185,7 +287,7 @@ class OAuth2Strategy extends token_based_auth_strategy_1.TokenBasedAuthStrategy
|
|
|
185
287
|
const response = await axios_1.default.get(this.config.endpoints.userInfoUrl, {
|
|
186
288
|
headers: { Authorization: `Bearer ${accessToken}` },
|
|
187
289
|
});
|
|
188
|
-
return (await this.config.validateUser
|
|
290
|
+
return (await this.config.user.validateUser(response.data)) || null;
|
|
189
291
|
}
|
|
190
292
|
catch (error) {
|
|
191
293
|
this.logger?.error("Failed to fetch user information:", error);
|
|
@@ -200,22 +302,22 @@ class OAuth2Strategy extends token_based_auth_strategy_1.TokenBasedAuthStrategy
|
|
|
200
302
|
});
|
|
201
303
|
return { accessToken: response.data.access_token };
|
|
202
304
|
}
|
|
203
|
-
async exchangePasswordGrant() {
|
|
204
|
-
if (!
|
|
305
|
+
async exchangePasswordGrant(username, password) {
|
|
306
|
+
if (!username || !password) {
|
|
205
307
|
throw new Error("Missing credentials for password grant.");
|
|
206
308
|
}
|
|
207
309
|
const response = await axios_1.default.post(this.config.endpoints.tokenUrl, {
|
|
208
310
|
grant_type: "password",
|
|
209
311
|
client_id: this.config.clientId,
|
|
210
312
|
client_secret: this.config.clientSecret,
|
|
211
|
-
username
|
|
212
|
-
password
|
|
313
|
+
username,
|
|
314
|
+
password,
|
|
213
315
|
});
|
|
214
316
|
return { accessToken: response.data.access_token };
|
|
215
317
|
}
|
|
216
318
|
async refreshAccessToken(context) {
|
|
217
319
|
try {
|
|
218
|
-
const refreshToken = await this.
|
|
320
|
+
const refreshToken = await this.retrieveRefreshToken(context);
|
|
219
321
|
if (!refreshToken) {
|
|
220
322
|
throw new errors_1.MissingTokenError("Refresh");
|
|
221
323
|
}
|
|
@@ -225,13 +327,20 @@ class OAuth2Strategy extends token_based_auth_strategy_1.TokenBasedAuthStrategy
|
|
|
225
327
|
client_secret: this.config.clientSecret,
|
|
226
328
|
refresh_token: refreshToken,
|
|
227
329
|
});
|
|
330
|
+
if (response.status !== 200 || !response.data.access_token) {
|
|
331
|
+
throw new Error("Failed to refresh token");
|
|
332
|
+
}
|
|
228
333
|
const refreshedTokens = {
|
|
229
334
|
accessToken: response.data.access_token,
|
|
230
335
|
refreshToken: response.data.refresh_token,
|
|
231
336
|
};
|
|
232
|
-
await this.
|
|
337
|
+
await this.storeAccessToken(refreshedTokens.accessToken, context);
|
|
338
|
+
if (refreshedTokens.refreshToken) {
|
|
339
|
+
await this.storeRefreshToken(refreshedTokens.refreshToken, context);
|
|
340
|
+
}
|
|
341
|
+
await this.embedAccessToken(refreshedTokens.accessToken, context);
|
|
233
342
|
if (refreshedTokens.refreshToken) {
|
|
234
|
-
await this.
|
|
343
|
+
await this.embedRefreshToken(refreshedTokens.refreshToken, context);
|
|
235
344
|
}
|
|
236
345
|
return refreshedTokens;
|
|
237
346
|
}
|
|
@@ -1,29 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CredentailsConfig, PKCEConfig, TokenAuthStrategyConfig } from "../../types";
|
|
2
2
|
export interface OAuth2Endpoints {
|
|
3
3
|
authorizationUrl: string;
|
|
4
4
|
tokenUrl: string;
|
|
5
5
|
userInfoUrl?: string;
|
|
6
6
|
introspectionUrl?: string;
|
|
7
7
|
revocationUrl?: string;
|
|
8
|
+
logoutUrl?: string;
|
|
8
9
|
}
|
|
9
|
-
export interface OAuth2StrategyConfig<TContext = unknown, TUser = unknown> extends
|
|
10
|
+
export interface OAuth2StrategyConfig<TContext = unknown, TUser = unknown> extends TokenAuthStrategyConfig<TContext, TUser> {
|
|
10
11
|
clientId: string;
|
|
11
12
|
clientSecret: string;
|
|
12
13
|
redirectUri: string;
|
|
13
|
-
scope?: string;
|
|
14
|
-
|
|
14
|
+
scope?: string | string[];
|
|
15
|
+
audience?: string;
|
|
16
|
+
responseType?: "code" | "token" | "id_token" | string;
|
|
17
|
+
grantType: "authorization_code" | "client_credentials" | "password" | "refresh_token" | string;
|
|
15
18
|
endpoints: OAuth2Endpoints;
|
|
16
|
-
|
|
17
|
-
credentials?: {
|
|
18
|
-
username: string;
|
|
19
|
-
password: string;
|
|
20
|
-
};
|
|
19
|
+
credentials?: CredentailsConfig<TContext>;
|
|
21
20
|
pkce?: PKCEConfig<TContext>;
|
|
22
|
-
accessToken?: TokenConfig;
|
|
23
|
-
refreshToken?: TokenConfig;
|
|
24
|
-
}
|
|
25
|
-
export interface PKCEConfig<TContext> {
|
|
26
|
-
generateCodeVerifier?: () => string;
|
|
27
|
-
storeCodeVerifier?: (context: TContext, codeVerifier: string) => void;
|
|
28
|
-
retrieveCodeVerifier?: (context: TContext) => string | null;
|
|
29
21
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as Soap from "@soapjs/soap";
|
|
2
|
+
import { AuthResult, TokenAuthStrategyConfig } from "../types";
|
|
3
|
+
import { BaseAuthStrategy } from "./base-auth.strategy";
|
|
4
|
+
import { SessionHandler } from "../session/session-handler";
|
|
5
|
+
export declare abstract class TokenAuthStrategy<TContext = unknown, TUser = unknown> extends BaseAuthStrategy<TContext, TUser> {
|
|
6
|
+
protected config: TokenAuthStrategyConfig<TContext, TUser>;
|
|
7
|
+
protected session?: SessionHandler;
|
|
8
|
+
protected logger?: Soap.Logger;
|
|
9
|
+
constructor(config: TokenAuthStrategyConfig<TContext, TUser>, session?: SessionHandler, logger?: Soap.Logger);
|
|
10
|
+
protected abstract retrieveAccessToken(context: TContext): Promise<string | undefined>;
|
|
11
|
+
protected abstract retrieveRefreshToken(context: TContext): Promise<string | undefined>;
|
|
12
|
+
protected abstract verifyAccessToken(token: string): Promise<any>;
|
|
13
|
+
protected abstract verifyRefreshToken(token: string): Promise<any>;
|
|
14
|
+
protected abstract generateAccessToken(payload: any): Promise<string>;
|
|
15
|
+
protected abstract generateRefreshToken(payload: any): Promise<string>;
|
|
16
|
+
protected abstract storeAccessToken(token: string, context: TContext): Promise<void>;
|
|
17
|
+
protected abstract storeRefreshToken(token: string, context: TContext): Promise<void>;
|
|
18
|
+
protected abstract embedAccessToken(token: string, context: TContext): void;
|
|
19
|
+
protected abstract embedRefreshToken(token: string, context: TContext): void;
|
|
20
|
+
authenticate(context: TContext): Promise<AuthResult<TUser>>;
|
|
21
|
+
rotateTokens(context: TContext): Promise<{
|
|
22
|
+
accessToken: string;
|
|
23
|
+
refreshToken?: string;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TokenAuthStrategy = void 0;
|
|
4
|
+
const base_auth_strategy_1 = require("./base-auth.strategy");
|
|
5
|
+
const errors_1 = require("../errors");
|
|
6
|
+
class TokenAuthStrategy extends base_auth_strategy_1.BaseAuthStrategy {
|
|
7
|
+
config;
|
|
8
|
+
session;
|
|
9
|
+
logger;
|
|
10
|
+
constructor(config, session, logger) {
|
|
11
|
+
super(config, session, logger);
|
|
12
|
+
this.config = config;
|
|
13
|
+
this.session = session;
|
|
14
|
+
this.logger = logger;
|
|
15
|
+
}
|
|
16
|
+
async authenticate(context) {
|
|
17
|
+
try {
|
|
18
|
+
let accessToken = await this.retrieveAccessToken(context);
|
|
19
|
+
let refreshToken;
|
|
20
|
+
await this.checkRateLimit(context);
|
|
21
|
+
if (accessToken) {
|
|
22
|
+
try {
|
|
23
|
+
const decoded = await this.verifyAccessToken(accessToken);
|
|
24
|
+
const user = await this.config.user.getUserData(decoded);
|
|
25
|
+
if (!user)
|
|
26
|
+
throw new errors_1.UserNotFoundError();
|
|
27
|
+
await this.isAuthorized(user);
|
|
28
|
+
return { user, tokens: { accessToken } };
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
this.logger?.warn("Access token is invalid or expired, trying refresh token...");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
refreshToken = await this.retrieveRefreshToken(context);
|
|
35
|
+
if (!refreshToken)
|
|
36
|
+
throw new errors_1.MissingTokenError("Refresh");
|
|
37
|
+
const newTokens = await this.rotateTokens(context);
|
|
38
|
+
accessToken = newTokens.accessToken;
|
|
39
|
+
refreshToken = newTokens.refreshToken;
|
|
40
|
+
if (!accessToken)
|
|
41
|
+
throw new errors_1.MissingTokenError("Access");
|
|
42
|
+
const decoded = await this.verifyAccessToken(accessToken);
|
|
43
|
+
const user = await this.config.user.getUserData(decoded);
|
|
44
|
+
if (!user)
|
|
45
|
+
throw new errors_1.UserNotFoundError();
|
|
46
|
+
await this.isAuthorized(user);
|
|
47
|
+
return { user, tokens: { accessToken, refreshToken } };
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
this.logger?.error("Authentication failed:", error);
|
|
51
|
+
throw new errors_1.AuthError(error, "Authentication failed.");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async rotateTokens(context) {
|
|
55
|
+
try {
|
|
56
|
+
const refreshToken = await this.retrieveRefreshToken(context);
|
|
57
|
+
if (!refreshToken)
|
|
58
|
+
throw new errors_1.MissingTokenError("Refresh");
|
|
59
|
+
const payload = await this.verifyRefreshToken(refreshToken);
|
|
60
|
+
if (!payload)
|
|
61
|
+
throw new errors_1.InvalidTokenError("Refresh");
|
|
62
|
+
const newAccessToken = await this.generateAccessToken(payload);
|
|
63
|
+
let newRefreshToken = await this.generateRefreshToken(payload);
|
|
64
|
+
await this.storeAccessToken(newAccessToken, context);
|
|
65
|
+
if (newRefreshToken) {
|
|
66
|
+
await this.storeRefreshToken(newRefreshToken, context);
|
|
67
|
+
}
|
|
68
|
+
this.embedAccessToken(newAccessToken, context);
|
|
69
|
+
this.embedRefreshToken(newRefreshToken, context);
|
|
70
|
+
return { accessToken: newAccessToken, refreshToken: newRefreshToken };
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
this.logger?.error("Token rotation failed:", error);
|
|
74
|
+
throw new errors_1.InvalidTokenError("Refresh");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.TokenAuthStrategy = TokenAuthStrategy;
|
|
@@ -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("./session.tools"), exports);
|
|
18
|
+
__exportStar(require("./token.tools"), exports);
|
|
19
|
+
__exportStar(require("./tools"), exports);
|
package/build/types.d.ts
CHANGED
|
@@ -36,21 +36,22 @@ export type Credentials = {
|
|
|
36
36
|
[key: string]: unknown;
|
|
37
37
|
};
|
|
38
38
|
export interface AuthResultConfig<TContext = unknown, TUser = unknown> {
|
|
39
|
-
onSuccess?: (context: AuthSuccessContext<TUser, TContext>) => Promise<void> | void;
|
|
40
|
-
onFailure?: (context: AuthFailureContext<TContext>) => Promise<void> | void;
|
|
39
|
+
onSuccess?: (action: string, context: AuthSuccessContext<TUser, TContext>) => Promise<void> | void;
|
|
40
|
+
onFailure?: (action: string, context: AuthFailureContext<TContext>) => Promise<void> | void;
|
|
41
41
|
}
|
|
42
42
|
export interface SecurityConfig {
|
|
43
43
|
maxFailedLoginAttempts?: number;
|
|
44
44
|
lockoutDuration?: number;
|
|
45
45
|
notifyOnLockout?: (account: any) => Promise<void>;
|
|
46
46
|
}
|
|
47
|
-
export interface BaseAuthStrategyConfig<TContext = unknown, TUser = unknown> {
|
|
47
|
+
export interface BaseAuthStrategyConfig<TContext = unknown, TUser = unknown> extends AuthResultConfig<TContext, TUser> {
|
|
48
48
|
mfa?: MfaConfig<TUser, TContext>;
|
|
49
49
|
session?: SessionConfig;
|
|
50
50
|
role?: RoleAuthorizationConfig<TUser>;
|
|
51
51
|
rateLimit?: RateLimitConfig;
|
|
52
52
|
security?: SecurityConfig;
|
|
53
53
|
lock?: AccountLockConfig<TContext>;
|
|
54
|
+
failedAttempts?: FailedAttemptsConfig;
|
|
54
55
|
}
|
|
55
56
|
export interface AuditLoggingConfig<TContext = unknown> {
|
|
56
57
|
logAttempt?: (userId: string, success: boolean, context?: TContext) => Promise<void>;
|
|
@@ -77,54 +78,58 @@ export interface PasswordPolicyConfig {
|
|
|
77
78
|
getLastPasswordChange?: (identifier: string) => Date;
|
|
78
79
|
forcePasswordChangeOnFirstLogin?: boolean;
|
|
79
80
|
passwordExpirationDays?: number;
|
|
81
|
+
isPasswordChangeRequired?: (identifier: string) => Promise<boolean>;
|
|
82
|
+
generateResetToken?: (identifier: string) => Promise<string>;
|
|
83
|
+
sendResetEmail?: (identifier: string, token: string) => Promise<void>;
|
|
84
|
+
validateResetToken?: (token: string) => Promise<boolean>;
|
|
85
|
+
updatePassword?: (identifier: string, newPassword: string) => Promise<void>;
|
|
80
86
|
}
|
|
81
|
-
export interface
|
|
87
|
+
export interface UserConfig {
|
|
88
|
+
getUserData: (payload: any) => Promise<any>;
|
|
89
|
+
validateUser?: (payload: any) => Promise<any>;
|
|
90
|
+
}
|
|
91
|
+
export interface CredentailsConfig<TContext = any> {
|
|
92
|
+
extractCredentials: <TCredentials = {
|
|
93
|
+
identifier: string;
|
|
94
|
+
password: string;
|
|
95
|
+
}>(context: TContext) => TCredentials;
|
|
96
|
+
verifyCredentials: (identifier: string, password: string) => Promise<boolean>;
|
|
97
|
+
}
|
|
98
|
+
export interface FailedAttemptsConfig {
|
|
99
|
+
incrementFailedAttempts?: (identifier: string, ...rest: unknown[]) => Promise<void>;
|
|
100
|
+
resetFailedAttempts?: (identifier: string, ...rest: unknown[]) => Promise<void>;
|
|
101
|
+
getFailedAttempts?: (identifier: string, ...rest: unknown[]) => Promise<number>;
|
|
102
|
+
}
|
|
103
|
+
export interface CredentialAuthStrategyConfig<TContext = unknown, TUser = unknown> extends BaseAuthStrategyConfig<TContext, TUser> {
|
|
82
104
|
passwordPolicy?: PasswordPolicyConfig;
|
|
83
105
|
audit?: AuditLoggingConfig<TContext>;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
retrieveUserData: (identifier: string) => Promise<TUser | null>;
|
|
107
|
-
} & AuthResultConfig<TContext, TUser>;
|
|
108
|
-
logout: {} & AuthResultConfig<TContext, TUser>;
|
|
109
|
-
}
|
|
110
|
-
export interface TokenRotationConfig<TUser = unknown> {
|
|
111
|
-
maxRotations?: number;
|
|
112
|
-
storeUsedTokens?: boolean;
|
|
113
|
-
getRefreshToken?: (user: TUser) => Promise<string>;
|
|
114
|
-
rotateToken?: (refreshToken: string) => Promise<{
|
|
115
|
-
accessToken: string;
|
|
116
|
-
refreshToken: string;
|
|
117
|
-
}>;
|
|
118
|
-
onTokenRotated?: (user: TUser, newTokens: {
|
|
119
|
-
accessToken: string;
|
|
120
|
-
refreshToken: string;
|
|
121
|
-
}) => Promise<void>;
|
|
122
|
-
onTokenRotationFailure?: (user: TUser, error: Error) => Promise<void>;
|
|
106
|
+
credentials?: CredentailsConfig<TContext>;
|
|
107
|
+
user?: UserConfig;
|
|
108
|
+
routes?: {
|
|
109
|
+
login?: AuthRouteConfig;
|
|
110
|
+
logout?: AuthRouteConfig;
|
|
111
|
+
resetPassword?: AuthRouteConfig;
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
export type AuthRouteConfig = {
|
|
115
|
+
path?: string;
|
|
116
|
+
method?: string;
|
|
117
|
+
};
|
|
118
|
+
export interface TokenAuthStrategyConfig<TContext = unknown, TUser = unknown> extends BaseAuthStrategyConfig<TContext, TUser> {
|
|
119
|
+
user?: UserConfig;
|
|
120
|
+
routes: {
|
|
121
|
+
login?: AuthRouteConfig;
|
|
122
|
+
logout?: AuthRouteConfig;
|
|
123
|
+
refresh?: AuthRouteConfig;
|
|
124
|
+
[key: string]: AuthRouteConfig;
|
|
125
|
+
};
|
|
126
|
+
accessToken?: TokenConfig<TContext>;
|
|
127
|
+
refreshToken?: TokenConfig<TContext>;
|
|
123
128
|
}
|
|
124
129
|
export interface AccountLockConfig<TContext = unknown> {
|
|
125
130
|
logFailedAttempt?: (account: any, context?: TContext) => Promise<void>;
|
|
126
|
-
lockAccount?: (account: any
|
|
127
|
-
isAccountLocked?: (account: any,
|
|
131
|
+
lockAccount?: (account: any) => Promise<void>;
|
|
132
|
+
isAccountLocked?: (account: any, context?: TContext) => Promise<boolean>;
|
|
128
133
|
}
|
|
129
134
|
export interface RateLimitConfig {
|
|
130
135
|
checkRateLimit?: (...args: unknown[]) => Promise<boolean>;
|
|
@@ -159,7 +164,7 @@ export type AuthResult<TUser, TSessionData = unknown> = {
|
|
|
159
164
|
};
|
|
160
165
|
export interface AuthStrategy<TContext = unknown, TUser = unknown> {
|
|
161
166
|
init?(...args: unknown[]): Promise<void>;
|
|
162
|
-
authenticate(context?: TContext, ...args: unknown[]): Promise<AuthResult<TUser
|
|
167
|
+
authenticate(context?: TContext, ...args: unknown[]): Promise<AuthResult<TUser> | null>;
|
|
163
168
|
authorize?(user: any, action: string, resource?: string): Promise<boolean>;
|
|
164
169
|
refresh?(refreshToken: string): Promise<string>;
|
|
165
170
|
logout?(context?: TContext): Promise<void>;
|
|
@@ -230,18 +235,43 @@ export interface StorageContext {
|
|
|
230
235
|
encrypt?: (data: string) => Promise<string> | string;
|
|
231
236
|
decrypt?: (data: string) => Promise<string> | string;
|
|
232
237
|
}
|
|
233
|
-
export interface
|
|
238
|
+
export interface TokenIssuerConfig {
|
|
234
239
|
secretKey: string;
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
+
options: {
|
|
241
|
+
expiresIn: string | number;
|
|
242
|
+
audience?: string | string[];
|
|
243
|
+
issuer?: string | string[];
|
|
244
|
+
subject?: string;
|
|
245
|
+
[option: string]: unknown;
|
|
246
|
+
};
|
|
240
247
|
generate?: (payload: any) => string;
|
|
248
|
+
}
|
|
249
|
+
export interface TokenVerifierConfig {
|
|
250
|
+
options: {
|
|
251
|
+
[option: string]: unknown;
|
|
252
|
+
};
|
|
241
253
|
verify?: (token: string) => Promise<any>;
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
254
|
+
}
|
|
255
|
+
export interface TokenPersistenceConfig {
|
|
256
|
+
store?: (token: string, data: any, expiresIn: string | number) => Promise<void>;
|
|
257
|
+
read?: (...args: any[]) => Promise<string | null>;
|
|
258
|
+
remove?: (...args: any[]) => Promise<void>;
|
|
259
|
+
}
|
|
260
|
+
export interface TokenConfig<TContext = any> {
|
|
261
|
+
embed?: (context: TContext, token: string) => void;
|
|
262
|
+
retrieve?: (context: TContext) => Promise<string | null>;
|
|
263
|
+
rotation?: TokenRotationConfig;
|
|
264
|
+
issuer?: TokenIssuerConfig;
|
|
265
|
+
verifier?: TokenVerifierConfig;
|
|
266
|
+
persistence?: TokenPersistenceConfig;
|
|
267
|
+
additional?: Record<string, unknown>;
|
|
268
|
+
}
|
|
269
|
+
export interface TokenRotationConfig {
|
|
270
|
+
maxRotations?: number;
|
|
271
|
+
rotateToken?: (token: string) => Promise<string>;
|
|
272
|
+
}
|
|
273
|
+
export interface PKCEConfig<TContext> {
|
|
274
|
+
generateCodeVerifier?: () => string;
|
|
275
|
+
storeCodeVerifier?: (context: TContext, codeVerifier: string) => void;
|
|
276
|
+
retrieveCodeVerifier?: (context: TContext) => string | null;
|
|
247
277
|
}
|