Phase 3.4: control_center self-managed auth cutover
Replaces Firebase Auth with the new JWT + httpOnly-cookie refresh flow. Smoke-tested end-to-end via curl (login → /me → refresh rotation → logout). - Remove firebase dep + firebase.js - New token-bridge decouples api-client from AuthContext and de-dupes concurrent 401 refreshes - AuthContext: in-memory access token (useRef), bootstrap via /internal/auth/refresh, login/logout/refresh methods - api-client: withCredentials, Bearer attach, auto-retry once on 401 - LoginPage: handle INVALID_CREDENTIALS / ACCOUNT_LOCKED / VALIDATION_ERROR - Layout: self-service "Ganti password" form - UsersPage: initial password field on create + per-row admin-forced reset - .env / .env.example: drop VITE_FIREBASE_* vars - backend/CLAUDE.md + control_center/CLAUDE.md: describe new auth (were stale on Firebase) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@ Fastify.js REST API serving both mobile apps and the internal control center.
|
||||
|
||||
- **Runtime:** Node.js + Fastify.js
|
||||
- **Database:** PostgreSQL via GCP Cloud SQL
|
||||
- **Auth:** Firebase Auth JWT verification (no session, stateless)
|
||||
- **Auth:** Self-managed JWT (HS256 access, 1h) + opaque refresh token (30d, rotated, bcrypt-hashed in `auth_sessions`). Firebase Auth removed in Phase 3.4 (commit `f860ab6`). `firebase-admin` is kept but only for FCM messaging.
|
||||
- **Payment:** Xendit
|
||||
- **Infra:** GCP Cloud Run
|
||||
|
||||
@@ -26,20 +26,25 @@ Internal listener must never be exposed to the public internet.
|
||||
```
|
||||
/api/client/... → client app routes
|
||||
/api/mitra/... → mitra app routes
|
||||
/api/shared/... → shared routes (e.g. auth, lookup)
|
||||
/api/shared/... → shared routes (e.g. auth, refresh, logout, anonymous)
|
||||
/internal/... → control center routes (internal listener only)
|
||||
```
|
||||
|
||||
## Auth Flow
|
||||
|
||||
1. Firebase Auth issues JWT token on mobile/web
|
||||
2. Client sends JWT in `Authorization: Bearer <token>` header
|
||||
3. Fastify verifies token using Firebase Admin SDK on every request
|
||||
4. User record fetched from PostgreSQL by Firebase UID
|
||||
- **Mobile (client/mitra):** `Authorization: Bearer <access_token>` header. Access token is our own JWT (HS256, `AUTH_JWT_SECRET`), with claims `{ sub, user_type, session_id }`. Refresh via `POST /api/shared/auth/refresh` with the opaque refresh token in the body.
|
||||
- **Control center:** Access token in `Authorization: Bearer` (kept in memory by the SPA). Refresh token lives in an `httpOnly` Secure cookie; refresh calls `POST /internal/auth/refresh` with `credentials: 'include'`.
|
||||
- **Entry points:**
|
||||
- Anonymous customer: `POST /api/shared/auth/anonymous`
|
||||
- Phone OTP (customer/mitra): `/api/{client,mitra}/auth/otp/{request,verify}` — **Fazpass is stubbed** in `otp.service.js`; code is logged to the backend console (`[OTP STUB] phone=… code=…`) until real API docs arrive.
|
||||
- Google/Apple: `/api/client/auth/{google,apple}` (client_app only — creds pending)
|
||||
- CC login: `POST /internal/auth/login` (email + bcrypt password)
|
||||
- **Middleware:** `authenticate` plugin verifies the JWT and attaches `request.auth = { userType, userId, sessionId }`. WebSocket handshake uses the same verification. **No DB lookup on every request** — the user ID is encoded in the token.
|
||||
|
||||
## Key Conventions
|
||||
|
||||
- All routes must be authenticated unless explicitly marked public
|
||||
- Internal routes have an additional role check (`role: admin`)
|
||||
- All routes must be authenticated unless explicitly marked public (auth + anonymous routes are the exceptions)
|
||||
- Internal routes additionally require `request.auth.userType === 'cc_user'`
|
||||
- Use Fastify plugins for shared middleware (auth, error handling, logging)
|
||||
- Business logic lives in `services/` — never directly in route handlers
|
||||
- Never reintroduce Firebase Auth. `firebase-admin` is FCM-only; do not import `.auth()` from it.
|
||||
|
||||
Reference in New Issue
Block a user