# OAuth (Google + Facebook) — Design & Plan Status: in progress (2026-08-19) Owner decision (clarify): **public signup** — new users from Google/FB join a single default org (`OAUTH_DEFAULT_ORG`), role `user`, seat-checked. If the verified email matches an existing user, log them in (link) instead of creating a duplicate. ## Model chosen - Supersedes "no self-registration" ONLY for social login. Username/password auth + admin provisioning remain unchanged. - New OAuth-registered users go to `OAUTH_DEFAULT_ORG` (configurable tenant id). Org is active (created if missing) so new signups pass the SaaS tenant gate. Seats apply via existing `UserStore.create_user` seat check. - Email-match linking: if `by_email(verified_email)` finds an active user, issue a token for them (OAuth sign-in); else create a new user. ## Security invariants (MUST preserve) - Users stay keyed by `username` with `id == username`, `USERNAME_RE [a-zA-Z0-9_.-]{2,64}`, matching `_token_identity` + `require_auth`. - OAuth username derivation must satisfy `USERNAME_RE` and be collision-safe: e.g. `g_` / `fb_` (subs are numeric → valid). Must not collide with an existing username; if it does, suffix with a counter until unique. - **Server-side token validation only.** Backend validates the provider token over HTTPS — never trusts a client-declared identity. Use stdlib `urllib.request` (no new dep, avoids `requirements.lock.txt` churn). - Google: verify ID token via `https://oauth2.googleapis.com/tokeninfo?id_token=...`; check `email_verified == "true"`, audience == `OAUTH_GOOGLE_CLIENT_ID`, take `sub` + `email`. - Facebook: exchange client token for app token `GET /oauth/access_token?client_id=..&client_secret=..&grant_type=client_credentials`, then `GET /me?fields=id,email,name&access_token=`, then verify the user token via `GET /debug_token?input_token=&access_token=` checking `data.is_valid`, `data.app_id == OAUTH_FACEBOOK_APP_ID`, `data.user_id`, and that the Graph `me` result's `id == data.user_id`. Only accept verified email. - Provider tokens are single-use inputs, never stored. Secret keys live in `.env` only, never logged or returned. - Rate-limit the OAuth exchange endpoint (per-IP + per-email) like login. - Reject tokens with invalid issuer/app/audience → `ApiError` 401/400, never 500. - All outbound provider calls must set a timeout and fail closed (`AuthError` → 401/400). ## Config (backend/app/config.py) New env vars (all optional; OAuth disabled unless configured): - `OAUTH_GOOGLE_CLIENT_ID`, `OAUTH_GOOGLE_CLIENT_SECRET` - `OAUTH_FACEBOOK_APP_ID`, `OAUTH_FACEBOOK_APP_SECRET` - `OAUTH_DEFAULT_ORG` (tenant id for new signups) - `OAUTH_ENABLED`-style toggles derived from presence of creds (fail closed: endpoint 404/disabled when creds absent). ## API - `POST /api/auth/oauth` body `{provider: "google"|"facebook", token: "..."}` → validates token, resolves/creates user, returns `{token, user, must_setup}` (same shape as login). - No new deps. ## Frontend - `frontend/src/views/Login.vue` + `frontend/src/api/index.js`: add `api.oauth(provider, token)`. - Login screen adds "เข้าสู่ระบบด้วย Google / Facebook" buttons (single-color line icons, no colored emoji per brand rule). - Provider SDK: Google Identity Services (GID) loaded from CDN on demand → `google.accounts.id` to get an ID token; FB SDK (fbLogin FB.getLoginStatus / FB.login scope email) to get an access token. Send the token to `/api/auth/oauth`. Handle `must_setup` like login. - Keep OAuth buttons hidden/disabled when provider creds are not configured (backend drives via a small public config endpoint or by the presence of the button config in the login page response). ## Tests (backend, mock/fake provider — no live network) - New `backend/tests/test_oauth.py`: fake provider responses via monkeypatched validation function; assert: new-user creation (org=OAUTH_DEFAULT_ORG, role user, seat-checked), email-match login-links existing user, invalid token → 401, unverified email rejected, disabled provider (creds absent) → 404/disabled, username collision gets a unique suffix, rate-limit enforced, response shape matches login. ## Deliverable Backend routes + validation + store integration + config; frontend buttons + api method + provider SDK wiring; tests; handoff docs.