# 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()` ### 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) |