@soapjs/soap-auth 0.4.0 → 0.4.4

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.
Files changed (53) hide show
  1. package/build/strategies/jwt/jwt.strategy.js +6 -3
  2. package/build/strategies/jwt/jwt.tools.js +8 -6
  3. package/build/strategies/local/local.strategy.d.ts +2 -2
  4. package/build/strategies/local/local.strategy.js +7 -7
  5. package/package.json +3 -3
  6. package/build/__tests__/soap-auth.test.d.ts +0 -1
  7. package/build/__tests__/soap-auth.test.js +0 -136
  8. package/build/services/__tests__/account-lock.service.test.d.ts +0 -1
  9. package/build/services/__tests__/account-lock.service.test.js +0 -55
  10. package/build/services/__tests__/auth-throttle.service.test.d.ts +0 -1
  11. package/build/services/__tests__/auth-throttle.service.test.js +0 -48
  12. package/build/services/__tests__/jwks.service.test.d.ts +0 -1
  13. package/build/services/__tests__/jwks.service.test.js +0 -39
  14. package/build/services/__tests__/mfa.service.test.d.ts +0 -1
  15. package/build/services/__tests__/mfa.service.test.js +0 -66
  16. package/build/services/__tests__/password.service.test.d.ts +0 -1
  17. package/build/services/__tests__/password.service.test.js +0 -73
  18. package/build/services/__tests__/pkce.service.test.d.ts +0 -1
  19. package/build/services/__tests__/pkce.service.test.js +0 -77
  20. package/build/services/__tests__/rate-limit.service.test.d.ts +0 -1
  21. package/build/services/__tests__/rate-limit.service.test.js +0 -37
  22. package/build/services/__tests__/role.service.test.d.ts +0 -1
  23. package/build/services/__tests__/role.service.test.js +0 -31
  24. package/build/services/__tests__/totp.service.test.d.ts +0 -1
  25. package/build/services/__tests__/totp.service.test.js +0 -120
  26. package/build/session/__tests__/file.session-store.test.d.ts +0 -1
  27. package/build/session/__tests__/file.session-store.test.js +0 -117
  28. package/build/session/__tests__/memory.session-store.test.d.ts +0 -1
  29. package/build/session/__tests__/memory.session-store.test.js +0 -77
  30. package/build/session/__tests__/session-handler.test.d.ts +0 -1
  31. package/build/session/__tests__/session-handler.test.js +0 -345
  32. package/build/strategies/__tests__/base-auth.strategy.test.d.ts +0 -15
  33. package/build/strategies/__tests__/base-auth.strategy.test.js +0 -138
  34. package/build/strategies/__tests__/credential-auth.strategy.test.d.ts +0 -15
  35. package/build/strategies/__tests__/credential-auth.strategy.test.js +0 -266
  36. package/build/strategies/__tests__/token-auth.strategy.test.d.ts +0 -29
  37. package/build/strategies/__tests__/token-auth.strategy.test.js +0 -299
  38. package/build/strategies/api-key/__tests__/api-key.strategy.test.d.ts +0 -1
  39. package/build/strategies/api-key/__tests__/api-key.strategy.test.js +0 -103
  40. package/build/strategies/basic/__tests__/basic.strategy.test.d.ts +0 -1
  41. package/build/strategies/basic/__tests__/basic.strategy.test.js +0 -104
  42. package/build/strategies/jwt/__tests__/jwt.strategy.test.d.ts +0 -1
  43. package/build/strategies/jwt/__tests__/jwt.strategy.test.js +0 -156
  44. package/build/strategies/jwt/__tests__/jwt.tools.test.d.ts +0 -1
  45. package/build/strategies/jwt/__tests__/jwt.tools.test.js +0 -98
  46. package/build/strategies/local/__tests__/local.strategy.test.d.ts +0 -1
  47. package/build/strategies/local/__tests__/local.strategy.test.js +0 -121
  48. package/build/strategies/oauth2/__tests__/oauth2.strategy.test.d.ts +0 -1
  49. package/build/strategies/oauth2/__tests__/oauth2.strategy.test.js +0 -239
  50. package/build/strategies/oauth2/providers/__tests__/social-providers.test.d.ts +0 -1
  51. package/build/strategies/oauth2/providers/__tests__/social-providers.test.js +0 -201
  52. package/build/utils/__tests__/validation.test.d.ts +0 -1
  53. package/build/utils/__tests__/validation.test.js +0 -181
@@ -105,12 +105,12 @@ class JwtStrategy extends token_auth_strategy_1.TokenAuthStrategy {
105
105
  return Promise.resolve(jwt_tools_1.JwtTools.generateRefreshToken(payload, this.refreshTokenConfig));
106
106
  }
107
107
  async storeAccessToken(token) {
108
- if (this.accessTokenConfig.persistence.store) {
108
+ if (this.accessTokenConfig.persistence?.store) {
109
109
  await this.accessTokenConfig.persistence.store(token, null, this.accessTokenConfig.issuer.options.expiresIn);
110
110
  }
111
111
  }
112
112
  async storeRefreshToken(token) {
113
- if (this.refreshTokenConfig.persistence.store) {
113
+ if (this.refreshTokenConfig?.persistence?.store) {
114
114
  await this.refreshTokenConfig.persistence.store(token, null, this.refreshTokenConfig.issuer.options.expiresIn);
115
115
  }
116
116
  }
@@ -141,6 +141,9 @@ class JwtStrategy extends token_auth_strategy_1.TokenAuthStrategy {
141
141
  }
142
142
  }
143
143
  extractRefreshToken(context) {
144
+ if (!this.refreshTokenConfig) {
145
+ return undefined;
146
+ }
144
147
  if (this.refreshTokenConfig.extract) {
145
148
  return this.refreshTokenConfig.extract(context);
146
149
  }
@@ -161,7 +164,7 @@ class JwtStrategy extends token_auth_strategy_1.TokenAuthStrategy {
161
164
  async invalidateRefreshToken(token, context) {
162
165
  const refreshToken = token || (await this.extractRefreshToken(context));
163
166
  if (refreshToken) {
164
- await this.refreshTokenConfig.persistence?.remove?.(refreshToken);
167
+ await this.refreshTokenConfig?.persistence?.remove?.(refreshToken);
165
168
  if (context) {
166
169
  jwt_tools_1.JwtTools.clearDefaultJwtCookie(context);
167
170
  jwt_tools_1.JwtTools.clearDefaultJwtHeader(context);
@@ -88,6 +88,7 @@ class JwtTools {
88
88
  return context.req.cookies?.refreshToken;
89
89
  }
90
90
  static prepareAccessTokenConfig = (config) => {
91
+ const verifierOptions = config.verifier?.options ?? {};
91
92
  return Soap.removeUndefinedProperties({
92
93
  ...config,
93
94
  issuer: {
@@ -101,14 +102,15 @@ class JwtTools {
101
102
  verifier: {
102
103
  ...config.verifier,
103
104
  options: {
104
- ...config.verifier.options,
105
- algorithms: config.verifier.options.algorithms || ["HS256"],
106
- expiresIn: config.verifier.options.expiresIn || "1h",
105
+ ...verifierOptions,
106
+ algorithms: verifierOptions.algorithms || ["HS256"],
107
+ expiresIn: verifierOptions.expiresIn || "1h",
107
108
  },
108
109
  },
109
110
  });
110
111
  };
111
112
  static prepareRefreshTokenConfig = (config) => {
113
+ const verifierOptions = config.verifier?.options ?? {};
112
114
  return Soap.removeUndefinedProperties({
113
115
  ...config,
114
116
  issuer: {
@@ -122,9 +124,9 @@ class JwtTools {
122
124
  verifier: {
123
125
  ...config.verifier,
124
126
  options: {
125
- ...config.verifier.options,
126
- algorithms: config.verifier.options.algorithms || ["HS256"],
127
- expiresIn: config.verifier.options.expiresIn || "7d",
127
+ ...verifierOptions,
128
+ algorithms: verifierOptions.algorithms || ["HS256"],
129
+ expiresIn: verifierOptions.expiresIn || "7d",
128
130
  },
129
131
  },
130
132
  });
@@ -15,9 +15,9 @@ export declare class LocalStrategy<TContext = Soap.HttpContext, TUser extends So
15
15
  password: string;
16
16
  }>;
17
17
  protected verifyCredentials(identifier: string, password: string): Promise<boolean>;
18
- protected fetchUser(credentials: {
18
+ protected fetchUser(identifierOrCredentials: string | {
19
19
  identifier: string;
20
- password: string;
20
+ password?: string;
21
21
  }): Promise<TUser | null>;
22
22
  changePassword(context: TContext): Promise<void>;
23
23
  }
@@ -61,13 +61,13 @@ class LocalStrategy extends credential_auth_strategy_1.CredentialAuthStrategy {
61
61
  validation_1.ValidationUtils.nonEmptyString(password, "password");
62
62
  return this.config.credentials.verifyCredentials(identifier, password);
63
63
  }
64
- async fetchUser(credentials) {
65
- validation_1.ValidationUtils.required(credentials, "credentials");
66
- if (credentials && typeof credentials === "object" && !Array.isArray(credentials)) {
67
- validation_1.ValidationUtils.nonEmptyString(credentials.identifier, "credentials.identifier");
68
- validation_1.ValidationUtils.nonEmptyString(credentials.password, "credentials.password");
69
- }
70
- return this.config.user.fetchUser(credentials.identifier);
64
+ async fetchUser(identifierOrCredentials) {
65
+ validation_1.ValidationUtils.required(identifierOrCredentials, "identifier");
66
+ const identifier = typeof identifierOrCredentials === "string"
67
+ ? identifierOrCredentials
68
+ : identifierOrCredentials?.identifier;
69
+ validation_1.ValidationUtils.nonEmptyString(identifier, "identifier");
70
+ return this.config.user.fetchUser(identifier);
71
71
  }
72
72
  async changePassword(context) {
73
73
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soapjs/soap-auth",
3
- "version": "0.4.0",
3
+ "version": "0.4.4",
4
4
  "description": "",
5
5
  "homepage": "https://docs.soapjs.com",
6
6
  "repository": "https://github.com/soapjs/soap-auth",
@@ -15,14 +15,14 @@
15
15
  "prepublish": "npm run clean && tsc --project tsconfig.build.json"
16
16
  },
17
17
  "devDependencies": {
18
- "@soapjs/soap": "^0.9.0",
18
+ "@soapjs/soap": "^0.12.0",
19
19
  "@types/jest": "^27.0.3",
20
20
  "jest": "^27.4.5",
21
21
  "ts-jest": "^27.1.3",
22
22
  "typescript": "^4.8.2"
23
23
  },
24
24
  "peerDependencies": {
25
- "@soapjs/soap": ">=0.8.0"
25
+ "@soapjs/soap": ">=0.12.0"
26
26
  },
27
27
  "engines": {
28
28
  "node": ">=18.0.0"
@@ -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,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,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 {};
@@ -1,77 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const pkce_service_1 = require("../pkce.service");
4
- class InMemoryPKCEPersistence {
5
- storeMap = new Map();
6
- async store(key, meta) {
7
- this.storeMap.set(key, meta || {});
8
- }
9
- async read(verifierOrChallenge) {
10
- return this.storeMap.get(verifierOrChallenge);
11
- }
12
- async remove(key) {
13
- this.storeMap.delete(key);
14
- }
15
- }
16
- describe("PKCEService", () => {
17
- let service;
18
- let config;
19
- beforeEach(() => {
20
- config = {
21
- verifier: {
22
- expiresIn: 1,
23
- embed: jest.fn(),
24
- extract: jest.fn().mockImplementation((ctx) => ctx.verifier),
25
- persistence: new InMemoryPKCEPersistence(),
26
- },
27
- challenge: {
28
- expiresIn: 1,
29
- embed: jest.fn(),
30
- extract: jest.fn().mockImplementation((ctx) => ctx.challenge),
31
- persistence: new InMemoryPKCEPersistence(),
32
- },
33
- };
34
- service = new pkce_service_1.PKCEService(config);
35
- });
36
- it("should generate and store a code verifier", async () => {
37
- const context = { key: "code_verifier_test" };
38
- const verifier = await service.generateCodeVerifier(context);
39
- expect(verifier).toBeDefined();
40
- expect(config.verifier.embed).toHaveBeenCalledWith(context, verifier);
41
- });
42
- it("should generate and store a code challenge", async () => {
43
- const context = { key: "code_challenge_test" };
44
- const challenge = await service.generateCodeChallenge("test_verifier", context);
45
- expect(challenge).toBeDefined();
46
- expect(config.challenge.embed).toHaveBeenCalledWith(context, challenge);
47
- });
48
- it("should detect expired code verifier", async () => {
49
- config.verifier.embed = (ctx, cv) => {
50
- ctx.verifier = cv;
51
- };
52
- const context = { key: "expired_verifier_test" };
53
- await service.generateCodeVerifier(context);
54
- await new Promise((resolve) => setTimeout(resolve, 1500));
55
- const isExpired = await service.isCodeVerifierExpired(context);
56
- expect(isExpired).toBe(true);
57
- });
58
- it("should detect not-expired code verifier", async () => {
59
- config.verifier.embed = (ctx, cv) => {
60
- ctx.verifier = cv;
61
- };
62
- const context = {
63
- key: "not_expired_verifier_test",
64
- };
65
- await service.generateCodeVerifier(context);
66
- await new Promise((resolve) => setTimeout(resolve, 500));
67
- const isExpired = await service.isCodeVerifierExpired(context);
68
- expect(isExpired).toBe(false);
69
- });
70
- it("should clear code verifier", async () => {
71
- const context = {
72
- key: "clear_verifier_test",
73
- verifier: "test_verifier_value",
74
- };
75
- await service.clearCodeVerifier(context);
76
- });
77
- });
@@ -1 +0,0 @@
1
- export {};