@siran/auth-core 0.9.0 → 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.
- package/CHANGELOG.md +44 -0
- package/COMPOSITE_AUTH_SERVICE.md +104 -0
- package/package.json +1 -1
- package/src/application/policies/account-already-exists.policy.ts +23 -0
- package/src/application/policies/magic-link-token.policy.ts +23 -0
- package/src/application/policies/oauth-code.policy.ts +23 -0
- package/src/application/policies/oauth-provider.policy.ts +29 -0
- package/src/application/policies/otp-code-format.policy.ts +23 -0
- package/src/application/policies/otp-code.policy.ts +23 -0
- package/src/application/policies/otp-identifier.policy.ts +23 -0
- package/src/application/policies/password-identifier.policy.ts +23 -0
- package/src/application/policies/password-min-length.policy.ts +29 -0
- package/src/application/policies/password-present.policy.ts +23 -0
- package/src/application/policies/password-strength.policy.ts +27 -0
- package/src/application/ports/auth-preference-service.port.ts +5 -0
- package/src/application/ports/auth-service.port.ts +53 -14
- package/src/application/ports/user-existence.port.ts +3 -0
- package/src/application/services/composite-auth.service.ts +96 -0
- package/src/auth-engine.factory.ts +35 -10
- package/src/index.ts +9 -0
- package/tests/application/policies/magic-link-token.policy.test.ts +74 -0
- package/tests/application/policies/oauth-code.policy.test.ts +79 -0
- package/tests/application/policies/oauth-provider.policy.test.ts +76 -0
- package/tests/application/policies/otp-code-format.policy.test.ts +90 -0
- package/tests/application/policies/otp-code.policy.test.ts +55 -0
- package/tests/application/policies/otp-identifier.policy.test.ts +55 -0
- package/tests/application/policies/password-identifier.policy.test.ts +55 -0
- package/tests/application/policies/password-min-length.policy.test.ts +66 -0
- package/tests/application/policies/password-present.policy.test.ts +55 -0
- package/tests/application/policies/password-strength.policy.test.ts +66 -0
- package/tests/application/ports/auth-service.port.mock.ts +0 -2
- package/tests/application/services/composite-auth.mock.ts +18 -0
- package/tests/application/services/composite-auth.test.ts +162 -0
- package/tsconfig.lib.json +3 -2
- package/src/application/services/account-already-exists.service.ts +0 -20
|
@@ -1,22 +1,47 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
1
|
+
import { AccountAlreadyExistsPolicy } from '@application/policies/account-already-exists.policy.js';
|
|
2
|
+
import { OtpCodePolicy } from '@application/policies/otp-code.policy.js';
|
|
3
|
+
import { OtpIdentifierPolicy } from '@application/policies/otp-identifier.policy.js';
|
|
4
|
+
import { PasswordIdentifierPolicy } from '@application/policies/password-identifier.policy.js';
|
|
5
|
+
import { PasswordPresentPolicy } from '@application/policies/password-present.policy.js';
|
|
6
|
+
import { PasswordStrengthPolicy } from '@application/policies/password-strength.policy.js';
|
|
7
|
+
import { AuthPreferencesServicePort } from '@application/ports/auth-preference-service.port.js';
|
|
8
|
+
import { AuthServicePort } from '@application/ports/auth-service.port.js';
|
|
9
|
+
import { PreRegisterPolicy } from '@application/ports/pre-register-policy.port.js';
|
|
10
|
+
import { UserExistencePort } from '@application/ports/user-existence.port.js';
|
|
11
|
+
import { authenticateUser } from '@application/use-cases/authenticate-user.js';
|
|
12
|
+
import { registerUser } from '@application/use-cases/register-user.js';
|
|
13
|
+
import { AuthEngine } from '@root/auth-engine.js';
|
|
8
14
|
|
|
9
15
|
export class AuthEngineFactory {
|
|
10
16
|
static async create(
|
|
11
17
|
authService: AuthServicePort,
|
|
12
18
|
authPreferencesService: AuthPreferencesServicePort,
|
|
13
|
-
|
|
19
|
+
userExistence: UserExistencePort,
|
|
20
|
+
basePolicies: PreRegisterPolicy[]
|
|
14
21
|
): Promise<AuthEngine> {
|
|
15
22
|
const preferences = await authPreferencesService.getAuthPreferences();
|
|
16
23
|
|
|
17
24
|
const policies = [
|
|
18
25
|
...basePolicies,
|
|
19
|
-
new AccountAlreadyExistsPolicy(
|
|
26
|
+
new AccountAlreadyExistsPolicy(
|
|
27
|
+
userExistence,
|
|
28
|
+
!preferences.allowDuplicatedAccount
|
|
29
|
+
),
|
|
30
|
+
new PasswordStrengthPolicy(
|
|
31
|
+
!preferences.allowWeakPassword
|
|
32
|
+
),
|
|
33
|
+
new PasswordIdentifierPolicy(
|
|
34
|
+
!preferences.allowMissingPasswordIdentifier
|
|
35
|
+
),
|
|
36
|
+
new PasswordPresentPolicy(
|
|
37
|
+
!preferences.allowMissingPassword
|
|
38
|
+
),
|
|
39
|
+
new OtpIdentifierPolicy(
|
|
40
|
+
!preferences.allowMissingOtpIdentifier
|
|
41
|
+
),
|
|
42
|
+
new OtpCodePolicy(
|
|
43
|
+
!preferences.allowMissingOtpCode
|
|
44
|
+
),
|
|
20
45
|
];
|
|
21
46
|
|
|
22
47
|
return new AuthEngine({
|
|
@@ -25,4 +50,4 @@ export class AuthEngineFactory {
|
|
|
25
50
|
logout: () => authService.logout(),
|
|
26
51
|
});
|
|
27
52
|
}
|
|
28
|
-
}
|
|
53
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -6,11 +6,20 @@ export * from '@domain/user-account.js';
|
|
|
6
6
|
/**
|
|
7
7
|
* Application
|
|
8
8
|
*/
|
|
9
|
+
export * from '@application/ports/auth-preference-service.port.js';
|
|
9
10
|
export * from '@application/ports/auth-service.port.js';
|
|
10
11
|
export * from '@application/ports/pre-register-policy.port.js';
|
|
12
|
+
export * from '@application/ports/user-existence.port.js';
|
|
13
|
+
export * from '@application/services/composite-auth.service.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Infrastructure
|
|
17
|
+
*/
|
|
18
|
+
// Infrastructure adapters are provided as separate packages
|
|
11
19
|
|
|
12
20
|
/**
|
|
13
21
|
* Auth Engine
|
|
14
22
|
*/
|
|
15
23
|
export * from '@root/auth-engine.factory.js';
|
|
16
24
|
export * from '@root/auth-engine.js';
|
|
25
|
+
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { MagicLinkTokenPolicy } from '@application/policies/magic-link-token.policy.js';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
describe('MagicLinkTokenPolicy', () => {
|
|
5
|
+
it('should return false when policy is disabled', async () => {
|
|
6
|
+
const policy = new MagicLinkTokenPolicy(false);
|
|
7
|
+
const method = {
|
|
8
|
+
type: 'magic_link' as const,
|
|
9
|
+
token: 'short',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
expect(await policy.isActive(method)).toBe(false);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should return true when policy is enabled and token meets minimum length', async () => {
|
|
16
|
+
const policy = new MagicLinkTokenPolicy(true);
|
|
17
|
+
const method = {
|
|
18
|
+
type: 'magic_link' as const,
|
|
19
|
+
token: 'valid-token-123456789',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
expect(await policy.isActive(method)).toBe(true);
|
|
23
|
+
expect(await policy.check(method)).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should return false when policy is enabled but token is missing', async () => {
|
|
27
|
+
const policy = new MagicLinkTokenPolicy(true);
|
|
28
|
+
const method = {
|
|
29
|
+
type: 'magic_link' as const,
|
|
30
|
+
token: '',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
expect(await policy.isActive(method)).toBe(true);
|
|
34
|
+
expect(await policy.check(method)).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should return false when policy is enabled but token is too short', async () => {
|
|
38
|
+
const policy = new MagicLinkTokenPolicy(true);
|
|
39
|
+
const method = {
|
|
40
|
+
type: 'magic_link' as const,
|
|
41
|
+
token: 'short', // 5 chars
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
expect(await policy.isActive(method)).toBe(true);
|
|
45
|
+
expect(await policy.check(method)).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should return false when policy is enabled but token is exactly at boundary', async () => {
|
|
49
|
+
const policy = new MagicLinkTokenPolicy(true);
|
|
50
|
+
const method = {
|
|
51
|
+
type: 'magic_link' as const,
|
|
52
|
+
token: '1234567890', // exactly 10 chars
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
expect(await policy.isActive(method)).toBe(true);
|
|
56
|
+
expect(await policy.check(method)).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should return false for non-magic link methods', async () => {
|
|
60
|
+
const policy = new MagicLinkTokenPolicy(true);
|
|
61
|
+
const passwordMethod = {
|
|
62
|
+
type: 'password' as const,
|
|
63
|
+
identifier: 'test@example.com',
|
|
64
|
+
password: 'password123',
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
expect(await policy.isActive(passwordMethod)).toBe(false);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should have correct error code', () => {
|
|
71
|
+
const policy = new MagicLinkTokenPolicy(true);
|
|
72
|
+
expect(policy.code).toBe('MISSING_MAGIC_LINK_TOKEN');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { OAuthCodePolicy } from '@application/policies/oauth-code.policy.js';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
describe('OAuthCodePolicy', () => {
|
|
5
|
+
it('should return false when policy is disabled', async () => {
|
|
6
|
+
const policy = new OAuthCodePolicy(false);
|
|
7
|
+
const method = {
|
|
8
|
+
type: 'oauth' as const,
|
|
9
|
+
provider: 'google' as const,
|
|
10
|
+
code: '',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
expect(await policy.isActive(method)).toBe(false);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should return true when policy is enabled and code meets minimum length', async () => {
|
|
17
|
+
const policy = new OAuthCodePolicy(true);
|
|
18
|
+
const method = {
|
|
19
|
+
type: 'oauth' as const,
|
|
20
|
+
provider: 'google' as const,
|
|
21
|
+
code: 'auth-code-12345',
|
|
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 code is missing', async () => {
|
|
29
|
+
const policy = new OAuthCodePolicy(true);
|
|
30
|
+
const method = {
|
|
31
|
+
type: 'oauth' as const,
|
|
32
|
+
provider: 'google' as const,
|
|
33
|
+
code: '',
|
|
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 when policy is enabled but code is too short', async () => {
|
|
41
|
+
const policy = new OAuthCodePolicy(true);
|
|
42
|
+
const method = {
|
|
43
|
+
type: 'oauth' as const,
|
|
44
|
+
provider: 'google' as const,
|
|
45
|
+
code: 'five', // 5 chars (too short)
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
expect(await policy.isActive(method)).toBe(true);
|
|
49
|
+
expect(await policy.check(method)).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should return true when policy is enabled and code is exactly at boundary', async () => {
|
|
53
|
+
const policy = new OAuthCodePolicy(true);
|
|
54
|
+
const method = {
|
|
55
|
+
type: 'oauth' as const,
|
|
56
|
+
provider: 'google' as const,
|
|
57
|
+
code: '12345', // exactly 5 chars
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
expect(await policy.isActive(method)).toBe(true);
|
|
61
|
+
expect(await policy.check(method)).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should return false for non-OAuth methods', async () => {
|
|
65
|
+
const policy = new OAuthCodePolicy(true);
|
|
66
|
+
const passwordMethod = {
|
|
67
|
+
type: 'password' as const,
|
|
68
|
+
identifier: 'test@example.com',
|
|
69
|
+
password: 'password123',
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
expect(await policy.isActive(passwordMethod)).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should have correct error code', () => {
|
|
76
|
+
const policy = new OAuthCodePolicy(true);
|
|
77
|
+
expect(policy.code).toBe('MISSING_OAUTH_CODE');
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { OAuthProviderPolicy } from '@application/policies/oauth-provider.policy.js';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
describe('OAuthProviderPolicy', () => {
|
|
5
|
+
it('should return false when policy is disabled', async () => {
|
|
6
|
+
const policy = new OAuthProviderPolicy(false);
|
|
7
|
+
const method = {
|
|
8
|
+
type: 'oauth' as const,
|
|
9
|
+
provider: 'unsupported' as any,
|
|
10
|
+
code: 'auth-code',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
expect(await policy.isActive(method)).toBe(false);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should return true when policy is enabled and provider is supported', async () => {
|
|
17
|
+
const policy = new OAuthProviderPolicy(true);
|
|
18
|
+
const method = {
|
|
19
|
+
type: 'oauth' as const,
|
|
20
|
+
provider: 'google' as const,
|
|
21
|
+
code: 'auth-code',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
expect(await policy.isActive(method)).toBe(true);
|
|
25
|
+
expect(await policy.check(method)).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should return true for all supported providers', async () => {
|
|
29
|
+
const policy = new OAuthProviderPolicy(true);
|
|
30
|
+
const supportedProviders = [
|
|
31
|
+
'google',
|
|
32
|
+
'github',
|
|
33
|
+
'apple',
|
|
34
|
+
'facebook',
|
|
35
|
+
] as const;
|
|
36
|
+
|
|
37
|
+
for (const provider of supportedProviders) {
|
|
38
|
+
const method = {
|
|
39
|
+
type: 'oauth' as const,
|
|
40
|
+
provider,
|
|
41
|
+
code: 'auth-code',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
expect(await policy.isActive(method)).toBe(true);
|
|
45
|
+
expect(await policy.check(method)).toBe(true);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should return false when policy is enabled but provider is unsupported', async () => {
|
|
50
|
+
const policy = new OAuthProviderPolicy(true);
|
|
51
|
+
const method = {
|
|
52
|
+
type: 'oauth' as const,
|
|
53
|
+
provider: 'unsupported-provider' as any,
|
|
54
|
+
code: 'auth-code',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
expect(await policy.isActive(method)).toBe(true);
|
|
58
|
+
expect(await policy.check(method)).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should return false for non-OAuth methods', async () => {
|
|
62
|
+
const policy = new OAuthProviderPolicy(true);
|
|
63
|
+
const passwordMethod = {
|
|
64
|
+
type: 'password' as const,
|
|
65
|
+
identifier: 'test@example.com',
|
|
66
|
+
password: 'password123',
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
expect(await policy.isActive(passwordMethod)).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should have correct error code', () => {
|
|
73
|
+
const policy = new OAuthProviderPolicy(true);
|
|
74
|
+
expect(policy.code).toBe('UNSUPPORTED_OAUTH_PROVIDER');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { OtpCodeFormatPolicy } from '@application/policies/otp-code-format.policy.js';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
describe('OtpCodeFormatPolicy', () => {
|
|
5
|
+
it('should return false when policy is disabled', async () => {
|
|
6
|
+
const policy = new OtpCodeFormatPolicy(false);
|
|
7
|
+
const method = {
|
|
8
|
+
type: 'otp' as const,
|
|
9
|
+
identifier: 'test@example.com',
|
|
10
|
+
code: 'invalid',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
expect(await policy.isActive(method)).toBe(false);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should return true when policy is enabled and code has valid format', async () => {
|
|
17
|
+
const policy = new OtpCodeFormatPolicy(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 code format is invalid', async () => {
|
|
29
|
+
const policy = new OtpCodeFormatPolicy(true);
|
|
30
|
+
const method = {
|
|
31
|
+
type: 'otp' as const,
|
|
32
|
+
identifier: 'test@example.com',
|
|
33
|
+
code: 'abc123', // Contains letters
|
|
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 when policy is enabled but code is too short', async () => {
|
|
41
|
+
const policy = new OtpCodeFormatPolicy(true);
|
|
42
|
+
const method = {
|
|
43
|
+
type: 'otp' as const,
|
|
44
|
+
identifier: 'test@example.com',
|
|
45
|
+
code: '12345', // 5 digits
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
expect(await policy.isActive(method)).toBe(true);
|
|
49
|
+
expect(await policy.check(method)).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should return false when policy is enabled but code is too long', async () => {
|
|
53
|
+
const policy = new OtpCodeFormatPolicy(true);
|
|
54
|
+
const method = {
|
|
55
|
+
type: 'otp' as const,
|
|
56
|
+
identifier: 'test@example.com',
|
|
57
|
+
code: '1234567', // 7 digits
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
expect(await policy.isActive(method)).toBe(true);
|
|
61
|
+
expect(await policy.check(method)).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should handle empty code string', async () => {
|
|
65
|
+
const policy = new OtpCodeFormatPolicy(true);
|
|
66
|
+
const method = {
|
|
67
|
+
type: 'otp' as const,
|
|
68
|
+
identifier: 'test@example.com',
|
|
69
|
+
code: '',
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
expect(await policy.check(method)).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should return false for non-OTP methods', async () => {
|
|
76
|
+
const policy = new OtpCodeFormatPolicy(true);
|
|
77
|
+
const passwordMethod = {
|
|
78
|
+
type: 'password' as const,
|
|
79
|
+
identifier: 'test@example.com',
|
|
80
|
+
password: 'password123',
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
expect(await policy.isActive(passwordMethod)).toBe(false);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should have correct error code', () => {
|
|
87
|
+
const policy = new OtpCodeFormatPolicy(true);
|
|
88
|
+
expect(policy.code).toBe('INVALID_OTP_FORMAT');
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { OtpCodePolicy } from '@application/policies/otp-code.policy.js';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
describe('OtpCodePolicy', () => {
|
|
5
|
+
it('should return false when policy is disabled', async () => {
|
|
6
|
+
const policy = new OtpCodePolicy(false);
|
|
7
|
+
const method = {
|
|
8
|
+
type: 'otp' as const,
|
|
9
|
+
identifier: 'test@example.com',
|
|
10
|
+
code: '',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
expect(await policy.isActive(method)).toBe(false);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should return true when policy is enabled and code is present', async () => {
|
|
17
|
+
const policy = new OtpCodePolicy(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 code is missing', async () => {
|
|
29
|
+
const policy = new OtpCodePolicy(true);
|
|
30
|
+
const method = {
|
|
31
|
+
type: 'otp' as const,
|
|
32
|
+
identifier: 'test@example.com',
|
|
33
|
+
code: '',
|
|
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 OtpCodePolicy(true);
|
|
42
|
+
const passwordMethod = {
|
|
43
|
+
type: 'password' as const,
|
|
44
|
+
identifier: 'test@example.com',
|
|
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 OtpCodePolicy(true);
|
|
53
|
+
expect(policy.code).toBe('MISSING_OTP_CODE');
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -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
|
+
});
|