@soapjs/soap-auth 0.1.0 → 0.2.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 +475 -8
- package/build/factories/http-auth-strategy.factory.js +1 -2
- 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 +22 -6
- 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 -45
- 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 +4 -14
- 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 +161 -52
- package/build/strategies/oauth2/oauth2.types.d.ts +9 -17
- 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 +94 -68
- package/package.json +3 -3
- package/build/strategies/token-based-auth.strategy.d.ts +0 -25
- package/build/strategies/token-based-auth.strategy.js +0 -124
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TokenAuthStrategyConfig } from "../../types";
|
|
2
2
|
export interface JwtVerifyOptions {
|
|
3
3
|
algorithms?: string[];
|
|
4
4
|
notBefore?: string | number;
|
|
@@ -12,22 +12,12 @@ export interface JwtVerifyOptions {
|
|
|
12
12
|
export interface JwtSignOptions {
|
|
13
13
|
algorithm?: "HS256" | "HS384" | "HS512" | "RS256" | "RS384" | "RS512" | "ES256" | "ES384" | "ES512" | "PS256" | "PS384" | "PS512" | "none";
|
|
14
14
|
notBefore?: string | number;
|
|
15
|
-
|
|
15
|
+
jti?: string;
|
|
16
16
|
issuedAt?: number;
|
|
17
17
|
mutatePayload?: (payload: Record<string, any>) => Record<string, any>;
|
|
18
18
|
noTimestamp?: boolean;
|
|
19
19
|
keyid?: string;
|
|
20
20
|
allowUnsafe?: boolean;
|
|
21
21
|
}
|
|
22
|
-
export
|
|
23
|
-
|
|
24
|
-
signOptions: JwtSignOptions;
|
|
25
|
-
} & TokenHandlerConfig;
|
|
26
|
-
export type JwtRefreshTokenHandlerConfig = {
|
|
27
|
-
verifyOptions?: JwtVerifyOptions;
|
|
28
|
-
signOptions: JwtSignOptions;
|
|
29
|
-
} & TokenHandlerConfig;
|
|
30
|
-
export type JwtConfig<TContext = unknown, TUser = unknown> = {
|
|
31
|
-
access: JwtAccessTokenHandlerConfig;
|
|
32
|
-
refresh?: JwtRefreshTokenHandlerConfig;
|
|
33
|
-
} & TokenBasedAuthStrategyConfig<TContext, TUser>;
|
|
22
|
+
export interface JwtConfig<TContext = unknown, TUser = unknown> extends TokenAuthStrategyConfig<TContext, TUser> {
|
|
23
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as Soap from "@soapjs/soap";
|
|
2
|
-
import {
|
|
2
|
+
import { CredentialAuthStrategy } from "../credential-auth.strategy";
|
|
3
3
|
import { LocalStrategyConfig } from "./local.types";
|
|
4
4
|
import { SessionHandler } from "../../session/session-handler";
|
|
5
|
-
export declare class LocalStrategy<TContext = unknown, TUser = unknown> extends
|
|
5
|
+
export declare class LocalStrategy<TContext = unknown, TUser = unknown> extends CredentialAuthStrategy<TContext, TUser> {
|
|
6
6
|
protected config: LocalStrategyConfig<TContext, TUser>;
|
|
7
7
|
protected session?: SessionHandler;
|
|
8
8
|
protected logger?: Soap.Logger;
|
|
@@ -11,15 +11,9 @@ export declare class LocalStrategy<TContext = unknown, TUser = unknown> extends
|
|
|
11
11
|
identifier: string;
|
|
12
12
|
password: string;
|
|
13
13
|
}>;
|
|
14
|
-
protected verifyCredentials(
|
|
15
|
-
identifier: string;
|
|
16
|
-
password: string;
|
|
17
|
-
}): Promise<boolean>;
|
|
14
|
+
protected verifyCredentials(identifier: string, password: string): Promise<boolean>;
|
|
18
15
|
protected retrieveUser(credentials: {
|
|
19
16
|
identifier: string;
|
|
20
17
|
password: string;
|
|
21
18
|
}): Promise<TUser | null>;
|
|
22
|
-
requestPasswordReset(email: string): Promise<void>;
|
|
23
|
-
resetPassword(email: string, token: string, newPassword: string): Promise<void>;
|
|
24
|
-
changePassword(email: string, oldPassword: string, newPassword: string): Promise<void>;
|
|
25
19
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.LocalStrategy = void 0;
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
class LocalStrategy extends credential_based_auth_strategy_1.CredentialBasedAuthStrategy {
|
|
4
|
+
const credential_auth_strategy_1 = require("../credential-auth.strategy");
|
|
5
|
+
class LocalStrategy extends credential_auth_strategy_1.CredentialAuthStrategy {
|
|
7
6
|
config;
|
|
8
7
|
session;
|
|
9
8
|
logger;
|
|
@@ -13,64 +12,14 @@ class LocalStrategy extends credential_based_auth_strategy_1.CredentialBasedAuth
|
|
|
13
12
|
this.session = session;
|
|
14
13
|
this.logger = logger;
|
|
15
14
|
}
|
|
16
|
-
|
|
17
|
-
return this.config.
|
|
15
|
+
extractCredentials(context) {
|
|
16
|
+
return this.config.credentials.extractCredentials(context);
|
|
18
17
|
}
|
|
19
|
-
async verifyCredentials(
|
|
20
|
-
return this.config.
|
|
18
|
+
async verifyCredentials(identifier, password) {
|
|
19
|
+
return this.config.credentials.verifyCredentials(identifier, password);
|
|
21
20
|
}
|
|
22
21
|
async retrieveUser(credentials) {
|
|
23
|
-
return this.config.
|
|
24
|
-
}
|
|
25
|
-
async requestPasswordReset(email) {
|
|
26
|
-
try {
|
|
27
|
-
if (!this.config.passwordReset?.generateResetToken) {
|
|
28
|
-
throw new Error("Password reset token generation is not configured.");
|
|
29
|
-
}
|
|
30
|
-
const token = await this.config.passwordReset.generateResetToken(email);
|
|
31
|
-
await this.config.passwordReset.sendResetEmail?.(email, token);
|
|
32
|
-
this.logger?.info(`Password reset requested for email: ${email}`);
|
|
33
|
-
await this.config.passwordReset.onSuccess?.({ email });
|
|
34
|
-
}
|
|
35
|
-
catch (error) {
|
|
36
|
-
this.logger?.error("Password reset request error:", error);
|
|
37
|
-
await this.config.passwordReset.onFailure?.({ email, error });
|
|
38
|
-
throw new errors_1.AuthError(error, "Password reset request failed.");
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
async resetPassword(email, token, newPassword) {
|
|
42
|
-
try {
|
|
43
|
-
if (!this.config.passwordReset?.validateResetToken) {
|
|
44
|
-
throw new Error("Password reset token validation is not configured.");
|
|
45
|
-
}
|
|
46
|
-
const isValid = await this.config.passwordReset.validateResetToken(token);
|
|
47
|
-
if (!isValid)
|
|
48
|
-
throw new Error("Invalid or expired reset token.");
|
|
49
|
-
await this.config.passwordReset.updatePassword(email, newPassword);
|
|
50
|
-
this.logger?.info(`Password reset successful for email: ${email}`);
|
|
51
|
-
await this.config.passwordReset.onSuccess?.({ email });
|
|
52
|
-
}
|
|
53
|
-
catch (error) {
|
|
54
|
-
this.logger?.error("Password reset error:", error);
|
|
55
|
-
await this.config.passwordReset.onFailure?.({ email, error });
|
|
56
|
-
throw new errors_1.AuthError(error, "Password reset failed.");
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
async changePassword(email, oldPassword, newPassword) {
|
|
60
|
-
try {
|
|
61
|
-
const isAuthenticated = await this.config.login.verifyUserCredentials(email, oldPassword);
|
|
62
|
-
if (!isAuthenticated) {
|
|
63
|
-
throw new errors_1.InvalidCredentialsError();
|
|
64
|
-
}
|
|
65
|
-
await this.config.passwordReset?.updatePassword?.(email, newPassword);
|
|
66
|
-
this.logger?.info(`Password changed successfully for email: ${email}`);
|
|
67
|
-
await this.config.passwordReset?.onSuccess?.({ email });
|
|
68
|
-
}
|
|
69
|
-
catch (error) {
|
|
70
|
-
this.logger?.error("Change password error:", error);
|
|
71
|
-
await this.config.passwordReset?.onFailure?.({ email, error });
|
|
72
|
-
throw new errors_1.AuthError(error, "Change password failed.");
|
|
73
|
-
}
|
|
22
|
+
return this.config.user.getUserData(credentials.identifier);
|
|
74
23
|
}
|
|
75
24
|
}
|
|
76
25
|
exports.LocalStrategy = LocalStrategy;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export interface LocalStrategyConfig<TContext = unknown, TUser = unknown> extends
|
|
1
|
+
import { CredentialAuthStrategyConfig } from "../../types";
|
|
2
|
+
export interface LocalStrategyConfig<TContext = unknown, TUser = unknown> extends CredentialAuthStrategyConfig<TContext, TUser> {
|
|
3
3
|
}
|
|
@@ -1,16 +1,30 @@
|
|
|
1
1
|
import * as Soap from "@soapjs/soap";
|
|
2
|
-
import {
|
|
3
|
-
import { TokenBasedAuthStrategy } from "../token-based-auth.strategy";
|
|
2
|
+
import { AuthResult } from "../../types";
|
|
4
3
|
import { OAuth2StrategyConfig } from "./oauth2.types";
|
|
5
4
|
import { SessionHandler } from "../../session/session-handler";
|
|
6
|
-
|
|
5
|
+
import { BaseAuthStrategy } from "../base-auth.strategy";
|
|
6
|
+
export declare class OAuth2Strategy<TContext = unknown, TUser = unknown> extends BaseAuthStrategy<TContext, TUser> {
|
|
7
7
|
protected config: OAuth2StrategyConfig<TContext, TUser>;
|
|
8
|
-
protected accessTokenHandler: TokenHandlerConfig;
|
|
9
|
-
protected refreshTokenHandler?: TokenHandlerConfig;
|
|
10
8
|
protected session?: SessionHandler;
|
|
11
9
|
protected logger?: Soap.Logger;
|
|
12
|
-
constructor(config: OAuth2StrategyConfig<TContext, TUser>,
|
|
10
|
+
constructor(config: OAuth2StrategyConfig<TContext, TUser>, session?: SessionHandler, logger?: Soap.Logger);
|
|
11
|
+
logout(context: TContext): Promise<void>;
|
|
12
|
+
protected getCredentialsForPasswordGrant(context: TContext): Promise<{
|
|
13
|
+
identifier: string;
|
|
14
|
+
password: string;
|
|
15
|
+
}>;
|
|
16
|
+
protected retrieveAccessToken(context: TContext): Promise<string | undefined>;
|
|
17
|
+
protected retrieveRefreshToken(context: TContext): Promise<string | undefined>;
|
|
18
|
+
protected storeAccessToken(token: string, context: TContext): Promise<void>;
|
|
19
|
+
protected storeRefreshToken(token: string, context: TContext): Promise<void>;
|
|
20
|
+
protected embedAccessToken(token: string, context: TContext): void;
|
|
21
|
+
protected embedRefreshToken(token: string, context: TContext): void;
|
|
22
|
+
isTokenExpired(token: string): Promise<boolean>;
|
|
13
23
|
authenticate(context: TContext): Promise<AuthResult<TUser>>;
|
|
24
|
+
protected processOAuthFlow(context: TContext): Promise<{
|
|
25
|
+
accessToken: string;
|
|
26
|
+
refreshToken?: string;
|
|
27
|
+
}>;
|
|
14
28
|
protected verifyAuthorizationCode(context: TContext, code: string): void;
|
|
15
29
|
protected extractAuthorizationCode(context: TContext): string | null;
|
|
16
30
|
protected redirectUser(context: TContext, authUrl: string): void;
|
|
@@ -24,7 +38,7 @@ export declare class OAuth2Strategy<TContext = unknown, TUser = unknown> extends
|
|
|
24
38
|
protected exchangeClientCredentials(): Promise<{
|
|
25
39
|
accessToken: string;
|
|
26
40
|
}>;
|
|
27
|
-
protected exchangePasswordGrant(): Promise<{
|
|
41
|
+
protected exchangePasswordGrant(username: string, password: string): Promise<{
|
|
28
42
|
accessToken: string;
|
|
29
43
|
}>;
|
|
30
44
|
refreshAccessToken(context: TContext): Promise<{
|
|
@@ -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
|
-
accessTokenHandler;
|
|
14
|
-
refreshTokenHandler;
|
|
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.accessTokenHandler = accessTokenHandler;
|
|
21
|
-
this.refreshTokenHandler = refreshTokenHandler;
|
|
22
18
|
this.session = session;
|
|
23
19
|
this.logger = logger;
|
|
24
|
-
this.config.scope = this.config.scope ?? "
|
|
20
|
+
this.config.scope = this.config.scope ?? "email";
|
|
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
|
+
}
|
|
25
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.accessTokenHandler.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.");
|
|
@@ -126,7 +228,7 @@ class OAuth2Strategy extends token_based_auth_strategy_1.TokenBasedAuthStrategy
|
|
|
126
228
|
}
|
|
127
229
|
buildAuthorizationUrl(context) {
|
|
128
230
|
let authorizationUrl = `${this.config.endpoints.authorizationUrl}?client_id=${this.config.clientId}&redirect_uri=${encodeURIComponent(this.config.redirectUri)}&response_type=code&scope=${this.config.scope ?? ""}`;
|
|
129
|
-
if (this.config.pkce
|
|
231
|
+
if (this.config.pkce) {
|
|
130
232
|
const codeVerifier = this.config.pkce.generateCodeVerifier
|
|
131
233
|
? this.config.pkce.generateCodeVerifier()
|
|
132
234
|
: oauth2_tools_1.OAuth2Tools.generateCodeVerifier();
|
|
@@ -143,7 +245,7 @@ class OAuth2Strategy extends token_based_auth_strategy_1.TokenBasedAuthStrategy
|
|
|
143
245
|
code,
|
|
144
246
|
redirect_uri: this.config.redirectUri,
|
|
145
247
|
};
|
|
146
|
-
if (this.config.pkce
|
|
248
|
+
if (this.config.pkce) {
|
|
147
249
|
const codeVerifier = this.config.pkce.retrieveCodeVerifier?.(context);
|
|
148
250
|
if (!codeVerifier) {
|
|
149
251
|
throw new Error("Missing PKCE code verifier in context.");
|
|
@@ -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
|
-
|
|
18
|
-
username: string;
|
|
19
|
-
password: string;
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
export interface PKCEConfig<TContext> {
|
|
23
|
-
pkce?: {
|
|
24
|
-
enabled?: boolean;
|
|
25
|
-
generateCodeVerifier?: () => string;
|
|
26
|
-
storeCodeVerifier?: (context: TContext, codeVerifier: string) => void;
|
|
27
|
-
retrieveCodeVerifier?: (context: TContext) => string | null;
|
|
28
|
-
};
|
|
19
|
+
credentials?: CredentailsConfig<TContext>;
|
|
20
|
+
pkce?: PKCEConfig<TContext>;
|
|
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;
|