Remaining docs updated to match code (the docs that the first pass had not covered):
- Flows: Chat, Referral, Rating, Registration, Google OAuth, Negotiation, Payout,
Trezor Safekeeping — corrected endpoints, socket events, status enums, auth gaps
- API Reference: User API, Trezor API — admin route prefix/verb/status corrections,
added undocumented endpoints (ton-proof challenge, profile email verify,
GET /trezor/account, POST /trezor/verify-operation)
- Data Models: Chat, Notification, Payment, PointTransaction, User — corrected
enums (PaymentProvider, escrowState, PointTransaction.type, User.status),
90-day notification TTL, soft-delete semantics, wallet fields
Trezor "zero frontend" finding (audit C31/C32) corrected as STALE:
- Verified current code HAS a full frontend Trezor implementation (admin/trezor
page, TrezorSettingsView, trezorConnector via @trezor/connect-web,
TrezorSignDialog, actions/trezor.ts building the {message,signature} object)
- Fixed Trezor Safekeeping Flow doc (removed false "no frontend" warnings)
- Reclassified ISSUE-012 as invalid/superseded with explanation
Issue set reconciled to a single canonical numbering (ISSUE-001..054):
- Adopted the comprehensive 51-issue set (long-slug, fully indexed)
- Removed 35 superseded short-slug duplicates from the first pass
- Removed a duplicate ISSUE-046 file
- Added 3 issues the 51-set lacked: ISSUE-052 (completed-not-counted-in-stats),
ISSUE-053 (axios 401-only interceptor), ISSUE-054 (rate limiter counts all attempts)
- Regenerated Issues Index: 53 open (14 critical, 39 major) + 1 invalid
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
157 lines
8.9 KiB
Markdown
157 lines
8.9 KiB
Markdown
---
|
|
title: Google OAuth Flow
|
|
tags: [flow, auth, oauth, google]
|
|
related_models: ["[[User]]"]
|
|
related_apis: ["POST /api/auth/google/signup", "POST /api/auth/google/signin"]
|
|
---
|
|
|
|
# Google OAuth Flow
|
|
|
|
> **Last updated:** 2026-05-29 — aligned with code (see [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md))
|
|
|
|
Google sign-in/up integration using **Google Identity Services** (`accounts.google.com/gsi/client`). The flow short-circuits email verification because Google accounts are pre-verified.
|
|
|
|
## Actors
|
|
|
|
- **User** with a Google account.
|
|
- **Google Identity Services (GSI)** — JS SDK loaded on demand by the frontend service.
|
|
- **Frontend** — `frontend/src/auth/services/google-oauth.ts`, consumed by `frontend/src/auth/view/jwt/jwt-sign-in-view.tsx` and `jwt-sign-up-view.tsx`.
|
|
- **Backend** — `AuthController.googleSignUp` and `googleSignIn` in `backend/src/services/auth/authController.ts`, backed by `googleOAuthService.verifyGoogleToken()` in `backend/src/services/auth/googleOAuthService.ts`.
|
|
- **MongoDB** — `User` collection (account linking by email).
|
|
|
|
## Preconditions
|
|
|
|
- `NEXT_PUBLIC_GOOGLE_CLIENT_ID` is set on the frontend (`frontend/src/auth/services/google-oauth.ts` line 2 — there is a hard-coded fallback for the dev project ID).
|
|
- Same client ID is whitelisted on the backend `googleOAuthService`.
|
|
- The current origin is registered under "Authorized JavaScript origins" in Google Cloud Console (see `frontend/GOOGLE_OAUTH_SETUP.md`).
|
|
|
|
## Step-by-step narrative
|
|
|
|
### Sign-up
|
|
|
|
1. User clicks the Google icon on `/auth/jwt/sign-up`. The form is configured with the chosen role (buyer/seller) and an optional referral code.
|
|
2. The frontend lazy-loads `https://accounts.google.com/gsi/client` if it is not yet on `window.google`.
|
|
3. `google.accounts.oauth2.initTokenClient({ client_id, scope: 'openid email profile' })` is initialised, and `.requestAccessToken()` opens the Google popup.
|
|
4. On success the popup returns an **ID token** (a Google-signed JWT containing `email`, `email_verified`, `name`, `given_name`, `family_name`, `picture`, `sub`).
|
|
5. Frontend calls `signUpWithGoogle({ googleToken, role, referralCode })` (`frontend/src/auth/context/jwt/action.ts:281-304`), which POSTs `POST /api/auth/google/signup`.
|
|
6. Backend `authController.googleSignUp` (`:781-872`) calls `googleOAuthService.verifyGoogleToken(googleToken)`. The verifier uses `google-auth-library` to validate the JWT signature, expiry, audience (`client_id`), and issuer.
|
|
7. **Duplicate check**: `User.findOne({ email: googleUser.email })` — if the email already exists, returns **`409 USER_EXISTS`** so the user can use *sign-in* instead.
|
|
8. **New user creation** with `password` omitted, `isEmailVerified: true`, `status: "active"`, `profile.avatar = googleUser.picture`, and the chosen `role` from the request.
|
|
9. **Referral attribution** (`authController.ts:817-838`): same logic as the email path — increment `referrer.referralStats.totalReferrals`, emit `referral-signup` on `user-${referrer._id}`.
|
|
10. Generate access + refresh tokens, push refresh into `user.refreshTokens[]`, respond with `{ user, tokens }`.
|
|
11. Frontend stores tokens in `localStorage` and redirects to the dashboard.
|
|
|
|
### Sign-in
|
|
|
|
1. User clicks the Google icon on `/auth/jwt/sign-in`.
|
|
2. Same GSI flow as sign-up — Google returns an ID token.
|
|
3. Frontend calls `signInWithGoogle(googleToken)` → `POST /api/auth/google/signin`.
|
|
4. Backend verifies the token, then looks up `User.findOne({ email: googleUser.email, status: "active" })` (`authController.ts:1194`). Note the **`status: "active"` filter**: the query only matches active accounts. If no active user matches, returns **`404 USER_NOT_FOUND`** ("please sign up first"). The frontend surfaces a localized prompt.
|
|
5. On hit: `existingUser.lastLoginAt = now`; if `profile.avatar` is empty and Google has a picture, it is back-filled (`authController.ts:905-907`).
|
|
6. Tokens issued and returned identically to email login.
|
|
|
|
> [!warning] No account merge
|
|
> There is **no** account-merge step between a Telegram-only / email account and a Google account. The Google sign-in path simply looks up an **active** user by email and reuses that document if one exists; it does not reconcile, link, or merge distinct identities. There is **no** separate `googleId` field stored today, so matching is a one-way trust on `googleUser.email`.
|
|
|
|
> [!warning] Soft-deleted accounts get a generic 404 on Google sign-in
|
|
> Because the sign-in lookup filters by `status: "active"`, a user who registered via Google and was later **soft-deleted** (`status: "deleted"`) is invisible to the query. They receive the **same generic `404 USER_NOT_FOUND`** as a never-registered user — there is **no** distinct "account deleted" / "account disabled" error.
|
|
|
|
## Sequence diagram
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
autonumber
|
|
actor U as User
|
|
participant FE as Frontend
|
|
participant G as Google GSI
|
|
participant BE as Backend
|
|
participant GA as google-auth-library
|
|
participant DB as MongoDB
|
|
|
|
U->>FE: Click Google icon
|
|
FE->>G: load gsi/client, initTokenClient(client_id)
|
|
FE->>G: requestAccessToken()
|
|
G-->>U: Popup → consent
|
|
U-->>G: Approve
|
|
G-->>FE: ID token (signed JWT)
|
|
alt Sign-up
|
|
FE->>BE: POST /api/auth/google/signup { googleToken, role, referralCode }
|
|
else Sign-in
|
|
FE->>BE: POST /api/auth/google/signin { googleToken }
|
|
end
|
|
BE->>GA: verifyGoogleToken(googleToken)
|
|
GA-->>BE: { email, name, picture, ... } or null
|
|
alt Sign-up
|
|
BE->>DB: User.findOne({ email })
|
|
else Sign-in
|
|
BE->>DB: User.findOne({ email, status: "active" })
|
|
end
|
|
alt Sign-up: email exists
|
|
BE-->>FE: 409 USER_EXISTS
|
|
else Sign-up: new
|
|
BE->>DB: User.create({ email, role, isEmailVerified:true, profile.avatar })
|
|
opt referral
|
|
BE->>DB: increment referrer.referralStats
|
|
end
|
|
else Sign-in: no active user (missing or soft-deleted)
|
|
BE-->>FE: 404 USER_NOT_FOUND
|
|
else Sign-in: ok
|
|
BE->>DB: set user.lastLoginAt = now
|
|
BE->>DB: back-fill avatar if blank
|
|
end
|
|
BE->>BE: generate access and refresh tokens
|
|
BE->>BE: push refresh token
|
|
BE-->>FE: 200 { user, tokens }
|
|
FE->>FE: localStorage.setItem(accessToken/refreshToken)
|
|
FE-->>U: Redirect /dashboard/{role}
|
|
```
|
|
|
|
## API calls
|
|
|
|
| Method | Endpoint | Source |
|
|
|---|---|---|
|
|
| `POST` | `/api/auth/google/signup` | `authRoutes.ts:30` → `authController.googleSignUp` |
|
|
| `POST` | `/api/auth/google/signin` | `authRoutes.ts:31` → `authController.googleSignIn` |
|
|
|
|
## Database writes
|
|
|
|
- **`users` collection**: on sign-up, full insert (no password). On sign-in, only `lastLoginAt`, possibly `profile.avatar`, and a new refresh token appended.
|
|
|
|
## Socket events emitted
|
|
|
|
- **`referral-signup`** → `user-${referrerId}` when sign-up includes a valid `referralCode`.
|
|
|
|
## Side effects
|
|
|
|
- **No email** is sent (Google handles trust). No `TempVerification` is created.
|
|
- The avatar URL is stored from Google's CDN; consider proxying or rehosting if Google's privacy rules change for `googleusercontent.com`.
|
|
|
|
## Error / edge cases
|
|
|
|
- **Invalid Google token** (bad signature, wrong audience, expired) → `googleOAuthService` returns `null` → `401 INVALID_GOOGLE_TOKEN`.
|
|
- **Email already exists during sign-up** → `409 USER_EXISTS`; frontend prompts to use sign-in instead.
|
|
- **User does not exist during sign-in** → `404 USER_NOT_FOUND`; frontend redirects to sign-up.
|
|
- **Soft-deleted user signs in via Google** → `404 USER_NOT_FOUND` (generic, indistinguishable from "never registered") because the lookup filters by `status: "active"`.
|
|
- **Popup blocker** → GSI throws a client-side error caught in the view and surfaced as a toast.
|
|
- **Network failure to `accounts.google.com`** → GSI rejects; frontend retries on next click.
|
|
- **`email_verified === false` on the Google token** → currently not enforced; the backend trusts any successful Google response. For an extra-strict mode, gate on `googleUser.email_verified === true` in `googleOAuthService`.
|
|
|
|
> [!warning] Single backup file
|
|
> `frontend/src/auth/services/google-oauth.ts.backup` is checked in. Delete or convert to a documentation note — it leaks a hard-coded client ID that should only live in `.env.*`.
|
|
|
|
## Linked flows
|
|
|
|
- [[Authentication Flow]] — token issuance and storage are identical from step 9 onward.
|
|
- [[Registration Flow]] — alternative path that requires email verification.
|
|
- [[Referral Flow]] — works identically for Google-signup referrals.
|
|
|
|
## Source files
|
|
|
|
- Frontend: `frontend/src/auth/services/google-oauth.ts`
|
|
- Frontend: `frontend/src/auth/context/jwt/action.ts:281-331`
|
|
- Frontend: `frontend/src/auth/view/jwt/jwt-sign-up-view.tsx`, `jwt-sign-in-view.tsx`
|
|
- Frontend: `frontend/GOOGLE_OAUTH_SETUP.md`
|
|
- Backend: `backend/src/services/auth/authController.ts:781-941`
|
|
- Backend: `backend/src/services/auth/googleOAuthService.ts`
|
|
- Backend: `backend/src/services/auth/authRoutes.ts:30-31`
|