@soapjs/soap-auth 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/__tests__/soap-auth.test.d.ts +1 -0
- package/build/__tests__/soap-auth.test.js +42 -0
- package/build/errors.d.ts +14 -3
- package/build/errors.js +29 -8
- package/build/index.d.ts +1 -1
- package/build/index.js +1 -1
- package/build/services/__tests__/account-lock.service.test.d.ts +1 -0
- package/build/services/__tests__/account-lock.service.test.js +55 -0
- package/build/services/__tests__/auth-throttle.service.test.d.ts +1 -0
- package/build/services/__tests__/auth-throttle.service.test.js +48 -0
- package/build/services/__tests__/jwks.service.test.d.ts +1 -0
- package/build/services/__tests__/jwks.service.test.js +39 -0
- package/build/services/__tests__/mfa.service.test.d.ts +1 -0
- package/build/services/__tests__/mfa.service.test.js +66 -0
- package/build/services/__tests__/password.service.test.d.ts +1 -0
- package/build/services/__tests__/password.service.test.js +66 -0
- package/build/services/__tests__/pkce.service.test.d.ts +1 -0
- package/build/services/__tests__/pkce.service.test.js +77 -0
- package/build/services/__tests__/rate-limit.service.test.d.ts +1 -0
- package/build/services/__tests__/rate-limit.service.test.js +37 -0
- package/build/services/__tests__/role.service.test.d.ts +1 -0
- package/build/services/__tests__/role.service.test.js +31 -0
- package/build/services/account-lock.service.d.ts +12 -0
- package/build/services/account-lock.service.js +39 -0
- package/build/services/auth-throttle.service.d.ts +10 -0
- package/build/services/auth-throttle.service.js +43 -0
- package/build/services/index.d.ts +8 -0
- package/build/{factories → services}/index.js +8 -3
- package/build/services/jwks.service.d.ts +7 -0
- package/build/services/jwks.service.js +41 -0
- package/build/services/mfa.service.d.ts +12 -0
- package/build/services/mfa.service.js +74 -0
- package/build/services/password.service.d.ts +14 -0
- package/build/services/password.service.js +78 -0
- package/build/services/pkce.service.d.ts +14 -0
- package/build/services/pkce.service.js +81 -0
- package/build/services/rate-limit.service.d.ts +9 -0
- package/build/services/rate-limit.service.js +26 -0
- package/build/services/role.service.d.ts +9 -0
- package/build/services/role.service.js +26 -0
- package/build/session/__tests__/file.session-store.test.d.ts +1 -0
- package/build/session/__tests__/file.session-store.test.js +117 -0
- package/build/session/__tests__/memory.session-store.test.d.ts +1 -0
- package/build/session/__tests__/memory.session-store.test.js +77 -0
- package/build/session/__tests__/session-handler.test.d.ts +1 -0
- package/build/session/__tests__/session-handler.test.js +337 -0
- package/build/session/file.session-store.d.ts +1 -0
- package/build/session/file.session-store.js +7 -0
- package/build/session/memory.session-store.d.ts +4 -1
- package/build/session/memory.session-store.js +11 -5
- package/build/session/session-handler.d.ts +12 -7
- package/build/session/session-handler.js +46 -13
- package/build/session/session.errors.d.ts +6 -0
- package/build/session/session.errors.js +15 -0
- package/build/soap-auth.d.ts +9 -8
- package/build/soap-auth.js +42 -29
- package/build/strategies/__tests__/base-auth.strategy.test.d.ts +14 -0
- package/build/strategies/__tests__/base-auth.strategy.test.js +137 -0
- package/build/strategies/__tests__/credential-auth.strategy.test.d.ts +14 -0
- package/build/strategies/__tests__/credential-auth.strategy.test.js +265 -0
- package/build/strategies/__tests__/token-auth.strategy.test.d.ts +28 -0
- package/build/strategies/__tests__/token-auth.strategy.test.js +298 -0
- package/build/strategies/api-key/__tests__/api-key.strategy.test.d.ts +1 -0
- package/build/strategies/api-key/__tests__/api-key.strategy.test.js +103 -0
- package/build/strategies/api-key/api-key.strategy.d.ts +5 -2
- package/build/strategies/api-key/api-key.strategy.js +43 -35
- package/build/strategies/api-key/api-key.tools.d.ts +2 -0
- package/build/strategies/api-key/api-key.tools.js +39 -0
- package/build/strategies/api-key/api-key.types.d.ts +10 -2
- package/build/strategies/base-auth.strategy.d.ts +11 -5
- package/build/strategies/base-auth.strategy.js +45 -52
- package/build/strategies/basic/__tests__/basic.strategy.test.d.ts +1 -0
- package/build/strategies/basic/__tests__/basic.strategy.test.js +104 -0
- package/build/strategies/basic/basic.strategy.d.ts +5 -7
- package/build/strategies/basic/basic.strategy.js +6 -6
- package/build/strategies/basic/basic.tools.d.ts +2 -0
- package/build/strategies/basic/basic.tools.js +44 -0
- package/build/strategies/credential-auth.strategy.d.ts +7 -17
- package/build/strategies/credential-auth.strategy.js +116 -181
- package/build/strategies/jwt/__tests__/jwt.strategy.test.d.ts +1 -0
- package/build/strategies/jwt/__tests__/jwt.strategy.test.js +156 -0
- package/build/strategies/jwt/__tests__/jwt.tools.test.d.ts +1 -0
- package/build/strategies/jwt/__tests__/jwt.tools.test.js +98 -0
- package/build/strategies/jwt/jwt.strategy.d.ts +13 -14
- package/build/strategies/jwt/jwt.strategy.js +57 -44
- package/build/strategies/jwt/jwt.tools.d.ts +20 -7
- package/build/strategies/jwt/jwt.tools.js +180 -81
- package/build/strategies/local/__tests__/local.strategy.test.d.ts +1 -0
- package/build/strategies/local/__tests__/local.strategy.test.js +115 -0
- package/build/strategies/local/local.strategy.d.ts +4 -3
- package/build/strategies/local/local.strategy.js +7 -6
- package/build/strategies/local/local.tools.d.ts +2 -0
- package/build/strategies/local/local.tools.js +44 -0
- package/build/strategies/oauth2/hybrid.oauth2.strategy.d.ts +5 -0
- package/build/strategies/oauth2/hybrid.oauth2.strategy.js +92 -0
- package/build/strategies/oauth2/oauth2.errors.d.ts +12 -0
- package/build/strategies/oauth2/oauth2.errors.js +24 -0
- package/build/strategies/oauth2/oauth2.strategy.d.ts +25 -15
- package/build/strategies/oauth2/oauth2.strategy.js +131 -141
- package/build/strategies/oauth2/oauth2.tools.d.ts +7 -2
- package/build/strategies/oauth2/oauth2.tools.js +119 -14
- package/build/strategies/oauth2/oauth2.types.d.ts +32 -1
- package/build/strategies/token-auth.strategy.d.ts +14 -8
- package/build/strategies/token-auth.strategy.js +162 -38
- package/build/tools/index.d.ts +0 -2
- package/build/tools/index.js +0 -2
- package/build/tools/tools.d.ts +2 -1
- package/build/tools/tools.js +9 -12
- package/build/types.d.ts +88 -57
- package/package.json +1 -1
- package/build/factories/auth-strategy.factory.d.ts +0 -9
- package/build/factories/auth-strategy.factory.js +0 -16
- package/build/factories/http-auth-strategy.factory.d.ts +0 -5
- package/build/factories/http-auth-strategy.factory.js +0 -41
- package/build/factories/index.d.ts +0 -3
- package/build/factories/socket-auth-strategy.factory.d.ts +0 -5
- package/build/factories/socket-auth-strategy.factory.js +0 -27
- package/build/tools/session.tools.d.ts +0 -6
- package/build/tools/session.tools.js +0 -15
- package/build/tools/token.tools.d.ts +0 -7
- package/build/tools/token.tools.js +0 -32
package/build/soap-auth.d.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AuthCategories, AuthStrategy, SoapAuthConfig } from "./types";
|
|
2
2
|
export declare class SoapAuth {
|
|
3
3
|
private requiredStrategyMethods;
|
|
4
4
|
private strategies;
|
|
5
5
|
private logger?;
|
|
6
6
|
constructor(config: SoapAuthConfig);
|
|
7
7
|
private isAuthStrategy;
|
|
8
|
-
addStrategy(strategyInstance: AuthStrategy | undefined, name: string, type:
|
|
9
|
-
removeStrategy(name: string | string[], type:
|
|
10
|
-
hasStrategy(name: string, type:
|
|
11
|
-
getStrategy(name: string, type:
|
|
12
|
-
|
|
8
|
+
addStrategy(strategyInstance: AuthStrategy | undefined, name: string, type: AuthCategories): void;
|
|
9
|
+
removeStrategy(name: string | string[], type: AuthCategories): void;
|
|
10
|
+
hasStrategy(name: string, type: AuthCategories): boolean;
|
|
11
|
+
getStrategy<T extends AuthStrategy>(name: string, type: AuthCategories): T;
|
|
12
|
+
getHttpStrategy<T extends AuthStrategy>(name: string): T;
|
|
13
|
+
getSocketStrategy<T extends AuthStrategy>(name: string): T;
|
|
14
|
+
getEventStrategy<T extends AuthStrategy>(name: string): T;
|
|
15
|
+
listStrategies(type: AuthCategories): string[];
|
|
13
16
|
init(sequential?: boolean): Promise<void>;
|
|
14
|
-
authenticate(type: "http" | "socket", name: string, context: any): Promise<AuthResult<any>>;
|
|
15
|
-
logout(type: "http" | "socket", name: string, context: any): Promise<void>;
|
|
16
17
|
}
|
package/build/soap-auth.js
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SoapAuth = void 0;
|
|
4
|
-
const http_auth_strategy_factory_1 = require("./factories/http-auth-strategy.factory");
|
|
5
|
-
const socket_auth_strategy_factory_1 = require("./factories/socket-auth-strategy.factory");
|
|
6
4
|
class SoapAuth {
|
|
7
5
|
requiredStrategyMethods = ["authenticate", "init"];
|
|
8
6
|
strategies = new Map();
|
|
9
7
|
logger;
|
|
10
8
|
constructor(config) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
this.strategies.set("
|
|
16
|
-
this.strategies.set("
|
|
9
|
+
this.strategies.set("http", new Map());
|
|
10
|
+
this.strategies.set("socket", new Map());
|
|
11
|
+
this.strategies.set("event", new Map());
|
|
12
|
+
this.strategies.set("isa", new Map());
|
|
13
|
+
this.strategies.set("webhook", new Map());
|
|
14
|
+
this.strategies.set("grpc", new Map());
|
|
15
|
+
this.strategies.set("edge", new Map());
|
|
17
16
|
this.logger = config.logger;
|
|
18
17
|
}
|
|
19
18
|
isAuthStrategy(strategy) {
|
|
@@ -23,7 +22,7 @@ class SoapAuth {
|
|
|
23
22
|
}
|
|
24
23
|
addStrategy(strategyInstance, name, type) {
|
|
25
24
|
if (!this.strategies.has(type)) {
|
|
26
|
-
throw new Error(`Invalid strategy type "${type}"
|
|
25
|
+
throw new Error(`Invalid strategy type "${type}".`);
|
|
27
26
|
}
|
|
28
27
|
if (this.isAuthStrategy(strategyInstance)) {
|
|
29
28
|
this.strategies.get(type).set(name, strategyInstance);
|
|
@@ -35,7 +34,7 @@ class SoapAuth {
|
|
|
35
34
|
}
|
|
36
35
|
removeStrategy(name, type) {
|
|
37
36
|
if (!this.strategies.has(type)) {
|
|
38
|
-
throw new Error(`Invalid strategy type "${type}"
|
|
37
|
+
throw new Error(`Invalid strategy type "${type}".`);
|
|
39
38
|
}
|
|
40
39
|
const names = Array.isArray(name) ? name : [name];
|
|
41
40
|
names.forEach((n) => {
|
|
@@ -44,13 +43,13 @@ class SoapAuth {
|
|
|
44
43
|
}
|
|
45
44
|
hasStrategy(name, type) {
|
|
46
45
|
if (!this.strategies.has(type)) {
|
|
47
|
-
throw new Error(`Invalid strategy type "${type}"
|
|
46
|
+
throw new Error(`Invalid strategy type "${type}".`);
|
|
48
47
|
}
|
|
49
48
|
return this.strategies.get(type).has(name);
|
|
50
49
|
}
|
|
51
50
|
getStrategy(name, type) {
|
|
52
51
|
if (!this.strategies.has(type)) {
|
|
53
|
-
throw new Error(`Invalid strategy type "${type}"
|
|
52
|
+
throw new Error(`Invalid strategy type "${type}".`);
|
|
54
53
|
}
|
|
55
54
|
const strategy = this.strategies.get(type).get(name);
|
|
56
55
|
if (!strategy) {
|
|
@@ -58,9 +57,39 @@ class SoapAuth {
|
|
|
58
57
|
}
|
|
59
58
|
return strategy;
|
|
60
59
|
}
|
|
60
|
+
getHttpStrategy(name) {
|
|
61
|
+
if (!this.strategies.has("http")) {
|
|
62
|
+
throw new Error(`Invalid strategy.`);
|
|
63
|
+
}
|
|
64
|
+
const strategy = this.strategies.get("http").get(name);
|
|
65
|
+
if (!strategy) {
|
|
66
|
+
throw new Error(`Authentication strategy "${name}" not found.`);
|
|
67
|
+
}
|
|
68
|
+
return strategy;
|
|
69
|
+
}
|
|
70
|
+
getSocketStrategy(name) {
|
|
71
|
+
if (!this.strategies.has("socket")) {
|
|
72
|
+
throw new Error(`Invalid strategy.`);
|
|
73
|
+
}
|
|
74
|
+
const strategy = this.strategies.get("socket").get(name);
|
|
75
|
+
if (!strategy) {
|
|
76
|
+
throw new Error(`Authentication strategy "${name}" not found.`);
|
|
77
|
+
}
|
|
78
|
+
return strategy;
|
|
79
|
+
}
|
|
80
|
+
getEventStrategy(name) {
|
|
81
|
+
if (!this.strategies.has("event")) {
|
|
82
|
+
throw new Error(`Invalid strategy.`);
|
|
83
|
+
}
|
|
84
|
+
const strategy = this.strategies.get("event").get(name);
|
|
85
|
+
if (!strategy) {
|
|
86
|
+
throw new Error(`Authentication strategy "${name}" not found.`);
|
|
87
|
+
}
|
|
88
|
+
return strategy;
|
|
89
|
+
}
|
|
61
90
|
listStrategies(type) {
|
|
62
91
|
if (!this.strategies.has(type)) {
|
|
63
|
-
throw new Error(`Invalid strategy type "${type}"
|
|
92
|
+
throw new Error(`Invalid strategy type "${type}".`);
|
|
64
93
|
}
|
|
65
94
|
return Array.from(this.strategies.get(type).keys());
|
|
66
95
|
}
|
|
@@ -85,21 +114,5 @@ class SoapAuth {
|
|
|
85
114
|
.catch((error) => this.logger?.error(`Failed to initialize strategy: ${error.message}`))));
|
|
86
115
|
}
|
|
87
116
|
}
|
|
88
|
-
async authenticate(type, name, context) {
|
|
89
|
-
const strategy = this.getStrategy(name, type);
|
|
90
|
-
if (!strategy) {
|
|
91
|
-
throw new Error(`Authentication strategy "${name}" not found.`);
|
|
92
|
-
}
|
|
93
|
-
return strategy.authenticate(context);
|
|
94
|
-
}
|
|
95
|
-
async logout(type, name, context) {
|
|
96
|
-
const strategy = this.getStrategy(name, type);
|
|
97
|
-
if (strategy?.logout) {
|
|
98
|
-
await strategy.logout(context);
|
|
99
|
-
}
|
|
100
|
-
else {
|
|
101
|
-
this.logger?.error(`No "logout" implementation in strategy "${name}".`);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
117
|
}
|
|
105
118
|
exports.SoapAuth = SoapAuth;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { AuthResult, BaseAuthStrategyConfig } from "../../../src/types";
|
|
2
|
+
import { SessionHandler } from "../../../src/session/session-handler";
|
|
3
|
+
import * as Soap from "@soapjs/soap";
|
|
4
|
+
import { BaseAuthStrategy } from "../base-auth.strategy";
|
|
5
|
+
export interface MockUser {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
}
|
|
9
|
+
export interface MockContext {
|
|
10
|
+
}
|
|
11
|
+
export declare class TestBaseAuthStrategy extends BaseAuthStrategy<MockContext, MockUser> {
|
|
12
|
+
constructor(config: BaseAuthStrategyConfig<MockContext, MockUser>, session?: SessionHandler, logger?: Soap.Logger);
|
|
13
|
+
authenticate(context?: MockContext): Promise<AuthResult<MockUser>>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TestBaseAuthStrategy = void 0;
|
|
4
|
+
const session_errors_1 = require("../../../src/session/session.errors");
|
|
5
|
+
const base_auth_strategy_1 = require("../base-auth.strategy");
|
|
6
|
+
class TestBaseAuthStrategy extends base_auth_strategy_1.BaseAuthStrategy {
|
|
7
|
+
constructor(config, session, logger) {
|
|
8
|
+
super(config, session, logger);
|
|
9
|
+
}
|
|
10
|
+
async authenticate(context) {
|
|
11
|
+
return { user: null };
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.TestBaseAuthStrategy = TestBaseAuthStrategy;
|
|
15
|
+
describe("BaseAuthStrategy", () => {
|
|
16
|
+
let config;
|
|
17
|
+
let mockSessionHandler;
|
|
18
|
+
let mockLogger;
|
|
19
|
+
let mockAccountLock;
|
|
20
|
+
let mockMfa;
|
|
21
|
+
let mockRateLimit;
|
|
22
|
+
let mockRole;
|
|
23
|
+
let mockThrottle;
|
|
24
|
+
let strategy;
|
|
25
|
+
let context;
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
config = {
|
|
28
|
+
onSuccess: jest.fn(),
|
|
29
|
+
onFailure: jest.fn(),
|
|
30
|
+
};
|
|
31
|
+
mockSessionHandler = {
|
|
32
|
+
getSessionId: jest.fn().mockReturnValue("session-id"),
|
|
33
|
+
getSessionData: jest
|
|
34
|
+
.fn()
|
|
35
|
+
.mockResolvedValue({ user: { id: "123", name: "TestUser" } }),
|
|
36
|
+
};
|
|
37
|
+
mockLogger = {
|
|
38
|
+
error: jest.fn(),
|
|
39
|
+
info: jest.fn(),
|
|
40
|
+
warn: jest.fn(),
|
|
41
|
+
};
|
|
42
|
+
mockAccountLock = {};
|
|
43
|
+
mockMfa = {};
|
|
44
|
+
mockRateLimit = {
|
|
45
|
+
checkRateLimit: jest.fn().mockResolvedValue(undefined),
|
|
46
|
+
};
|
|
47
|
+
mockRole = {
|
|
48
|
+
isAuthorized: jest.fn().mockResolvedValue(true),
|
|
49
|
+
};
|
|
50
|
+
mockThrottle = {};
|
|
51
|
+
strategy = new TestBaseAuthStrategy(config, mockSessionHandler, mockLogger);
|
|
52
|
+
strategy.accountLock = mockAccountLock;
|
|
53
|
+
strategy.mfa = mockMfa;
|
|
54
|
+
strategy.rateLimit = mockRateLimit;
|
|
55
|
+
strategy.role = mockRole;
|
|
56
|
+
strategy.throttle = mockThrottle;
|
|
57
|
+
context = {};
|
|
58
|
+
});
|
|
59
|
+
describe("constructor & init", () => {
|
|
60
|
+
it("should instantiate services and allow optional init override", async () => {
|
|
61
|
+
expect(strategy).toBeDefined();
|
|
62
|
+
await expect(strategy.init()).resolves.toBeUndefined();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
describe("onSuccess", () => {
|
|
66
|
+
it("should call config.onSuccess if defined and log errors if it fails", async () => {
|
|
67
|
+
const spy = jest.spyOn(config, "onSuccess");
|
|
68
|
+
await strategy.onSuccess("someAction", {
|
|
69
|
+
user: { id: "1", name: "Bob" },
|
|
70
|
+
context,
|
|
71
|
+
});
|
|
72
|
+
expect(spy).toHaveBeenCalledWith("someAction", {
|
|
73
|
+
user: { id: "1", name: "Bob" },
|
|
74
|
+
context,
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
it("should log error if config.onSuccess throws", async () => {
|
|
78
|
+
const error = new Error("onSuccess error");
|
|
79
|
+
config.onSuccess = jest.fn().mockRejectedValue(error);
|
|
80
|
+
await strategy.onSuccess("someAction", {
|
|
81
|
+
user: { id: "1", name: "Bob" },
|
|
82
|
+
context,
|
|
83
|
+
});
|
|
84
|
+
expect(mockLogger.error).toHaveBeenCalledWith(error);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
describe("onFailure", () => {
|
|
88
|
+
it("should call config.onFailure if defined and log with logger.error", async () => {
|
|
89
|
+
const spy = jest.spyOn(config, "onFailure");
|
|
90
|
+
await strategy.onFailure("someAction", {
|
|
91
|
+
context,
|
|
92
|
+
error: new Error("Failure reason"),
|
|
93
|
+
});
|
|
94
|
+
expect(mockLogger.error).toHaveBeenCalledWith("someAction failed:", expect.any(Error));
|
|
95
|
+
expect(spy).toHaveBeenCalledWith("someAction", {
|
|
96
|
+
context,
|
|
97
|
+
error: expect.any(Error),
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
it("should log error if config.onFailure throws", async () => {
|
|
101
|
+
config.onFailure = jest
|
|
102
|
+
.fn()
|
|
103
|
+
.mockRejectedValue(new Error("onFailure error"));
|
|
104
|
+
await strategy.onFailure("someAction", {
|
|
105
|
+
context,
|
|
106
|
+
error: new Error("boom"),
|
|
107
|
+
});
|
|
108
|
+
expect(mockLogger.error).toHaveBeenCalledWith("someAction failed:", expect.any(Error));
|
|
109
|
+
expect(mockLogger.error).toHaveBeenCalledWith(expect.any(Error));
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
describe("authenticateWithSession", () => {
|
|
113
|
+
it("should throw MissingSessionIdError if getSessionId returns undefined", async () => {
|
|
114
|
+
mockSessionHandler.getSessionId.mockReturnValueOnce(undefined);
|
|
115
|
+
await expect(strategy.authenticateWithSession(context)).rejects.toThrow(session_errors_1.MissingSessionIdError);
|
|
116
|
+
});
|
|
117
|
+
it("should throw InvalidSessionError if getSessionData returns null/undefined", async () => {
|
|
118
|
+
mockSessionHandler.getSessionData.mockResolvedValueOnce(null);
|
|
119
|
+
await expect(strategy.authenticateWithSession(context)).rejects.toThrow(session_errors_1.InvalidSessionError);
|
|
120
|
+
});
|
|
121
|
+
it("should call rateLimit.checkRateLimit, role.isAuthorized, and return user from session", async () => {
|
|
122
|
+
const result = await strategy.authenticateWithSession(context);
|
|
123
|
+
expect(mockRateLimit.checkRateLimit).toHaveBeenCalledWith(context);
|
|
124
|
+
expect(mockRole.isAuthorized).toHaveBeenCalledWith({
|
|
125
|
+
id: "123",
|
|
126
|
+
name: "TestUser",
|
|
127
|
+
});
|
|
128
|
+
expect(result).toEqual({ user: { id: "123", name: "TestUser" } });
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
describe("authenticate", () => {
|
|
132
|
+
it("should be implemented by the concrete class", async () => {
|
|
133
|
+
const res = await strategy.authenticate(context);
|
|
134
|
+
expect(res).toEqual({ user: null });
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { CredentialAuthStrategyConfig } from "../../../src/types";
|
|
2
|
+
import { SessionHandler } from "../../../src/session/session-handler";
|
|
3
|
+
import { JwtStrategy } from "../../../src/strategies/jwt/jwt.strategy";
|
|
4
|
+
import * as Soap from "@soapjs/soap";
|
|
5
|
+
import { CredentialAuthStrategy } from "../credential-auth.strategy";
|
|
6
|
+
export declare class TestCredentialAuthStrategy extends CredentialAuthStrategy<any, any> {
|
|
7
|
+
constructor(config: CredentialAuthStrategyConfig<any, any>, session?: SessionHandler, jwt?: JwtStrategy<any, any>, logger?: Soap.Logger);
|
|
8
|
+
protected verifyCredentials(identifier: string, password: string): Promise<boolean>;
|
|
9
|
+
protected extractCredentials(context: any): Promise<{
|
|
10
|
+
identifier: string;
|
|
11
|
+
password: string;
|
|
12
|
+
}>;
|
|
13
|
+
protected fetchUser(credentials: any): Promise<any | null>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.TestCredentialAuthStrategy = void 0;
|
|
27
|
+
const Soap = __importStar(require("@soapjs/soap"));
|
|
28
|
+
const errors_1 = require("../../../src/errors");
|
|
29
|
+
const credential_auth_strategy_1 = require("../credential-auth.strategy");
|
|
30
|
+
jest.mock("../../session/session-handler");
|
|
31
|
+
jest.mock("../jwt/jwt.strategy");
|
|
32
|
+
class TestCredentialAuthStrategy extends credential_auth_strategy_1.CredentialAuthStrategy {
|
|
33
|
+
constructor(config, session, jwt, logger) {
|
|
34
|
+
super(config, session, jwt, logger);
|
|
35
|
+
}
|
|
36
|
+
async verifyCredentials(identifier, password) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
async extractCredentials(context) {
|
|
40
|
+
return { identifier: "testUser", password: "testPass" };
|
|
41
|
+
}
|
|
42
|
+
async fetchUser(credentials) {
|
|
43
|
+
return { id: "1", name: "Test User" };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
exports.TestCredentialAuthStrategy = TestCredentialAuthStrategy;
|
|
47
|
+
describe("CredentialAuthStrategy", () => {
|
|
48
|
+
let strategy;
|
|
49
|
+
let config;
|
|
50
|
+
let mockSession;
|
|
51
|
+
let mockJwt;
|
|
52
|
+
let mockLogger;
|
|
53
|
+
const mockUser = { id: "user123", username: "testuser" };
|
|
54
|
+
const context = { headers: {}, body: {} };
|
|
55
|
+
beforeEach(() => {
|
|
56
|
+
mockLogger = {
|
|
57
|
+
info: jest.fn(),
|
|
58
|
+
warn: jest.fn(),
|
|
59
|
+
error: jest.fn(),
|
|
60
|
+
};
|
|
61
|
+
mockSession = {
|
|
62
|
+
logoutSession: jest.fn().mockResolvedValue(undefined),
|
|
63
|
+
issueSession: jest.fn().mockResolvedValue("session-id"),
|
|
64
|
+
getSessionId: jest.fn().mockReturnValue("session-id"),
|
|
65
|
+
getSessionData: jest
|
|
66
|
+
.fn()
|
|
67
|
+
.mockResolvedValue({ user: { id: "1", name: "Test" } }),
|
|
68
|
+
};
|
|
69
|
+
mockJwt = {
|
|
70
|
+
authenticate: jest.fn(),
|
|
71
|
+
issueTokens: jest.fn().mockResolvedValue({
|
|
72
|
+
accessToken: "fakeAccess",
|
|
73
|
+
refreshToken: "fakeRefresh",
|
|
74
|
+
}),
|
|
75
|
+
};
|
|
76
|
+
config = {
|
|
77
|
+
failedAttempts: {
|
|
78
|
+
incrementFailedAttempts: jest.fn(),
|
|
79
|
+
resetFailedAttempts: jest.fn(),
|
|
80
|
+
getFailedAttempts: jest.fn(),
|
|
81
|
+
},
|
|
82
|
+
role: {
|
|
83
|
+
authorizeByRoles: jest.fn().mockResolvedValue(true),
|
|
84
|
+
roles: ["user"],
|
|
85
|
+
},
|
|
86
|
+
rateLimit: {
|
|
87
|
+
checkRateLimit: jest.fn().mockResolvedValue(false),
|
|
88
|
+
},
|
|
89
|
+
mfa: {
|
|
90
|
+
isMfaRequired: jest.fn().mockReturnValue(false),
|
|
91
|
+
},
|
|
92
|
+
passwordPolicy: {
|
|
93
|
+
updatePassword: jest.fn(),
|
|
94
|
+
generateResetToken: jest.fn(),
|
|
95
|
+
validateResetToken: jest.fn(),
|
|
96
|
+
sendResetEmail: jest.fn(),
|
|
97
|
+
},
|
|
98
|
+
security: {
|
|
99
|
+
maxFailedLoginAttempts: 3,
|
|
100
|
+
lockoutDuration: 10,
|
|
101
|
+
notifyOnLockout: jest.fn(),
|
|
102
|
+
},
|
|
103
|
+
lock: {
|
|
104
|
+
isAccountLocked: jest.fn(),
|
|
105
|
+
},
|
|
106
|
+
user: {
|
|
107
|
+
fetchUser: jest.fn(),
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
strategy = new TestCredentialAuthStrategy(config, mockSession, mockJwt, mockLogger);
|
|
111
|
+
});
|
|
112
|
+
describe("authenticate", () => {
|
|
113
|
+
it("should use JWT if available and return its result", async () => {
|
|
114
|
+
mockJwt.authenticate.mockResolvedValue({
|
|
115
|
+
user: { id: "1", name: "JWT User" },
|
|
116
|
+
});
|
|
117
|
+
const result = await strategy.authenticate(context);
|
|
118
|
+
expect(mockJwt.authenticate).toHaveBeenCalledWith(context);
|
|
119
|
+
expect(result).toEqual({
|
|
120
|
+
user: { id: "1", name: "JWT User" },
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
it("should fall back to authenticateWithSession if JWT throws an error", async () => {
|
|
124
|
+
mockJwt.authenticate.mockRejectedValue(new Error("JWT error"));
|
|
125
|
+
const result = await strategy.authenticate(context);
|
|
126
|
+
expect(mockLogger.warn).toHaveBeenCalledWith("JWT authentication failed, falling back to session.");
|
|
127
|
+
expect(result.user).toEqual({ id: "1", name: "Test" });
|
|
128
|
+
});
|
|
129
|
+
it("should use session if JWT is not defined but session is available", async () => {
|
|
130
|
+
strategy = new TestCredentialAuthStrategy(config, mockSession, undefined, mockLogger);
|
|
131
|
+
const result = await strategy.authenticate(context);
|
|
132
|
+
expect(result.user).toEqual({ id: "1", name: "Test" });
|
|
133
|
+
});
|
|
134
|
+
it("should return user=null if no JWT/session and allowGuest = true", async () => {
|
|
135
|
+
config.allowGuest = true;
|
|
136
|
+
strategy = new TestCredentialAuthStrategy(config, undefined, undefined, mockLogger);
|
|
137
|
+
const result = await strategy.authenticate(context);
|
|
138
|
+
expect(mockLogger.warn).toHaveBeenCalledWith("No authentication method found. Proceeding as guest.");
|
|
139
|
+
expect(result.user).toBeNull();
|
|
140
|
+
});
|
|
141
|
+
it("should throw UserNotFoundError if no JWT/session and allowGuest = false", async () => {
|
|
142
|
+
config.allowGuest = false;
|
|
143
|
+
strategy = new TestCredentialAuthStrategy(config, undefined, undefined, mockLogger);
|
|
144
|
+
await expect(strategy.authenticate(context)).rejects.toThrow(errors_1.UserNotFoundError);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
describe("login", () => {
|
|
148
|
+
it("should throw MissingCredentialsError if credentials are missing", async () => {
|
|
149
|
+
jest
|
|
150
|
+
.spyOn(strategy, "extractCredentials")
|
|
151
|
+
.mockResolvedValueOnce(undefined);
|
|
152
|
+
await expect(strategy.login(context)).rejects.toThrow(errors_1.MissingCredentialsError);
|
|
153
|
+
});
|
|
154
|
+
it("should throw InvalidCredentialsError if verifyCredentials returns false", async () => {
|
|
155
|
+
jest
|
|
156
|
+
.spyOn(strategy, "verifyCredentials")
|
|
157
|
+
.mockResolvedValueOnce(false);
|
|
158
|
+
await expect(strategy.login(context)).rejects.toThrow(errors_1.InvalidCredentialsError);
|
|
159
|
+
});
|
|
160
|
+
it("should throw ExpiredPasswordError if the password is expired", async () => {
|
|
161
|
+
jest
|
|
162
|
+
.spyOn(strategy.password, "isPasswordChangeRequired")
|
|
163
|
+
.mockResolvedValueOnce(true);
|
|
164
|
+
await expect(strategy.login(context)).rejects.toThrow(errors_1.ExpiredPasswordError);
|
|
165
|
+
});
|
|
166
|
+
it("should return user, tokens, and session on success", async () => {
|
|
167
|
+
const result = await strategy.login(context);
|
|
168
|
+
expect(result.user).toBeDefined();
|
|
169
|
+
expect(result.tokens).toBeDefined();
|
|
170
|
+
expect(result.session).toBeDefined();
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
describe("logout", () => {
|
|
174
|
+
it("should call session.logoutSession and onSuccess", async () => {
|
|
175
|
+
const onSuccessSpy = jest.spyOn(strategy, "onSuccess");
|
|
176
|
+
await strategy.logout(context);
|
|
177
|
+
expect(mockSession.logoutSession).toHaveBeenCalledWith(context);
|
|
178
|
+
expect(onSuccessSpy).toHaveBeenCalledWith("logout", context);
|
|
179
|
+
});
|
|
180
|
+
it("should call onFailure and rethrow the error if session.logoutSession fails", async () => {
|
|
181
|
+
const onFailureSpy = jest.spyOn(strategy, "onFailure");
|
|
182
|
+
mockSession.logoutSession.mockRejectedValueOnce(new Error("Session error"));
|
|
183
|
+
await expect(strategy.logout(context)).rejects.toThrow("Session error");
|
|
184
|
+
expect(onFailureSpy).toHaveBeenCalledWith("logout", {
|
|
185
|
+
error: expect.any(Error),
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
describe("requestPasswordReset", () => {
|
|
190
|
+
it("should throw NotImplementedError if generateResetToken is not configured", async () => {
|
|
191
|
+
config.passwordPolicy.generateResetToken = undefined;
|
|
192
|
+
await expect(strategy.requestPasswordReset("testUser")).rejects.toThrow(Soap.NotImplementedError);
|
|
193
|
+
});
|
|
194
|
+
it("should call generateResetToken and sendResetEmail (if email is provided)", async () => {
|
|
195
|
+
config.passwordPolicy.generateResetToken = jest
|
|
196
|
+
.fn()
|
|
197
|
+
.mockResolvedValue("mockToken");
|
|
198
|
+
config.passwordPolicy.sendResetEmail = jest
|
|
199
|
+
.fn()
|
|
200
|
+
.mockResolvedValue(undefined);
|
|
201
|
+
await strategy.requestPasswordReset("testUser", "test@example.com");
|
|
202
|
+
expect(config.passwordPolicy.generateResetToken).toHaveBeenCalledWith("testUser");
|
|
203
|
+
expect(config.passwordPolicy.sendResetEmail).toHaveBeenCalledWith("test@example.com", "mockToken");
|
|
204
|
+
});
|
|
205
|
+
it("should call onSuccess with the correct data", async () => {
|
|
206
|
+
config.passwordPolicy.generateResetToken = jest
|
|
207
|
+
.fn()
|
|
208
|
+
.mockResolvedValue("mockToken");
|
|
209
|
+
const onSuccessSpy = jest.spyOn(strategy, "onSuccess");
|
|
210
|
+
await strategy.requestPasswordReset("testUser");
|
|
211
|
+
expect(onSuccessSpy).toHaveBeenCalledWith("request_password_reset", {
|
|
212
|
+
identifier: "testUser",
|
|
213
|
+
tokens: { reset: "mockToken" },
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
describe("resetPassword", () => {
|
|
218
|
+
it("should throw NotImplementedError if validateResetToken or updatePassword are not configured", async () => {
|
|
219
|
+
config.passwordPolicy.updatePassword = undefined;
|
|
220
|
+
await expect(strategy.resetPassword("testUser", "token", "newPass")).rejects.toThrow(Soap.NotImplementedError);
|
|
221
|
+
});
|
|
222
|
+
it("should throw ExpiredResetTokenError if validateResetToken returns false", async () => {
|
|
223
|
+
config.passwordPolicy.validateResetToken = jest
|
|
224
|
+
.fn()
|
|
225
|
+
.mockResolvedValue(false);
|
|
226
|
+
config.passwordPolicy.updatePassword = jest.fn();
|
|
227
|
+
await expect(strategy.resetPassword("testUser", "token", "newPass")).rejects.toThrow(errors_1.ExpiredResetTokenError);
|
|
228
|
+
});
|
|
229
|
+
it("should call updatePassword if the token is valid", async () => {
|
|
230
|
+
config.passwordPolicy.validateResetToken = jest
|
|
231
|
+
.fn()
|
|
232
|
+
.mockResolvedValue(true);
|
|
233
|
+
config.passwordPolicy.updatePassword = jest
|
|
234
|
+
.fn()
|
|
235
|
+
.mockResolvedValue(undefined);
|
|
236
|
+
await strategy.resetPassword("testUser", "validToken", "newPass");
|
|
237
|
+
expect(config.passwordPolicy.updatePassword).toHaveBeenCalledWith("testUser", "newPass");
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
describe("changePassword", () => {
|
|
241
|
+
it("should throw NotImplementedError if updatePassword is not configured", async () => {
|
|
242
|
+
config.passwordPolicy.updatePassword = undefined;
|
|
243
|
+
await expect(strategy.changePassword("testUser", "oldPass", "newPass")).rejects.toThrow(Soap.NotImplementedError);
|
|
244
|
+
});
|
|
245
|
+
it("should throw InvalidCredentialsError if verifyCredentials returns false", async () => {
|
|
246
|
+
config.passwordPolicy.updatePassword = jest
|
|
247
|
+
.fn()
|
|
248
|
+
.mockResolvedValue(undefined);
|
|
249
|
+
jest
|
|
250
|
+
.spyOn(strategy, "verifyCredentials")
|
|
251
|
+
.mockResolvedValueOnce(false);
|
|
252
|
+
await expect(strategy.changePassword("testUser", "oldPass", "newPass")).rejects.toThrow(errors_1.InvalidCredentialsError);
|
|
253
|
+
});
|
|
254
|
+
it("should call updatePassword with new password if old credentials are correct", async () => {
|
|
255
|
+
config.passwordPolicy.updatePassword = jest
|
|
256
|
+
.fn()
|
|
257
|
+
.mockResolvedValue(undefined);
|
|
258
|
+
jest
|
|
259
|
+
.spyOn(strategy, "verifyCredentials")
|
|
260
|
+
.mockResolvedValueOnce(true);
|
|
261
|
+
await strategy.changePassword("testUser", "oldPass", "newPass");
|
|
262
|
+
expect(config.passwordPolicy.updatePassword).toHaveBeenCalledWith("testUser", "newPass");
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as Soap from "@soapjs/soap";
|
|
2
|
+
import { TokenAuthStrategy } from "../token-auth.strategy";
|
|
3
|
+
import { SessionHandler } from "../../session";
|
|
4
|
+
import { TokenAuthStrategyConfig } from "../../../src/types";
|
|
5
|
+
export interface MockUser {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
}
|
|
9
|
+
export interface MockContext {
|
|
10
|
+
accessToken?: string;
|
|
11
|
+
refreshToken?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare class TestTokenAuthStrategy extends TokenAuthStrategy<MockContext, MockUser> {
|
|
14
|
+
protected invalidateAccessToken(token: string, context?: MockContext): Promise<void>;
|
|
15
|
+
protected invalidateRefreshToken(token: string, context?: MockContext): Promise<void>;
|
|
16
|
+
constructor(config: TokenAuthStrategyConfig<MockContext, MockUser>, session?: SessionHandler, logger?: Soap.Logger);
|
|
17
|
+
protected fetchUser(payload: any): Promise<MockUser | null>;
|
|
18
|
+
protected extractAccessToken(context: MockContext): string | undefined;
|
|
19
|
+
protected extractRefreshToken(context: MockContext): string | undefined;
|
|
20
|
+
protected verifyAccessToken(token: string): Promise<any>;
|
|
21
|
+
protected verifyRefreshToken(token: string): Promise<any>;
|
|
22
|
+
protected generateAccessToken(user: MockUser, context: MockContext): Promise<string>;
|
|
23
|
+
protected generateRefreshToken(user: MockUser, context: MockContext): Promise<string>;
|
|
24
|
+
protected storeAccessToken(token: string): Promise<void>;
|
|
25
|
+
protected storeRefreshToken(token: string): Promise<void>;
|
|
26
|
+
protected embedAccessToken(token: string, context: MockContext): void;
|
|
27
|
+
protected embedRefreshToken(token: string, context: MockContext): void;
|
|
28
|
+
}
|