@soapjs/soap-auth 0.4.0 → 1.0.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 +261 -128
- package/build/index.d.ts +1 -0
- package/build/index.js +1 -0
- package/build/recipes/auth-config.recipes.d.ts +40 -0
- package/build/recipes/auth-config.recipes.js +135 -0
- package/build/recipes/http-context.helpers.d.ts +13 -0
- package/build/recipes/http-context.helpers.js +64 -0
- package/build/recipes/index.d.ts +3 -0
- package/build/recipes/index.js +19 -0
- package/build/recipes/oauth2-presets.d.ts +20 -0
- package/build/recipes/oauth2-presets.js +74 -0
- package/build/soap-auth.js +62 -0
- package/build/strategies/jwt/jwt.strategy.js +6 -3
- package/build/strategies/jwt/jwt.tools.js +8 -6
- package/build/strategies/local/local.strategy.d.ts +2 -2
- package/build/strategies/local/local.strategy.js +7 -7
- package/build/strategies/oauth2/hybrid.oauth2.strategy.js +1 -1
- package/build/strategies/oauth2/oauth2.strategy.js +2 -2
- package/build/strategies/oauth2/oauth2.types.d.ts +5 -0
- package/build/strategies/oauth2/providers/configurable-hybrid-oauth2.strategy.d.ts +19 -0
- package/build/strategies/oauth2/providers/configurable-hybrid-oauth2.strategy.js +85 -0
- package/build/strategies/oauth2/providers/configurable-oauth2.strategy.d.ts +11 -0
- package/build/strategies/oauth2/providers/configurable-oauth2.strategy.js +46 -0
- package/build/strategies/oauth2/providers/index.d.ts +2 -0
- package/build/strategies/oauth2/providers/index.js +2 -0
- package/build/strategies/oauth2/providers/provider.types.d.ts +3 -0
- package/build/types.d.ts +5 -2
- package/package.json +91 -13
- package/.claude/settings.local.json +0 -20
- package/build/__tests__/soap-auth.test.d.ts +0 -1
- package/build/__tests__/soap-auth.test.js +0 -136
- package/build/services/__tests__/account-lock.service.test.d.ts +0 -1
- package/build/services/__tests__/account-lock.service.test.js +0 -55
- package/build/services/__tests__/auth-throttle.service.test.d.ts +0 -1
- package/build/services/__tests__/auth-throttle.service.test.js +0 -48
- package/build/services/__tests__/jwks.service.test.d.ts +0 -1
- package/build/services/__tests__/jwks.service.test.js +0 -39
- package/build/services/__tests__/mfa.service.test.d.ts +0 -1
- package/build/services/__tests__/mfa.service.test.js +0 -66
- package/build/services/__tests__/password.service.test.d.ts +0 -1
- package/build/services/__tests__/password.service.test.js +0 -73
- package/build/services/__tests__/pkce.service.test.d.ts +0 -1
- package/build/services/__tests__/pkce.service.test.js +0 -77
- package/build/services/__tests__/rate-limit.service.test.d.ts +0 -1
- package/build/services/__tests__/rate-limit.service.test.js +0 -37
- package/build/services/__tests__/role.service.test.d.ts +0 -1
- package/build/services/__tests__/role.service.test.js +0 -31
- package/build/services/__tests__/totp.service.test.d.ts +0 -1
- package/build/services/__tests__/totp.service.test.js +0 -120
- package/build/session/__tests__/file.session-store.test.d.ts +0 -1
- package/build/session/__tests__/file.session-store.test.js +0 -117
- package/build/session/__tests__/memory.session-store.test.d.ts +0 -1
- package/build/session/__tests__/memory.session-store.test.js +0 -77
- package/build/session/__tests__/session-handler.test.d.ts +0 -1
- package/build/session/__tests__/session-handler.test.js +0 -345
- package/build/strategies/__tests__/base-auth.strategy.test.d.ts +0 -15
- package/build/strategies/__tests__/base-auth.strategy.test.js +0 -138
- package/build/strategies/__tests__/credential-auth.strategy.test.d.ts +0 -15
- package/build/strategies/__tests__/credential-auth.strategy.test.js +0 -266
- package/build/strategies/__tests__/token-auth.strategy.test.d.ts +0 -29
- package/build/strategies/__tests__/token-auth.strategy.test.js +0 -299
- package/build/strategies/api-key/__tests__/api-key.strategy.test.d.ts +0 -1
- package/build/strategies/api-key/__tests__/api-key.strategy.test.js +0 -103
- package/build/strategies/basic/__tests__/basic.strategy.test.d.ts +0 -1
- package/build/strategies/basic/__tests__/basic.strategy.test.js +0 -104
- package/build/strategies/jwt/__tests__/jwt.strategy.test.d.ts +0 -1
- package/build/strategies/jwt/__tests__/jwt.strategy.test.js +0 -156
- package/build/strategies/jwt/__tests__/jwt.tools.test.d.ts +0 -1
- package/build/strategies/jwt/__tests__/jwt.tools.test.js +0 -98
- package/build/strategies/local/__tests__/local.strategy.test.d.ts +0 -1
- package/build/strategies/local/__tests__/local.strategy.test.js +0 -121
- package/build/strategies/oauth2/__tests__/oauth2.strategy.test.d.ts +0 -1
- package/build/strategies/oauth2/__tests__/oauth2.strategy.test.js +0 -239
- package/build/strategies/oauth2/providers/__tests__/social-providers.test.d.ts +0 -1
- package/build/strategies/oauth2/providers/__tests__/social-providers.test.js +0 -201
- package/build/utils/__tests__/validation.test.d.ts +0 -1
- package/build/utils/__tests__/validation.test.js +0 -181
- package/jest.config.unit.json +0 -10
package/package.json
CHANGED
|
@@ -1,35 +1,113 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soapjs/soap-auth",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Authentication strategies, sessions, MFA, and token helpers for the SoapJS ecosystem.",
|
|
5
5
|
"homepage": "https://docs.soapjs.com",
|
|
6
|
-
"repository":
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/soapjs/soap-auth.git"
|
|
9
|
+
},
|
|
7
10
|
"main": "build/index.js",
|
|
8
11
|
"types": "build/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./build/index.d.ts",
|
|
15
|
+
"require": "./build/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./session": {
|
|
18
|
+
"types": "./build/session/index.d.ts",
|
|
19
|
+
"require": "./build/session/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./session/*": {
|
|
22
|
+
"types": "./build/session/*.d.ts",
|
|
23
|
+
"require": "./build/session/*.js"
|
|
24
|
+
},
|
|
25
|
+
"./services": {
|
|
26
|
+
"types": "./build/services/index.d.ts",
|
|
27
|
+
"require": "./build/services/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./services/*": {
|
|
30
|
+
"types": "./build/services/*.d.ts",
|
|
31
|
+
"require": "./build/services/*.js"
|
|
32
|
+
},
|
|
33
|
+
"./strategies": {
|
|
34
|
+
"types": "./build/strategies/index.d.ts",
|
|
35
|
+
"require": "./build/strategies/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./strategies/*": {
|
|
38
|
+
"types": "./build/strategies/*.d.ts",
|
|
39
|
+
"require": "./build/strategies/*.js"
|
|
40
|
+
},
|
|
41
|
+
"./tools": {
|
|
42
|
+
"types": "./build/tools/index.d.ts",
|
|
43
|
+
"require": "./build/tools/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./tools/*": {
|
|
46
|
+
"types": "./build/tools/*.d.ts",
|
|
47
|
+
"require": "./build/tools/*.js"
|
|
48
|
+
},
|
|
49
|
+
"./recipes": {
|
|
50
|
+
"types": "./build/recipes/index.d.ts",
|
|
51
|
+
"require": "./build/recipes/index.js"
|
|
52
|
+
},
|
|
53
|
+
"./recipes/*": {
|
|
54
|
+
"types": "./build/recipes/*.d.ts",
|
|
55
|
+
"require": "./build/recipes/*.js"
|
|
56
|
+
},
|
|
57
|
+
"./errors": {
|
|
58
|
+
"types": "./build/errors.d.ts",
|
|
59
|
+
"require": "./build/errors.js"
|
|
60
|
+
},
|
|
61
|
+
"./types": {
|
|
62
|
+
"types": "./build/types.d.ts",
|
|
63
|
+
"require": "./build/types.js"
|
|
64
|
+
},
|
|
65
|
+
"./soap-auth": {
|
|
66
|
+
"types": "./build/soap-auth.d.ts",
|
|
67
|
+
"require": "./build/soap-auth.js"
|
|
68
|
+
},
|
|
69
|
+
"./utils/validation": {
|
|
70
|
+
"types": "./build/utils/validation.d.ts",
|
|
71
|
+
"require": "./build/utils/validation.js"
|
|
72
|
+
},
|
|
73
|
+
"./package.json": "./package.json"
|
|
74
|
+
},
|
|
75
|
+
"files": [
|
|
76
|
+
"build",
|
|
77
|
+
"README.md",
|
|
78
|
+
"LICENSE",
|
|
79
|
+
"ldap.md",
|
|
80
|
+
"saml.md"
|
|
81
|
+
],
|
|
82
|
+
"sideEffects": false,
|
|
9
83
|
"license": "MIT",
|
|
10
84
|
"author": "Radoslaw Kamysz",
|
|
11
85
|
"scripts": {
|
|
12
86
|
"test:unit": "jest --config=jest.config.unit.json",
|
|
13
87
|
"clean": "rm -rf ./build",
|
|
14
|
-
"build": "npm run clean && tsc
|
|
15
|
-
"
|
|
88
|
+
"build": "npm run clean && tsc --project tsconfig.build.json",
|
|
89
|
+
"prepack": "npm run build",
|
|
90
|
+
"prepublishOnly": "npm run test:unit && npm run build"
|
|
91
|
+
},
|
|
92
|
+
"publishConfig": {
|
|
93
|
+
"access": "public"
|
|
16
94
|
},
|
|
17
95
|
"devDependencies": {
|
|
18
|
-
"@soapjs/soap": "^0.
|
|
19
|
-
"@types/jest": "^
|
|
20
|
-
"jest": "^
|
|
21
|
-
"ts-jest": "^
|
|
96
|
+
"@soapjs/soap": "^0.12.1",
|
|
97
|
+
"@types/jest": "^29.5.14",
|
|
98
|
+
"jest": "^29.7.0",
|
|
99
|
+
"ts-jest": "^29.4.11",
|
|
22
100
|
"typescript": "^4.8.2"
|
|
23
101
|
},
|
|
24
102
|
"peerDependencies": {
|
|
25
|
-
"@soapjs/soap": ">=0.
|
|
103
|
+
"@soapjs/soap": ">=0.12.0"
|
|
26
104
|
},
|
|
27
105
|
"engines": {
|
|
28
|
-
"node": ">=
|
|
106
|
+
"node": ">=24.17.0"
|
|
29
107
|
},
|
|
30
108
|
"dependencies": {
|
|
31
109
|
"bcrypt": "^6.0.0",
|
|
32
|
-
"jsonwebtoken": "^9.0.
|
|
33
|
-
"jwks-rsa": "^3.
|
|
110
|
+
"jsonwebtoken": "^9.0.3",
|
|
111
|
+
"jwks-rsa": "^3.2.2"
|
|
34
112
|
}
|
|
35
113
|
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(npm run *)",
|
|
5
|
-
"Bash(npx tsc *)",
|
|
6
|
-
"Bash(npm audit *)",
|
|
7
|
-
"Bash(npm uninstall *)",
|
|
8
|
-
"Bash(npm install *)",
|
|
9
|
-
"Bash(echo \"=== SOAP-EXPRESS src ===\" && find /Users/rad/git/soapjs/soap-express/src -type f -name \"*.ts\" | grep -v __tests__ | grep -v \".test.\" | sort && echo \"\" && echo \"=== SOAP-EXPRESS package.json ===\" && cat /Users/rad/git/soapjs/soap-express/package.json)",
|
|
10
|
-
"Bash(echo \"=== SOAP src \\(http + common\\) ===\" && find /Users/rad/git/soapjs/soap/src -type f -name \"*.ts\" | grep -v __tests__ | grep -v \".test.\" | grep -E \"\\(http|common|config\\)\" | sort)",
|
|
11
|
-
"Bash(cat /Users/rad/git/soapjs/soap/package.json | grep '\"version\"' | head -1)",
|
|
12
|
-
"Bash(grep -rln \"AuthStrategy\\\\|\\\\.configure\\(\\\\|\\\\.middleware\\(\\\\|serializeUser\\\\|AuthConfig\\\\b\" src --include=\"*.test.ts\")",
|
|
13
|
-
"Bash(grep -rln \"AuthStrategy\\\\|AuthConfig\" src/**/__tests__)",
|
|
14
|
-
"Bash(npm pack *)",
|
|
15
|
-
"Read(//Users/rad/git/soapjs/**)",
|
|
16
|
-
"Bash(npm view *)",
|
|
17
|
-
"Bash(grep -v \"^$\")"
|
|
18
|
-
]
|
|
19
|
-
}
|
|
20
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const globals_1 = require("@jest/globals");
|
|
4
|
-
const soap_auth_1 = require("../soap-auth");
|
|
5
|
-
const memory_session_store_1 = require("../session/memory.session-store");
|
|
6
|
-
describe("SoapAuth", () => {
|
|
7
|
-
let soapAuth;
|
|
8
|
-
const mockLogger = { error: globals_1.jest.fn(), info: globals_1.jest.fn() };
|
|
9
|
-
const mockStrategy = {
|
|
10
|
-
authenticate: globals_1.jest.fn(),
|
|
11
|
-
init: globals_1.jest.fn(),
|
|
12
|
-
logout: globals_1.jest.fn(),
|
|
13
|
-
};
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
globals_1.jest.clearAllMocks();
|
|
16
|
-
soapAuth = new soap_auth_1.SoapAuth({ logger: mockLogger });
|
|
17
|
-
});
|
|
18
|
-
test("addStrategy should add a valid strategy", () => {
|
|
19
|
-
expect(() => soapAuth.addStrategy(mockStrategy, "jwt", "http")).not.toThrow();
|
|
20
|
-
expect(soapAuth.hasStrategy("jwt", "http")).toBe(true);
|
|
21
|
-
});
|
|
22
|
-
test("addStrategy should throw error if strategy is invalid", () => {
|
|
23
|
-
expect(() => soapAuth.addStrategy({}, "invalid", "http")).toThrow("Invalid authentication strategy: does not implement required methods.");
|
|
24
|
-
});
|
|
25
|
-
test("removeStrategy should remove an existing strategy", () => {
|
|
26
|
-
soapAuth.addStrategy(mockStrategy, "jwt", "http");
|
|
27
|
-
expect(soapAuth.hasStrategy("jwt", "http")).toBe(true);
|
|
28
|
-
soapAuth.removeStrategy("jwt", "http");
|
|
29
|
-
expect(soapAuth.hasStrategy("jwt", "http")).toBe(false);
|
|
30
|
-
});
|
|
31
|
-
test("getStrategy should return an existing strategy", () => {
|
|
32
|
-
soapAuth.addStrategy(mockStrategy, "jwt", "http");
|
|
33
|
-
expect(soapAuth.getStrategy("jwt", "http")).toBe(mockStrategy);
|
|
34
|
-
});
|
|
35
|
-
test("getStrategy should throw an error if strategy does not exist", () => {
|
|
36
|
-
expect(() => soapAuth.getStrategy("nonexistent", "http")).toThrow('Authentication strategy "nonexistent" not found.');
|
|
37
|
-
});
|
|
38
|
-
test("listStrategies should return all registered strategy names", () => {
|
|
39
|
-
soapAuth.addStrategy(mockStrategy, "jwt", "http");
|
|
40
|
-
soapAuth.addStrategy(mockStrategy, "oauth", "http");
|
|
41
|
-
expect(soapAuth.listStrategies("http")).toEqual(["jwt", "oauth"]);
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
describe("SoapAuth.create()", () => {
|
|
45
|
-
const mockLogger = { error: globals_1.jest.fn(), info: globals_1.jest.fn(), warn: globals_1.jest.fn() };
|
|
46
|
-
const apiKeyConfig = {
|
|
47
|
-
keyType: "session",
|
|
48
|
-
extractApiKey: (ctx) => ctx?.headers?.["x-api-key"],
|
|
49
|
-
retrieveUserByApiKey: async (_key) => ({ id: "1", email: "test@test.com" }),
|
|
50
|
-
};
|
|
51
|
-
const jwtConfig = {
|
|
52
|
-
accessToken: {
|
|
53
|
-
issuer: {
|
|
54
|
-
secretKey: "test-secret-key-for-tests",
|
|
55
|
-
options: { expiresIn: "1h" },
|
|
56
|
-
},
|
|
57
|
-
verifier: { options: {} },
|
|
58
|
-
},
|
|
59
|
-
routes: {
|
|
60
|
-
login: { path: "/auth/jwt/login", method: "POST" },
|
|
61
|
-
logout: { path: "/auth/jwt/logout", method: "POST" },
|
|
62
|
-
},
|
|
63
|
-
};
|
|
64
|
-
const localConfig = {
|
|
65
|
-
credentials: {
|
|
66
|
-
extractCredentials: (ctx) => ({
|
|
67
|
-
identifier: ctx?.body?.username,
|
|
68
|
-
password: ctx?.body?.password,
|
|
69
|
-
}),
|
|
70
|
-
verifyCredentials: async (_id, _pwd) => true,
|
|
71
|
-
},
|
|
72
|
-
user: { fetchUser: async (_id) => ({ id: "1", email: "u@test.com" }) },
|
|
73
|
-
routes: {
|
|
74
|
-
login: { path: "/auth/login", method: "POST" },
|
|
75
|
-
logout: { path: "/auth/logout", method: "POST" },
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
test("registers api-key strategy from config", async () => {
|
|
79
|
-
const auth = await soap_auth_1.SoapAuth.create({
|
|
80
|
-
http: { apiKey: apiKeyConfig },
|
|
81
|
-
logger: mockLogger,
|
|
82
|
-
});
|
|
83
|
-
expect(auth.hasStrategy("api-key", "http")).toBe(true);
|
|
84
|
-
});
|
|
85
|
-
test("registers jwt as standalone HTTP strategy from http.jwt", async () => {
|
|
86
|
-
const auth = await soap_auth_1.SoapAuth.create({
|
|
87
|
-
http: { jwt: jwtConfig },
|
|
88
|
-
logger: mockLogger,
|
|
89
|
-
});
|
|
90
|
-
expect(auth.hasStrategy("jwt", "http")).toBe(true);
|
|
91
|
-
});
|
|
92
|
-
test("registers local strategy from config", async () => {
|
|
93
|
-
const auth = await soap_auth_1.SoapAuth.create({
|
|
94
|
-
http: { local: localConfig },
|
|
95
|
-
logger: mockLogger,
|
|
96
|
-
});
|
|
97
|
-
expect(auth.hasStrategy("local", "http")).toBe(true);
|
|
98
|
-
});
|
|
99
|
-
test("registers multiple HTTP strategies simultaneously", async () => {
|
|
100
|
-
const auth = await soap_auth_1.SoapAuth.create({
|
|
101
|
-
http: { apiKey: apiKeyConfig, jwt: jwtConfig, local: localConfig },
|
|
102
|
-
logger: mockLogger,
|
|
103
|
-
});
|
|
104
|
-
expect(auth.hasStrategy("api-key", "http")).toBe(true);
|
|
105
|
-
expect(auth.hasStrategy("jwt", "http")).toBe(true);
|
|
106
|
-
expect(auth.hasStrategy("local", "http")).toBe(true);
|
|
107
|
-
});
|
|
108
|
-
test("registers custom strategies from config", async () => {
|
|
109
|
-
const customStrategy = { name: "custom", authenticate: globals_1.jest.fn() };
|
|
110
|
-
const auth = await soap_auth_1.SoapAuth.create({
|
|
111
|
-
http: { custom: { myStrategy: customStrategy } },
|
|
112
|
-
logger: mockLogger,
|
|
113
|
-
});
|
|
114
|
-
expect(auth.hasStrategy("myStrategy", "http")).toBe(true);
|
|
115
|
-
});
|
|
116
|
-
test("registers socket JWT strategy from config", async () => {
|
|
117
|
-
const auth = await soap_auth_1.SoapAuth.create({
|
|
118
|
-
socket: { jwt: jwtConfig },
|
|
119
|
-
logger: mockLogger,
|
|
120
|
-
});
|
|
121
|
-
expect(auth.hasStrategy("jwt", "socket")).toBe(true);
|
|
122
|
-
});
|
|
123
|
-
test("returns empty SoapAuth when no strategy configs provided", async () => {
|
|
124
|
-
const auth = await soap_auth_1.SoapAuth.create({ logger: mockLogger });
|
|
125
|
-
expect(auth.listStrategies("http")).toEqual([]);
|
|
126
|
-
});
|
|
127
|
-
test("uses session handler when session config provided", async () => {
|
|
128
|
-
const store = new memory_session_store_1.MemorySessionStore();
|
|
129
|
-
const auth = await soap_auth_1.SoapAuth.create({
|
|
130
|
-
session: { secret: "test-secret", store, getSessionId: () => "sid" },
|
|
131
|
-
http: { local: localConfig },
|
|
132
|
-
logger: mockLogger,
|
|
133
|
-
});
|
|
134
|
-
expect(auth.hasStrategy("local", "http")).toBe(true);
|
|
135
|
-
});
|
|
136
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const errors_1 = require("../../errors");
|
|
4
|
-
const account_lock_service_1 = require("../account-lock.service");
|
|
5
|
-
const mockConfig = {
|
|
6
|
-
isAccountLocked: jest.fn(),
|
|
7
|
-
lockAccount: jest.fn(),
|
|
8
|
-
hasAccountLockExpired: jest.fn(),
|
|
9
|
-
removeAccountLock: jest.fn(),
|
|
10
|
-
logFailedAttempt: jest.fn(),
|
|
11
|
-
notifyOnLockout: jest.fn(),
|
|
12
|
-
};
|
|
13
|
-
const mockLogger = {
|
|
14
|
-
error: jest.fn(),
|
|
15
|
-
};
|
|
16
|
-
describe("AccountLockService", () => {
|
|
17
|
-
let service;
|
|
18
|
-
const mockAccount = { id: "123" };
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
jest.clearAllMocks();
|
|
21
|
-
service = new account_lock_service_1.AccountLockService(mockConfig, mockLogger);
|
|
22
|
-
});
|
|
23
|
-
it("isAccountLocked throws error if account is locked", async () => {
|
|
24
|
-
mockConfig.isAccountLocked.mockResolvedValue(true);
|
|
25
|
-
await expect(service.isAccountLocked(mockAccount)).rejects.toThrow(errors_1.AccountLockedError);
|
|
26
|
-
});
|
|
27
|
-
it("isAccountLocked returns false if account is not locked", async () => {
|
|
28
|
-
mockConfig.isAccountLocked.mockResolvedValue(false);
|
|
29
|
-
await expect(service.isAccountLocked(mockAccount)).resolves.toBe(false);
|
|
30
|
-
});
|
|
31
|
-
it("lockAccount locks the account and notifies if enabled", async () => {
|
|
32
|
-
mockConfig.notifyOnLockout = jest.fn();
|
|
33
|
-
await service.lockAccount(mockAccount);
|
|
34
|
-
expect(mockConfig.lockAccount).toHaveBeenCalledWith(mockAccount);
|
|
35
|
-
expect(mockConfig.notifyOnLockout).toHaveBeenCalledWith(mockAccount);
|
|
36
|
-
});
|
|
37
|
-
it("lockAccount does not notify if notifyOnLockout is not defined", async () => {
|
|
38
|
-
mockConfig.notifyOnLockout = undefined;
|
|
39
|
-
await service.lockAccount(mockAccount);
|
|
40
|
-
expect(mockConfig.lockAccount).toHaveBeenCalledWith(mockAccount);
|
|
41
|
-
});
|
|
42
|
-
it("hasAccountLockExpired returns expected value", async () => {
|
|
43
|
-
mockConfig.hasAccountLockExpired.mockResolvedValue(true);
|
|
44
|
-
await expect(service.hasAccountLockExpired(mockAccount)).resolves.toBe(true);
|
|
45
|
-
});
|
|
46
|
-
it("removeAccountLock calls the correct method", async () => {
|
|
47
|
-
await service.removeAccountLock(mockAccount);
|
|
48
|
-
expect(mockConfig.removeAccountLock).toHaveBeenCalledWith(mockAccount);
|
|
49
|
-
});
|
|
50
|
-
it("logFailedAttempt logs action and handles error if logging fails", async () => {
|
|
51
|
-
mockConfig.logFailedAttempt.mockRejectedValue(new Error("Logging failed"));
|
|
52
|
-
await service.logFailedAttempt("LOGIN_ATTEMPT", mockAccount, {});
|
|
53
|
-
expect(mockLogger.error).toHaveBeenCalled();
|
|
54
|
-
});
|
|
55
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const errors_1 = require("../../errors");
|
|
4
|
-
const auth_throttle_service_1 = require("../auth-throttle.service");
|
|
5
|
-
const mockConfig = {
|
|
6
|
-
getFailedAttempts: jest.fn(),
|
|
7
|
-
incrementFailedAttempts: jest.fn(),
|
|
8
|
-
resetFailedAttempts: jest.fn(),
|
|
9
|
-
maxFailedAttempts: 3,
|
|
10
|
-
};
|
|
11
|
-
const mockLogger = {
|
|
12
|
-
warn: jest.fn(),
|
|
13
|
-
error: jest.fn(),
|
|
14
|
-
};
|
|
15
|
-
describe("AuthThrottleService", () => {
|
|
16
|
-
let service;
|
|
17
|
-
const mockIdentifier = "user123";
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
jest.clearAllMocks();
|
|
20
|
-
service = new auth_throttle_service_1.AuthThrottleService(mockConfig, mockLogger);
|
|
21
|
-
});
|
|
22
|
-
it("checkFailedAttempts throws error if max failed attempts reached", async () => {
|
|
23
|
-
mockConfig.getFailedAttempts.mockResolvedValue(3);
|
|
24
|
-
await expect(service.checkFailedAttempts(mockIdentifier)).rejects.toThrow(errors_1.AccountLockedError);
|
|
25
|
-
expect(mockLogger.warn).toHaveBeenCalledWith(`User ${mockIdentifier} is temporarily locked out.`);
|
|
26
|
-
});
|
|
27
|
-
it("checkFailedAttempts does not throw error if failed attempts are below threshold", async () => {
|
|
28
|
-
mockConfig.getFailedAttempts.mockResolvedValue(2);
|
|
29
|
-
await expect(service.checkFailedAttempts(mockIdentifier)).resolves.not.toThrow();
|
|
30
|
-
});
|
|
31
|
-
it("checkFailedAttempts logs error if an exception occurs", async () => {
|
|
32
|
-
mockConfig.getFailedAttempts.mockRejectedValue(new Error("DB error"));
|
|
33
|
-
await service.checkFailedAttempts(mockIdentifier);
|
|
34
|
-
expect(mockLogger.error).toHaveBeenCalledWith("Check failed attempts:", expect.any(Error));
|
|
35
|
-
});
|
|
36
|
-
it("incrementFailedAttempts increments counter and throws if max reached", async () => {
|
|
37
|
-
mockConfig.getFailedAttempts.mockResolvedValue(3);
|
|
38
|
-
await expect(service.incrementFailedAttempts(mockIdentifier)).rejects.toThrow(errors_1.AccountLockedError);
|
|
39
|
-
});
|
|
40
|
-
it("incrementFailedAttempts increments counter without throwing if below threshold", async () => {
|
|
41
|
-
mockConfig.getFailedAttempts.mockResolvedValue(2);
|
|
42
|
-
await expect(service.incrementFailedAttempts(mockIdentifier)).resolves.not.toThrow();
|
|
43
|
-
});
|
|
44
|
-
it("resetFailedAttempts calls the correct method", async () => {
|
|
45
|
-
await service.resetFailedAttempts(mockIdentifier);
|
|
46
|
-
expect(mockConfig.resetFailedAttempts).toHaveBeenCalledWith(mockIdentifier);
|
|
47
|
-
});
|
|
48
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
7
|
-
const jwks_service_1 = require("../jwks.service");
|
|
8
|
-
const oauth2_errors_1 = require("../../strategies/oauth2/oauth2.errors");
|
|
9
|
-
describe("JwtService", () => {
|
|
10
|
-
let service;
|
|
11
|
-
let mockConfig;
|
|
12
|
-
const mockIdToken = "mock.id.token";
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
jest.clearAllMocks();
|
|
15
|
-
mockConfig = {
|
|
16
|
-
jwks: {
|
|
17
|
-
jwksUri: "https://mock-jwks-uri.com",
|
|
18
|
-
algorithms: ["RS256"],
|
|
19
|
-
issuer: "mock-issuer",
|
|
20
|
-
audience: "mock-client-id",
|
|
21
|
-
},
|
|
22
|
-
};
|
|
23
|
-
service = new jwks_service_1.JwtService(mockConfig);
|
|
24
|
-
});
|
|
25
|
-
it("verify throws error if ID token structure is invalid", async () => {
|
|
26
|
-
jest.spyOn(jsonwebtoken_1.default, "decode").mockReturnValue(null);
|
|
27
|
-
await expect(service.verify(mockIdToken)).rejects.toThrow("Invalid ID Token structure.");
|
|
28
|
-
});
|
|
29
|
-
it("verify throws error if ID token is expired", async () => {
|
|
30
|
-
jest.spyOn(jsonwebtoken_1.default, "decode").mockReturnValue({ header: { kid: "mock-kid" } });
|
|
31
|
-
jest
|
|
32
|
-
.spyOn(service.client, "getSigningKey")
|
|
33
|
-
.mockResolvedValue({ getPublicKey: () => "mock-public-key" });
|
|
34
|
-
jest
|
|
35
|
-
.spyOn(jsonwebtoken_1.default, "verify")
|
|
36
|
-
.mockReturnValue({ exp: Math.floor(Date.now() / 1000) - 10 });
|
|
37
|
-
await expect(service.verify(mockIdToken)).rejects.toThrow(oauth2_errors_1.InvalidIdTokenError);
|
|
38
|
-
});
|
|
39
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const mfa_service_1 = require("../mfa.service");
|
|
4
|
-
const mockConfig = {
|
|
5
|
-
isMfaRequired: jest.fn(),
|
|
6
|
-
extractMfaCode: jest.fn(),
|
|
7
|
-
sendMfaCode: jest.fn(),
|
|
8
|
-
validateMfaCode: jest.fn(),
|
|
9
|
-
maxMfaAttempts: 3,
|
|
10
|
-
getMfaAttempts: jest.fn(),
|
|
11
|
-
incrementMfaAttempts: jest.fn(),
|
|
12
|
-
resetMfaAttempts: jest.fn(),
|
|
13
|
-
lockMfaOnFailure: jest.fn(),
|
|
14
|
-
};
|
|
15
|
-
const mockLogger = {
|
|
16
|
-
info: jest.fn(),
|
|
17
|
-
warn: jest.fn(),
|
|
18
|
-
error: jest.fn(),
|
|
19
|
-
};
|
|
20
|
-
describe("MfaService", () => {
|
|
21
|
-
let service;
|
|
22
|
-
const mockUser = { id: "user123" };
|
|
23
|
-
const mockContext = { token: "mock-token" };
|
|
24
|
-
beforeEach(() => {
|
|
25
|
-
jest.clearAllMocks();
|
|
26
|
-
service = new mfa_service_1.MfaService(mockConfig, mockLogger);
|
|
27
|
-
});
|
|
28
|
-
it("checkMfa sends code if required and no code provided", async () => {
|
|
29
|
-
mockConfig.isMfaRequired.mockReturnValue(true);
|
|
30
|
-
mockConfig.extractMfaCode.mockReturnValue(null);
|
|
31
|
-
await expect(service.checkMfa(mockUser, mockContext)).rejects.toThrow("2FA required. A verification code has been sent.");
|
|
32
|
-
expect(mockConfig.sendMfaCode).toHaveBeenCalledWith(mockUser, mockContext);
|
|
33
|
-
});
|
|
34
|
-
it("checkMfa locks account after too many failed attempts", async () => {
|
|
35
|
-
mockConfig.isMfaRequired.mockReturnValue(true);
|
|
36
|
-
mockConfig.extractMfaCode.mockReturnValue("wrong-code");
|
|
37
|
-
mockConfig.getMfaAttempts.mockResolvedValue(3);
|
|
38
|
-
await expect(service.checkMfa(mockUser, mockContext)).rejects.toThrow("Your account has been temporarily locked due to too many failed 2FA attempts.");
|
|
39
|
-
expect(mockLogger.warn).toHaveBeenCalledWith(`User ${mockUser} exceeded maximum MFA attempts.`);
|
|
40
|
-
expect(mockConfig.lockMfaOnFailure).toHaveBeenCalledWith(mockUser);
|
|
41
|
-
});
|
|
42
|
-
it("checkMfa rejects invalid MFA codes", async () => {
|
|
43
|
-
mockConfig.isMfaRequired.mockReturnValue(true);
|
|
44
|
-
mockConfig.extractMfaCode.mockReturnValue("invalid-code");
|
|
45
|
-
mockConfig.getMfaAttempts.mockResolvedValue(1);
|
|
46
|
-
mockConfig.validateMfaCode.mockResolvedValue(false);
|
|
47
|
-
await expect(service.checkMfa(mockUser, mockContext)).rejects.toThrow("Invalid 2FA code provided.");
|
|
48
|
-
expect(mockLogger.warn).toHaveBeenCalledWith(`Invalid MFA code attempt for user: ${mockUser}`);
|
|
49
|
-
expect(mockConfig.incrementMfaAttempts).toHaveBeenCalledWith(mockUser);
|
|
50
|
-
});
|
|
51
|
-
it("checkMfa resets attempts on successful MFA validation", async () => {
|
|
52
|
-
mockConfig.isMfaRequired.mockReturnValue(true);
|
|
53
|
-
mockConfig.extractMfaCode.mockReturnValue("valid-code");
|
|
54
|
-
mockConfig.validateMfaCode.mockResolvedValue(true);
|
|
55
|
-
await expect(service.checkMfa(mockUser, mockContext)).resolves.not.toThrow();
|
|
56
|
-
expect(mockConfig.resetMfaAttempts).toHaveBeenCalledWith(mockUser);
|
|
57
|
-
expect(mockLogger.info).toHaveBeenCalledWith(`2FA successfully validated for user: ${mockUser}`);
|
|
58
|
-
});
|
|
59
|
-
it("lockMfaOnFailure logs error if an exception occurs", async () => {
|
|
60
|
-
mockConfig.lockMfaOnFailure.mockImplementation(() => {
|
|
61
|
-
throw new Error("Lock failed");
|
|
62
|
-
});
|
|
63
|
-
service.lockMfaOnFailure(mockUser);
|
|
64
|
-
expect(mockLogger.error).toHaveBeenCalledWith(expect.any(Error));
|
|
65
|
-
});
|
|
66
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const password_service_1 = require("../password.service");
|
|
4
|
-
const globals_1 = require("@jest/globals");
|
|
5
|
-
describe("PasswordService", () => {
|
|
6
|
-
let service;
|
|
7
|
-
let mockConfig;
|
|
8
|
-
const mockLogger = { error: globals_1.jest.fn() };
|
|
9
|
-
const mockIdentifier = "user123";
|
|
10
|
-
const mockPassword = "SecurePass123!";
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
globals_1.jest.clearAllMocks();
|
|
13
|
-
mockConfig = {
|
|
14
|
-
validatePassword: globals_1.jest.fn(),
|
|
15
|
-
getPasswordPasswordInfo: globals_1.jest.fn(),
|
|
16
|
-
generateResetToken: globals_1.jest.fn(),
|
|
17
|
-
sendPasswordResetEmail: globals_1.jest.fn(),
|
|
18
|
-
validatePasswordResetToken: globals_1.jest.fn(),
|
|
19
|
-
updatePassword: globals_1.jest.fn(),
|
|
20
|
-
passwordExpirationDays: 30,
|
|
21
|
-
};
|
|
22
|
-
service = new password_service_1.PasswordService(mockConfig, mockLogger);
|
|
23
|
-
});
|
|
24
|
-
it("validatePassword calls config method", async () => {
|
|
25
|
-
mockConfig.validatePassword.mockReturnValue(true);
|
|
26
|
-
await expect(service.validatePassword(mockPassword)).resolves.toBe(true);
|
|
27
|
-
expect(mockConfig.validatePassword).toHaveBeenCalledWith(mockPassword, undefined);
|
|
28
|
-
});
|
|
29
|
-
it("getPasswordPasswordInfo calls config method", async () => {
|
|
30
|
-
const mockPasswordInfo = { type: "default" };
|
|
31
|
-
mockConfig.getPasswordPasswordInfo.mockReturnValue(mockPasswordInfo);
|
|
32
|
-
await expect(service.getPasswordPasswordInfo(mockIdentifier)).resolves.toBe(mockPasswordInfo);
|
|
33
|
-
expect(mockConfig.getPasswordPasswordInfo).toHaveBeenCalledWith(mockIdentifier);
|
|
34
|
-
});
|
|
35
|
-
it("generateResetToken calls config method", async () => {
|
|
36
|
-
mockConfig.generateResetToken.mockResolvedValue("mock-token");
|
|
37
|
-
await expect(service.generateResetToken(mockIdentifier)).resolves.toBe("mock-token");
|
|
38
|
-
});
|
|
39
|
-
it("sendResetEmail calls config method", async () => {
|
|
40
|
-
await expect(service.sendResetEmail(mockIdentifier, "mock-token")).resolves.not.toThrow();
|
|
41
|
-
expect(mockConfig.sendPasswordResetEmail).toHaveBeenCalledWith(mockIdentifier, "mock-token");
|
|
42
|
-
});
|
|
43
|
-
it("validateResetToken calls config method", async () => {
|
|
44
|
-
mockConfig.validatePasswordResetToken.mockResolvedValue(true);
|
|
45
|
-
await expect(service.validateResetToken("mock-token")).resolves.toBeUndefined();
|
|
46
|
-
});
|
|
47
|
-
it("updatePassword calls config method", async () => {
|
|
48
|
-
await expect(service.updatePassword(mockIdentifier, mockPassword)).resolves.not.toThrow();
|
|
49
|
-
expect(mockConfig.updatePassword).toHaveBeenCalledWith(mockIdentifier, mockPassword, undefined);
|
|
50
|
-
});
|
|
51
|
-
it("isPasswordChangeRequired returns true if password is expired", async () => {
|
|
52
|
-
const pastDate = new Date(Date.now() - 31 * 86400000);
|
|
53
|
-
mockConfig.getPasswordPasswordInfo.mockResolvedValue({
|
|
54
|
-
type: "temporary",
|
|
55
|
-
lastChangeDate: pastDate,
|
|
56
|
-
expiresIn: 30 * 86400000
|
|
57
|
-
});
|
|
58
|
-
await expect(service.isPasswordChangeRequired(mockIdentifier)).resolves.toBe(true);
|
|
59
|
-
});
|
|
60
|
-
it("isPasswordChangeRequired returns false if password is within expiration period", async () => {
|
|
61
|
-
const recentDate = new Date(Date.now() - 15 * 86400000);
|
|
62
|
-
mockConfig.getPasswordPasswordInfo.mockResolvedValue({
|
|
63
|
-
type: "temporary",
|
|
64
|
-
lastChangeDate: recentDate,
|
|
65
|
-
expiresIn: 30 * 86400000
|
|
66
|
-
});
|
|
67
|
-
await expect(service.isPasswordChangeRequired(mockIdentifier)).resolves.toBe(false);
|
|
68
|
-
});
|
|
69
|
-
it("isPasswordChangeRequired returns false if getPasswordPasswordInfo is not defined", async () => {
|
|
70
|
-
service = new password_service_1.PasswordService({ passwordExpirationDays: 30 }, mockLogger);
|
|
71
|
-
await expect(service.isPasswordChangeRequired(mockIdentifier)).resolves.toBe(false);
|
|
72
|
-
});
|
|
73
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|