@siran/auth-core 0.1.1 → 0.9.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/.vscode/settings.json +21 -0
- package/CHANGELOG.md +95 -0
- package/package.json +1 -1
- package/src/application/ports/auth-preference-service.port.ts +9 -0
- package/src/application/ports/auth-service.port.ts +12 -4
- package/src/application/ports/pre-register-policy.port.ts +7 -0
- package/src/application/services/account-already-exists.service.ts +20 -0
- package/src/application/use-cases/authenticate-user.ts +17 -4
- package/src/application/use-cases/register-user.ts +28 -0
- package/src/auth-engine.factory.ts +28 -0
- package/src/auth-engine.ts +25 -0
- package/src/domain/user-account.ts +3 -9
- package/src/index.ts +7 -2
- package/tests/application/ports/auth-preferences-service.port.mock.ts +14 -0
- package/tests/application/ports/auth-service.port.mock.ts +21 -0
- package/tests/application/ports/pre-register-policy.port.mock.ts +16 -0
- package/tests/application/use-cases/authenticate-user.test.ts +61 -14
- package/tests/application/use-cases/register-user.test.ts +89 -0
- package/tests/domain/user-account.data.ts +5 -0
- package/tsconfig.lib.json +2 -0
- package/tsconfig.spec.json +13 -1
- package/vite.config.mts +4 -1
- package/src/domain/session.ts +0 -16
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"editor.formatOnSave": true,
|
|
3
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
4
|
+
"editor.codeActionsOnSave": {
|
|
5
|
+
"source.fixAll": "explicit",
|
|
6
|
+
"source.organizeImports": "explicit",
|
|
7
|
+
"source.fixAll.eslint": "explicit"
|
|
8
|
+
},
|
|
9
|
+
"eslint.enable": true,
|
|
10
|
+
"eslint.run": "onSave",
|
|
11
|
+
"eslint.validate": [
|
|
12
|
+
"javascript",
|
|
13
|
+
"javascriptreact",
|
|
14
|
+
"typescript",
|
|
15
|
+
"typescriptreact"
|
|
16
|
+
],
|
|
17
|
+
"editor.tabSize": 2,
|
|
18
|
+
"editor.detectIndentation": false,
|
|
19
|
+
"editor.insertSpaces": true,
|
|
20
|
+
"editor.formatOnSaveMode": "file",
|
|
21
|
+
}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,98 @@
|
|
|
1
|
+
## 0.9.0 (2026-01-24)
|
|
2
|
+
|
|
3
|
+
### 🚀 Features
|
|
4
|
+
|
|
5
|
+
- Introduce AuthEngine and related components for enhanced authentication flow ([fe11cd9](https://github.com/narisraz/auth/commit/fe11cd9))
|
|
6
|
+
|
|
7
|
+
### ❤️ Thank You
|
|
8
|
+
|
|
9
|
+
- Naris Razafimahatratra
|
|
10
|
+
|
|
11
|
+
## 0.8.1 (2026-01-24)
|
|
12
|
+
|
|
13
|
+
### 🩹 Fixes
|
|
14
|
+
|
|
15
|
+
- Update error handling in registerUser to include violatedPolicies ([80c2363](https://github.com/narisraz/auth/commit/80c2363))
|
|
16
|
+
|
|
17
|
+
### ❤️ Thank You
|
|
18
|
+
|
|
19
|
+
- Naris Razafimahatratra
|
|
20
|
+
|
|
21
|
+
## 0.8.0 (2026-01-24)
|
|
22
|
+
|
|
23
|
+
### 🚀 Features
|
|
24
|
+
|
|
25
|
+
- Enhance AuthResult and PreRegisterPolicy for detailed error reporting ([492bf1a](https://github.com/narisraz/auth/commit/492bf1a))
|
|
26
|
+
|
|
27
|
+
### ❤️ Thank You
|
|
28
|
+
|
|
29
|
+
- Naris Razafimahatratra
|
|
30
|
+
|
|
31
|
+
## 0.7.0 (2026-01-24)
|
|
32
|
+
|
|
33
|
+
### 🚀 Features
|
|
34
|
+
|
|
35
|
+
- Add isActive method to PreRegisterPolicy and update user registration logic ([2fdb3d3](https://github.com/narisraz/auth/commit/2fdb3d3))
|
|
36
|
+
|
|
37
|
+
### ❤️ Thank You
|
|
38
|
+
|
|
39
|
+
- Naris Razafimahatratra
|
|
40
|
+
|
|
41
|
+
## 0.6.0 (2026-01-24)
|
|
42
|
+
|
|
43
|
+
### 🚀 Features
|
|
44
|
+
|
|
45
|
+
- Enhance user registration with pre-register policy validation ([6ae97bb](https://github.com/narisraz/auth/commit/6ae97bb))
|
|
46
|
+
|
|
47
|
+
### ❤️ Thank You
|
|
48
|
+
|
|
49
|
+
- Naris Razafimahatratra
|
|
50
|
+
|
|
51
|
+
## 0.5.0 (2026-01-24)
|
|
52
|
+
|
|
53
|
+
### 🚀 Features
|
|
54
|
+
|
|
55
|
+
- Add user existence check to registration process ([cd6e3b9](https://github.com/narisraz/auth/commit/cd6e3b9))
|
|
56
|
+
|
|
57
|
+
### 🩹 Fixes
|
|
58
|
+
|
|
59
|
+
- Restore export of authenticate-user use case in index file ([aec572f](https://github.com/narisraz/auth/commit/aec572f))
|
|
60
|
+
- Update user registration tests for accurate assertions ([5c0e047](https://github.com/narisraz/auth/commit/5c0e047))
|
|
61
|
+
|
|
62
|
+
### ❤️ Thank You
|
|
63
|
+
|
|
64
|
+
- Naris Razafimahatratra
|
|
65
|
+
|
|
66
|
+
## 0.4.0 (2026-01-23)
|
|
67
|
+
|
|
68
|
+
### 🚀 Features
|
|
69
|
+
|
|
70
|
+
- Introduce AuthPreferencesService for flexible account verification handling ([63490c6](https://github.com/narisraz/auth/commit/63490c6))
|
|
71
|
+
|
|
72
|
+
### ❤️ Thank You
|
|
73
|
+
|
|
74
|
+
- Naris Razafimahatratra
|
|
75
|
+
|
|
76
|
+
## 0.3.0 (2026-01-23)
|
|
77
|
+
|
|
78
|
+
### 🚀 Features
|
|
79
|
+
|
|
80
|
+
- Extend authentication logic to include account verification status ([e7d5ffa](https://github.com/narisraz/auth/commit/e7d5ffa))
|
|
81
|
+
|
|
82
|
+
### ❤️ Thank You
|
|
83
|
+
|
|
84
|
+
- Naris Razafimahatratra
|
|
85
|
+
|
|
86
|
+
## 0.2.0 (2026-01-23)
|
|
87
|
+
|
|
88
|
+
### 🚀 Features
|
|
89
|
+
|
|
90
|
+
- Enhance user authentication to handle account status ([0188b61](https://github.com/narisraz/auth/commit/0188b61))
|
|
91
|
+
|
|
92
|
+
### ❤️ Thank You
|
|
93
|
+
|
|
94
|
+
- Naris Razafimahatratra
|
|
95
|
+
|
|
1
96
|
## 0.1.1 (2026-01-23)
|
|
2
97
|
|
|
3
98
|
### 🩹 Fixes
|
package/package.json
CHANGED
|
@@ -6,15 +6,23 @@ export type AuthMethod =
|
|
|
6
6
|
| { type: "magic_link"; token: string }
|
|
7
7
|
| { type: "oauth"; provider: "google" | "github" | "apple" | "facebook"; code: string };
|
|
8
8
|
|
|
9
|
-
export type AuthErrorCode =
|
|
9
|
+
export type AuthErrorCode =
|
|
10
|
+
| "INVALID_CREDENTIALS"
|
|
11
|
+
| "ACCOUNT_DISABLED"
|
|
12
|
+
| "ACCOUNT_NOT_VERIFIED"
|
|
13
|
+
| "ACCOUNT_LOCKED"
|
|
14
|
+
| "PRE_REGISTER_POLICY_VIOLATED";
|
|
10
15
|
|
|
11
|
-
export type
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
export type AuthResultSuccess = { ok: true; user: UserAccount };
|
|
17
|
+
export type AuthResultFailure = { ok: false; error: AuthErrorCode; violatedPolicies: string[] };
|
|
18
|
+
|
|
19
|
+
export type AuthResult = AuthResultSuccess | AuthResultFailure;
|
|
14
20
|
|
|
15
21
|
export interface AuthServicePort {
|
|
16
22
|
authenticate(method: AuthMethod): Promise<AuthResult>;
|
|
23
|
+
register(method: AuthMethod): Promise<AuthResult>;
|
|
17
24
|
logout(): Promise<void>;
|
|
25
|
+
userExists(method: AuthMethod): Promise<boolean>;
|
|
18
26
|
}
|
|
19
27
|
|
|
20
28
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
import { AuthMethod, AuthServicePort } from "@application/ports/auth-service.port.js";
|
|
3
|
+
import { PreRegisterPolicy } from "@application/ports/pre-register-policy.port.js";
|
|
4
|
+
|
|
5
|
+
export class AccountAlreadyExistsPolicy implements PreRegisterPolicy {
|
|
6
|
+
constructor(
|
|
7
|
+
private readonly authService: AuthServicePort,
|
|
8
|
+
private readonly enabled: boolean,
|
|
9
|
+
) { }
|
|
10
|
+
|
|
11
|
+
code: string = 'ACCOUNT_ALREADY_EXISTS';
|
|
12
|
+
|
|
13
|
+
async check(method: AuthMethod): Promise<boolean> {
|
|
14
|
+
return this.authService.userExists(method);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async isActive(method: AuthMethod): Promise<boolean> {
|
|
18
|
+
return this.enabled;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -1,8 +1,21 @@
|
|
|
1
|
+
import { AuthPreferencesServicePort } from "@application/ports/auth-preference-service.port.js";
|
|
2
|
+
import type { AuthMethod, AuthResult } from "@application/ports/auth-service.port.js";
|
|
1
3
|
import { AuthServicePort } from "@application/ports/auth-service.port.js";
|
|
2
|
-
import type { AuthMethod } from "@application/ports/auth-service.port.js";
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
export const authenticateUser =
|
|
6
|
-
(authService: AuthServicePort) =>
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
(authService: AuthServicePort, authPreferencesService: AuthPreferencesServicePort) =>
|
|
8
|
+
async (method: AuthMethod): Promise<AuthResult> => {
|
|
9
|
+
const result = await authService.authenticate(method);
|
|
10
|
+
if (!result.ok) {
|
|
11
|
+
return { ok: false, error: result.error, violatedPolicies: [] };
|
|
12
|
+
}
|
|
13
|
+
if (result.user.status === 'disabled') {
|
|
14
|
+
return { ok: false, error: 'ACCOUNT_DISABLED', violatedPolicies: [] };
|
|
15
|
+
}
|
|
16
|
+
const preferences = await authPreferencesService.getAuthPreferences();
|
|
17
|
+
if (!result.user.isVerified && !preferences.allowLoginWithNotVerifiedAccount) {
|
|
18
|
+
return { ok: false, error: 'ACCOUNT_NOT_VERIFIED', violatedPolicies: [] };
|
|
19
|
+
}
|
|
20
|
+
return { ok: true, user: result.user };
|
|
21
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { AuthMethod, AuthResult, AuthServicePort } from "@application/ports/auth-service.port.js";
|
|
2
|
+
import { PreRegisterPolicy } from "@application/ports/pre-register-policy.port.js";
|
|
3
|
+
|
|
4
|
+
export const registerUser =
|
|
5
|
+
(authService: AuthServicePort, policies: PreRegisterPolicy[]) =>
|
|
6
|
+
async (method: AuthMethod): Promise<AuthResult> => {
|
|
7
|
+
const activePolicies = await Promise.all(policies
|
|
8
|
+
.map(async policy => ({
|
|
9
|
+
isActive: await policy.isActive(method),
|
|
10
|
+
value: policy,
|
|
11
|
+
}))
|
|
12
|
+
);
|
|
13
|
+
const results = await Promise.all(activePolicies
|
|
14
|
+
.filter(policy => policy.isActive)
|
|
15
|
+
.map(async policy => ({
|
|
16
|
+
code: policy.value.code,
|
|
17
|
+
ok: await policy.value.check(method),
|
|
18
|
+
})));
|
|
19
|
+
const violdatedPolicies = results.filter(result => !result.ok);
|
|
20
|
+
if (violdatedPolicies.length > 0) {
|
|
21
|
+
return {
|
|
22
|
+
ok: false,
|
|
23
|
+
error: 'PRE_REGISTER_POLICY_VIOLATED',
|
|
24
|
+
violatedPolicies: violdatedPolicies.map(result => result.code)
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return authService.register(method);
|
|
28
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { AuthPreferencesServicePort } from "@application/ports/auth-preference-service.port.js";
|
|
2
|
+
import { AuthServicePort } from "@application/ports/auth-service.port.js";
|
|
3
|
+
import { PreRegisterPolicy } from "@application/ports/pre-register-policy.port.js";
|
|
4
|
+
import { AccountAlreadyExistsPolicy } from "@application/services/account-already-exists.service.js";
|
|
5
|
+
import { authenticateUser } from "@application/use-cases/authenticate-user.js";
|
|
6
|
+
import { registerUser } from "@application/use-cases/register-user.js";
|
|
7
|
+
import { AuthEngine } from "@root/auth-engine.js";
|
|
8
|
+
|
|
9
|
+
export class AuthEngineFactory {
|
|
10
|
+
static async create(
|
|
11
|
+
authService: AuthServicePort,
|
|
12
|
+
authPreferencesService: AuthPreferencesServicePort,
|
|
13
|
+
basePolicies: PreRegisterPolicy[],
|
|
14
|
+
): Promise<AuthEngine> {
|
|
15
|
+
const preferences = await authPreferencesService.getAuthPreferences();
|
|
16
|
+
|
|
17
|
+
const policies = [
|
|
18
|
+
...basePolicies,
|
|
19
|
+
new AccountAlreadyExistsPolicy(authService, !preferences.allowDuplicatedAccount),
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
return new AuthEngine({
|
|
23
|
+
register: registerUser(authService, policies),
|
|
24
|
+
authenticate: authenticateUser(authService, authPreferencesService),
|
|
25
|
+
logout: () => authService.logout(),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { AuthMethod, AuthResult } from "@application/ports/auth-service.port.js";
|
|
2
|
+
|
|
3
|
+
export interface AuthActions {
|
|
4
|
+
register(method: AuthMethod): Promise<AuthResult>;
|
|
5
|
+
authenticate(method: AuthMethod): Promise<AuthResult>;
|
|
6
|
+
logout(): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class AuthEngine implements AuthActions {
|
|
10
|
+
constructor(
|
|
11
|
+
private readonly useCases: AuthActions,
|
|
12
|
+
) { }
|
|
13
|
+
|
|
14
|
+
async register(method: AuthMethod): Promise<AuthResult> {
|
|
15
|
+
return this.useCases.register(method);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async authenticate(method: AuthMethod): Promise<AuthResult> {
|
|
19
|
+
return await this.useCases.authenticate(method);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async logout(): Promise<void> {
|
|
23
|
+
return await this.useCases.logout();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -1,22 +1,16 @@
|
|
|
1
1
|
export type UserStatus = "active" | "disabled" | "lockedTemporarily";
|
|
2
2
|
|
|
3
|
-
export type UserRole = "parent" | "staff" | "admin" | "student" | "teacher";
|
|
4
|
-
|
|
5
3
|
/**
|
|
6
4
|
* Domaine: compte utilisateur Sekoliko.
|
|
7
5
|
* Indépendant de toute technologie (pas de dépendance à Supabase ou React).
|
|
8
6
|
*/
|
|
9
7
|
export interface UserAccount {
|
|
10
8
|
id: string;
|
|
11
|
-
establishmentIds: string[];
|
|
12
9
|
displayName: string;
|
|
13
|
-
firstName?: string;
|
|
14
|
-
lastName?: string;
|
|
15
|
-
contactEmail: string;
|
|
16
10
|
status: UserStatus;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
failedLoginAttempts?: number;
|
|
12
|
+
lastFailedLoginAt?: Date | null;
|
|
13
|
+
isVerified?: boolean;
|
|
20
14
|
}
|
|
21
15
|
|
|
22
16
|
export const canLogin = (user: UserAccount): boolean =>
|
package/src/index.ts
CHANGED
|
@@ -2,10 +2,15 @@
|
|
|
2
2
|
* Domain
|
|
3
3
|
*/
|
|
4
4
|
export * from '@domain/user-account.js';
|
|
5
|
-
export * from '@domain/session.js';
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* Application
|
|
9
8
|
*/
|
|
10
|
-
export * from '@application/use-cases/authenticate-user.js';
|
|
11
9
|
export * from '@application/ports/auth-service.port.js';
|
|
10
|
+
export * from '@application/ports/pre-register-policy.port.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Auth Engine
|
|
14
|
+
*/
|
|
15
|
+
export * from '@root/auth-engine.factory.js';
|
|
16
|
+
export * from '@root/auth-engine.js';
|
|
@@ -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,21 @@
|
|
|
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
|
+
userExists?: (method: AuthMethod) => Promise<boolean>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function createAuthServiceMock(authService?: AuthServiceMock): AuthServicePort {
|
|
15
|
+
return {
|
|
16
|
+
authenticate: authService?.authenticate ?? vi.fn().mockResolvedValue({ ok: true, user: activeUser }),
|
|
17
|
+
logout: authService?.logout ?? vi.fn().mockResolvedValue(undefined),
|
|
18
|
+
register: authService?.register ?? vi.fn().mockResolvedValue({ ok: true, user: activeUser }),
|
|
19
|
+
userExists: authService?.userExists ?? vi.fn().mockResolvedValue(false),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -1,25 +1,72 @@
|
|
|
1
1
|
import { authenticateUser } from "@application/use-cases/authenticate-user.js";
|
|
2
|
-
import {
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
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
|
-
|
|
20
|
+
// Given
|
|
21
|
+
const authService = createAuthServiceMock({
|
|
18
22
|
authenticate: vi.fn().mockResolvedValue({ ok: false, error: 'INVALID_CREDENTIALS' }),
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { AuthMethod, AuthResultFailure } from "@application/ports/auth-service.port.js";
|
|
2
|
+
import { registerUser } from "@application/use-cases/register-user.js";
|
|
3
|
+
import { createAuthServiceMock } from "@tests/application/ports/auth-service.port.mock.js";
|
|
4
|
+
import { createPreRegisterPolicyMock } from "@tests/application/ports/pre-register-policy.port.mock.js";
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
|
|
7
|
+
describe('RegisterUser', () => {
|
|
8
|
+
it('should register a user', async () => {
|
|
9
|
+
// Given
|
|
10
|
+
const authService = createAuthServiceMock();
|
|
11
|
+
const policy = createPreRegisterPolicyMock();
|
|
12
|
+
const signUpUser = registerUser(authService, [policy]);
|
|
13
|
+
// When
|
|
14
|
+
const result = await signUpUser(
|
|
15
|
+
{ type: 'password', identifier: 'test@test.com', password: 'test' },
|
|
16
|
+
);
|
|
17
|
+
// Then
|
|
18
|
+
expect(result.ok).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should fail to register a user if a pre-register policy is violated', async () => {
|
|
22
|
+
// Given
|
|
23
|
+
const authService = createAuthServiceMock();
|
|
24
|
+
const policy = createPreRegisterPolicyMock({
|
|
25
|
+
code: 'POLICY_ERROR',
|
|
26
|
+
check: vi.fn().mockResolvedValue(false),
|
|
27
|
+
isActive: vi.fn().mockResolvedValue(true),
|
|
28
|
+
});
|
|
29
|
+
const method: AuthMethod = { type: 'password', identifier: 'test@test.com', password: 'test' };
|
|
30
|
+
const signUpUser = registerUser(authService, [policy]);
|
|
31
|
+
// When
|
|
32
|
+
const result = await signUpUser(method);
|
|
33
|
+
// Then
|
|
34
|
+
expect(policy.check).toHaveBeenCalledWith(method);
|
|
35
|
+
expect(authService.register).not.toHaveBeenCalled();
|
|
36
|
+
expect(result).toEqual({
|
|
37
|
+
ok: false,
|
|
38
|
+
error: 'PRE_REGISTER_POLICY_VIOLATED',
|
|
39
|
+
violatedPolicies: ['POLICY_ERROR']
|
|
40
|
+
} as AuthResultFailure);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should fail to register a user if active pre-register policies are violated', async () => {
|
|
44
|
+
// Given
|
|
45
|
+
const authService = createAuthServiceMock();
|
|
46
|
+
const policy1 = createPreRegisterPolicyMock({
|
|
47
|
+
code: 'POLICY_1_ERROR',
|
|
48
|
+
check: vi.fn().mockResolvedValue(false),
|
|
49
|
+
isActive: vi.fn().mockResolvedValue(false),
|
|
50
|
+
});
|
|
51
|
+
const policy2 = createPreRegisterPolicyMock({
|
|
52
|
+
code: 'POLICY_2_ERROR',
|
|
53
|
+
check: vi.fn().mockResolvedValue(false),
|
|
54
|
+
isActive: vi.fn().mockResolvedValue(true),
|
|
55
|
+
});
|
|
56
|
+
const method: AuthMethod = { type: 'password', identifier: 'test@test.com', password: 'test' };
|
|
57
|
+
const signUpUser = registerUser(authService, [policy1, policy2]);
|
|
58
|
+
// When
|
|
59
|
+
const result = await signUpUser(method);
|
|
60
|
+
// Then
|
|
61
|
+
expect(policy1.isActive).toHaveBeenCalledWith(method);
|
|
62
|
+
expect(policy2.isActive).toHaveBeenCalledWith(method);
|
|
63
|
+
expect(policy1.check).not.toHaveBeenCalled();
|
|
64
|
+
expect(policy2.check).toHaveBeenCalledWith(method);
|
|
65
|
+
expect(authService.register).not.toHaveBeenCalled();
|
|
66
|
+
expect(result).toEqual({
|
|
67
|
+
ok: false,
|
|
68
|
+
error: 'PRE_REGISTER_POLICY_VIOLATED',
|
|
69
|
+
violatedPolicies: ['POLICY_2_ERROR']
|
|
70
|
+
} as AuthResultFailure);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should register a user if the pre-register policy is deactivated', async () => {
|
|
74
|
+
// Given
|
|
75
|
+
const authService = createAuthServiceMock();
|
|
76
|
+
const policy = createPreRegisterPolicyMock({
|
|
77
|
+
isActive: vi.fn().mockResolvedValue(false),
|
|
78
|
+
});
|
|
79
|
+
const method: AuthMethod = { type: 'password', identifier: 'test@test.com', password: 'test' };
|
|
80
|
+
const signUpUser = registerUser(authService, [policy]);
|
|
81
|
+
// When
|
|
82
|
+
const result = await signUpUser(method);
|
|
83
|
+
// Then
|
|
84
|
+
expect(policy.isActive).toHaveBeenCalledWith(method);
|
|
85
|
+
expect(policy.check).not.toHaveBeenCalled();
|
|
86
|
+
expect(authService.register).toHaveBeenCalledWith(method);
|
|
87
|
+
expect(result).toEqual({ ok: true, user: { id: '1', displayName: 'test@test.com', status: 'active', isVerified: true } });
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { UserAccount } from "@domain/user-account.js";
|
|
2
|
+
|
|
3
|
+
export const activeUser: UserAccount = { id: '1', displayName: 'test@test.com', status: 'active', isVerified: true };
|
|
4
|
+
export const inactiveUser: UserAccount = { id: '1', displayName: 'test@test.com', status: 'disabled', isVerified: true };
|
|
5
|
+
export const notVerifiedUser: UserAccount = { id: '1', displayName: 'test@test.com', status: 'active', isVerified: false };
|
package/tsconfig.lib.json
CHANGED
package/tsconfig.spec.json
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
"baseUrl": ".",
|
|
5
5
|
"paths": {
|
|
6
6
|
"@domain/*": ["src/domain/*"],
|
|
7
|
-
"@application/*": ["src/application/*"]
|
|
7
|
+
"@application/*": ["src/application/*"],
|
|
8
|
+
"@infrastructure/*": ["src/infrastructure/*"],
|
|
9
|
+
"@tests/*": ["tests/*"]
|
|
8
10
|
},
|
|
9
11
|
"outDir": "./out-tsc/vitest",
|
|
10
12
|
"types": [
|
|
@@ -30,6 +32,16 @@
|
|
|
30
32
|
"tests/**/*.test.jsx",
|
|
31
33
|
"tests/**/*.spec.jsx",
|
|
32
34
|
"tests/**/*.d.ts",
|
|
35
|
+
"tests/**/*.mock.ts",
|
|
36
|
+
"tests/**/*.mock.tsx",
|
|
37
|
+
"tests/**/*.mock.js",
|
|
38
|
+
"tests/**/*.mock.jsx",
|
|
39
|
+
"tests/**/*.mock.d.ts",
|
|
40
|
+
"tests/**/*.mock.json",
|
|
41
|
+
"tests/**/*.mock.yaml",
|
|
42
|
+
"tests/**/*.mock.yml",
|
|
43
|
+
"tests/**/*.mock.xml",
|
|
44
|
+
"tests/**/*.data.ts",
|
|
33
45
|
],
|
|
34
46
|
"references": [
|
|
35
47
|
{
|
package/vite.config.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference types='vitest' />
|
|
2
|
+
import * as path from 'path';
|
|
2
3
|
import { defineConfig } from 'vite';
|
|
3
4
|
import dts from 'vite-plugin-dts';
|
|
4
|
-
import * as path from 'path';
|
|
5
5
|
import tsconfigPaths from 'vite-tsconfig-paths';
|
|
6
6
|
|
|
7
7
|
export default defineConfig(() => ({
|
|
@@ -18,6 +18,9 @@ export default defineConfig(() => ({
|
|
|
18
18
|
alias: {
|
|
19
19
|
'@domain': path.resolve(__dirname, 'src/domain'),
|
|
20
20
|
'@application': path.resolve(__dirname, 'src/application'),
|
|
21
|
+
'@infrastructure': path.resolve(__dirname, 'src/infrastructure'),
|
|
22
|
+
'@root': path.resolve(__dirname, 'src'),
|
|
23
|
+
'@tests': path.resolve(__dirname, 'tests'),
|
|
21
24
|
},
|
|
22
25
|
},
|
|
23
26
|
// Uncomment this if you are using workers.
|
package/src/domain/session.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Domaine: session utilisateur Sekoliko.
|
|
3
|
-
* Ne contient pas de détails d’implémentation (cookies, tokens, etc.).
|
|
4
|
-
*/
|
|
5
|
-
export interface Session {
|
|
6
|
-
id: string;
|
|
7
|
-
userId: string;
|
|
8
|
-
createdAt: Date;
|
|
9
|
-
lastActivityAt: Date;
|
|
10
|
-
expiresAt: Date;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const isSessionActive = (session: Session, now: Date = new Date()): boolean =>
|
|
14
|
-
now < session.expiresAt;
|
|
15
|
-
|
|
16
|
-
|