Files
nick-doc/04 - Flows/Google OAuth Flow.md
Siavash Sameni 7a616744f4 docs: complete code-reality alignment for remaining docs + reconcile issue set
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>
2026-05-29 15:15:02 +04:00

8.9 KiB

title, tags, related_models, related_apis
title tags related_models related_apis
Google OAuth Flow
flow
auth
oauth
google
User
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)

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.
  • Frontendfrontend/src/auth/services/google-oauth.ts, consumed by frontend/src/auth/view/jwt/jwt-sign-in-view.tsx and jwt-sign-up-view.tsx.
  • BackendAuthController.googleSignUp and googleSignIn in backend/src/services/auth/authController.ts, backed by googleOAuthService.verifyGoogleToken() in backend/src/services/auth/googleOAuthService.ts.
  • MongoDBUser 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

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:30authController.googleSignUp
POST /api/auth/google/signin authRoutes.ts:31authController.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-signupuser-${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 null401 INVALID_GOOGLE_TOKEN.
  • Email already exists during sign-up409 USER_EXISTS; frontend prompts to use sign-in instead.
  • User does not exist during sign-in404 USER_NOT_FOUND; frontend redirects to sign-up.
  • Soft-deleted user signs in via Google404 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

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