@soapjs/soap-auth 0.1.1 → 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 -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 +1 -1
- package/build/strategies/token-based-auth.strategy.d.ts +0 -25
- package/build/strategies/token-based-auth.strategy.js +0 -130
|
@@ -23,57 +23,96 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
23
23
|
return result;
|
|
24
24
|
};
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.prepareRefreshTokenConfig = exports.prepareAccessTokenConfig = void 0;
|
|
26
|
+
exports.clearDefaultJwtCookie = exports.clearDefaultJwtHeader = exports.setDefaultJwtHeader = exports.setDefaultJwtCookie = exports.prepareRefreshTokenConfig = exports.prepareAccessTokenConfig = void 0;
|
|
27
27
|
const Soap = __importStar(require("@soapjs/soap"));
|
|
28
28
|
const prepareAccessTokenConfig = (config) => {
|
|
29
29
|
return Soap.removeUndefinedProperties({
|
|
30
|
-
...config
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
30
|
+
...config,
|
|
31
|
+
generation: {
|
|
32
|
+
...config.issuer.options,
|
|
33
|
+
expiresIn: config.issuer.options.expiresIn || "1h",
|
|
34
|
+
algorithm: config.issuer.options.algorithm || "HS256",
|
|
35
|
+
},
|
|
36
|
+
verification: {
|
|
37
|
+
...config.verifier.options,
|
|
38
|
+
algorithms: config.verifier.options.algorithms || ["HS256"],
|
|
39
|
+
expiresIn: config.verifier.options.expiresIn || "1h",
|
|
40
40
|
},
|
|
41
|
-
verifyOptions: config.accessToken.verifyOptions
|
|
42
|
-
? {
|
|
43
|
-
...config.accessToken.verifyOptions,
|
|
44
|
-
algorithms: config.accessToken.verifyOptions.algorithms || ["HS256"],
|
|
45
|
-
expiresIn: config.accessToken.expiresIn || "1h",
|
|
46
|
-
audience: config.accessToken.audience,
|
|
47
|
-
issuer: config.accessToken.issuer,
|
|
48
|
-
subject: config.accessToken.subject,
|
|
49
|
-
}
|
|
50
|
-
: {},
|
|
51
41
|
});
|
|
52
42
|
};
|
|
53
43
|
exports.prepareAccessTokenConfig = prepareAccessTokenConfig;
|
|
54
44
|
const prepareRefreshTokenConfig = (config) => {
|
|
55
45
|
return Soap.removeUndefinedProperties({
|
|
56
|
-
...config
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
46
|
+
...config,
|
|
47
|
+
generation: {
|
|
48
|
+
...config.issuer,
|
|
49
|
+
expiresIn: config.issuer.options.expiresIn || "7d",
|
|
50
|
+
algorithm: config.issuer.options.algorithm || "HS256",
|
|
51
|
+
},
|
|
52
|
+
verification: {
|
|
53
|
+
...config.verifier.options,
|
|
54
|
+
algorithms: config.verifier.options.algorithms || ["HS256"],
|
|
55
|
+
expiresIn: config.verifier.options.expiresIn || "7d",
|
|
66
56
|
},
|
|
67
|
-
verifyOptions: config.refreshToken.verifyOptions
|
|
68
|
-
? {
|
|
69
|
-
...config.refreshToken.verifyOptions,
|
|
70
|
-
algorithm: config.refreshToken.verifyOptions.algorithms || ["HS256"],
|
|
71
|
-
expiresIn: config.refreshToken.expiresIn || "7d",
|
|
72
|
-
audience: config.refreshToken.audience,
|
|
73
|
-
issuer: config.refreshToken.issuer,
|
|
74
|
-
subject: config.refreshToken.subject,
|
|
75
|
-
}
|
|
76
|
-
: {},
|
|
77
57
|
});
|
|
78
58
|
};
|
|
79
59
|
exports.prepareRefreshTokenConfig = prepareRefreshTokenConfig;
|
|
60
|
+
const setDefaultJwtCookie = (token, context) => {
|
|
61
|
+
const options = {
|
|
62
|
+
httpOnly: true,
|
|
63
|
+
secure: true,
|
|
64
|
+
sameSite: "Strict",
|
|
65
|
+
maxAge: 7 * 24 * 60 * 60 * 1000,
|
|
66
|
+
};
|
|
67
|
+
if (context?.res) {
|
|
68
|
+
context.res.cookie("refreshToken", token, options);
|
|
69
|
+
}
|
|
70
|
+
else if (context?.response) {
|
|
71
|
+
context.response.cookie("refreshToken", token, options);
|
|
72
|
+
}
|
|
73
|
+
else if (context?.cookie) {
|
|
74
|
+
context.cookie("refreshToken", token, options);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
exports.setDefaultJwtCookie = setDefaultJwtCookie;
|
|
78
|
+
const setDefaultJwtHeader = (token, context) => {
|
|
79
|
+
if (typeof context?.res?.setHeader === "function") {
|
|
80
|
+
context.res.setHeader("Authorization", `Bearer ${token}`);
|
|
81
|
+
}
|
|
82
|
+
else if (typeof context?.response?.setHeader === "function") {
|
|
83
|
+
context.response.setHeader("Authorization", `Bearer ${token}`);
|
|
84
|
+
}
|
|
85
|
+
else if (typeof context?.setHeader === "function") {
|
|
86
|
+
context.setHeader("Authorization", `Bearer ${token}`);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
exports.setDefaultJwtHeader = setDefaultJwtHeader;
|
|
90
|
+
const clearDefaultJwtHeader = (context) => {
|
|
91
|
+
if (typeof context?.res?.setHeader === "function") {
|
|
92
|
+
context.res.setHeader("Authorization", ``);
|
|
93
|
+
}
|
|
94
|
+
else if (typeof context?.response?.setHeader === "function") {
|
|
95
|
+
context.response.setHeader("Authorization", ``);
|
|
96
|
+
}
|
|
97
|
+
else if (typeof context?.setHeader === "function") {
|
|
98
|
+
context.setHeader("Authorization", ``);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
exports.clearDefaultJwtHeader = clearDefaultJwtHeader;
|
|
102
|
+
const clearDefaultJwtCookie = (context) => {
|
|
103
|
+
const options = {
|
|
104
|
+
httpOnly: true,
|
|
105
|
+
secure: true,
|
|
106
|
+
sameSite: "Strict",
|
|
107
|
+
};
|
|
108
|
+
if (typeof context?.res?.clearCookie === "function") {
|
|
109
|
+
context.res.clearCookie("refreshToken", options);
|
|
110
|
+
}
|
|
111
|
+
else if (typeof context?.response?.clearCookie === "function") {
|
|
112
|
+
context.response.clearCookie("refreshToken", options);
|
|
113
|
+
}
|
|
114
|
+
else if (typeof context?.clearCookie === "function") {
|
|
115
|
+
context.clearCookie("refreshToken", options);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
exports.clearDefaultJwtCookie = clearDefaultJwtCookie;
|
|
@@ -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,36 +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
|
-
verifyOptions?: JwtVerifyOptions;
|
|
24
|
-
signOptions: JwtSignOptions;
|
|
25
|
-
} & TokenConfig;
|
|
26
|
-
export type JwtRefreshTokenConfig = {
|
|
27
|
-
verifyOptions?: JwtVerifyOptions;
|
|
28
|
-
signOptions: JwtSignOptions;
|
|
29
|
-
} & TokenConfig;
|
|
30
|
-
export interface JwtConfig<TContext = unknown, TUser = unknown> extends TokenBasedAuthStrategyConfig<TContext, TUser> {
|
|
31
|
-
accessToken: JwtAccessTokenConfig;
|
|
32
|
-
refreshToken?: JwtRefreshTokenConfig;
|
|
33
|
-
routes?: {
|
|
34
|
-
login?: {
|
|
35
|
-
path: string;
|
|
36
|
-
method?: "POST" | "GET";
|
|
37
|
-
};
|
|
38
|
-
logout?: {
|
|
39
|
-
path: string;
|
|
40
|
-
method?: "POST" | "GET";
|
|
41
|
-
};
|
|
42
|
-
refresh?: {
|
|
43
|
-
path: string;
|
|
44
|
-
method?: "POST" | "GET";
|
|
45
|
-
};
|
|
46
|
-
};
|
|
22
|
+
export interface JwtConfig<TContext = unknown, TUser = unknown> extends TokenAuthStrategyConfig<TContext, TUser> {
|
|
47
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 accessTokenConfig: TokenConfig;
|
|
9
|
-
protected refreshTokenConfig?: TokenConfig;
|
|
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
|
-
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
|
}
|