@soapjs/soap-auth 0.3.3 → 0.4.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/.claude/settings.local.json +20 -0
- package/build/__tests__/soap-auth.test.js +94 -0
- package/build/errors.d.ts +1 -1
- package/build/errors.js +2 -2
- package/build/index.d.ts +1 -0
- package/build/index.js +1 -0
- package/build/services/__tests__/password.service.test.js +25 -18
- package/build/services/__tests__/totp.service.test.d.ts +1 -0
- package/build/services/__tests__/totp.service.test.js +120 -0
- package/build/services/auth-throttle.service.d.ts +2 -2
- package/build/services/auth-throttle.service.js +2 -2
- package/build/services/index.d.ts +1 -0
- package/build/services/index.js +1 -0
- package/build/services/password.service.d.ts +7 -5
- package/build/services/password.service.js +76 -18
- package/build/services/totp.service.d.ts +16 -0
- package/build/services/totp.service.js +96 -0
- package/build/session/__tests__/session-handler.test.js +22 -14
- package/build/session/session-handler.d.ts +1 -0
- package/build/session/session-handler.js +39 -6
- package/build/soap-auth.d.ts +13 -6
- package/build/soap-auth.js +132 -5
- package/build/strategies/__tests__/base-auth.strategy.test.d.ts +3 -2
- package/build/strategies/__tests__/base-auth.strategy.test.js +1 -0
- package/build/strategies/__tests__/credential-auth.strategy.test.d.ts +1 -0
- package/build/strategies/__tests__/credential-auth.strategy.test.js +18 -17
- package/build/strategies/__tests__/token-auth.strategy.test.d.ts +1 -0
- package/build/strategies/__tests__/token-auth.strategy.test.js +1 -0
- package/build/strategies/api-key/api-key.strategy.d.ts +4 -4
- package/build/strategies/api-key/api-key.strategy.js +3 -2
- package/build/strategies/base-auth.strategy.d.ts +5 -4
- package/build/strategies/basic/basic.strategy.d.ts +2 -1
- package/build/strategies/basic/basic.strategy.js +1 -0
- package/build/strategies/credential-auth.strategy.d.ts +7 -7
- package/build/strategies/credential-auth.strategy.js +9 -14
- package/build/strategies/index.d.ts +1 -0
- package/build/strategies/index.js +1 -0
- package/build/strategies/jwt/__tests__/jwt.strategy.test.js +2 -2
- package/build/strategies/jwt/jwt.strategy.d.ts +3 -1
- package/build/strategies/jwt/jwt.strategy.js +35 -6
- package/build/strategies/jwt/jwt.tools.js +8 -8
- package/build/strategies/local/__tests__/local.strategy.test.js +8 -2
- package/build/strategies/local/local.strategy.d.ts +4 -1
- package/build/strategies/local/local.strategy.js +81 -0
- package/build/strategies/oauth2/__tests__/oauth2.strategy.test.d.ts +1 -0
- package/build/strategies/oauth2/__tests__/oauth2.strategy.test.js +239 -0
- package/build/strategies/oauth2/hybrid.oauth2.strategy.d.ts +3 -3
- package/build/strategies/oauth2/hybrid.oauth2.strategy.js +1 -6
- package/build/strategies/oauth2/oauth2.errors.d.ts +1 -0
- package/build/strategies/oauth2/oauth2.errors.js +4 -0
- package/build/strategies/oauth2/oauth2.strategy.d.ts +6 -4
- package/build/strategies/oauth2/oauth2.strategy.js +114 -46
- package/build/strategies/oauth2/oauth2.tools.js +2 -2
- package/build/strategies/oauth2/oauth2.types.d.ts +2 -2
- package/build/strategies/oauth2/providers/__tests__/social-providers.test.d.ts +1 -0
- package/build/strategies/oauth2/providers/__tests__/social-providers.test.js +201 -0
- package/build/strategies/oauth2/providers/facebook.strategy.d.ts +11 -0
- package/build/strategies/oauth2/providers/facebook.strategy.js +58 -0
- package/build/strategies/oauth2/providers/github.strategy.d.ts +11 -0
- package/build/strategies/oauth2/providers/github.strategy.js +56 -0
- package/build/strategies/oauth2/providers/google.strategy.d.ts +11 -0
- package/build/strategies/oauth2/providers/google.strategy.js +52 -0
- package/build/strategies/oauth2/providers/http-oauth2.strategy.d.ts +16 -0
- package/build/strategies/oauth2/providers/http-oauth2.strategy.js +49 -0
- package/build/strategies/oauth2/providers/index.d.ts +5 -0
- package/build/strategies/oauth2/providers/index.js +21 -0
- package/build/strategies/oauth2/providers/provider.types.d.ts +7 -0
- package/build/strategies/oauth2/providers/provider.types.js +2 -0
- package/build/strategies/token-auth.strategy.d.ts +4 -4
- package/build/strategies/token-auth.strategy.js +2 -3
- package/build/tools/tools.js +1 -2
- package/build/types.d.ts +31 -32
- package/build/utils/__tests__/validation.test.d.ts +1 -0
- package/build/utils/__tests__/validation.test.js +181 -0
- package/build/utils/validation.d.ts +23 -0
- package/build/utils/validation.js +139 -0
- package/package.json +8 -7
|
@@ -30,6 +30,7 @@ const credential_auth_strategy_1 = require("../credential-auth.strategy");
|
|
|
30
30
|
jest.mock("../../session/session-handler");
|
|
31
31
|
jest.mock("../jwt/jwt.strategy");
|
|
32
32
|
class TestCredentialAuthStrategy extends credential_auth_strategy_1.CredentialAuthStrategy {
|
|
33
|
+
name = "test-credential";
|
|
33
34
|
constructor(config, session, jwt, logger) {
|
|
34
35
|
super(config, session, jwt, logger);
|
|
35
36
|
}
|
|
@@ -92,8 +93,8 @@ describe("CredentialAuthStrategy", () => {
|
|
|
92
93
|
passwordPolicy: {
|
|
93
94
|
updatePassword: jest.fn(),
|
|
94
95
|
generateResetToken: jest.fn(),
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
validatePasswordResetToken: jest.fn(),
|
|
97
|
+
sendPasswordResetEmail: jest.fn(),
|
|
97
98
|
},
|
|
98
99
|
security: {
|
|
99
100
|
maxFailedLoginAttempts: 3,
|
|
@@ -189,25 +190,25 @@ describe("CredentialAuthStrategy", () => {
|
|
|
189
190
|
describe("requestPasswordReset", () => {
|
|
190
191
|
it("should throw NotImplementedError if generateResetToken is not configured", async () => {
|
|
191
192
|
config.passwordPolicy.generateResetToken = undefined;
|
|
192
|
-
await expect(strategy.
|
|
193
|
+
await expect(strategy.requestPasswordResetLink("testUser")).rejects.toThrow(Soap.NotImplementedError);
|
|
193
194
|
});
|
|
194
195
|
it("should call generateResetToken and sendResetEmail (if email is provided)", async () => {
|
|
195
196
|
config.passwordPolicy.generateResetToken = jest
|
|
196
197
|
.fn()
|
|
197
198
|
.mockResolvedValue("mockToken");
|
|
198
|
-
config.passwordPolicy.
|
|
199
|
+
config.passwordPolicy.sendPasswordResetEmail = jest
|
|
199
200
|
.fn()
|
|
200
201
|
.mockResolvedValue(undefined);
|
|
201
|
-
await strategy.
|
|
202
|
+
await strategy.requestPasswordResetLink("testUser", "test@example.com");
|
|
202
203
|
expect(config.passwordPolicy.generateResetToken).toHaveBeenCalledWith("testUser");
|
|
203
|
-
expect(config.passwordPolicy.
|
|
204
|
+
expect(config.passwordPolicy.sendPasswordResetEmail).toHaveBeenCalledWith("test@example.com", "mockToken");
|
|
204
205
|
});
|
|
205
206
|
it("should call onSuccess with the correct data", async () => {
|
|
206
207
|
config.passwordPolicy.generateResetToken = jest
|
|
207
208
|
.fn()
|
|
208
209
|
.mockResolvedValue("mockToken");
|
|
209
210
|
const onSuccessSpy = jest.spyOn(strategy, "onSuccess");
|
|
210
|
-
await strategy.
|
|
211
|
+
await strategy.requestPasswordResetLink("testUser");
|
|
211
212
|
expect(onSuccessSpy).toHaveBeenCalledWith("request_password_reset", {
|
|
212
213
|
identifier: "testUser",
|
|
213
214
|
tokens: { reset: "mockToken" },
|
|
@@ -217,30 +218,30 @@ describe("CredentialAuthStrategy", () => {
|
|
|
217
218
|
describe("resetPassword", () => {
|
|
218
219
|
it("should throw NotImplementedError if validateResetToken or updatePassword are not configured", async () => {
|
|
219
220
|
config.passwordPolicy.updatePassword = undefined;
|
|
220
|
-
await expect(strategy.
|
|
221
|
+
await expect(strategy.resetPasswordWithToken("testUser", "token", "newPass")).rejects.toThrow(Soap.NotImplementedError);
|
|
221
222
|
});
|
|
222
223
|
it("should throw ExpiredResetTokenError if validateResetToken returns false", async () => {
|
|
223
|
-
config.passwordPolicy.
|
|
224
|
+
config.passwordPolicy.validatePasswordResetToken = jest
|
|
224
225
|
.fn()
|
|
225
226
|
.mockResolvedValue(false);
|
|
226
227
|
config.passwordPolicy.updatePassword = jest.fn();
|
|
227
|
-
await expect(strategy.
|
|
228
|
+
await expect(strategy.resetPasswordWithToken("testUser", "token", "newPass")).rejects.toThrow(errors_1.ExpiredResetTokenError);
|
|
228
229
|
});
|
|
229
230
|
it("should call updatePassword if the token is valid", async () => {
|
|
230
|
-
config.passwordPolicy.
|
|
231
|
+
config.passwordPolicy.validatePasswordResetToken = jest
|
|
231
232
|
.fn()
|
|
232
233
|
.mockResolvedValue(true);
|
|
233
234
|
config.passwordPolicy.updatePassword = jest
|
|
234
235
|
.fn()
|
|
235
236
|
.mockResolvedValue(undefined);
|
|
236
|
-
await strategy.
|
|
237
|
-
expect(config.passwordPolicy.updatePassword).toHaveBeenCalledWith("testUser", "newPass");
|
|
237
|
+
await strategy.resetPasswordWithToken("testUser", "validToken", "newPass");
|
|
238
|
+
expect(config.passwordPolicy.updatePassword).toHaveBeenCalledWith("testUser", "newPass", undefined);
|
|
238
239
|
});
|
|
239
240
|
});
|
|
240
241
|
describe("changePassword", () => {
|
|
241
242
|
it("should throw NotImplementedError if updatePassword is not configured", async () => {
|
|
242
243
|
config.passwordPolicy.updatePassword = undefined;
|
|
243
|
-
await expect(strategy.
|
|
244
|
+
await expect(strategy.replacePassword("testUser", "oldPass", "newPass")).rejects.toThrow(Soap.NotImplementedError);
|
|
244
245
|
});
|
|
245
246
|
it("should throw InvalidCredentialsError if verifyCredentials returns false", async () => {
|
|
246
247
|
config.passwordPolicy.updatePassword = jest
|
|
@@ -249,7 +250,7 @@ describe("CredentialAuthStrategy", () => {
|
|
|
249
250
|
jest
|
|
250
251
|
.spyOn(strategy, "verifyCredentials")
|
|
251
252
|
.mockResolvedValueOnce(false);
|
|
252
|
-
await expect(strategy.
|
|
253
|
+
await expect(strategy.replacePassword("testUser", "oldPass", "newPass")).rejects.toThrow(errors_1.InvalidCredentialsError);
|
|
253
254
|
});
|
|
254
255
|
it("should call updatePassword with new password if old credentials are correct", async () => {
|
|
255
256
|
config.passwordPolicy.updatePassword = jest
|
|
@@ -258,8 +259,8 @@ describe("CredentialAuthStrategy", () => {
|
|
|
258
259
|
jest
|
|
259
260
|
.spyOn(strategy, "verifyCredentials")
|
|
260
261
|
.mockResolvedValueOnce(true);
|
|
261
|
-
await strategy.
|
|
262
|
-
expect(config.passwordPolicy.updatePassword).toHaveBeenCalledWith("testUser", "newPass");
|
|
262
|
+
await strategy.replacePassword("testUser", "oldPass", "newPass");
|
|
263
|
+
expect(config.passwordPolicy.updatePassword).toHaveBeenCalledWith("testUser", "newPass", undefined);
|
|
263
264
|
});
|
|
264
265
|
});
|
|
265
266
|
});
|
|
@@ -11,6 +11,7 @@ export interface MockContext {
|
|
|
11
11
|
refreshToken?: string;
|
|
12
12
|
}
|
|
13
13
|
export declare class TestTokenAuthStrategy extends TokenAuthStrategy<MockContext, MockUser> {
|
|
14
|
+
readonly name = "test-token";
|
|
14
15
|
protected invalidateAccessToken(token: string, context?: MockContext): Promise<void>;
|
|
15
16
|
protected invalidateRefreshToken(token: string, context?: MockContext): Promise<void>;
|
|
16
17
|
constructor(config: TokenAuthStrategyConfig<MockContext, MockUser>, session?: SessionHandler, logger?: Soap.Logger);
|
|
@@ -4,6 +4,7 @@ exports.TestTokenAuthStrategy = void 0;
|
|
|
4
4
|
const token_auth_strategy_1 = require("../token-auth.strategy");
|
|
5
5
|
const errors_1 = require("../../../src/errors");
|
|
6
6
|
class TestTokenAuthStrategy extends token_auth_strategy_1.TokenAuthStrategy {
|
|
7
|
+
name = "test-token";
|
|
7
8
|
async invalidateAccessToken(token, context) {
|
|
8
9
|
context.accessToken = undefined;
|
|
9
10
|
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import * as Soap from "@soapjs/soap";
|
|
2
|
-
import { AuthResult, AuthStrategy } from "../../types";
|
|
3
2
|
import { ApiKeyStrategyConfig } from "./api-key.types";
|
|
4
3
|
import { BaseAuthStrategy } from "../base-auth.strategy";
|
|
5
|
-
export declare class ApiKeyStrategy<TContext =
|
|
4
|
+
export declare class ApiKeyStrategy<TContext = Soap.HttpContext, TUser extends Soap.AuthUser = Soap.AuthUser> extends BaseAuthStrategy<TContext, TUser> {
|
|
6
5
|
protected config: ApiKeyStrategyConfig<TContext, TUser>;
|
|
6
|
+
readonly name = "api-key";
|
|
7
7
|
protected apiKeyValidity: {
|
|
8
8
|
sessionDuration: number;
|
|
9
9
|
longTermDuration: number;
|
|
10
10
|
};
|
|
11
|
-
constructor(config: ApiKeyStrategyConfig<TContext, TUser>, logger
|
|
11
|
+
constructor(config: ApiKeyStrategyConfig<TContext, TUser>, logger?: Soap.Logger);
|
|
12
12
|
protected fetchUser(apiKey: string, context: TContext): Promise<TUser | null>;
|
|
13
13
|
init(): Promise<void>;
|
|
14
|
-
authenticate(context?: TContext): Promise<AuthResult<TUser
|
|
14
|
+
authenticate(context?: TContext): Promise<Soap.AuthResult<TUser> | null>;
|
|
15
15
|
authorize(user: TUser, action: string, resource?: string): Promise<boolean>;
|
|
16
16
|
revoke(apiKey: string): Promise<void>;
|
|
17
17
|
private trackApiKeyUsage;
|
|
@@ -7,6 +7,7 @@ const base_auth_strategy_1 = require("../base-auth.strategy");
|
|
|
7
7
|
const api_key_tools_1 = require("./api-key.tools");
|
|
8
8
|
class ApiKeyStrategy extends base_auth_strategy_1.BaseAuthStrategy {
|
|
9
9
|
config;
|
|
10
|
+
name = "api-key";
|
|
10
11
|
apiKeyValidity;
|
|
11
12
|
constructor(config, logger) {
|
|
12
13
|
super((0, api_key_tools_1.prepareApiKeyConfig)(config), null, logger);
|
|
@@ -28,7 +29,7 @@ class ApiKeyStrategy extends base_auth_strategy_1.BaseAuthStrategy {
|
|
|
28
29
|
return user;
|
|
29
30
|
}
|
|
30
31
|
catch (error) {
|
|
31
|
-
this.logger
|
|
32
|
+
this.logger?.warn(`Attempt ${attempt + 1} failed: ${error}`);
|
|
32
33
|
if (attempt < maxRetries) {
|
|
33
34
|
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
34
35
|
}
|
|
@@ -99,7 +100,7 @@ class ApiKeyStrategy extends base_auth_strategy_1.BaseAuthStrategy {
|
|
|
99
100
|
await this.config.trackApiKeyUsage?.(apiKey);
|
|
100
101
|
}
|
|
101
102
|
catch (error) {
|
|
102
|
-
this.logger
|
|
103
|
+
this.logger?.warn("Failed to track API key usage:", error);
|
|
103
104
|
}
|
|
104
105
|
}
|
|
105
106
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import * as Soap from "@soapjs/soap";
|
|
2
|
-
import { AuthFailureContext,
|
|
2
|
+
import { AuthFailureContext, AuthSuccessContext, BaseAuthStrategyConfig } from "../types";
|
|
3
3
|
import { SessionHandler } from "../session/session-handler";
|
|
4
4
|
import { AccountLockService } from "../services/account-lock.service";
|
|
5
5
|
import { MfaService } from "../services/mfa.service";
|
|
6
6
|
import { RateLimitService } from "../services/rate-limit.service";
|
|
7
7
|
import { RoleService } from "../services/role.service";
|
|
8
8
|
import { AuthThrottleService } from "../services/auth-throttle.service";
|
|
9
|
-
export declare abstract class BaseAuthStrategy<TContext =
|
|
9
|
+
export declare abstract class BaseAuthStrategy<TContext = Soap.HttpContext, TUser extends Soap.AuthUser = Soap.AuthUser> {
|
|
10
10
|
protected config: BaseAuthStrategyConfig<TContext, TUser>;
|
|
11
11
|
protected session?: SessionHandler;
|
|
12
12
|
protected logger?: Soap.Logger;
|
|
@@ -15,10 +15,11 @@ export declare abstract class BaseAuthStrategy<TContext = unknown, TUser = unkno
|
|
|
15
15
|
protected rateLimit: RateLimitService;
|
|
16
16
|
protected role: RoleService<TUser>;
|
|
17
17
|
protected throttle: AuthThrottleService;
|
|
18
|
-
abstract
|
|
18
|
+
abstract readonly name: string;
|
|
19
|
+
abstract authenticate(ctx: TContext): Promise<Soap.AuthResult<TUser> | null>;
|
|
19
20
|
constructor(config: BaseAuthStrategyConfig<TContext, TUser>, session?: SessionHandler, logger?: Soap.Logger);
|
|
20
21
|
init(): Promise<void>;
|
|
21
22
|
protected onSuccess(action: string, context: AuthSuccessContext<TUser, TContext>): Promise<void>;
|
|
22
23
|
protected onFailure(action: string, context: AuthFailureContext<TContext>): Promise<void>;
|
|
23
|
-
protected authenticateWithSession(context: TContext): Promise<AuthResult<TUser>>;
|
|
24
|
+
protected authenticateWithSession(context: TContext): Promise<Soap.AuthResult<TUser>>;
|
|
24
25
|
}
|
|
@@ -3,11 +3,12 @@ import { CredentialAuthStrategy } from "../credential-auth.strategy";
|
|
|
3
3
|
import { BasicStrategyConfig } from "./basic.types";
|
|
4
4
|
import { SessionHandler } from "../../session/session-handler";
|
|
5
5
|
import { JwtStrategy } from "../jwt/jwt.strategy";
|
|
6
|
-
export declare class BasicStrategy<TContext =
|
|
6
|
+
export declare class BasicStrategy<TContext = Soap.HttpContext, TUser extends Soap.AuthUser = Soap.AuthUser> extends CredentialAuthStrategy<TContext, TUser> {
|
|
7
7
|
protected config: BasicStrategyConfig<TContext, TUser>;
|
|
8
8
|
protected session?: SessionHandler;
|
|
9
9
|
protected jwt?: JwtStrategy<TContext, TUser>;
|
|
10
10
|
protected logger?: Soap.Logger;
|
|
11
|
+
readonly name = "basic";
|
|
11
12
|
constructor(config: BasicStrategyConfig<TContext, TUser>, session?: SessionHandler, jwt?: JwtStrategy<TContext, TUser>, logger?: Soap.Logger);
|
|
12
13
|
protected extractCredentials(context?: TContext): {
|
|
13
14
|
identifier: string;
|
|
@@ -9,6 +9,7 @@ class BasicStrategy extends credential_auth_strategy_1.CredentialAuthStrategy {
|
|
|
9
9
|
session;
|
|
10
10
|
jwt;
|
|
11
11
|
logger;
|
|
12
|
+
name = "basic";
|
|
12
13
|
constructor(config, session, jwt, logger) {
|
|
13
14
|
super((0, basic_tools_1.prepareBasicConfig)(config), session, jwt, logger);
|
|
14
15
|
this.config = config;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as Soap from "@soapjs/soap";
|
|
2
|
-
import {
|
|
2
|
+
import { CredentialAuthStrategyConfig, NewPasswordOptions } from "../types";
|
|
3
3
|
import { BaseAuthStrategy } from "./base-auth.strategy";
|
|
4
4
|
import { SessionHandler } from "../session/session-handler";
|
|
5
5
|
import { JwtStrategy } from "./jwt/jwt.strategy";
|
|
6
6
|
import { PasswordService } from "../services/password.service";
|
|
7
|
-
export declare abstract class CredentialAuthStrategy<TContext =
|
|
7
|
+
export declare abstract class CredentialAuthStrategy<TContext = Soap.HttpContext, TUser extends Soap.AuthUser = Soap.AuthUser> extends BaseAuthStrategy<TContext, TUser> {
|
|
8
8
|
protected config: CredentialAuthStrategyConfig<TContext, TUser>;
|
|
9
9
|
protected session?: SessionHandler;
|
|
10
10
|
protected jwt?: JwtStrategy<TContext, TUser>;
|
|
@@ -14,10 +14,10 @@ export declare abstract class CredentialAuthStrategy<TContext = unknown, TUser =
|
|
|
14
14
|
protected abstract extractCredentials(context: TContext): any;
|
|
15
15
|
constructor(config: CredentialAuthStrategyConfig<TContext, TUser>, session?: SessionHandler, jwt?: JwtStrategy<TContext, TUser>, logger?: Soap.Logger);
|
|
16
16
|
protected fetchUser(payload: unknown): Promise<TUser | null>;
|
|
17
|
-
authenticate(context: TContext): Promise<AuthResult<TUser
|
|
18
|
-
login(context: TContext): Promise<AuthResult<TUser>>;
|
|
17
|
+
authenticate(context: TContext): Promise<Soap.AuthResult<TUser> | null>;
|
|
18
|
+
login(context: TContext): Promise<Soap.AuthResult<TUser>>;
|
|
19
19
|
logout(context: TContext): Promise<void>;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
requestPasswordResetLink(identifier: string, email?: string): Promise<void>;
|
|
21
|
+
resetPasswordWithToken(identifier: string, token: string, newPassword: string, passwordOptions?: NewPasswordOptions): Promise<void>;
|
|
22
|
+
replacePassword(identifier: string, oldPassword: string, newPassword: string, options?: NewPasswordOptions): Promise<void>;
|
|
23
23
|
}
|
|
@@ -126,7 +126,7 @@ class CredentialAuthStrategy extends base_auth_strategy_1.BaseAuthStrategy {
|
|
|
126
126
|
throw error;
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
|
-
async
|
|
129
|
+
async requestPasswordResetLink(identifier, email) {
|
|
130
130
|
try {
|
|
131
131
|
const token = await this.password.generateResetToken(identifier);
|
|
132
132
|
if (email) {
|
|
@@ -136,7 +136,7 @@ class CredentialAuthStrategy extends base_auth_strategy_1.BaseAuthStrategy {
|
|
|
136
136
|
identifier,
|
|
137
137
|
tokens: { reset: token },
|
|
138
138
|
});
|
|
139
|
-
this.logger
|
|
139
|
+
this.logger?.info(`Password reset requested for identifier: ${identifier}`);
|
|
140
140
|
}
|
|
141
141
|
catch (error) {
|
|
142
142
|
await this.onFailure("request_password_reset", {
|
|
@@ -147,17 +147,12 @@ class CredentialAuthStrategy extends base_auth_strategy_1.BaseAuthStrategy {
|
|
|
147
147
|
throw error;
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
|
-
async
|
|
150
|
+
async resetPasswordWithToken(identifier, token, newPassword, passwordOptions) {
|
|
151
151
|
try {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
if ((await this.password.validateResetToken(token)) === false) {
|
|
156
|
-
throw new errors_1.ExpiredResetTokenError();
|
|
157
|
-
}
|
|
158
|
-
await this.password.updatePassword(identifier, newPassword);
|
|
152
|
+
await this.password?.validateResetToken(token);
|
|
153
|
+
await this.password.updatePassword(identifier, newPassword, passwordOptions);
|
|
159
154
|
await this.onSuccess("password_reset", { identifier });
|
|
160
|
-
this.logger
|
|
155
|
+
this.logger?.info(`Password successfully reset for identifier: ${identifier}`);
|
|
161
156
|
}
|
|
162
157
|
catch (error) {
|
|
163
158
|
await this.onFailure("password_reset", {
|
|
@@ -168,14 +163,14 @@ class CredentialAuthStrategy extends base_auth_strategy_1.BaseAuthStrategy {
|
|
|
168
163
|
throw error;
|
|
169
164
|
}
|
|
170
165
|
}
|
|
171
|
-
async
|
|
166
|
+
async replacePassword(identifier, oldPassword, newPassword, options) {
|
|
172
167
|
try {
|
|
173
168
|
if (!(await this.verifyCredentials(identifier, oldPassword))) {
|
|
174
169
|
throw new errors_1.InvalidCredentialsError();
|
|
175
170
|
}
|
|
176
|
-
await this.password.updatePassword(identifier, newPassword);
|
|
171
|
+
await this.password.updatePassword(identifier, newPassword, options);
|
|
177
172
|
await this.onSuccess("change_password", { identifier });
|
|
178
|
-
this.logger
|
|
173
|
+
this.logger?.info(`Password changed successfully for identifier: ${identifier}`);
|
|
179
174
|
}
|
|
180
175
|
catch (error) {
|
|
181
176
|
await this.onFailure("change_password", {
|
|
@@ -29,4 +29,5 @@ __exportStar(require("./local/local.types"), exports);
|
|
|
29
29
|
__exportStar(require("./oauth2/oauth2.strategy"), exports);
|
|
30
30
|
__exportStar(require("./oauth2/oauth2.tools"), exports);
|
|
31
31
|
__exportStar(require("./oauth2/oauth2.types"), exports);
|
|
32
|
+
__exportStar(require("./oauth2/providers"), exports);
|
|
32
33
|
__exportStar(require("./token-auth.strategy"), exports);
|
|
@@ -149,8 +149,8 @@ describe("JWTStrategy", () => {
|
|
|
149
149
|
const token = await strategy.extractRefreshToken(mockContext);
|
|
150
150
|
expect(token).toBe("mock-refresh-token");
|
|
151
151
|
});
|
|
152
|
-
it("should throw
|
|
152
|
+
it("should throw ValidationError if access secret key is missing", async () => {
|
|
153
153
|
mockConfig.accessToken.issuer.secretKey = undefined;
|
|
154
|
-
expect(() => new jwt_strategy_1.JwtStrategy(mockConfig, mockLogger)).toThrow(
|
|
154
|
+
expect(() => new jwt_strategy_1.JwtStrategy(mockConfig, mockLogger)).toThrow("config.accessToken.issuer.secretKey is required");
|
|
155
155
|
});
|
|
156
156
|
});
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import * as Soap from "@soapjs/soap";
|
|
2
2
|
import { TokenAuthStrategy } from "../token-auth.strategy";
|
|
3
3
|
import { TokenAuthStrategyConfig, TokenConfig } from "../../types";
|
|
4
|
-
export declare class JwtStrategy<TContext =
|
|
4
|
+
export declare class JwtStrategy<TContext = Soap.HttpContext, TUser extends Soap.AuthUser = Soap.AuthUser> extends TokenAuthStrategy<TContext, TUser> {
|
|
5
5
|
protected config: TokenAuthStrategyConfig<TContext, TUser>;
|
|
6
6
|
protected logger?: Soap.Logger;
|
|
7
|
+
readonly name = "jwt";
|
|
7
8
|
protected accessTokenConfig: TokenConfig<TContext>;
|
|
8
9
|
protected refreshTokenConfig: TokenConfig<TContext>;
|
|
9
10
|
constructor(config: TokenAuthStrategyConfig<TContext, TUser>, logger?: Soap.Logger);
|
|
11
|
+
private static validateConfig;
|
|
10
12
|
protected verifyAccessToken(token: string): Promise<any>;
|
|
11
13
|
protected verifyRefreshToken(token: string): Promise<any>;
|
|
12
14
|
protected generateAccessToken(user: TUser, context: TContext): Promise<string>;
|
|
@@ -8,12 +8,15 @@ const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
|
8
8
|
const token_auth_strategy_1 = require("../token-auth.strategy");
|
|
9
9
|
const errors_1 = require("../../errors");
|
|
10
10
|
const jwt_tools_1 = require("./jwt.tools");
|
|
11
|
+
const validation_1 = require("../../utils/validation");
|
|
11
12
|
class JwtStrategy extends token_auth_strategy_1.TokenAuthStrategy {
|
|
12
13
|
config;
|
|
13
14
|
logger;
|
|
15
|
+
name = "jwt";
|
|
14
16
|
accessTokenConfig;
|
|
15
17
|
refreshTokenConfig;
|
|
16
18
|
constructor(config, logger) {
|
|
19
|
+
JwtStrategy.validateConfig(config);
|
|
17
20
|
if (!config.accessToken.issuer.secretKey) {
|
|
18
21
|
throw new errors_1.UndefinedTokenSecretError("Access");
|
|
19
22
|
}
|
|
@@ -24,13 +27,39 @@ class JwtStrategy extends token_auth_strategy_1.TokenAuthStrategy {
|
|
|
24
27
|
this.config = config;
|
|
25
28
|
this.logger = logger;
|
|
26
29
|
this.accessTokenConfig = jwt_tools_1.JwtTools.prepareAccessTokenConfig(config.accessToken);
|
|
27
|
-
|
|
30
|
+
if (config.refreshToken) {
|
|
31
|
+
this.refreshTokenConfig = jwt_tools_1.JwtTools.prepareRefreshTokenConfig(config.refreshToken);
|
|
32
|
+
}
|
|
28
33
|
this.logger?.info("JWTStrategy initialized with provided configurations.");
|
|
29
34
|
}
|
|
35
|
+
static validateConfig(config) {
|
|
36
|
+
try {
|
|
37
|
+
validation_1.ValidationUtils.required(config, "config");
|
|
38
|
+
validation_1.ValidationUtils.required(config.accessToken, "config.accessToken");
|
|
39
|
+
validation_1.ValidationUtils.required(config.accessToken.issuer, "config.accessToken.issuer");
|
|
40
|
+
validation_1.ValidationUtils.required(config.accessToken.issuer.secretKey, "config.accessToken.issuer.secretKey");
|
|
41
|
+
validation_1.ValidationUtils.nonEmptyString(config.accessToken.issuer.secretKey, "config.accessToken.issuer.secretKey");
|
|
42
|
+
if (config.refreshToken) {
|
|
43
|
+
validation_1.ValidationUtils.required(config.refreshToken.issuer, "config.refreshToken.issuer");
|
|
44
|
+
validation_1.ValidationUtils.required(config.refreshToken.issuer.secretKey, "config.refreshToken.issuer.secretKey");
|
|
45
|
+
validation_1.ValidationUtils.nonEmptyString(config.refreshToken.issuer.secretKey, "config.refreshToken.issuer.secretKey");
|
|
46
|
+
}
|
|
47
|
+
if (config.user) {
|
|
48
|
+
validation_1.ValidationUtils.required(config.user.fetchUser, "config.user.fetchUser");
|
|
49
|
+
validation_1.ValidationUtils.function(config.user.fetchUser, "config.user.fetchUser");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (error instanceof validation_1.ValidationError) {
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
throw new validation_1.ValidationError(`Invalid JWT configuration: ${error.message}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
30
59
|
verifyAccessToken(token) {
|
|
31
60
|
try {
|
|
32
|
-
|
|
33
|
-
|
|
61
|
+
validation_1.ValidationUtils.required(token, "token");
|
|
62
|
+
validation_1.ValidationUtils.jwtToken(token, "token");
|
|
34
63
|
if (!this.accessTokenConfig.issuer.secretKey)
|
|
35
64
|
throw new errors_1.UndefinedTokenSecretError("Access");
|
|
36
65
|
return new Promise((resolve, reject) => {
|
|
@@ -49,8 +78,8 @@ class JwtStrategy extends token_auth_strategy_1.TokenAuthStrategy {
|
|
|
49
78
|
}
|
|
50
79
|
verifyRefreshToken(token) {
|
|
51
80
|
try {
|
|
52
|
-
|
|
53
|
-
|
|
81
|
+
validation_1.ValidationUtils.required(token, "token");
|
|
82
|
+
validation_1.ValidationUtils.jwtToken(token, "token");
|
|
54
83
|
if (!this.refreshTokenConfig.issuer.secretKey)
|
|
55
84
|
throw new errors_1.UndefinedTokenSecretError("Refresh");
|
|
56
85
|
return new Promise((resolve, reject) => {
|
|
@@ -64,7 +93,7 @@ class JwtStrategy extends token_auth_strategy_1.TokenAuthStrategy {
|
|
|
64
93
|
}
|
|
65
94
|
catch (error) {
|
|
66
95
|
this.logger?.error("JWT verification failed:", error);
|
|
67
|
-
error;
|
|
96
|
+
throw error;
|
|
68
97
|
}
|
|
69
98
|
}
|
|
70
99
|
async generateAccessToken(user, context) {
|
|
@@ -189,6 +189,14 @@ exports.JwtTools = JwtTools;
|
|
|
189
189
|
const prepareJwtConfig = (config) => {
|
|
190
190
|
return Soap.removeUndefinedProperties({
|
|
191
191
|
...config,
|
|
192
|
+
user: {
|
|
193
|
+
...config.user,
|
|
194
|
+
validateUser: config.user?.validateUser ?? (() => Promise.resolve(true)),
|
|
195
|
+
},
|
|
196
|
+
accessToken: JwtTools.prepareAccessTokenConfig(config.accessToken),
|
|
197
|
+
refreshToken: config.refreshToken
|
|
198
|
+
? JwtTools.prepareRefreshTokenConfig(config.refreshToken)
|
|
199
|
+
: undefined,
|
|
192
200
|
routes: {
|
|
193
201
|
login: config.routes?.login ?? {
|
|
194
202
|
path: "/auth/jwt/login",
|
|
@@ -203,14 +211,6 @@ const prepareJwtConfig = (config) => {
|
|
|
203
211
|
method: "POST",
|
|
204
212
|
},
|
|
205
213
|
...config.routes,
|
|
206
|
-
user: {
|
|
207
|
-
...config.user,
|
|
208
|
-
validateUser: config.user?.validateUser ?? (() => Promise.resolve(true)),
|
|
209
|
-
},
|
|
210
|
-
accessToken: JwtTools.prepareAccessTokenConfig(config.accessToken),
|
|
211
|
-
refreshToken: config.refreshToken
|
|
212
|
-
? JwtTools.prepareRefreshTokenConfig(config.refreshToken)
|
|
213
|
-
: undefined,
|
|
214
214
|
},
|
|
215
215
|
});
|
|
216
216
|
};
|
|
@@ -17,8 +17,14 @@ describe("LocalStrategy", () => {
|
|
|
17
17
|
fetchUser: jest.fn(),
|
|
18
18
|
},
|
|
19
19
|
routes: {
|
|
20
|
-
login: {
|
|
21
|
-
|
|
20
|
+
login: {
|
|
21
|
+
path: "/login",
|
|
22
|
+
method: "POST"
|
|
23
|
+
},
|
|
24
|
+
logout: {
|
|
25
|
+
path: "/logout",
|
|
26
|
+
method: "POST"
|
|
27
|
+
},
|
|
22
28
|
},
|
|
23
29
|
};
|
|
24
30
|
mockSession = {
|
|
@@ -3,11 +3,13 @@ import { CredentialAuthStrategy } from "../credential-auth.strategy";
|
|
|
3
3
|
import { LocalStrategyConfig } from "./local.types";
|
|
4
4
|
import { SessionHandler } from "../../session/session-handler";
|
|
5
5
|
import { JwtStrategy } from "../jwt/jwt.strategy";
|
|
6
|
-
export declare class LocalStrategy<TContext =
|
|
6
|
+
export declare class LocalStrategy<TContext = Soap.HttpContext, TUser extends Soap.AuthUser = Soap.AuthUser> extends CredentialAuthStrategy<TContext, TUser> {
|
|
7
7
|
protected session?: SessionHandler;
|
|
8
8
|
protected jwt?: JwtStrategy<TContext, TUser>;
|
|
9
9
|
protected logger?: Soap.Logger;
|
|
10
|
+
readonly name = "local";
|
|
10
11
|
constructor(config: LocalStrategyConfig<TContext, TUser>, session?: SessionHandler, jwt?: JwtStrategy<TContext, TUser>, logger?: Soap.Logger);
|
|
12
|
+
private static validateConfig;
|
|
11
13
|
protected extractCredentials(context?: TContext): Promise<{
|
|
12
14
|
identifier: string;
|
|
13
15
|
password: string;
|
|
@@ -17,4 +19,5 @@ export declare class LocalStrategy<TContext = unknown, TUser = unknown> extends
|
|
|
17
19
|
identifier: string;
|
|
18
20
|
password: string;
|
|
19
21
|
}): Promise<TUser | null>;
|
|
22
|
+
changePassword(context: TContext): Promise<void>;
|
|
20
23
|
}
|
|
@@ -3,24 +3,105 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.LocalStrategy = void 0;
|
|
4
4
|
const credential_auth_strategy_1 = require("../credential-auth.strategy");
|
|
5
5
|
const local_tools_1 = require("./local.tools");
|
|
6
|
+
const errors_1 = require("../../errors");
|
|
7
|
+
const validation_1 = require("../../utils/validation");
|
|
6
8
|
class LocalStrategy extends credential_auth_strategy_1.CredentialAuthStrategy {
|
|
7
9
|
session;
|
|
8
10
|
jwt;
|
|
9
11
|
logger;
|
|
12
|
+
name = "local";
|
|
10
13
|
constructor(config, session, jwt, logger) {
|
|
14
|
+
LocalStrategy.validateConfig(config);
|
|
11
15
|
super((0, local_tools_1.prepareLocalConfig)(config), session, jwt, logger);
|
|
12
16
|
this.session = session;
|
|
13
17
|
this.jwt = jwt;
|
|
14
18
|
this.logger = logger;
|
|
15
19
|
}
|
|
20
|
+
static validateConfig(config) {
|
|
21
|
+
try {
|
|
22
|
+
validation_1.ValidationUtils.required(config, "config");
|
|
23
|
+
if (config.credentials) {
|
|
24
|
+
validation_1.ValidationUtils.required(config.credentials.extractCredentials, "config.credentials.extractCredentials");
|
|
25
|
+
validation_1.ValidationUtils.function(config.credentials.extractCredentials, "config.credentials.extractCredentials");
|
|
26
|
+
validation_1.ValidationUtils.required(config.credentials.verifyCredentials, "config.credentials.verifyCredentials");
|
|
27
|
+
validation_1.ValidationUtils.function(config.credentials.verifyCredentials, "config.credentials.verifyCredentials");
|
|
28
|
+
}
|
|
29
|
+
if (config.user) {
|
|
30
|
+
validation_1.ValidationUtils.required(config.user.fetchUser, "config.user.fetchUser");
|
|
31
|
+
validation_1.ValidationUtils.function(config.user.fetchUser, "config.user.fetchUser");
|
|
32
|
+
}
|
|
33
|
+
if (config.routes) {
|
|
34
|
+
validation_1.ValidationUtils.required(config.routes.login, "config.routes.login");
|
|
35
|
+
validation_1.ValidationUtils.required(config.routes.logout, "config.routes.logout");
|
|
36
|
+
validation_1.ValidationUtils.object(config.routes.login, "config.routes.login");
|
|
37
|
+
validation_1.ValidationUtils.object(config.routes.logout, "config.routes.logout");
|
|
38
|
+
if (config.routes.login) {
|
|
39
|
+
validation_1.ValidationUtils.nonEmptyString(config.routes.login.path, "config.routes.login.path");
|
|
40
|
+
validation_1.ValidationUtils.nonEmptyString(config.routes.login.method, "config.routes.login.method");
|
|
41
|
+
}
|
|
42
|
+
if (config.routes.logout) {
|
|
43
|
+
validation_1.ValidationUtils.nonEmptyString(config.routes.logout.path, "config.routes.logout.path");
|
|
44
|
+
validation_1.ValidationUtils.nonEmptyString(config.routes.logout.method, "config.routes.logout.method");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (error instanceof validation_1.ValidationError) {
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
throw new validation_1.ValidationError(`Invalid Local strategy configuration: ${error.message}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
16
55
|
extractCredentials(context) {
|
|
56
|
+
validation_1.ValidationUtils.required(context, "context");
|
|
17
57
|
return this.config.credentials.extractCredentials(context);
|
|
18
58
|
}
|
|
19
59
|
async verifyCredentials(identifier, password) {
|
|
60
|
+
validation_1.ValidationUtils.nonEmptyString(identifier, "identifier");
|
|
61
|
+
validation_1.ValidationUtils.nonEmptyString(password, "password");
|
|
20
62
|
return this.config.credentials.verifyCredentials(identifier, password);
|
|
21
63
|
}
|
|
22
64
|
async fetchUser(credentials) {
|
|
65
|
+
validation_1.ValidationUtils.required(credentials, "credentials");
|
|
66
|
+
if (credentials && typeof credentials === "object" && !Array.isArray(credentials)) {
|
|
67
|
+
validation_1.ValidationUtils.nonEmptyString(credentials.identifier, "credentials.identifier");
|
|
68
|
+
validation_1.ValidationUtils.nonEmptyString(credentials.password, "credentials.password");
|
|
69
|
+
}
|
|
23
70
|
return this.config.user.fetchUser(credentials.identifier);
|
|
24
71
|
}
|
|
72
|
+
async changePassword(context) {
|
|
73
|
+
try {
|
|
74
|
+
const credentials = await this.config.credentials.extractCredentials(context);
|
|
75
|
+
if (!credentials) {
|
|
76
|
+
throw new errors_1.MissingCredentialsError();
|
|
77
|
+
}
|
|
78
|
+
if (!credentials.identifier ||
|
|
79
|
+
!credentials.password ||
|
|
80
|
+
!credentials.newPassword) {
|
|
81
|
+
throw new errors_1.InvalidCredentialsError();
|
|
82
|
+
}
|
|
83
|
+
await this.accountLock?.isAccountLocked(credentials.identifier);
|
|
84
|
+
await this.throttle?.checkFailedAttempts(credentials.identifier);
|
|
85
|
+
const user = await this.fetchUser(credentials);
|
|
86
|
+
if (!user) {
|
|
87
|
+
throw new errors_1.UserNotFoundError();
|
|
88
|
+
}
|
|
89
|
+
if ((await this.verifyCredentials(credentials.identifier, credentials.password)) === false) {
|
|
90
|
+
await this.throttle?.incrementFailedAttempts(credentials.identifier);
|
|
91
|
+
throw new errors_1.InvalidCredentialsError();
|
|
92
|
+
}
|
|
93
|
+
await this.password.validatePassword(credentials.newPassword, credentials.password);
|
|
94
|
+
await this.password.updatePassword(credentials.identifier, credentials.newPassword);
|
|
95
|
+
await this.throttle?.resetFailedAttempts(credentials.identifier);
|
|
96
|
+
await this.onSuccess("change_password", {
|
|
97
|
+
identifier: credentials.identifier,
|
|
98
|
+
});
|
|
99
|
+
this.logger?.info(`User password ${credentials.identifier} changed.`);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
await this.onFailure("change_password", { context, error });
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
25
106
|
}
|
|
26
107
|
exports.LocalStrategy = LocalStrategy;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|