@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
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const api_key_strategy_1 = require("../api-key.strategy");
|
|
4
|
-
const errors_1 = require("../../../errors");
|
|
5
|
-
const api_key_errors_1 = require("../api-key.errors");
|
|
6
|
-
describe("ApiKeyStrategy", () => {
|
|
7
|
-
let strategy;
|
|
8
|
-
let mockConfig;
|
|
9
|
-
let mockLogger;
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
mockLogger = {
|
|
12
|
-
error: jest.fn(),
|
|
13
|
-
warn: jest.fn(),
|
|
14
|
-
info: jest.fn(),
|
|
15
|
-
};
|
|
16
|
-
mockConfig = {
|
|
17
|
-
extractApiKey: jest.fn(),
|
|
18
|
-
retrieveUserByApiKey: jest.fn(),
|
|
19
|
-
lock: {
|
|
20
|
-
isAccountLocked: jest.fn(),
|
|
21
|
-
logFailedAttempt: jest.fn(),
|
|
22
|
-
},
|
|
23
|
-
isApiKeyExpired: jest.fn(),
|
|
24
|
-
rateLimit: {
|
|
25
|
-
checkRateLimit: jest.fn(),
|
|
26
|
-
incrementRequestCount: jest.fn(),
|
|
27
|
-
},
|
|
28
|
-
role: { authorizeByRoles: jest.fn(), roles: [] },
|
|
29
|
-
revokeApiKey: jest.fn(),
|
|
30
|
-
trackApiKeyUsage: jest.fn(),
|
|
31
|
-
keyType: "long-term",
|
|
32
|
-
};
|
|
33
|
-
strategy = new api_key_strategy_1.ApiKeyStrategy(mockConfig, mockLogger);
|
|
34
|
-
});
|
|
35
|
-
it("should authenticate a user with a valid API key", async () => {
|
|
36
|
-
mockConfig.extractApiKey.mockReturnValue("valid-api-key");
|
|
37
|
-
mockConfig.retrieveUserByApiKey.mockResolvedValue({
|
|
38
|
-
id: 1,
|
|
39
|
-
name: "John Doe",
|
|
40
|
-
});
|
|
41
|
-
const result = await strategy.authenticate({});
|
|
42
|
-
expect(result).toEqual({ user: { id: 1, name: "John Doe" } });
|
|
43
|
-
expect(mockConfig.trackApiKeyUsage).toHaveBeenCalledWith("valid-api-key");
|
|
44
|
-
expect(mockConfig.rateLimit.incrementRequestCount).toHaveBeenCalledWith("valid-api-key");
|
|
45
|
-
});
|
|
46
|
-
it("should throw MissingApiKeyError if no API key is provided", async () => {
|
|
47
|
-
mockConfig.extractApiKey.mockReturnValue(null);
|
|
48
|
-
await expect(strategy.authenticate({})).rejects.toThrow(api_key_errors_1.MissingApiKeyError);
|
|
49
|
-
expect(mockConfig.lock.logFailedAttempt).toHaveBeenCalled();
|
|
50
|
-
});
|
|
51
|
-
it("should throw InvalidApiKeyError if the API key is invalid", async () => {
|
|
52
|
-
mockConfig.extractApiKey.mockReturnValue("invalid-api-key");
|
|
53
|
-
mockConfig.retrieveUserByApiKey.mockResolvedValue(null);
|
|
54
|
-
await expect(strategy.authenticate({})).rejects.toThrow(api_key_errors_1.InvalidApiKeyError);
|
|
55
|
-
expect(mockConfig.lock.logFailedAttempt).toHaveBeenCalledWith("invalid-api-key", {});
|
|
56
|
-
});
|
|
57
|
-
it("should throw AccountLockedError if account is locked", async () => {
|
|
58
|
-
mockConfig.extractApiKey.mockReturnValue("valid-api-key");
|
|
59
|
-
mockConfig.lock.isAccountLocked.mockResolvedValue(true);
|
|
60
|
-
await expect(strategy.authenticate({})).rejects.toThrow(errors_1.AccountLockedError);
|
|
61
|
-
});
|
|
62
|
-
it("should throw ExpiredApiKeyError if API key is expired", async () => {
|
|
63
|
-
mockConfig.extractApiKey.mockReturnValue("valid-api-key");
|
|
64
|
-
mockConfig.isApiKeyExpired.mockResolvedValue(true);
|
|
65
|
-
await expect(strategy.authenticate({})).rejects.toThrow(api_key_errors_1.ExpiredApiKeyError);
|
|
66
|
-
});
|
|
67
|
-
it("should throw RateLimitExceededError if API key exceeds rate limit", async () => {
|
|
68
|
-
mockConfig.extractApiKey.mockReturnValue("valid-api-key");
|
|
69
|
-
mockConfig.rateLimit.checkRateLimit.mockResolvedValue(true);
|
|
70
|
-
await expect(strategy.authenticate({})).rejects.toThrow(errors_1.RateLimitExceededError);
|
|
71
|
-
});
|
|
72
|
-
it("should throw UnauthorizedRoleError if user has no access", async () => {
|
|
73
|
-
mockConfig.extractApiKey.mockReturnValue("valid-api-key");
|
|
74
|
-
mockConfig.retrieveUserByApiKey.mockResolvedValue({ id: 1, role: "guest" });
|
|
75
|
-
mockConfig.role.authorizeByRoles.mockResolvedValue(false);
|
|
76
|
-
strategy.role.roles = ["admin", "user"];
|
|
77
|
-
await expect(strategy.authenticate({})).rejects.toThrow(errors_1.UnauthorizedRoleError);
|
|
78
|
-
});
|
|
79
|
-
it("should revoke one-time API keys after authentication", async () => {
|
|
80
|
-
mockConfig.keyType = "one-time";
|
|
81
|
-
mockConfig.extractApiKey.mockReturnValue("one-time-key");
|
|
82
|
-
mockConfig.retrieveUserByApiKey.mockResolvedValue({ id: 1 });
|
|
83
|
-
await strategy.authenticate({});
|
|
84
|
-
expect(mockConfig.revokeApiKey).toHaveBeenCalledWith("one-time-key");
|
|
85
|
-
});
|
|
86
|
-
it("should retry retrieving user on failure", async () => {
|
|
87
|
-
mockConfig.retrieveUserMaxRetries = 1;
|
|
88
|
-
mockConfig.extractApiKey.mockReturnValue("valid-api-key");
|
|
89
|
-
mockConfig.retrieveUserByApiKey
|
|
90
|
-
.mockRejectedValueOnce(new Error("Temporary failure"))
|
|
91
|
-
.mockResolvedValueOnce({ id: 1, name: "John Doe" });
|
|
92
|
-
const result = await strategy.authenticate({});
|
|
93
|
-
expect(result).toEqual({ user: { id: 1, name: "John Doe" } });
|
|
94
|
-
expect(mockConfig.retrieveUserByApiKey).toHaveBeenCalledTimes(2);
|
|
95
|
-
});
|
|
96
|
-
it("should log failed authentication attempts", async () => {
|
|
97
|
-
mockConfig.extractApiKey.mockReturnValue("invalid-api-key");
|
|
98
|
-
mockConfig.retrieveUserByApiKey.mockResolvedValue(null);
|
|
99
|
-
const ctx = {};
|
|
100
|
-
await expect(strategy.authenticate(ctx)).rejects.toThrow(api_key_errors_1.InvalidApiKeyError);
|
|
101
|
-
expect(mockConfig.lock.logFailedAttempt).toHaveBeenCalledWith("invalid-api-key", ctx);
|
|
102
|
-
});
|
|
103
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const basic_strategy_1 = require("../basic.strategy");
|
|
4
|
-
const errors_1 = require("../../../errors");
|
|
5
|
-
describe("BasicStrategy", () => {
|
|
6
|
-
let strategy;
|
|
7
|
-
let mockConfig;
|
|
8
|
-
let mockSession;
|
|
9
|
-
let mockJwt;
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
mockConfig = {
|
|
12
|
-
credentials: {
|
|
13
|
-
extractCredentials: jest.fn(),
|
|
14
|
-
verifyCredentials: jest.fn(),
|
|
15
|
-
},
|
|
16
|
-
user: {
|
|
17
|
-
fetchUser: jest.fn(),
|
|
18
|
-
},
|
|
19
|
-
routes: {
|
|
20
|
-
login: {},
|
|
21
|
-
logout: {},
|
|
22
|
-
},
|
|
23
|
-
};
|
|
24
|
-
mockSession = {
|
|
25
|
-
issueSession: jest.fn(),
|
|
26
|
-
};
|
|
27
|
-
mockJwt = {
|
|
28
|
-
issueTokens: jest.fn(),
|
|
29
|
-
};
|
|
30
|
-
strategy = new basic_strategy_1.BasicStrategy(mockConfig, mockSession, mockJwt);
|
|
31
|
-
});
|
|
32
|
-
describe("extractCredentials", () => {
|
|
33
|
-
it("should extract credentials from Authorization header", () => {
|
|
34
|
-
mockConfig.credentials.extractCredentials = null;
|
|
35
|
-
const username = "testuser";
|
|
36
|
-
const password = "securepassword";
|
|
37
|
-
const encoded = Buffer.from(`${username}:${password}`).toString("base64");
|
|
38
|
-
const context = { headers: { authorization: `Basic ${encoded}` } };
|
|
39
|
-
const credentials = strategy.extractCredentials(context);
|
|
40
|
-
expect(credentials).toEqual({ identifier: username, password });
|
|
41
|
-
});
|
|
42
|
-
it("should throw MissingCredentialsError if Authorization header is missing", () => {
|
|
43
|
-
const context = { headers: {} };
|
|
44
|
-
expect(() => strategy.extractCredentials(context)).toThrow(errors_1.MissingCredentialsError);
|
|
45
|
-
});
|
|
46
|
-
it("should throw InvalidCredentialsError if Authorization header is malformed", () => {
|
|
47
|
-
const context = { headers: { authorization: "Bearer token" } };
|
|
48
|
-
mockConfig.credentials.extractCredentials = null;
|
|
49
|
-
expect(() => strategy.extractCredentials(context)).toThrow(errors_1.InvalidCredentialsError);
|
|
50
|
-
});
|
|
51
|
-
it("should throw InvalidCredentialsError if Authorization header is not base64-encoded properly", () => {
|
|
52
|
-
const context = { headers: { authorization: "Basic not_base64_data" } };
|
|
53
|
-
mockConfig.credentials.extractCredentials = null;
|
|
54
|
-
expect(() => strategy.extractCredentials(context)).toThrow(errors_1.InvalidCredentialsError);
|
|
55
|
-
});
|
|
56
|
-
it("should throw InvalidCredentialsError if decoded credentials are invalid", () => {
|
|
57
|
-
const encoded = Buffer.from(`username`).toString("base64");
|
|
58
|
-
const context = { headers: { authorization: `Basic ${encoded}` } };
|
|
59
|
-
mockConfig.credentials.extractCredentials = null;
|
|
60
|
-
expect(() => strategy.extractCredentials(context)).toThrow(errors_1.InvalidCredentialsError);
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
describe("verifyCredentials", () => {
|
|
64
|
-
it("should verify valid credentials", async () => {
|
|
65
|
-
mockConfig.credentials.verifyCredentials.mockResolvedValue(true);
|
|
66
|
-
const result = await strategy.verifyCredentials("testuser", "password");
|
|
67
|
-
expect(result).toBe(true);
|
|
68
|
-
expect(mockConfig.credentials.verifyCredentials).toHaveBeenCalledWith("testuser", "password");
|
|
69
|
-
});
|
|
70
|
-
it("should return false if credentials are invalid", async () => {
|
|
71
|
-
mockConfig.credentials.verifyCredentials.mockResolvedValue(false);
|
|
72
|
-
const result = await strategy.verifyCredentials("testuser", "wrongpass");
|
|
73
|
-
expect(result).toBe(false);
|
|
74
|
-
expect(mockConfig.credentials.verifyCredentials).toHaveBeenCalledWith("testuser", "wrongpass");
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
describe("fetchUser", () => {
|
|
78
|
-
it("should retrieve user data when credentials are valid", async () => {
|
|
79
|
-
const mockUser = { id: 1, username: "testuser" };
|
|
80
|
-
mockConfig.user.fetchUser.mockResolvedValue(mockUser);
|
|
81
|
-
const user = await strategy.fetchUser({
|
|
82
|
-
identifier: "testuser",
|
|
83
|
-
password: "securepassword",
|
|
84
|
-
});
|
|
85
|
-
expect(user).toEqual(mockUser);
|
|
86
|
-
expect(mockConfig.user.fetchUser).toHaveBeenCalledWith({
|
|
87
|
-
identifier: "testuser",
|
|
88
|
-
password: "securepassword",
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
it("should return null if user is not found", async () => {
|
|
92
|
-
mockConfig.user.fetchUser.mockResolvedValue(null);
|
|
93
|
-
const user = await strategy.fetchUser({
|
|
94
|
-
identifier: "unknownuser",
|
|
95
|
-
password: "securepassword",
|
|
96
|
-
});
|
|
97
|
-
expect(user).toBeNull();
|
|
98
|
-
expect(mockConfig.user.fetchUser).toHaveBeenCalledWith({
|
|
99
|
-
identifier: "unknownuser",
|
|
100
|
-
password: "securepassword",
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const jsonwebtoken_1 = require("jsonwebtoken");
|
|
4
|
-
const jwt_strategy_1 = require("../jwt.strategy");
|
|
5
|
-
const errors_1 = require("../../../errors");
|
|
6
|
-
describe("JWTStrategy", () => {
|
|
7
|
-
let strategy;
|
|
8
|
-
let mockLogger;
|
|
9
|
-
let mockConfig;
|
|
10
|
-
let mockUser;
|
|
11
|
-
let mockContext;
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
mockLogger = { error: jest.fn(), warn: jest.fn(), info: jest.fn() };
|
|
14
|
-
mockUser = { id: "123", email: "test@example.com" };
|
|
15
|
-
mockContext = {
|
|
16
|
-
req: {
|
|
17
|
-
headers: { authorization: "Bearer mock-access-token" },
|
|
18
|
-
cookies: { refreshToken: "mock-refresh-token" },
|
|
19
|
-
},
|
|
20
|
-
res: { setHeader: jest.fn(), cookie: jest.fn(), clearCookie: jest.fn() },
|
|
21
|
-
};
|
|
22
|
-
mockConfig = {
|
|
23
|
-
accessToken: {
|
|
24
|
-
issuer: { secretKey: "access-secret", options: { expiresIn: "1h" } },
|
|
25
|
-
verifier: { options: {} },
|
|
26
|
-
persistence: { store: jest.fn() },
|
|
27
|
-
extract: jest.fn(),
|
|
28
|
-
embed: jest.fn(),
|
|
29
|
-
},
|
|
30
|
-
refreshToken: {
|
|
31
|
-
issuer: { secretKey: "refresh-secret", options: { expiresIn: "7d" } },
|
|
32
|
-
verifier: { options: {} },
|
|
33
|
-
persistence: { store: jest.fn(), remove: jest.fn() },
|
|
34
|
-
extract: jest.fn(),
|
|
35
|
-
embed: jest.fn(),
|
|
36
|
-
},
|
|
37
|
-
user: { fetchUser: jest.fn().mockResolvedValue(mockUser) },
|
|
38
|
-
routes: {},
|
|
39
|
-
};
|
|
40
|
-
strategy = new jwt_strategy_1.JwtStrategy(mockConfig, mockLogger);
|
|
41
|
-
});
|
|
42
|
-
it("should authenticate user with valid access token", async () => {
|
|
43
|
-
const mockAccessToken = "mock-access-token";
|
|
44
|
-
jest
|
|
45
|
-
.spyOn(mockConfig.accessToken, "extract")
|
|
46
|
-
.mockReturnValue(mockAccessToken);
|
|
47
|
-
jest.spyOn(strategy, "verifyAccessToken").mockResolvedValue(mockUser);
|
|
48
|
-
const result = await strategy.authenticate(mockContext);
|
|
49
|
-
expect(result.user).toEqual(mockUser);
|
|
50
|
-
expect(result.tokens.accessToken).toEqual("mock-access-token");
|
|
51
|
-
});
|
|
52
|
-
it("should refresh tokens when access token is invalid", async () => {
|
|
53
|
-
const mockAccessToken = "mock-access-token";
|
|
54
|
-
const mockRefreshToken = "mock-refresh-token";
|
|
55
|
-
jest
|
|
56
|
-
.spyOn(mockConfig.accessToken, "extract")
|
|
57
|
-
.mockReturnValue(mockAccessToken);
|
|
58
|
-
jest
|
|
59
|
-
.spyOn(mockConfig.refreshToken, "extract")
|
|
60
|
-
.mockReturnValue(mockRefreshToken);
|
|
61
|
-
jest
|
|
62
|
-
.spyOn(strategy, "verifyAccessToken")
|
|
63
|
-
.mockRejectedValue(new jsonwebtoken_1.TokenExpiredError("Access", new Date()));
|
|
64
|
-
jest.spyOn(strategy, "verifyRefreshToken").mockResolvedValue(mockUser);
|
|
65
|
-
jest
|
|
66
|
-
.spyOn(strategy, "generateAccessToken")
|
|
67
|
-
.mockResolvedValue("new-access-token");
|
|
68
|
-
jest
|
|
69
|
-
.spyOn(strategy, "generateRefreshToken")
|
|
70
|
-
.mockResolvedValue("new-refresh-token");
|
|
71
|
-
jest.spyOn(strategy, "storeAccessToken").mockResolvedValue(null);
|
|
72
|
-
jest.spyOn(strategy, "storeRefreshToken").mockResolvedValue(null);
|
|
73
|
-
const result = await strategy.authenticate(mockContext);
|
|
74
|
-
expect(result.user).toEqual(mockUser);
|
|
75
|
-
expect(result.tokens.accessToken).toEqual("new-access-token");
|
|
76
|
-
expect(result.tokens.refreshToken).toEqual("new-refresh-token");
|
|
77
|
-
});
|
|
78
|
-
it("should throw MissingTokenError when no tokens are provided", async () => {
|
|
79
|
-
jest.spyOn(mockConfig.accessToken, "extract").mockReturnValue(undefined);
|
|
80
|
-
jest.spyOn(mockConfig.refreshToken, "extract").mockReturnValue(undefined);
|
|
81
|
-
await expect(strategy.authenticate(mockContext)).rejects.toThrow(errors_1.MissingTokenError);
|
|
82
|
-
});
|
|
83
|
-
it("should throw InvalidTokenError if user does not exist", async () => {
|
|
84
|
-
const mockAccessToken = "mock-access-token";
|
|
85
|
-
const mockRefreshToken = null;
|
|
86
|
-
jest
|
|
87
|
-
.spyOn(mockConfig.accessToken, "extract")
|
|
88
|
-
.mockReturnValue(mockAccessToken);
|
|
89
|
-
jest
|
|
90
|
-
.spyOn(mockConfig.refreshToken, "extract")
|
|
91
|
-
.mockReturnValue(mockRefreshToken);
|
|
92
|
-
jest.spyOn(strategy, "verifyAccessToken").mockResolvedValue(mockUser);
|
|
93
|
-
mockConfig.user.fetchUser = jest.fn().mockResolvedValue(null);
|
|
94
|
-
await expect(strategy.authenticate(mockContext)).rejects.toThrow(errors_1.InvalidTokenError);
|
|
95
|
-
});
|
|
96
|
-
it("should throw InvalidTokenError when refresh token is invalid", async () => {
|
|
97
|
-
const mockAccessToken = "mock-access-token";
|
|
98
|
-
const mockRefreshToken = "mock-refresh-token";
|
|
99
|
-
jest
|
|
100
|
-
.spyOn(mockConfig.accessToken, "extract")
|
|
101
|
-
.mockReturnValue(mockAccessToken);
|
|
102
|
-
jest
|
|
103
|
-
.spyOn(mockConfig.refreshToken, "extract")
|
|
104
|
-
.mockReturnValue(mockRefreshToken);
|
|
105
|
-
jest
|
|
106
|
-
.spyOn(strategy, "verifyAccessToken")
|
|
107
|
-
.mockRejectedValue(new errors_1.InvalidTokenError("Access"));
|
|
108
|
-
jest
|
|
109
|
-
.spyOn(strategy, "verifyRefreshToken")
|
|
110
|
-
.mockRejectedValue(new errors_1.InvalidTokenError("Refresh"));
|
|
111
|
-
await expect(strategy.authenticate(mockContext)).rejects.toThrow(errors_1.InvalidTokenError);
|
|
112
|
-
});
|
|
113
|
-
it("should generate and store access and refresh tokens", async () => {
|
|
114
|
-
jest
|
|
115
|
-
.spyOn(strategy, "generateAccessToken")
|
|
116
|
-
.mockResolvedValue("new-access-token");
|
|
117
|
-
jest
|
|
118
|
-
.spyOn(strategy, "generateRefreshToken")
|
|
119
|
-
.mockResolvedValue("new-refresh-token");
|
|
120
|
-
jest.spyOn(strategy, "storeAccessToken").mockResolvedValue(null);
|
|
121
|
-
jest.spyOn(strategy, "storeRefreshToken").mockResolvedValue(null);
|
|
122
|
-
const tokens = await strategy.issueTokens(mockUser, mockContext);
|
|
123
|
-
expect(tokens).toHaveProperty("accessToken", "new-access-token");
|
|
124
|
-
expect(tokens).toHaveProperty("refreshToken", "new-refresh-token");
|
|
125
|
-
expect(strategy.storeAccessToken).toHaveBeenCalledWith("new-access-token");
|
|
126
|
-
expect(strategy.storeRefreshToken).toHaveBeenCalledWith("new-refresh-token");
|
|
127
|
-
});
|
|
128
|
-
it("should invalidate refresh token", async () => {
|
|
129
|
-
jest
|
|
130
|
-
.spyOn(strategy, "extractRefreshToken")
|
|
131
|
-
.mockResolvedValue("mock-refresh-token");
|
|
132
|
-
jest.spyOn(strategy, "invalidateRefreshToken").mockResolvedValue(null);
|
|
133
|
-
await strategy.invalidateRefreshToken(mockContext);
|
|
134
|
-
expect(strategy.invalidateRefreshToken).toHaveBeenCalledWith(mockContext);
|
|
135
|
-
});
|
|
136
|
-
it("should extract access token from context", async () => {
|
|
137
|
-
const mockAccessToken = "mock-access-token";
|
|
138
|
-
jest
|
|
139
|
-
.spyOn(mockConfig.accessToken, "extract")
|
|
140
|
-
.mockReturnValue(mockAccessToken);
|
|
141
|
-
const token = await strategy.extractAccessToken(mockContext);
|
|
142
|
-
expect(token).toBe("mock-access-token");
|
|
143
|
-
});
|
|
144
|
-
it("should extract refresh token from context", async () => {
|
|
145
|
-
const mockRefreshToken = "mock-refresh-token";
|
|
146
|
-
jest
|
|
147
|
-
.spyOn(mockConfig.refreshToken, "extract")
|
|
148
|
-
.mockReturnValueOnce(mockRefreshToken);
|
|
149
|
-
const token = await strategy.extractRefreshToken(mockContext);
|
|
150
|
-
expect(token).toBe("mock-refresh-token");
|
|
151
|
-
});
|
|
152
|
-
it("should throw ValidationError if access secret key is missing", async () => {
|
|
153
|
-
mockConfig.accessToken.issuer.secretKey = undefined;
|
|
154
|
-
expect(() => new jwt_strategy_1.JwtStrategy(mockConfig, mockLogger)).toThrow("config.accessToken.issuer.secretKey is required");
|
|
155
|
-
});
|
|
156
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const globals_1 = require("@jest/globals");
|
|
4
|
-
const jwt_tools_1 = require("../jwt.tools");
|
|
5
|
-
const errors_1 = require("../../../errors");
|
|
6
|
-
(0, globals_1.describe)("JwtTools", () => {
|
|
7
|
-
const secretKey = "test-secret";
|
|
8
|
-
const payload = { id: "user123", email: "test@example.com" };
|
|
9
|
-
const config = {
|
|
10
|
-
accessToken: {
|
|
11
|
-
issuer: { secretKey, options: { expiresIn: "1h" } },
|
|
12
|
-
verifier: { options: {} },
|
|
13
|
-
},
|
|
14
|
-
refreshToken: {
|
|
15
|
-
issuer: { secretKey, options: { expiresIn: "7d" } },
|
|
16
|
-
verifier: { options: {} },
|
|
17
|
-
},
|
|
18
|
-
routes: {},
|
|
19
|
-
};
|
|
20
|
-
(0, globals_1.it)("should generate an access token", () => {
|
|
21
|
-
const token = jwt_tools_1.JwtTools.generateAccessToken(payload, config.accessToken);
|
|
22
|
-
(0, globals_1.expect)(typeof token).toBe("string");
|
|
23
|
-
});
|
|
24
|
-
(0, globals_1.it)("should throw error when generating access token without secret key", () => {
|
|
25
|
-
const invalidConfig = {
|
|
26
|
-
...config,
|
|
27
|
-
accessToken: { issuer: { secretKey: "" } },
|
|
28
|
-
};
|
|
29
|
-
(0, globals_1.expect)(() => jwt_tools_1.JwtTools.generateAccessToken(payload, invalidConfig.accessToken)).toThrow(errors_1.UndefinedTokenSecretError);
|
|
30
|
-
});
|
|
31
|
-
(0, globals_1.it)("should generate a refresh token", () => {
|
|
32
|
-
const token = jwt_tools_1.JwtTools.generateRefreshToken(payload, config.refreshToken);
|
|
33
|
-
(0, globals_1.expect)(typeof token).toBe("string");
|
|
34
|
-
});
|
|
35
|
-
(0, globals_1.it)("should throw error when generating refresh token without secret key", () => {
|
|
36
|
-
const invalidConfig = {
|
|
37
|
-
...config,
|
|
38
|
-
refreshToken: { issuer: { secretKey: "" } },
|
|
39
|
-
};
|
|
40
|
-
(0, globals_1.expect)(() => jwt_tools_1.JwtTools.generateRefreshToken(payload, invalidConfig.refreshToken)).toThrow(errors_1.UndefinedTokenSecretError);
|
|
41
|
-
});
|
|
42
|
-
(0, globals_1.it)("should verify a valid access token", () => {
|
|
43
|
-
const token = jwt_tools_1.JwtTools.generateAccessToken(payload, config.accessToken);
|
|
44
|
-
const decoded = jwt_tools_1.JwtTools.verifyAccessToken(token, config.accessToken);
|
|
45
|
-
(0, globals_1.expect)(decoded.id).toBe(payload.id);
|
|
46
|
-
});
|
|
47
|
-
(0, globals_1.it)("should throw error for undefined access token", () => {
|
|
48
|
-
(0, globals_1.expect)(() => jwt_tools_1.JwtTools.verifyAccessToken("", config.accessToken)).toThrow(errors_1.UndefinedTokenError);
|
|
49
|
-
});
|
|
50
|
-
(0, globals_1.it)("should throw error for invalid access token", () => {
|
|
51
|
-
(0, globals_1.expect)(() => jwt_tools_1.JwtTools.verifyAccessToken("invalid-token", config.accessToken)).toThrow(errors_1.InvalidTokenError);
|
|
52
|
-
});
|
|
53
|
-
(0, globals_1.it)("should verify a valid refresh token", () => {
|
|
54
|
-
const token = jwt_tools_1.JwtTools.generateRefreshToken(payload, config.refreshToken);
|
|
55
|
-
const decoded = jwt_tools_1.JwtTools.verifyRefreshToken(token, config.refreshToken);
|
|
56
|
-
(0, globals_1.expect)(decoded.id).toBe(payload.id);
|
|
57
|
-
});
|
|
58
|
-
(0, globals_1.it)("should throw error for undefined refresh token", () => {
|
|
59
|
-
(0, globals_1.expect)(() => jwt_tools_1.JwtTools.verifyRefreshToken("", config.refreshToken)).toThrow(errors_1.UndefinedTokenError);
|
|
60
|
-
});
|
|
61
|
-
(0, globals_1.it)("should throw error for invalid refresh token", () => {
|
|
62
|
-
(0, globals_1.expect)(() => jwt_tools_1.JwtTools.verifyRefreshToken("invalid-token", config.refreshToken)).toThrow(errors_1.InvalidTokenError);
|
|
63
|
-
});
|
|
64
|
-
(0, globals_1.it)("should set the access token in the response header", () => {
|
|
65
|
-
const token = jwt_tools_1.JwtTools.generateAccessToken(payload, config.accessToken);
|
|
66
|
-
const context = { res: { setHeader: globals_1.jest.fn() } };
|
|
67
|
-
jwt_tools_1.JwtTools.setAccessTokenHeader(token, context);
|
|
68
|
-
(0, globals_1.expect)(context.res.setHeader).toHaveBeenCalledWith("Authorization", `Bearer ${token}`);
|
|
69
|
-
});
|
|
70
|
-
(0, globals_1.it)("should set the refresh token in cookies", () => {
|
|
71
|
-
const token = jwt_tools_1.JwtTools.generateRefreshToken(payload, config.refreshToken);
|
|
72
|
-
const context = { res: { cookie: globals_1.jest.fn() } };
|
|
73
|
-
jwt_tools_1.JwtTools.setRefreshTokenCookie(token, context);
|
|
74
|
-
(0, globals_1.expect)(context.res.cookie).toHaveBeenCalledWith("refreshToken", token, globals_1.expect.any(Object));
|
|
75
|
-
});
|
|
76
|
-
(0, globals_1.it)("should clear tokens from headers and cookies", () => {
|
|
77
|
-
const context = {
|
|
78
|
-
res: { clearCookie: globals_1.jest.fn(), setHeader: globals_1.jest.fn() },
|
|
79
|
-
};
|
|
80
|
-
jwt_tools_1.JwtTools.clearTokens(context);
|
|
81
|
-
(0, globals_1.expect)(context.res.clearCookie).toHaveBeenCalledWith("refreshToken");
|
|
82
|
-
(0, globals_1.expect)(context.res.setHeader).toHaveBeenCalledWith("Authorization", "");
|
|
83
|
-
});
|
|
84
|
-
(0, globals_1.it)("should retrieve an access token from request headers", () => {
|
|
85
|
-
const token = jwt_tools_1.JwtTools.generateAccessToken(payload, config.accessToken);
|
|
86
|
-
const context = {
|
|
87
|
-
req: { headers: { authorization: `Bearer ${token}` } },
|
|
88
|
-
};
|
|
89
|
-
const extractedToken = jwt_tools_1.JwtTools.getAccessToken(context);
|
|
90
|
-
(0, globals_1.expect)(extractedToken).toBe(token);
|
|
91
|
-
});
|
|
92
|
-
(0, globals_1.it)("should retrieve a refresh token from request cookies", () => {
|
|
93
|
-
const token = jwt_tools_1.JwtTools.generateRefreshToken(payload, config.refreshToken);
|
|
94
|
-
const context = { req: { cookies: { refreshToken: token } } };
|
|
95
|
-
const extractedToken = jwt_tools_1.JwtTools.getRefreshToken(context);
|
|
96
|
-
(0, globals_1.expect)(extractedToken).toBe(token);
|
|
97
|
-
});
|
|
98
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const local_strategy_1 = require("../local.strategy");
|
|
4
|
-
const errors_1 = require("../../../errors");
|
|
5
|
-
describe("LocalStrategy", () => {
|
|
6
|
-
let strategy;
|
|
7
|
-
let mockConfig;
|
|
8
|
-
let mockSession;
|
|
9
|
-
let mockJwt;
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
mockConfig = {
|
|
12
|
-
credentials: {
|
|
13
|
-
extractCredentials: jest.fn(),
|
|
14
|
-
verifyCredentials: jest.fn(),
|
|
15
|
-
},
|
|
16
|
-
user: {
|
|
17
|
-
fetchUser: jest.fn(),
|
|
18
|
-
},
|
|
19
|
-
routes: {
|
|
20
|
-
login: {
|
|
21
|
-
path: "/login",
|
|
22
|
-
method: "POST"
|
|
23
|
-
},
|
|
24
|
-
logout: {
|
|
25
|
-
path: "/logout",
|
|
26
|
-
method: "POST"
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
};
|
|
30
|
-
mockSession = {
|
|
31
|
-
issueSession: jest.fn(),
|
|
32
|
-
};
|
|
33
|
-
mockJwt = {
|
|
34
|
-
issueTokens: jest.fn(),
|
|
35
|
-
};
|
|
36
|
-
strategy = new local_strategy_1.LocalStrategy(mockConfig, mockSession, mockJwt);
|
|
37
|
-
});
|
|
38
|
-
describe("extractCredentials", () => {
|
|
39
|
-
it("should extract credentials from context", async () => {
|
|
40
|
-
const mockContext = { body: { username: "test", password: "pass123" } };
|
|
41
|
-
mockConfig.credentials.extractCredentials.mockResolvedValue({
|
|
42
|
-
identifier: "test",
|
|
43
|
-
password: "pass123",
|
|
44
|
-
});
|
|
45
|
-
const credentials = await strategy.extractCredentials(mockContext);
|
|
46
|
-
expect(credentials).toEqual({ identifier: "test", password: "pass123" });
|
|
47
|
-
expect(mockConfig.credentials.extractCredentials).toHaveBeenCalledWith(mockContext);
|
|
48
|
-
});
|
|
49
|
-
it("should throw MissingCredentialsError if no credentials are provided", async () => {
|
|
50
|
-
mockConfig.credentials.extractCredentials.mockRejectedValue(new errors_1.MissingCredentialsError());
|
|
51
|
-
await expect(strategy.extractCredentials({})).rejects.toThrow(errors_1.MissingCredentialsError);
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
describe("verifyCredentials", () => {
|
|
55
|
-
it("should verify credentials successfully", async () => {
|
|
56
|
-
mockConfig.credentials.verifyCredentials.mockResolvedValue(true);
|
|
57
|
-
const result = await strategy.verifyCredentials("test", "pass123");
|
|
58
|
-
expect(result).toBe(true);
|
|
59
|
-
expect(mockConfig.credentials.verifyCredentials).toHaveBeenCalledWith("test", "pass123");
|
|
60
|
-
});
|
|
61
|
-
it("should return false if credentials are invalid", async () => {
|
|
62
|
-
mockConfig.credentials.verifyCredentials.mockResolvedValue(false);
|
|
63
|
-
const result = await strategy.verifyCredentials("test", "wrongpass");
|
|
64
|
-
expect(result).toBe(false);
|
|
65
|
-
expect(mockConfig.credentials.verifyCredentials).toHaveBeenCalledWith("test", "wrongpass");
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
describe("fetchUser", () => {
|
|
69
|
-
it("should fetch user data", async () => {
|
|
70
|
-
const mockUser = { id: 1, username: "test" };
|
|
71
|
-
mockConfig.user.fetchUser.mockResolvedValue(mockUser);
|
|
72
|
-
const user = await strategy.fetchUser({
|
|
73
|
-
identifier: "test",
|
|
74
|
-
password: "pass123",
|
|
75
|
-
});
|
|
76
|
-
expect(user).toEqual(mockUser);
|
|
77
|
-
expect(mockConfig.user.fetchUser).toHaveBeenCalledWith("test");
|
|
78
|
-
});
|
|
79
|
-
it("should return null if user is not found", async () => {
|
|
80
|
-
mockConfig.user.fetchUser.mockResolvedValue(null);
|
|
81
|
-
const user = await strategy.fetchUser({
|
|
82
|
-
identifier: "unknown",
|
|
83
|
-
password: "pass123",
|
|
84
|
-
});
|
|
85
|
-
expect(user).toBeNull();
|
|
86
|
-
expect(mockConfig.user.fetchUser).toHaveBeenCalledWith("unknown");
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
describe("login", () => {
|
|
90
|
-
const mockContext = { body: { username: "test", password: "pass123" } };
|
|
91
|
-
const mockUser = { id: 1, username: "test" };
|
|
92
|
-
const mockTokens = { accessToken: "access", refreshToken: "refresh" };
|
|
93
|
-
const mockSessionData = { sessionId: "session123", data: mockUser };
|
|
94
|
-
beforeEach(() => {
|
|
95
|
-
mockConfig.credentials.extractCredentials.mockResolvedValue({
|
|
96
|
-
identifier: "test",
|
|
97
|
-
password: "pass123",
|
|
98
|
-
});
|
|
99
|
-
mockConfig.credentials.verifyCredentials.mockResolvedValue(true);
|
|
100
|
-
mockConfig.user.fetchUser.mockResolvedValue(mockUser);
|
|
101
|
-
mockJwt.issueTokens.mockResolvedValue(mockTokens);
|
|
102
|
-
mockSession.issueSession.mockResolvedValue(mockSessionData);
|
|
103
|
-
});
|
|
104
|
-
it("should login and return user with tokens and session", async () => {
|
|
105
|
-
const result = await strategy.login(mockContext);
|
|
106
|
-
expect(result.user).toEqual(mockUser);
|
|
107
|
-
expect(result.tokens).toEqual(mockTokens);
|
|
108
|
-
expect(result.session).toEqual(mockSessionData);
|
|
109
|
-
expect(mockJwt.issueTokens).toHaveBeenCalledWith(mockUser, mockContext);
|
|
110
|
-
expect(mockSession.issueSession).toHaveBeenCalledWith(mockUser, mockContext);
|
|
111
|
-
});
|
|
112
|
-
it("should throw InvalidCredentialsError if credentials are incorrect", async () => {
|
|
113
|
-
mockConfig.credentials.verifyCredentials.mockResolvedValue(false);
|
|
114
|
-
await expect(strategy.login(mockContext)).rejects.toThrow(errors_1.InvalidCredentialsError);
|
|
115
|
-
});
|
|
116
|
-
it("should throw UserNotFoundError if user is not found", async () => {
|
|
117
|
-
mockConfig.user.fetchUser.mockResolvedValue(null);
|
|
118
|
-
await expect(strategy.login(mockContext)).rejects.toThrow(errors_1.UserNotFoundError);
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|