[CLAUDE] Phase5.1/3.2: IDOR filter + SLA auto-approve job + admin password warning

IDOR filter ContractsController:
- ListContractsQueryHandler + ICurrentUser: non-admin chi thay HD minh la Drafter hoac role eligible phase hien tai
- GetContractQueryHandler + ICurrentUser: throw ForbiddenException neu truy cap HD khong lien quan
- GetEligiblePhases() internal static trong ListContractsQueryHandler — mirror GetMyInboxQueryHandler.PhaseActorRoles (Drafter/DeptManager → DangSoanThao/DangDamPhan/DangInKy, ProjectManager+PRO+CCM+FIN+ACT+EQU → DangGopY, CostControl → DangKiemTraCCM, Director+AuthorizedSigner → DangTrinhKy, HrAdmin → DangDongDau)

SLA Expiry BackgroundService (Phase 3 iteration 2 partial):
- Infrastructure/HostedServices/SlaExpiryJob MOI: BackgroundService moi 15 phut (delay 30s startup)
- Query Contracts WHERE SlaDeadline < UtcNow AND Phase NOT IN (DaPhatHanh, TuChoi)
- Map phase → next (happy path). Goi IContractWorkflowService.TransitionAsync voi actorUserId=null + Decision=AutoApprove + comment 'AUTO: het SLA phase X (Nh qua han)'
- Try-catch tung contract, 1 fail khong block batch
- Log structured: 'SlaExpiryJob: auto-approved contract {Id} {From} → {To}'
- Package Microsoft.Extensions.Hosting added to Infrastructure
- DI register AddHostedService<SlaExpiryJob>

Admin password warning (Phase 5.1):
- DbInitializer.WarnDefaultAdminPasswordAsync: check CheckPasswordAsync voi AdminPassword default → log WRN '⚠️  Admin user vẫn dùng password mặc định. ĐỔI NGAY trong production!'
- Chain vao InitializeAsync sau cac seed

E2E verified:
- Admin GET /contracts → total 1 (see all)
- Drafter GET /contracts → total 0 (IDOR filter, chua tao HD nao)
- API startup log: '⚠️  Admin user admin@solutionerp.local vẫn dùng password mặc định'
- Build + TS check → pass

Docs:
- STATUS.md: Phase 5.1 hau nhu xong (IDOR + admin warning + SLA job tick), cumulative BE 3900 LOC
- migration-todos.md: tick Phase 5.1 IDOR + admin warning, Phase 3 iter 2 SlaExpiryJob + E2E non-admin + admin warning
- session log 2026-04-21-1730-idor-sla-job.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-04-21 13:15:14 +07:00
parent 11e61c9c39
commit 1b5ef2ed51
8 changed files with 267 additions and 19 deletions

View File

@ -0,0 +1,77 @@
# Session 2026-04-21 17:30 — IDOR filter + SLA auto-approve job + admin warning
**Dev:** Claude (Opus 4.7)
**Duration:** ~45m
**Base commit:** `11e61c9`
## Làm được
### Chunk X — IDOR filter ContractsController
- `ListContractsQueryHandler`: thêm `ICurrentUser` dep. Non-admin user chỉ thấy:
- HĐ mình là `DrafterUserId` (người tạo), HOẶC
- HĐ có `Phase ∈ eligiblePhases` (role eligible xử lý phase đó)
- `GetContractQueryHandler`: thêm IDOR guard — non-admin truy cập HĐ không liên quan → `ForbiddenException`
- `GetEligiblePhases()` helper `internal static` trong `ListContractsQueryHandler` — mirror `GetMyInboxQueryHandler.PhaseActorRoles`, shared qua assembly
### Chunk Y — SLA auto-approve BackgroundService
- `Infrastructure/HostedServices/SlaExpiryJob.cs` MỚI:
- `BackgroundService` chạy mỗi **15 phút** (delay 30s khi app start)
- Query `Contracts WHERE SlaDeadline < UtcNow AND Phase NOT IN (DaPhatHanh, TuChoi)`
- Gọi `IContractWorkflowService.TransitionAsync` với `actorUserId=null`, `Decision=AutoApprove`
- Map phase → next (happy path): DangSoanThao → DangGopY → … → DangDongDau → DaPhatHanh
- Log structured: `auto-approved contract {Id} {From} → {To}`
- Try-catch từng contract — 1 fail không block cả batch
- NuGet: `Microsoft.Extensions.Hosting` (add vào Infrastructure)
- DI register `services.AddHostedService<SlaExpiryJob>()`
### Admin password warning (Phase 5.1)
- `DbInitializer.WarnDefaultAdminPasswordAsync`: check `CheckPasswordAsync` với default "Admin@123456" → log `WRN` warning force đổi prod
- Thêm vào chain `InitializeAsync`
- Test: API khởi động → thấy log `⚠️ Admin user 'admin@solutionerp.local' vẫn dùng password mặc định. ĐỔI NGAY trong production!`
## E2E verified
```bash
# Admin
GET /api/contracts → total: 1 (see all)
# Drafter (test.drafter@solutionerp.local)
GET /api/contracts → total: 0 (IDOR filter — drafter chưa tạo HĐ nào)
# SlaExpiryJob
Start API → log "SlaExpiryJob: no expired contracts" sau ~15 min (vì SLA của HĐ test là +7d)
Admin warning log xuất hiện: "⚠️ Admin user vẫn dùng password mặc định..."
# Build + TS: pass
```
## Handoff cho session tiếp theo
### Còn lại Phase 5.1 (nhỏ)
- [ ] Dependencies scan vào CI workflow (`dotnet list package --vulnerable --include-transitive`, `npm audit --audit-level=high`)
- [ ] BE Roles CRUD (custom role) — optional, 12 role seed đủ dùng
### Phase 3 iteration 2 còn
- [ ] Warning notification khi còn 20% SLA (track `SlaWarningSent` flag)
- [ ] Email notification (MailKit) khi chuyển phase
- [ ] In-app notification (DB-backed + polling) — cần table Notifications
- [ ] Upload attachment endpoint + FE multipart
- [ ] RowVersion optimistic concurrency
### Phase 5 deploy thật
Chờ Gitea URL để push + CI/CD test.
## Thông số cumulative
| | P5.1 prev | **This session** |
|---|---:|---:|
| BE LOC | ~3700 | **~3900** (+SlaExpiryJob 90 + IDOR + admin warning) |
| API endpoints | ~42 | 42 |
| HostedServices | 0 | **1** (SlaExpiryJob) |
| Commits | 10 | **11** (sắp) |