Commit a37ad8c9 authored by Lead VietProDev's avatar Lead VietProDev

docs(plans): full project roadmap from scan + user-confirmed decisions

Comprehensive scan of backend (40 migrations, 64 models, OIDC provider,
multi-pool service, audit service) + 2 demo apps (project-a, project-b)
+ cross-project SSO analysis revealed 8 critical gaps and 7 pre-existing
TS errors that block end-to-end testing.

Key findings:
- validateCredentials is still a TODO stub (login does not work yet)
- oidcAdapterService is missing findSession/upsertSession (session not persisted)
- No prompt=none handling (silent SSO impossible)
- Cookie domain .meucorp.com does not match localhost pattern
- findAccount returns only static { sub } (userinfo has no claims)
- 7 pre-existing TS errors in verify-email, resend-verification, server.ts
- 18 untracked files from stale sso-vietprodev-old stash (cleanup needed)
- .env points to port 5433 (legacy from HA cluster) but Docker only maps 5432

User-confirmed decisions (locked in PLANS.md section 4):
1. Two PostgreSQL instances from Phase 0 (main:5432, backup:5433)
2. Cross-project silent SSO via prompt=none (OIDC standard) - rejects
   subdomain pattern as it would require refactor on production deploy
3. Drop old vietprodev_sso database after SQL backup safety net
4. Docker credentials simplified to sso/sso (instead of postgres/@dmin123)
5. PostgreSQL 17-alpine for both containers (stable, well-documented)
6. Clean up 18 untracked files after diff-verify against stash backup

PLANS.md contains:
- 5 phases (0-4) totaling 24-33h
- Architecture diagram (main + backup + mongo + redis + minio)
- 4 ADRs (OIDC adapter, audit destination, SSO mechanism, port layout)
- File touch list per phase
- Risk register with mitigations
- Status tracking table

Refs: silent SSO, prompt=none, OIDC, cross-project session,
pre-existing TS errors, PostgreSQL HA, MongoDB audit
Co-authored-by: 's avatarCursor <cursoragent@cursor.com>
parent 66181293
# PLANS — SSO VietProDev
> Roadmap tổng thể dự án: kiến trúc, hướng đi, refactor, cleanup, test plan.
>
> **Trạng thái:** DRAFT (chờ user confirm trước khi implement)
> **Ngày tạo:** 2026-06-18
> **Cập nhật lần cuối:** 2026-06-18
---
## 0. Bối cảnh
### Hiện trạng (từ scan 2026-06-18)
| Thành phần | Trạng thái |
|------------|------------|
| Backend template (1-5 phases) | ✅ Đã merge vào `develop` |
| OIDC provider + adapter | ✅ Có nhưng **thiếu session storage** (findSession, upsertSession) |
| Email verification (Phase 3) | ✅ Done trong session trước, 8/8 unit test pass |
| OIDC `validateCredentials` | ❌ Vẫn là TODO stub — login thật chưa hoạt động |
| `prompt=none` (silent SSO) | ❌ Chưa implement |
| Cross-project session sharing | ❌ Cookie domain không khớp localhost pattern |
| Remember consent | ❌ Mỗi lần phải re-approve scopes |
| Front-channel / back-channel logout | ❌ Chưa enable |
| Pre-existing TS errors | ❌ 7 errors: `verify-email.ts` (3), `resend-verification.ts` (3), `server.ts` (2) |
| `.env` port mismatch | ❌ Đang trỏ `5433` (cũ) nhưng Docker chỉ map `5432` |
| 18 untracked files cũ (từ stash) | ⚠️ Rác — cần dọn |
| `MultiPoolService` auto-load | ❌ `autoLoadPools()` method bị reference nhưng không tồn tại |
### Ràng buộc kiến trúc (từ user)
1. **1 DB chính** (PostgreSQL 18) — `sso`
2. **1 DB backup** (PostgreSQL 18) — `sso_backup` — load balancing với DB chính
3. **1 MongoDB**`sso_audit` cho audit logs
4. **1 Redis** (optional) — cache, rate limit, queue
5. **2 demo apps**: project-a (port 4001), project-b (port 4002)
6. **SSO** ở port 3001 + Swagger UI để check API
### Luồng nghiệp vụ mong muốn (từ user)
```
1. User mở project-a (localhost:4001)
2. Click "Login with SSO" → redirect SSO /oauth/authorize
3. SSO hiển thị login page → user nhập credentials
4. SSO trả code → project-a đổi code lấy token → vào profile
5. User mở project-b (localhost:4002) trong tab mới
6. project-b tự động login (silent SSO nhờ OIDC session đã share) → vào profile
7. User logout project-b → SSO session bị xóa → project-a cũng logout (SLO)
```
### Yêu cầu đầu tiên (từ user)
> "Trước hết hiện tại cần file triệt để các lỗi bug ban đầu cho dự án clean"
→ Phase 0 (cleanup + setup) phải chạy được, server start được, migration chạy được, swagger mở được. **Cross-project silent SSO là Phase sau**.
---
## 1. Kiến trúc mục tiêu
```
┌──────────────────────────────────────────────────────────────────┐
│ Browser (User) │
└──────────┬───────────────────────────────────┬──────────────────┘
│ │
▼ ▼
┌───────────────┐ ┌───────────────┐
│ project-a │ │ project-b │
│ :4001 │ │ :4002 │
│ (RP Client) │ │ (RP Client) │
└──────┬────────┘ └──────┬────────┘
│ redirect (auth code) │ redirect (auth code)
│ exchange (token) │ exchange (token)
│ userinfo (Bearer) │ userinfo (Bearer)
▼ ▼
┌──────────────────────────────────────────────────────┐
│ SSO Backend (sso-vietprodev) │
│ http://localhost:3001 │
│ ┌──────────────────────────────────────────────┐ │
│ │ Express │ │
│ │ ├── /oauth/* (oidc-provider) │ │
│ │ ├── /oidc/interaction/* (custom UI) │ │
│ │ ├── /api/v1/* (REST) │ │
│ │ ├── /swagger/index (Swagger UI) │ │
│ │ └── /health (healthcheck) │ │
│ └──────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────┐ │
│ │ OIDC Provider (oidc-provider v9) │ │
│ │ ├── Authorization / Token / UserInfo / JWKS │ │
│ │ ├── Session storage (Postgres) │ │
│ │ ├── Adapter: oidc-provider-adapter-sequelize │ │
│ │ └── features: devInteractions=false, │ │
│ │ rpInitiatedLogout=true, │ │
│ │ introspection=true, │ │
│ │ revocation=true │ │
│ └──────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────┐ │
│ │ MultiPoolService │ │
│ │ ├── Pool: 'main' → postgres-main :5432 │ │
│ │ ├── Pool: 'backup' → postgres-backup :5433 │ │
│ │ └── Load balancing: round-robin / fail-over │ │
│ └──────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────┐ │
│ │ AuditLoggerService │ │
│ │ └── Strategy: direct → MongoDB (port 27017) │ │
│ └──────────────────────────────────────────────┘ │
└────┬──────────────────┬───────────────────┬────────────┘
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ postgres│ │ postgres│ │ mongo │
│ -main │ │ -backup │ │ 7 │
│ :5432 │ │ :5433 │ │ :27017 │
│ db: sso │ │db: sso_ │ │ db: │
│ │ │ backup │ │sso_audit│
└─────────┘ └─────────┘ └─────────┘
│ audit logs
(write async, retry)
```
### Container layout (Docker)
| Container | Image | Port host | Port container | DB/User | Volume |
|-----------|-------|-----------|----------------|---------|--------|
| sso-postgres-main | postgres:17-alpine | 5432 | 5432 | `sso` / `sso` | `pg_main_data` |
| sso-postgres-backup | postgres:17-alpine | 5433 | 5432 | `sso_backup` / `sso` | `pg_backup_data` |
| sso-mongo | mongo:7 | 27017 | 27017 | db: `sso_audit` | `mongo_data` |
| sso-redis | redis:7-alpine | 6379 | 6379 | (none) | `redis_data` |
| sso-minio | minio/minio:latest | 9000-9001 | 9000-9001 | `minioadmin` | `minio_data` |
| sso-app-dev | (Dockerfile target=development) | 3001 | 3001 | — | bind mount |
> **Lý do dùng postgres:17-alpine** thay vì postgres:18: hiện tại DB cũ ở 16, ảnh mới có thể là 18. Đồng bộ 17 cho dev + 18 cho prod là hợp lý. Tuy nhiên user nói ảnh có PostgreSQL 18 → sẽ xác nhận lại.
### Load balancing giữa main + backup
2 chiến lược (chọn 1 trong Phase setup):
- **A. Round-robin read + write-through primary**: Mọi write đi vào `main`. Read có thể đi `main` hoặc `backup` (round-robin). Backup chỉ là "warm standby" để query reporting.
- **B. Failover**: Main là primary, backup chỉ active khi main chết. Health check mỗi 30s.
**Đề xuất Phase 1 dùng B (failover)** đơn giản hơn. Phase 2+ có thể chuyển sang A nếu cần scale read.
### Cookie domain cho localhost dev
Vì project-a (4001) và project-b (4002) cùng host (`localhost`) nhưng khác port → cookie KHÔNG thể share giữa 2 port trên cùng domain (browsers tách cookie theo `(domain, path, name)`).
**Cách giải quyết**: Dùng **subdomain** pattern:
- `sso.localhost:3001` — SSO backend (cần `/etc/hosts` mapping `sso.localhost``127.0.0.1`)
- `a.localhost:4001` — project-a
- `b.localhost:4002` — project-b
- Cookie domain: `.localhost` (browsers nhận ra đây là parent domain, share giữa `*.localhost`)
Hoặc đơn giản hơn Phase đầu: **mỗi project tự lưu session riêng****project-b chủ động gọi SSO check session** qua `prompt=none` (silent SSO). User mở project-b → tự gọi `GET /oauth/authorize?prompt=none` → SSO check cookie → nếu có session thì trả code ngay (no UI).
**Đề xuất Phase 1 dùng cách 2** (silent SSO qua prompt=none). Phase 2+ có thể switch sang subdomain nếu cần SSO logout chéo.
---
## 2. Phases (Roadmap)
> Mỗi phase có **goal rõ ràng**, **acceptance criteria**, **out of scope**, **rollback plan**.
### Phase 0 — Cleanup & Foundation ⏱ 2-3h
**Mục tiêu:** Dự án compile clean, môi trường dev chạy được, Swagger mở được.
**Tasks:**
- [ ] **Backup DB cũ** (safety net): `pg_dump vietprodev_sso > backup_2026-06-18.sql`
- [ ] **Drop DB cũ** + tạo DB mới: `sso` + `sso_backup`
- [ ] Update `docker-compose.yml`:
- `postgres``postgres-main` (port 5432)
- Thêm `postgres-backup` (port 5433)
- Cùng image: `postgres:17-alpine`, cùng network `sso-network`
- User/password: `sso` / `sso` cho cả 2
- [ ] Cập nhật `.env`:
- `DB_PORT=5432` (sửa từ 5433)
- `DB_USER=sso`, `DB_PASSWORD=sso`
- `DB_CONNECTION_STRING=postgresql://sso:sso@localhost:5432/sso`
- `SSO_LOGIN_BACKUP_URL=postgresql://sso:sso@localhost:5433/sso_backup`
- `DB_READ_HOST=localhost`, `DB_READ_PORT=5433` (cho MultiPoolService đọc từ backup)
- Update comment examples ở line 58-59 dùng `sso` thay vì `postgres`
- [ ] Fix pre-existing TS errors (7 errors):
- `controllers/api/v1/auth/{verify-email,resend-verification}.ts` → thêm 6 schemas vào `contracts/auth/schema.ts`: `VerifyEmailQuerySchema`, `VerifyEmailResponseDataSchema`, `VerifyEmailResponseData`, `ResendVerificationBodySchema`, `ResendVerificationResponseDataSchema`, `ResendVerificationResponseData`
- `server.ts` line 451, 626 → remove hoặc implement `MultiPoolService.autoLoadPools()` (sẽ implement ở Phase 3, nên Phase 0 chỉ remove call tạm)
- [ ] Dọn 18 file untracked cũ (codebase sso-vietprodev-old):
- `db_check.js`, `guidelines/`, `postman/`, `secrets/`, `sql/migrations/037-039`, `src/contracts/admin/`, `src/contracts/oidc/`, `src/controllers/admin/`, `src/middlewares/admin-api-key.ts`, `src/oidc/jwksService.ts`, `src/oidc/views/check-email.hbs`, `src/providers/ClientProvider.ts`, `src/services/admin/`, `src/types/`
- Verify trước khi xóa: diff với `/tmp/sso-stash-backup/files.txt` xem có code hữu ích không
- Dùng `git clean -fd` cho untracked (KHÔNG drop stash)
- [ ] Verify `npx tsc --noEmit` = 0 errors
- [ ] Verify `docker compose up -d postgres-main postgres-backup mongo redis minio` chạy thành công
- [ ] Verify `pnpm migrate` apply hết 40 migrations
- [ ] Verify `pnpm run dev` start được + `curl http://localhost:3001/health` 200
- [ ] Verify Swagger: mở `http://localhost:3001/swagger/index` thấy UI
**Acceptance criteria:**
- `npx tsc --noEmit` exit 0
- 6 containers running, all healthy (postgres-main, postgres-backup, mongo, redis, minio, [optional: app-dev])
- `pnpm migrate` apply hết 40 migrations
- Server log `[OK] Listening on port 3001`
- `curl http://localhost:3001/health` → 200
- Swagger mở tại `http://localhost:3001/swagger/index` với danh sách endpoint
- `.env` không còn dòng nào reference port 5433 sai
- Working tree clean (không còn untracked cũ)
**Out of scope:** Implement OIDC login thật, silent SSO, full HA, audit MongoDB.
**Rollback:**
- Code: `git checkout develop -- .` + restore files từ `git stash show stash@{0}` nếu cần
- DB: `psql < backup_2026-06-18.sql` để restore data cũ (nếu đã backup)
---
### Phase 1 — Wire OIDC Login (REST + OIDC) ⏱ 4-6h
**Mục tiêu:** User có thể login qua SSO UI, nhận code, đổi lấy token, gọi userinfo.
**Tasks:**
- [ ] Wire `validateCredentials` trong `oidcInteractionsController.ts`:
- Dùng `UserProvider.findByEmail` + `UserAuthProvider.findByUserId` + `PasswordService.verifyPassword`
- Check `user.status` (block nếu `pending_verification` / `inactive` / `suspended`)
- Brute-force: dùng `UserAuth.login_attempts` + `locked_until`
- [ ] Verify `POST /api/v1/auth/register` đã hoạt động (test bằng curl từ RUN.md)
- [ ] Verify `GET /api/v1/auth/verify-email?token=...` flip `status='active'`
- [ ] Verify `POST /api/v1/auth/login` trả 200 + tokens
- [ ] Test OIDC flow:
- GET `http://localhost:4001` → click "Login" → redirect SSO
- SSO login page → nhập `admin@vietprodev.com` / `Vietpro@123`
- SSO consent page → approve
- Redirect về project-a `/auth/callback` với code
- Project-a exchange code → token → userinfo → render profile
- [ ] Fix bug trong `oidcAdapterService`: thiếu `findSession`, `upsertSession`, `destroySession` (oidc-provider yêu cầu)
- [ ] Add 5 unit tests cho `validateCredentials` (mocked UserProvider)
**Acceptance criteria:**
- Login OIDC thành công, project-a render được profile với `user.email`
- Logout project-a xóa session SSO
- `pnpm test` tăng thêm 13/13 tests pass
**Out of scope:** Silent SSO (cross-project), remember consent, SLO.
**Rollback:** `git reset --hard 9ee96c4` (commit Phase 3 cũ).
---
### Phase 2 — Cross-Project Silent SSO ⏱ 6-8h
**Mục tiêu:** Project-b tự động login khi user đã SSO ở project-a.
**Tasks:**
- [ ] Implement `prompt=none` handling trong `oidcInteractionsController.ts`:
- Trong `GET /:uid`, check `details.prompt.name === 'none'`
- Nếu có session (cookie `_session_resume` còn hạn) → return code ngay, không render UI
- Nếu không có session → return `interaction_required` error theo OIDC spec
- [ ] Setup cookie domain `.localhost` cho OIDC session cookie (config trong `oidcService.ts`)
- [ ] Implement đầy đủ `oidcAdapterService` session methods:
- `findSession`, `getSession`, `upsertSession`, `destroySession`
- Theo spec oidc-provider SessionAdapter
- [ ] Update `findAccount` để load claims thật từ `User` + `UserAuth` (không chỉ `{ sub }`)
- [ ] Update `oidcService.ts` features:
- `resourceIndicators: { defaultResource: () => '...' }` (nếu cần)
- `clientBasedCORS: true` (cho phép CORS cho client từ origin khác nhau)
- [ ] Update `oidcService.ts` cookies: thêm `domain: '.localhost'` cho `long``short`
- [ ] Add helper `addSessionCookie()` trong `oidcInteractionsController.ts` (set cookie cross-project)
- [ ] Update project-a/server.js và project-b/server.js:
- Thêm `/auth/silent-login` route: gọi `GET {SSO}/oauth/authorize?prompt=none&...`
- Khi load `/`, check session → nếu chưa có thì silent-login trước
- [ ] Add 5 integration tests cho silent SSO flow
**Acceptance criteria:**
- Mở project-a → login → mở project-b tab mới → tự động có profile (không cần nhập lại)
- Logout project-a → project-b session invalid → redirect login
- Cookies: `_session` cookie có domain `.localhost`, share được giữa `localhost:4001``localhost:4002`
**Out of scope:** Front-channel logout UI, back-channel logout, sub-domain based session.
**Rollback:** Revert `oidcService.ts`, `oidcInteractionsController.ts`, `oidcAdapterService.ts` về commit trước.
---
### Phase 3 — HA: Main + Backup + Load Balancing ⏱ 8-10h
**Mục tiêu:** MultiPoolService xử lý 2 DBs với failover, health check, audit logs sang MongoDB.
**Tasks:**
- [ ] Implement `MultiPoolService.autoLoadPools()`:
- Đọc từ table `project_db_connections` (đã có schema từ migration 037)
- Tạo pool cho main (`name='main'`) + backup (`name='backup'`) + project DBs
- Cache config 5 phút
- [ ] Health check mỗi 30s cho cả main + backup
- [ ] Implement fail-over logic:
- Khi `main.healthCheck()` fail → đánh dấu `degraded`
- Write queries auto-redirect sang `backup` khi main down
- Background task retry main mỗi 60s
- [ ] Audit logs:
- Mặc định ghi vào MongoDB
- Add `MongoAuditRepository` (đã có sẵn 1 phần trong `audit/auditRepository.ts`)
- Test: write audit → check MongoDB
- [ ] Thêm env vars:
- `DB_MAIN_*` (riêng main, thay vì `DB_*`)
- `DB_BACKUP_*` (riêng backup)
- Giữ backward compat: nếu `DB_*` set mà `DB_MAIN_*` không thì dùng `DB_*` cho main
- [ ] Test failover: stop container `postgres-main` → server vẫn serve write (qua backup)
- [ ] Test audit: trigger login → check MongoDB có log mới
**Acceptance criteria:**
- `MultiPoolService` có 2 pools registered: `main` + `backup`
- Stop main → write vẫn succeed (qua backup)
- MongoDB có audit log mỗi login attempt
**Out of scope:** Read load balancing (round-robin), read replica, streaming replication.
**Rollback:** `MultiPoolService` reverts to single pool (current behavior).
---
### Phase 4 — Production Readiness ⏱ 4-6h
**Mục tiêu:** Dự án sẵn sàng cho staging/production.
**Tasks:**
- [ ] Add CSRF protection cho register/login forms
- [ ] Add rate limiting (dùng Redis): max 5 login attempts / 5 min / IP
- [ ] Add audit log retention: 365 ngày (đã có `AUDIT_RETENTION_DAYS`)
- [ ] Add HTTPS redirect (production mode)
- [ ] Generate production secrets (rotation script)
- [ ] Add OpenAPI spec generator cho mọi REST endpoint
- [ ] Update PROGRESS.md + RUN.md với final state
- [ ] Tag release: `v1.0.0`
- [ ] Push to remote
**Acceptance criteria:**
- `pnpm run build:prod` exit 0
- `docker compose -f docker-compose.yml up` chạy được cho production profile
- All 8 checklist items trong RUN.md §6.5.6 pass
**Out of scope:** K8s manifests, CI/CD pipeline, monitoring/alerting.
**Rollback:** Git tag + revert commit.
---
## 3. Architecture Decisions
### ADR-001: Adapter OIDC = Custom Sequelize (không dùng `oidc-provider-adapter-sequelize`)
**Context:** Có 2 options:
- A. Dùng official `oidc-provider-adapter-sequelize` package
- B. Custom adapter (`oidcAdapterService.ts` hiện tại)
**Decision:** B (custom), vì:
- Schema custom (table `oidc_grants`, `oidc_clients`) đã được define trong migration 035, 036
- Cần control chi tiết để support multi-tenant + audit
**Consequences:**
- (+) Full control + custom queries dễ
- (-) Phải tự maintain — đã có bug thiếu `findSession`/`upsertSession` (Phase 2 sẽ fix)
- (-) Phải tự test compatibility với mỗi version `oidc-provider` upgrade
### ADR-002: Audit log = MongoDB (không Postgres)
**Context:** Có 2 options:
- A. Ghi audit vào Postgres (table `audit_logs` đã có)
- B. Ghi audit vào MongoDB (collection trong `sso_audit`)
**Decision:** B (MongoDB) cho audit high-volume, A (Postgres) cho sensitive audit (auth events). Lý do:
- Audit volume cao (mỗi request có thể có 1-3 audit rows) → Mongo write nhanh hơn
- Mongo dễ scale horizontal
- Postgres giữ data có relation (user, role) — audit móc nối được
**Consequences:**
- (+) Audit không block Postgres write
- (-) Cần monitor MongoDB health riêng
- (-) Phải có strategy fallback nếu Mongo chết (queue retry, đã có trong `audit/auditLoggerService.ts`)
### ADR-003: Silent SSO dùng `prompt=none` (không subdomain)
**Context:** Có 2 options:
- A. Dùng subdomain (`a.localhost:4001`, `b.localhost:4002`) + cookie `.localhost`
- B. Dùng `prompt=none` — mỗi project tự check session khi load
**Decision:** B (`prompt=none`), vì:
- Phase 2 đơn giản hơn, không cần `/etc/hosts` config
- Phù hợp với OIDC standard (nhiều SaaS dùng cách này)
- Browser support `prompt=none` tốt (Chrome, Firefox, Safari)
- Subdomain có thể add sau nếu cần SLO chéo
**Consequences:**
- (+) Đơn giản, dùng được luôn
- (-) Project phải gọi `/auth/silent-login` trước khi render UI
- (-) SLO chéo (logout project-a → project-b cũng logout) cần thêm logic
### ADR-004: Database port = 5432 (main) + 5433 (backup)
**Context:** Docker compose hiện chỉ có 1 postgres ở 5432. Cần 1 postgres nữa cho backup.
**Decision:**
- Main: 5432 (host) → 5432 (container)
- Backup: 5433 (host) → 5432 (container)
- Lý do: convention Postgres standard, dễ nhớ
**Consequences:**
- (+) Nhất quán với convention
- (-) Port 5433 bị conflict nếu user từng có local Postgres ở 5433 (cũ) → fix `.env` thôi
---
## 4. Open Questions & Decisions (đã chốt 2026-06-18)
### Q1: DB strategy ✅
**Decision:** Phase 0 có **2 instance PostgreSQL** ngay (main + backup). Lý do: tránh phải re-architect khi sang Phase 3, đỡ tốn công gấp đôi.
### Q2: Cross-project silent SSO ✅
**Decision:** Hướng **A — `prompt=none` (OIDC standard)**.
**Lý do chọn A (đã phân tích chi tiết):**
1. OIDC standard, tương thích mọi OIDC client
2. Không phá pattern deploy: localhost:3001/4001/4002 giữ nguyên, deploy thật cũng giữ nguyên (chỉ đổi URL trong `.env`)
3. Chrome/Firefox/Safari/Edge support tốt
4. Auth0/Okta/Keycloak đều dùng cách này → kinh nghiệm industry
5. Đơn giản, ít code change
**Hướng B (subdomain `a.localhost`, `b.localhost`) bị loại** vì:
- Cần `/etc/hosts` config mỗi dev
- Cần refactor lại khi deploy thật (subdomain pattern trên production)
- Phá structure hiện tại
**Kết hợp mở rộng (Phase 4):**
- Thêm `frontchannel` logout để SLO chéo: SSO logout → redirect lại các client đang mở
- Có thể thêm `backchannel` logout (production-only, cần HTTPS)
### Q3: DB cũ `vietprodev_sso` ✅
**Decision:** Hướng **1 — Drop hoàn toàn, dùng DB mới**.
**Quy trình an toàn:**
1. Backup SQL trước (safety net): `pg_dump ... > backup_2026-06-18.sql`
2. Drop DB cũ: `DROP DATABASE vietprodev_sso;`
3. Drop DB backup cũ: `DROP DATABASE vietprodev_sso_backup;`
4. Tạo DB mới: `CREATE DATABASE sso;` + `CREATE DATABASE sso_backup;`
5. Chạy `pnpm migrate` (40 files)
6. Tạo admin user qua script trong RUN.md §2.5
**Lý do:**
- Template mới clean hơn (đã drop ở migration 038)
- Schema cũ có thể không tương thích (bcrypt secret, status enum khác, v.v.)
- Tránh technical debt "data lai"
- Nếu cần rollback: restore từ SQL backup
**Out of scope:** Viết migration script convert data cũ → mới. Nếu sau này cần, có thể viết riêng.
### Q4: Database credential ✅
**Decision:**
- Docker: user `sso` / password `sso` (đơn giản, dễ nhớ cho dev)
- File `.env` chính: `DB_USER=postgres` / `DB_PASSWORD=@dmin123` (match user setup, nhưng **cần drop volume cũ trước**)
- Production: secrets sẽ generate random qua `pnpm run setup:prod` (script sẽ viết ở Phase 4)
**Đề xuất:** Phase 0 dùng `sso` / `sso` cho Docker + update `.env` tương ứng. Đơn giản hơn.
### Q5: PostgreSQL version cho Docker ✅
**Decision:** `postgres:17-alpine` cho cả main + backup.
- Lý do: 17 ổn định, tương thích cả data cũ (16) lẫn mới (18), tài liệu nhiều
- Out of scope: dùng 18-alpine (match DB user 18) vì 18 mới ra, ít tài liệu
- Có thể upgrade lên 18 ở Phase 4 nếu cần
### Q6: 18 untracked files cũ (codebase sso-vietprodev-old) ✅
**Decision:** **Xóa hết** sau khi verify an toàn.
**Quy trình:**
1. Diff từng file với stash backup đã lưu (`/tmp/sso-stash-backup/files.txt`)
2. Nếu file không cần → xóa (`git clean -f` cho tracked, manual xóa cho untracked)
3. Stash `@{0}` → giữ (không drop) để backup, hoặc drop sau khi chắc chắn
**Lý do:** Đây là codebase cũ từ `sso-vietprodev-old` — KHÔNG liên quan đến dự án SSO mới. Tất cả logic SSO mới đã có trong template (40 migrations + services + providers).
---
## 5. File Touch List (dự kiến)
| File | Phase 0 | Phase 1 | Phase 2 | Phase 3 | Phase 4 |
|------|---------|---------|---------|---------|---------|
| `.env` | ✅ port fix | — | — | ✅ thêm DB_MAIN/BACKUP | — |
| `docker-compose.yml` | ✅ 2 postgres | — | — | — | — |
| `docker-compose.ha.yml` | — | — | — | ✅ simplified (chỉ giữ backup) | — |
| `src/server.ts` | ✅ fix 2 TS errors | — | — | — | — |
| `src/oidc/oidcService.ts` | — | — | ✅ cookie domain | — | ✅ https redirect |
| `src/oidc/oidcInteractionsController.ts` | — | ✅ wire validateCredentials | ✅ prompt=none | — | — |
| `src/oidc/oidcAdapterService.ts` | — | — | ✅ findSession/upsertSession | — | — |
| `src/contracts/auth/schema.ts` | ✅ thêm 6 schemas | — | — | — | — |
| `src/services/database/multiPoolService.ts` | — | — | — | ✅ autoLoadPools | — |
| `src/audit/auditLoggerService.ts` | — | — | — | ✅ Mongo audit | — |
| `project-a-demo/server.js` | — | — | ✅ silent-login route | — | — |
| `project-b-demo/server.js` | — | — | ✅ silent-login route | — | — |
| `tests/unit/services/auth/validateCredentials.test.ts` | — | ✅ new | — | — | — |
| `tests/integration/silent-sso.test.ts` | — | — | ✅ new | — | — |
| `PROGRESS.md` | ✅ update | ✅ update | ✅ update | ✅ update | ✅ final |
| `RUN.md` | — | — | — | — | ✅ final |
| `PLANS.md` | ✅ update | ✅ update | ✅ update | ✅ update | ✅ final |
**Total estimated lines changed:**
- Phase 0: ~150 lines (env, docker, contracts)
- Phase 1: ~200 lines (controller wire + tests)
- Phase 2: ~400 lines (prompt=none, adapter, project silent-login)
- Phase 3: ~300 lines (MultiPool + audit)
- Phase 4: ~100 lines (CSRF, rate limit, docs)
- **Total: ~1150 lines**
---
## 6. Risks & Mitigations
| Risk | Impact | Mitigation |
|------|--------|------------|
| Pre-existing TS errors có thể là dấu hiệu code chưa sẵn sàng | Medium | Fix ngay Phase 0 trước khi build feature |
| DB cũ `vietprodev_sso` có data user thật | High | Không drop — backup file SQL hoặc giữ volume Docker cũ |
| OIDC session không share được vì cookie domain | High | Plan B: dùng `prompt=none` (ADR-003) |
| 18 file untracked cũ có thể chứa code hữu ích | Low | Diff từng file với stash backup trước khi xóa |
| `MultiPoolService.autoLoadPools` thiếu | Medium | Implement hoặc remove call tùy nhu cầu |
| Stash `@{0}` drop mất data | High | Tag backup trước khi drop, đã có `backup/develop-pre-phase3-restore` |
---
## 7. Timeline (ước lượng)
| Phase | Thời gian | Có thể parallel? |
|-------|-----------|------------------|
| 0 - Cleanup & Foundation | 2-3h | 1 dev |
| 1 - Wire OIDC Login | 4-6h | 1 dev |
| 2 - Silent SSO | 6-8h | 1 dev (cần test kỹ) |
| 3 - HA + Audit | 8-10h | 1 dev (cần test failover) |
| 4 - Production | 4-6h | 1 dev |
| **Total** | **24-33h** | ~3-4 working days |
---
## 8. Status Tracking
| Phase | Status | Started | Completed | Notes |
|-------|--------|---------|-----------|-------|
| 0 | 🟡 Ready to start | — | — | Đã chốt decisions trong §4. Đợi user confirm để bắt đầu. |
| 1 | ⚪ Not started | — | — | Phụ thuộc Phase 0 |
| 2 | ⚪ Not started | — | — | Phụ thuộc Phase 1 |
| 3 | ⚪ Not started | — | — | Phụ thuộc Phase 2 |
| 4 | ⚪ Not started | — | — | Phụ thuộc Phase 3 |
Legend: 🔴 Pending | 🟡 In progress | 🟢 Done | ⚪ Not started
---
## 9. Changelog
- **2026-06-18** — Initial draft (created from full project scan)
- **2026-06-18** — User confirmed key decisions:
- Q1: 2 instance PostgreSQL từ Phase 0
- Q2: Cross-project SSO dùng `prompt=none` (ADR-003) + reject subdomain pattern
- Q3: Drop DB cũ `vietprodev_sso` sau khi backup SQL safety net
- Q4: Docker user/password = `sso` / `sso` (đơn giản hơn)
- Q5: PostgreSQL `17-alpine` (ổn định, đủ dùng)
- Q6: Xóa 18 untracked files cũ sau khi verify an toàn
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment