[CLAUDE] Docs: database-guide + 6 flow diagrams

docs/database/database-guide.md:
- Conventions (naming, data types, audit fields, soft delete)
- Schema hien tai (Identity tables sau migration Init) + seed 12 role + admin
- Schema planned: Phase 1 dot 2 (Supplier/Project/Department + Permission Matrix)
- Schema planned: Phase 3 (Contract + Approval + Comment + Attachment + Template + Clause + CodeSequence)
- Mermaid ERD cho tung phase
- Migration workflow (create/apply/revert)
- Index strategy + unique indexes
- Backup/restore SQL
- Common pitfalls + SQL cheatsheet

docs/flows/ — 6 flow documentation:
- README.md: index
- auth-flow.md: login/refresh/me/logout (IMPLEMENTED, sequence + edge cases + security checklist)
- permission-flow.md: Phase 1 dot 2 - Role x MenuKey x CRUD resolution + FE guard + BE policy
- contract-creation-flow.md: Phase 2 - Drafter flow chon template -> fill -> preview -> save draft
- contract-approval-flow.md: Phase 3 - state machine 9 phase chi tiet + reject flow + timeline UI
- form-render-flow.md: Phase 2 - OpenXml + ClosedXML + LibreOffice PDF convert
- sla-expiry-flow.md: Phase 3 - BackgroundService auto-approve qua SLA + warning notify

Update references:
- CLAUDE.md (root): them 2 row Tai lieu quan trong
- docs/CLAUDE.md: update project layout voi flows/ + database/
- docs/STATUS.md: log docs addition
- docs/changelog/migration-todos.md: tick Phase 0 docs items

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-04-21 11:15:28 +07:00
parent 702411fcc8
commit 49a5f57a50
12 changed files with 1982 additions and 2 deletions

212
docs/flows/auth-flow.md Normal file
View File

@ -0,0 +1,212 @@
# Auth Flow — Login / Refresh / Logout / Me
> **Status:** ✅ Implemented (Phase 1 foundation, commit `702411f`)
> **Actors:** Anyone (login) | Authenticated user (refresh, me, logout)
## 1. Tổng quan
- **Schema:** JWT Bearer (HS256) + refresh token (random 64-byte base64)
- **Access token:** 1 giờ (config `Jwt:AccessTokenExpiryMinutes`)
- **Refresh token:** 7 ngày (config `Jwt:RefreshTokenExpiryDays`)
- **Storage FE:** `localStorage` — key `solution-erp-admin-token` (fe-admin) / `solution-erp-user-token` (fe-user)
- **Truyền:** header `Authorization: Bearer <token>` (axios interceptor auto)
## 2. Login
```mermaid
sequenceDiagram
actor U as User
participant FE as Frontend<br/>(LoginPage)
participant API as SolutionErp.Api<br/>AuthController
participant M as MediatR<br/>LoginCommandHandler
participant UM as UserManager<User>
participant J as IJwtTokenService
participant DB as SQL Server
U->>FE: Nhập email + password → Submit
FE->>API: POST /api/auth/login<br/>{email, password}
API->>M: Send(LoginCommand)
M->>M: Validate (FluentValidation)<br/>email format + password min 6
alt Validation fail
M-->>API: throw ValidationException
API-->>FE: 400 ProblemDetails + errors{}
FE->>U: Toast error
end
M->>UM: FindByEmailAsync(email)
UM->>DB: SELECT FROM Users WHERE Email = ?
alt User not found OR IsActive = false
M-->>API: throw UnauthorizedException
API-->>FE: 401 "Email hoặc mật khẩu không đúng"
end
M->>UM: CheckPasswordAsync(user, password)
alt Password sai
M-->>API: throw UnauthorizedException
API-->>FE: 401
end
M->>UM: GetRolesAsync(user)
UM->>DB: SELECT Roles JOIN UserRoles
UM-->>M: roles[]
M->>J: GenerateTokensAsync(user, roles)
J->>J: Build claims (sub, email, jti, fullName, roles)
J->>J: Sign HS256 + random refresh token
J-->>M: (accessToken, refreshToken, refreshTokenExpiresAt)
M->>UM: UpdateAsync(user) — save refresh token + expiry
UM->>DB: UPDATE Users SET RefreshToken=?, RefreshTokenExpiresAt=?
M-->>API: AuthResponseDto
API-->>FE: 200 {accessToken, refreshToken, user: {id, email, fullName, roles}}
FE->>FE: localStorage.setItem('solution-erp-*-token', accessToken)<br/>+ lưu user JSON
FE->>FE: setUser(user) via AuthContext
FE->>U: Redirect /dashboard hoặc /inbox + toast success
```
**Code pointers:**
- FE: [`fe-admin/src/pages/LoginPage.tsx`](../../fe-admin/src/pages/LoginPage.tsx), [`fe-admin/src/contexts/AuthContext.tsx`](../../fe-admin/src/contexts/AuthContext.tsx)
- BE controller: [`AuthController.Login`](../../src/Backend/SolutionErp.Api/Controllers/AuthController.cs)
- Handler: [`LoginCommandHandler`](../../src/Backend/SolutionErp.Application/Auth/Commands/Login/LoginCommand.cs)
- Token gen: [`JwtTokenService`](../../src/Backend/SolutionErp.Infrastructure/Identity/JwtTokenService.cs)
## 3. Authenticated request (mọi API khác)
```mermaid
sequenceDiagram
participant FE as Frontend
participant AX as axios interceptor
participant API as API
participant JWT as JwtBearerMiddleware
participant CTRL as Controller
FE->>AX: api.get('/suppliers')
AX->>AX: Đọc localStorage token
AX->>API: GET /api/suppliers<br/>Authorization: Bearer <token>
API->>JWT: ValidateToken
alt Token invalid / expired
JWT-->>API: 401
API-->>AX: 401
AX->>AX: Clear localStorage + redirect /login
end
JWT->>API: HttpContext.User = ClaimsPrincipal
API->>CTRL: Invoke action (có [Authorize])
CTRL->>CTRL: ICurrentUser.UserId (từ claim "sub")
CTRL-->>FE: 200 data
```
**401 handling:** [`fe-admin/src/lib/api.ts`](../../fe-admin/src/lib/api.ts) interceptor catches 401 → clear localStorage → `window.location.href = '/login'`.
## 4. Refresh token
```mermaid
sequenceDiagram
participant FE as Frontend
participant API as AuthController.Refresh
participant M as RefreshTokenCommandHandler
participant UM as UserManager
participant J as IJwtTokenService
participant DB
Note over FE: Access token expired<br/>(server trả 401 từ /api/whatever)
FE->>API: POST /api/auth/refresh<br/>{refreshToken}
API->>M: Send(RefreshTokenCommand)
M->>M: Validate not empty
M->>UM: Users.First(RefreshToken == ?)
UM->>DB: SELECT
alt Not found OR RefreshTokenExpiresAt < UtcNow
M-->>API: throw UnauthorizedException
API-->>FE: 401 "Refresh token không hợp lệ hoặc đã hết hạn"
FE->>FE: Clear localStorage + redirect /login
end
M->>UM: GetRolesAsync(user)
M->>J: GenerateTokensAsync(user, roles)
J-->>M: new (access, refresh, expiry)
M->>UM: UpdateAsync — rotate refresh token
DB-->>UM: OK
M-->>API: AuthResponseDto
API-->>FE: 200 new tokens
FE->>FE: Update localStorage + retry original request
```
**⚠️ Chưa implement ở FE:** hiện tại 401 → logout luôn. Phase 1 đợt 2 sẽ thêm logic auto-refresh trong axios interceptor (retry failed request sau khi refresh thành công, queue các request song song).
## 5. /me (lấy user hiện tại)
```mermaid
sequenceDiagram
actor U
participant FE
participant API as AuthController.Me
participant M as GetMeQueryHandler
participant CU as ICurrentUser
participant UM as UserManager
U->>FE: App bootstrap (sau login)
FE->>API: GET /api/auth/me<br/>Bearer <token>
API->>M: Send(GetMeQuery)
M->>CU: UserId (from claim "sub")
alt Not authenticated
M-->>API: throw UnauthorizedException
end
M->>UM: FindByIdAsync(userId.ToString())
alt Not found
M-->>API: throw UnauthorizedException
end
M->>UM: GetRolesAsync(user)
M-->>API: UserInfoDto{id, email, fullName, roles}
API-->>FE: 200
```
**Use case:** FE dùng để refresh user info sau page reload (nếu localStorage cũ nhưng role có thể thay đổi). Hiện Phase 1 FE lấy user từ localStorage trực tiếp — Phase 1 đợt 2 sẽ gọi `/me` ở mount AuthContext để verify token + sync role.
## 6. Logout
```mermaid
sequenceDiagram
actor U
participant FE
participant API as AuthController.Logout
U->>FE: Click "Đăng xuất"
FE->>FE: logout() in AuthContext:<br/>localStorage.removeItem(token + user)<br/>setUser(null)
FE->>API: POST /api/auth/logout (optional)
API-->>FE: 204 No Content
FE->>FE: Redirect /login (via ProtectedRoute)
```
**Lưu ý:** Hiện endpoint logout chỉ trả 204. Phase 3 sẽ upgrade: invalidate refresh token trong DB + log audit event.
## 7. Edge cases
| Case | Hiện tại handle | Phase nào fix |
|---|---|---|
| Access token expired mid-request | FE 401 → logout | Phase 1 đợt 2: auto refresh |
| Refresh token expired | FE 401 → logout | OK — đúng hành vi |
| User bị disable (`IsActive=false`) sau khi đã login | Token vẫn valid tới khi expire | Phase 4: check `IsActive` trong middleware |
| Admin reset password | Refresh token cũ vẫn valid | Phase 4: invalidate refresh token khi reset password |
| Multi-device login | Ghi đè refresh token → device cũ bị logout khi cần refresh | OK cho Phase 1; Phase 4 optional: bảng `UserRefreshTokens` riêng |
| JWT key leak | Toàn bộ user có thể forge token | Phase 5: dùng user secrets prod + rotate annually |
| Clock skew giữa API + client | `ClockSkew = 1 minute` đã config | OK |
## 8. Security checklist (Phase 5 review)
- [ ] `Jwt:Secret` trong user-secrets / env var prod (không trong appsettings)
- [ ] HTTPS enforce (`RequireHttpsMetadata = true` production)
- [ ] Rate limit `/api/auth/login` (5 attempts/min/IP) — prevent brute force
- [ ] Account lockout sau N lần sai password (config `UserLockout` trong Identity)
- [ ] Password policy production (min 12 chars, unique, etc.)
- [ ] Audit log cho login success/fail
- [ ] Refresh token rotation detection (nếu dùng refresh cũ sau khi đã rotate → compromise alert)