Appearance
SDD: Thiết kế hệ thống
1. Kiến trúc tổng quan
┌────────────────────────┐
Trình duyệt│ spa.anhdac.com │ Cloudflare Pages (static, SPA)
──────────►│ Vite + React 19 │
└──────────┬─────────────┘
│ fetch (credentials: include, cookie kmx_sid)
▼
┌────────────────────────┐ ┌─────────────────────┐
│ api.anhdac.com │───────►│ Cloudflare D1 │
│ Worker (Hono) │ │ database: kmxspa │
└──────────┬─────────────┘ └─────────────────────┘
│ verify ID token (JWKS)
▼
accounts.google.com
┌────────────────────────┐
│ docs.anhdac.com │ Cloudflare Pages (VitePress, public)
└────────────────────────┘
GitHub dac2205/kmxspa ──push──► Cloudflare Pages / Workers Builds (build trên Cloudflare)2. Công nghệ
| Lớp | Công nghệ |
|---|---|
| Frontend | Vite 8, React 19, TypeScript, Tailwind CSS v4, shadcn/ui (Base UI), Motion (Framer Motion), React Router, Recharts, sonner |
| Auth (client) | Google Identity Services qua @react-oauth/google |
| API | Cloudflare Workers, Hono, jose (xác thực JWT Google) |
| Database | Cloudflare D1 (SQLite), migrations trong apps/api/migrations |
| Docs | VitePress |
| Monorepo | pnpm workspaces |
| CI/CD | Cloudflare Pages Git integration + Workers Builds (không dùng GitHub Actions) |
3. Cấu trúc repo
kmxspa/
├─ apps/spa # spa.anhdac.com
│ └─ src/{pages,components,hooks,lib}
├─ apps/api # api.anhdac.com
│ ├─ src/{index.ts,auth.ts,types.ts}
│ └─ migrations/*.sql
└─ apps/docs # docs.anhdac.com4. Luồng đăng nhập
- Trình duyệt hiển thị nút Sign in with Google; Google trả về ID token (JWT).
- SPA gửi
POST /auth/google { credential }. - Worker xác thực chữ ký qua JWKS của Google, kiểm tra
aud = GOOGLE_CLIENT_ID,issvàemail_verified. - Upsert
users; tạo token ngẫu nhiên 256-bit; lưu SHA-256(token) vàologin_sessionskèm IP và thông tinrequest.cf. - Trả cookie
kmx_sid(HttpOnly, Secure, SameSite=Lax, Domain=.anhdac.com, 7 ngày). - Mọi request
/api/*kiểm tra session còn hạn, chưa logout và cập nhậtlast_seen_at. POST /auth/logoutghilogout_atvàlogout_reason='user'.- Cron mỗi giờ đóng các phiên hết hạn (
logout_reason='expired').
spa.anhdac.com và api.anhdac.com là same-site, nên cookie SameSite=Lax vẫn được gửi kèm fetch có credentials: "include". CORS chỉ cho phép các origin trong ALLOWED_ORIGINS.
5. Ghi dữ liệu
- Endpoint chung
PUT /api/:entity/:idvới whitelist cột cho từng entity, kiểm tra kiểu và giới hạn giá trị. - Mỗi lần ghi cập nhật
updated_at,updated_byvà thêm một bản ghi vàochange_log(before/after JSON). - Client cập nhật lạc quan (optimistic), rollback khi lỗi.
6. Bảo mật & quyền riêng tư
- Không lưu token phiên gốc, chỉ lưu hash.
- Không cần client secret Google (chỉ dùng ID token).
- Dữ liệu IP/thiết bị chỉ admin xem được (
ADMIN_EMAILS). - Màn hình đăng nhập thông báo rõ việc ghi nhận IP và thiết bị.
7. Chi phí
| Dịch vụ | Gói | Ghi chú |
|---|---|---|
| Cloudflare Pages ×2 | Free | 500 build/tháng |
| Cloudflare Workers | Free | 100k request/ngày |
| Cloudflare D1 | Free | 5 GB, 5M row read/ngày |
| GitHub | Free | Repo private, không dùng Actions |
| Google Identity | Free |