@siran/auth-core 0.1.1 → 0.14.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.
Files changed (48) hide show
  1. package/.vscode/settings.json +21 -0
  2. package/CHANGELOG.md +139 -0
  3. package/COMPOSITE_AUTH_SERVICE.md +104 -0
  4. package/package.json +1 -1
  5. package/src/application/policies/account-already-exists.policy.ts +23 -0
  6. package/src/application/policies/magic-link-token.policy.ts +23 -0
  7. package/src/application/policies/oauth-code.policy.ts +23 -0
  8. package/src/application/policies/oauth-provider.policy.ts +29 -0
  9. package/src/application/policies/otp-code-format.policy.ts +23 -0
  10. package/src/application/policies/otp-code.policy.ts +23 -0
  11. package/src/application/policies/otp-identifier.policy.ts +23 -0
  12. package/src/application/policies/password-identifier.policy.ts +23 -0
  13. package/src/application/policies/password-min-length.policy.ts +29 -0
  14. package/src/application/policies/password-present.policy.ts +23 -0
  15. package/src/application/policies/password-strength.policy.ts +27 -0
  16. package/src/application/ports/auth-preference-service.port.ts +14 -0
  17. package/src/application/ports/auth-service.port.ts +58 -11
  18. package/src/application/ports/pre-register-policy.port.ts +7 -0
  19. package/src/application/ports/user-existence.port.ts +3 -0
  20. package/src/application/services/composite-auth.service.ts +96 -0
  21. package/src/application/use-cases/authenticate-user.ts +17 -4
  22. package/src/application/use-cases/register-user.ts +28 -0
  23. package/src/auth-engine.factory.ts +53 -0
  24. package/src/auth-engine.ts +25 -0
  25. package/src/domain/user-account.ts +3 -9
  26. package/src/index.ts +16 -2
  27. package/tests/application/policies/magic-link-token.policy.test.ts +74 -0
  28. package/tests/application/policies/oauth-code.policy.test.ts +79 -0
  29. package/tests/application/policies/oauth-provider.policy.test.ts +76 -0
  30. package/tests/application/policies/otp-code-format.policy.test.ts +90 -0
  31. package/tests/application/policies/otp-code.policy.test.ts +55 -0
  32. package/tests/application/policies/otp-identifier.policy.test.ts +55 -0
  33. package/tests/application/policies/password-identifier.policy.test.ts +55 -0
  34. package/tests/application/policies/password-min-length.policy.test.ts +66 -0
  35. package/tests/application/policies/password-present.policy.test.ts +55 -0
  36. package/tests/application/policies/password-strength.policy.test.ts +66 -0
  37. package/tests/application/ports/auth-preferences-service.port.mock.ts +14 -0
  38. package/tests/application/ports/auth-service.port.mock.ts +19 -0
  39. package/tests/application/ports/pre-register-policy.port.mock.ts +16 -0
  40. package/tests/application/services/composite-auth.mock.ts +18 -0
  41. package/tests/application/services/composite-auth.test.ts +162 -0
  42. package/tests/application/use-cases/authenticate-user.test.ts +61 -14
  43. package/tests/application/use-cases/register-user.test.ts +89 -0
  44. package/tests/domain/user-account.data.ts +5 -0
  45. package/tsconfig.lib.json +4 -1
  46. package/tsconfig.spec.json +13 -1
  47. package/vite.config.mts +4 -1
  48. package/src/domain/session.ts +0 -16
@@ -0,0 +1,55 @@
1
+ import { OtpIdentifierPolicy } from '@application/policies/otp-identifier.policy.js';
2
+ import { describe, expect, it } from 'vitest';
3
+
4
+ describe('OtpIdentifierPolicy', () => {
5
+ it('should return false when policy is disabled', async () => {
6
+ const policy = new OtpIdentifierPolicy(false);
7
+ const method = {
8
+ type: 'otp' as const,
9
+ identifier: '',
10
+ code: '123456',
11
+ };
12
+
13
+ expect(await policy.isActive(method)).toBe(false);
14
+ });
15
+
16
+ it('should return true when policy is enabled and identifier is present', async () => {
17
+ const policy = new OtpIdentifierPolicy(true);
18
+ const method = {
19
+ type: 'otp' as const,
20
+ identifier: 'test@example.com',
21
+ code: '123456',
22
+ };
23
+
24
+ expect(await policy.isActive(method)).toBe(true);
25
+ expect(await policy.check(method)).toBe(true);
26
+ });
27
+
28
+ it('should return false when policy is enabled but identifier is missing', async () => {
29
+ const policy = new OtpIdentifierPolicy(true);
30
+ const method = {
31
+ type: 'otp' as const,
32
+ identifier: '',
33
+ code: '123456',
34
+ };
35
+
36
+ expect(await policy.isActive(method)).toBe(true);
37
+ expect(await policy.check(method)).toBe(false);
38
+ });
39
+
40
+ it('should return false for non-OTP methods', async () => {
41
+ const policy = new OtpIdentifierPolicy(true);
42
+ const passwordMethod = {
43
+ type: 'password' as const,
44
+ identifier: '',
45
+ password: 'password123',
46
+ };
47
+
48
+ expect(await policy.isActive(passwordMethod)).toBe(false);
49
+ });
50
+
51
+ it('should have correct error code', () => {
52
+ const policy = new OtpIdentifierPolicy(true);
53
+ expect(policy.code).toBe('MISSING_OTP_IDENTIFIER');
54
+ });
55
+ });
@@ -0,0 +1,55 @@
1
+ import { PasswordIdentifierPolicy } from '@application/policies/password-identifier.policy.js';
2
+ import { describe, expect, it } from 'vitest';
3
+
4
+ describe('PasswordIdentifierPolicy', () => {
5
+ it('should return false when policy is disabled', async () => {
6
+ const policy = new PasswordIdentifierPolicy(false);
7
+ const method = {
8
+ type: 'password' as const,
9
+ identifier: '',
10
+ password: 'password123',
11
+ };
12
+
13
+ expect(await policy.isActive(method)).toBe(false);
14
+ });
15
+
16
+ it('should return true when policy is enabled and identifier is present', async () => {
17
+ const policy = new PasswordIdentifierPolicy(true);
18
+ const method = {
19
+ type: 'password' as const,
20
+ identifier: 'test@example.com',
21
+ password: 'password123',
22
+ };
23
+
24
+ expect(await policy.isActive(method)).toBe(true);
25
+ expect(await policy.check(method)).toBe(true);
26
+ });
27
+
28
+ it('should return false when policy is enabled but identifier is missing', async () => {
29
+ const policy = new PasswordIdentifierPolicy(true);
30
+ const method = {
31
+ type: 'password' as const,
32
+ identifier: '',
33
+ password: 'password123',
34
+ };
35
+
36
+ expect(await policy.isActive(method)).toBe(true);
37
+ expect(await policy.check(method)).toBe(false);
38
+ });
39
+
40
+ it('should return false for non-password methods', async () => {
41
+ const policy = new PasswordIdentifierPolicy(true);
42
+ const otpMethod = {
43
+ type: 'otp' as const,
44
+ identifier: '',
45
+ code: '123456',
46
+ };
47
+
48
+ expect(await policy.isActive(otpMethod)).toBe(false);
49
+ });
50
+
51
+ it('should have correct error code', () => {
52
+ const policy = new PasswordIdentifierPolicy(true);
53
+ expect(policy.code).toBe('MISSING_PASSWORD_IDENTIFIER');
54
+ });
55
+ });
@@ -0,0 +1,66 @@
1
+ import { PasswordMinLengthPolicy } from '@application/policies/password-min-length.policy.js';
2
+ import { describe, expect, it } from 'vitest';
3
+
4
+ describe('PasswordMinLengthPolicy', () => {
5
+ it('should return false when policy is disabled', async () => {
6
+ const policy = new PasswordMinLengthPolicy(false);
7
+ const method = {
8
+ type: 'password' as const,
9
+ identifier: 'test@example.com',
10
+ password: 'short',
11
+ };
12
+
13
+ expect(await policy.isActive(method)).toBe(false);
14
+ });
15
+
16
+ it('should return true when policy is enabled and password meets minimum length', async () => {
17
+ const policy = new PasswordMinLengthPolicy(true, 8);
18
+ const method = {
19
+ type: 'password' as const,
20
+ identifier: 'test@example.com',
21
+ password: 'password123',
22
+ };
23
+
24
+ expect(await policy.isActive(method)).toBe(true);
25
+ expect(await policy.check(method)).toBe(true);
26
+ });
27
+
28
+ it('should return false when policy is enabled but password is too short', async () => {
29
+ const policy = new PasswordMinLengthPolicy(true, 8);
30
+ const method = {
31
+ type: 'password' as const,
32
+ identifier: 'test@example.com',
33
+ password: 'short',
34
+ };
35
+
36
+ expect(await policy.isActive(method)).toBe(true);
37
+ expect(await policy.check(method)).toBe(false);
38
+ });
39
+
40
+ it('should use default minimum length of 8', async () => {
41
+ const policy = new PasswordMinLengthPolicy(true);
42
+ const method = {
43
+ type: 'password' as const,
44
+ identifier: 'test@example.com',
45
+ password: '1234567', // 7 chars
46
+ };
47
+
48
+ expect(await policy.check(method)).toBe(false);
49
+ });
50
+
51
+ it('should return false for non-password methods', async () => {
52
+ const policy = new PasswordMinLengthPolicy(true, 8);
53
+ const otpMethod = {
54
+ type: 'otp' as const,
55
+ identifier: 'test@example.com',
56
+ code: '123456',
57
+ };
58
+
59
+ expect(await policy.isActive(otpMethod)).toBe(false);
60
+ });
61
+
62
+ it('should have correct error code', () => {
63
+ const policy = new PasswordMinLengthPolicy(true, 8);
64
+ expect(policy.code).toBe('PASSWORD_TOO_SHORT');
65
+ });
66
+ });
@@ -0,0 +1,55 @@
1
+ import { PasswordPresentPolicy } from '@application/policies/password-present.policy.js';
2
+ import { describe, expect, it } from 'vitest';
3
+
4
+ describe('PasswordPresentPolicy', () => {
5
+ it('should return false when policy is disabled', async () => {
6
+ const policy = new PasswordPresentPolicy(false);
7
+ const method = {
8
+ type: 'password' as const,
9
+ identifier: 'test@example.com',
10
+ password: '',
11
+ };
12
+
13
+ expect(await policy.isActive(method)).toBe(false);
14
+ });
15
+
16
+ it('should return true when policy is enabled and password is present', async () => {
17
+ const policy = new PasswordPresentPolicy(true);
18
+ const method = {
19
+ type: 'password' as const,
20
+ identifier: 'test@example.com',
21
+ password: 'password123',
22
+ };
23
+
24
+ expect(await policy.isActive(method)).toBe(true);
25
+ expect(await policy.check(method)).toBe(true);
26
+ });
27
+
28
+ it('should return false when policy is enabled but password is missing', async () => {
29
+ const policy = new PasswordPresentPolicy(true);
30
+ const method = {
31
+ type: 'password' as const,
32
+ identifier: 'test@example.com',
33
+ password: '',
34
+ };
35
+
36
+ expect(await policy.isActive(method)).toBe(true);
37
+ expect(await policy.check(method)).toBe(false);
38
+ });
39
+
40
+ it('should return false for non-password methods', async () => {
41
+ const policy = new PasswordPresentPolicy(true);
42
+ const otpMethod = {
43
+ type: 'otp' as const,
44
+ identifier: 'test@example.com',
45
+ code: '123456',
46
+ };
47
+
48
+ expect(await policy.isActive(otpMethod)).toBe(false);
49
+ });
50
+
51
+ it('should have correct error code', () => {
52
+ const policy = new PasswordPresentPolicy(true);
53
+ expect(policy.code).toBe('MISSING_PASSWORD');
54
+ });
55
+ });
@@ -0,0 +1,66 @@
1
+ import { PasswordStrengthPolicy } from '@application/policies/password-strength.policy.js';
2
+ import { describe, expect, it } from 'vitest';
3
+
4
+ describe('PasswordStrengthPolicy', () => {
5
+ it('should return false when policy is disabled', async () => {
6
+ const policy = new PasswordStrengthPolicy(false);
7
+ const method = {
8
+ type: 'password' as const,
9
+ identifier: 'test@example.com',
10
+ password: 'weak',
11
+ };
12
+
13
+ expect(await policy.isActive(method)).toBe(false);
14
+ });
15
+
16
+ it('should return true when policy is enabled and password meets minimum length', async () => {
17
+ const policy = new PasswordStrengthPolicy(true, 6);
18
+ const method = {
19
+ type: 'password' as const,
20
+ identifier: 'test@example.com',
21
+ password: 'strong123',
22
+ };
23
+
24
+ expect(await policy.isActive(method)).toBe(true);
25
+ expect(await policy.check(method)).toBe(true);
26
+ });
27
+
28
+ it('should return false when policy is enabled but password is too short', async () => {
29
+ const policy = new PasswordStrengthPolicy(true, 12);
30
+ const method = {
31
+ type: 'password' as const,
32
+ identifier: 'test@example.com',
33
+ password: 'short',
34
+ };
35
+
36
+ expect(await policy.isActive(method)).toBe(true);
37
+ expect(await policy.check(method)).toBe(false);
38
+ });
39
+
40
+ it('should use default minimum length of 8', async () => {
41
+ const policy = new PasswordStrengthPolicy(true);
42
+ const method = {
43
+ type: 'password' as const,
44
+ identifier: 'test@example.com',
45
+ password: '1234567', // 7 chars
46
+ };
47
+
48
+ expect(await policy.check(method)).toBe(false);
49
+ });
50
+
51
+ it('should return true for non-password methods', async () => {
52
+ const policy = new PasswordStrengthPolicy(true, 8);
53
+ const otpMethod = {
54
+ type: 'otp' as const,
55
+ identifier: 'test@example.com',
56
+ code: '123456',
57
+ };
58
+
59
+ expect(await policy.isActive(otpMethod)).toBe(false);
60
+ });
61
+
62
+ it('should have correct error code', () => {
63
+ const policy = new PasswordStrengthPolicy(true, 8);
64
+ expect(policy.code).toBe('WEAK_PASSWORD');
65
+ });
66
+ });
@@ -0,0 +1,14 @@
1
+ import { AuthPreferences, AuthPreferencesServicePort } from "@application/ports/auth-preference-service.port.js";
2
+ import { vi } from "vitest";
3
+
4
+ interface AuthPreferencesServiceMock {
5
+ getAuthPreferences?: () => Promise<AuthPreferences>;
6
+ }
7
+
8
+ export function createAuthPreferencesServiceMock(
9
+ authPreferencesService?: AuthPreferencesServiceMock,
10
+ ): AuthPreferencesServicePort {
11
+ return {
12
+ getAuthPreferences: authPreferencesService?.getAuthPreferences ?? vi.fn().mockResolvedValue({ allowLoginWithNotVerifiedAccount: false }),
13
+ };
14
+ }
@@ -0,0 +1,19 @@
1
+ import { AuthMethod, AuthResult, AuthServicePort } from "@application/ports/auth-service.port.js";
2
+ import { UserAccount } from "@domain/user-account.js";
3
+ import { vi } from "vitest";
4
+
5
+ const activeUser: UserAccount = { id: '1', displayName: 'test@test.com', status: 'active', isVerified: true };
6
+
7
+ interface AuthServiceMock {
8
+ authenticate?: (method: AuthMethod) => Promise<AuthResult>;
9
+ register?: (method: AuthMethod) => Promise<AuthResult>;
10
+ logout?: () => Promise<void>;
11
+ }
12
+
13
+ export function createAuthServiceMock(authService?: AuthServiceMock): AuthServicePort {
14
+ return {
15
+ authenticate: authService?.authenticate ?? vi.fn().mockResolvedValue({ ok: true, user: activeUser }),
16
+ logout: authService?.logout ?? vi.fn().mockResolvedValue(undefined),
17
+ register: authService?.register ?? vi.fn().mockResolvedValue({ ok: true, user: activeUser }),
18
+ };
19
+ }
@@ -0,0 +1,16 @@
1
+ import { AuthMethod } from "@application/ports/auth-service.port.js";
2
+ import { PreRegisterPolicy } from "@application/ports/pre-register-policy.port.js";
3
+
4
+ export interface PreRegisterPolicyMock {
5
+ check?: (method: AuthMethod) => Promise<boolean>;
6
+ isActive?: (method: AuthMethod) => Promise<boolean>;
7
+ code?: string;
8
+ }
9
+
10
+ export function createPreRegisterPolicyMock(preRegisterPolicy?: PreRegisterPolicyMock): PreRegisterPolicy {
11
+ return {
12
+ check: preRegisterPolicy?.check ?? vi.fn().mockResolvedValue(true),
13
+ isActive: preRegisterPolicy?.isActive ?? vi.fn().mockResolvedValue(false),
14
+ code: preRegisterPolicy?.code ?? 'POLICY_ERROR',
15
+ };
16
+ }
@@ -0,0 +1,18 @@
1
+ import { AuthServicePort } from '@application/ports/auth-service.port.js';
2
+ import { vi } from 'vitest';
3
+
4
+ export function createPasswordAdapterMock(): AuthServicePort {
5
+ return {
6
+ authenticate: vi.fn(),
7
+ register: vi.fn(),
8
+ logout: vi.fn(),
9
+ };
10
+ }
11
+
12
+ export function createOAuthAdapterMock(): AuthServicePort {
13
+ return {
14
+ authenticate: vi.fn(),
15
+ register: vi.fn(),
16
+ logout: vi.fn(),
17
+ };
18
+ }
@@ -0,0 +1,162 @@
1
+ import type { AuthServicePort } from '@application/ports/auth-service.port.js';
2
+ import { CompositeAuthService } from '@application/services/composite-auth.service.js';
3
+ import { beforeEach, describe, expect, it } from 'vitest';
4
+ import {
5
+ createOAuthAdapterMock,
6
+ createPasswordAdapterMock,
7
+ } from '../services/composite-auth.mock.js';
8
+
9
+ describe('CompositeAuthService', () => {
10
+ let compositeAuthService: CompositeAuthService;
11
+ let mockPasswordAdapter: AuthServicePort;
12
+ let mockOAuthAdapter: AuthServicePort;
13
+
14
+ beforeEach(() => {
15
+ mockPasswordAdapter = createPasswordAdapterMock();
16
+ mockOAuthAdapter = createOAuthAdapterMock();
17
+
18
+ compositeAuthService = new CompositeAuthService({
19
+ password: mockPasswordAdapter,
20
+ oauth: mockOAuthAdapter,
21
+ });
22
+ });
23
+
24
+ it('should create composite service with method-based config', () => {
25
+ expect(compositeAuthService).toBeDefined();
26
+ expect(compositeAuthService.getMethodConfig()).toEqual({
27
+ password: mockPasswordAdapter,
28
+ oauth: mockOAuthAdapter,
29
+ });
30
+ });
31
+
32
+ it('should authenticate with password adapter', async () => {
33
+ const mockUser = {
34
+ id: 'test-id',
35
+ displayName: 'Test User',
36
+ status: 'active' as const,
37
+ isVerified: true,
38
+ };
39
+
40
+ (mockPasswordAdapter.authenticate as any).mockResolvedValue({
41
+ ok: true,
42
+ user: mockUser,
43
+ });
44
+
45
+ const result = await compositeAuthService.authenticate({
46
+ type: 'password',
47
+ identifier: 'test@example.com',
48
+ password: 'password',
49
+ });
50
+
51
+ expect(result.ok).toBe(true);
52
+ if (result.ok) {
53
+ expect(result.user).toEqual(mockUser);
54
+ }
55
+ expect(mockPasswordAdapter.authenticate).toHaveBeenCalled();
56
+ expect(mockOAuthAdapter.authenticate).not.toHaveBeenCalled();
57
+ });
58
+
59
+ it('should authenticate with oauth adapter', async () => {
60
+ const mockUser = {
61
+ id: 'test-id',
62
+ displayName: 'Test User',
63
+ status: 'active' as const,
64
+ isVerified: true,
65
+ };
66
+
67
+ (mockOAuthAdapter.authenticate as any).mockResolvedValue({
68
+ ok: true,
69
+ user: mockUser,
70
+ });
71
+
72
+ const result = await compositeAuthService.authenticate({
73
+ type: 'oauth',
74
+ provider: 'google',
75
+ code: 'auth-code',
76
+ });
77
+
78
+ expect(result.ok).toBe(true);
79
+ if (result.ok) {
80
+ expect(result.user).toEqual(mockUser);
81
+ }
82
+ expect(mockOAuthAdapter.authenticate).toHaveBeenCalled();
83
+ expect(mockPasswordAdapter.authenticate).not.toHaveBeenCalled();
84
+ });
85
+
86
+ it('should validate magic link token format', async () => {
87
+ const result = await compositeAuthService.authenticate({
88
+ type: 'magic_link',
89
+ token: 'short', // Too short
90
+ });
91
+
92
+ expect(result.ok).toBe(false);
93
+ if (!result.ok) {
94
+ expect(result.error).toBe('ADAPTER_UNAVAILABLE'); // No magic_link adapter configured
95
+ }
96
+ });
97
+
98
+ it('should return error for unsupported auth method', async () => {
99
+ const result = await compositeAuthService.authenticate({
100
+ type: 'unknown_method' as any, // Unsupported method type
101
+ token: 'magic-token',
102
+ });
103
+
104
+ expect(result.ok).toBe(false);
105
+ if (!result.ok) {
106
+ expect(result.error).toBe('ADAPTER_UNAVAILABLE');
107
+ }
108
+ });
109
+
110
+ it('should handle register with correct adapter', async () => {
111
+ const mockUser = {
112
+ id: 'test-id',
113
+ displayName: 'Test User',
114
+ status: 'active' as const,
115
+ isVerified: true,
116
+ };
117
+
118
+ (mockPasswordAdapter.register as any).mockResolvedValue({
119
+ ok: true,
120
+ user: mockUser,
121
+ });
122
+
123
+ const result = await compositeAuthService.register({
124
+ type: 'password',
125
+ identifier: 'test@example.com',
126
+ password: 'password',
127
+ });
128
+
129
+ expect(result.ok).toBe(true);
130
+ if (result.ok) {
131
+ expect(result.user).toEqual(mockUser);
132
+ }
133
+ expect(mockPasswordAdapter.register).toHaveBeenCalled();
134
+ });
135
+
136
+ it('should validate OTP code format', async () => {
137
+ const result = await compositeAuthService.authenticate({
138
+ type: 'otp',
139
+ identifier: 'test@example.com',
140
+ code: '123', // Too short
141
+ });
142
+
143
+ expect(result.ok).toBe(false);
144
+ if (!result.ok) {
145
+ expect(result.error).toBe('ADAPTER_UNAVAILABLE'); // No OTP adapter configured
146
+ }
147
+ });
148
+
149
+ it('should demonstrate exact requested syntax', () => {
150
+ // This is exactly how the user wants to use it:
151
+ const engine = new CompositeAuthService({
152
+ password: createPasswordAdapterMock(),
153
+ oauth: createOAuthAdapterMock(),
154
+ });
155
+
156
+ expect(engine).toBeDefined();
157
+ expect(engine.getMethodConfig()).toEqual({
158
+ password: expect.any(Object),
159
+ oauth: expect.any(Object),
160
+ });
161
+ });
162
+ });
@@ -1,25 +1,72 @@
1
1
  import { authenticateUser } from "@application/use-cases/authenticate-user.js";
2
- import { AuthServicePort } from "@application/ports/auth-service.port.js";
2
+ import { createAuthPreferencesServiceMock } from "@tests/application/ports/auth-preferences-service.port.mock.js";
3
+ import { createAuthServiceMock } from "@tests/application/ports/auth-service.port.mock.js";
4
+ import { inactiveUser, notVerifiedUser } from "@tests/domain/user-account.data.js";
3
5
  import { describe, expect, it, vi } from "vitest";
4
6
 
5
7
  describe('AuthenticateUser', () => {
6
8
  it('should authenticate a user', async () => {
7
- const authService: AuthServicePort = {
8
- authenticate: vi.fn().mockResolvedValue({ ok: true, user: { id: '1', email: 'test@test.com' } }),
9
- logout: vi.fn().mockResolvedValue(undefined),
10
- };
11
- const loginUser = authenticateUser(authService);
12
- const user = await loginUser({ type: 'password', identifier: 'test@test.com', password: 'test' });
13
- expect(user).toEqual({ ok: true, user: { id: '1', email: 'test@test.com' } });
9
+ // Given
10
+ const authService = createAuthServiceMock();
11
+ const authPreferencesService = createAuthPreferencesServiceMock();
12
+ const loginUser = authenticateUser(authService, authPreferencesService);
13
+ // When
14
+ const result = await loginUser({ type: 'password', identifier: 'test@test.com', password: 'test' });
15
+ // Then
16
+ expect(result.ok).toBe(true);
14
17
  });
15
18
 
16
19
  it('should return an error if the authentication fails', async () => {
17
- const authService: AuthServicePort = {
20
+ // Given
21
+ const authService = createAuthServiceMock({
18
22
  authenticate: vi.fn().mockResolvedValue({ ok: false, error: 'INVALID_CREDENTIALS' }),
19
- logout: vi.fn().mockResolvedValue(undefined),
20
- };
21
- const loginUser = authenticateUser(authService);
23
+ });
24
+ const authPreferencesService = createAuthPreferencesServiceMock();
25
+ // When
26
+ const loginUser = authenticateUser(authService, authPreferencesService);
27
+ const user = await loginUser({ type: 'password', identifier: 'test@test.com', password: 'test' });
28
+ // Then
29
+ expect(user).toEqual({ ok: false, error: 'INVALID_CREDENTIALS', violatedPolicies: [] });
30
+ });
31
+
32
+ it('should not be possible to authenticate with deactivated account', async () => {
33
+ // Given
34
+ const authService = createAuthServiceMock({
35
+ authenticate: vi.fn().mockResolvedValue({ ok: true, user: inactiveUser }),
36
+ });
37
+ const authPreferencesService = createAuthPreferencesServiceMock();
38
+ // When
39
+ const loginUser = authenticateUser(authService, authPreferencesService);
22
40
  const user = await loginUser({ type: 'password', identifier: 'test@test.com', password: 'test' });
23
- expect(user).toEqual({ ok: false, error: 'INVALID_CREDENTIALS' });
41
+ // Then
42
+ expect(user).toEqual({ ok: false, error: 'ACCOUNT_DISABLED', violatedPolicies: [] });
43
+ });
44
+
45
+ it('should not be possible to authenticate with not verified account', async () => {
46
+ // Given
47
+ const authService = createAuthServiceMock({
48
+ authenticate: vi.fn().mockResolvedValue({ ok: true, user: notVerifiedUser }),
49
+ });
50
+ const authPreferencesService = createAuthPreferencesServiceMock();
51
+ // When
52
+ const loginUser = authenticateUser(authService, authPreferencesService);
53
+ // Then
54
+ const result = await loginUser({ type: 'password', identifier: 'test@test.com', password: 'test' });
55
+ expect(result).toEqual({ ok: false, error: 'ACCOUNT_NOT_VERIFIED', violatedPolicies: [] });
56
+ });
57
+
58
+ it('should be possible to authenticate with not verified account if unckeched in settings', async () => {
59
+ // Given
60
+ const authService = createAuthServiceMock({
61
+ authenticate: vi.fn().mockResolvedValue({ ok: true, user: notVerifiedUser }),
62
+ });
63
+ const authPreferencesService = createAuthPreferencesServiceMock({
64
+ getAuthPreferences: vi.fn().mockResolvedValue({ allowLoginWithNotVerifiedAccount: true }),
65
+ });
66
+ // When
67
+ const loginUser = authenticateUser(authService, authPreferencesService);
68
+ // Then
69
+ const result = await loginUser({ type: 'password', identifier: 'test@test.com', password: 'test' });
70
+ expect(result.ok).toEqual(true);
24
71
  });
25
- });
72
+ });