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>
8.9 KiB
title, tags, related_models, related_apis
| title | tags | related_models | related_apis | |||||||
|---|---|---|---|---|---|---|---|---|---|---|
| Google OAuth Flow |
|
|
|
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.
- Frontend —
frontend/src/auth/services/google-oauth.ts, consumed byfrontend/src/auth/view/jwt/jwt-sign-in-view.tsxandjwt-sign-up-view.tsx. - Backend —
AuthController.googleSignUpandgoogleSignIninbackend/src/services/auth/authController.ts, backed bygoogleOAuthService.verifyGoogleToken()inbackend/src/services/auth/googleOAuthService.ts. - MongoDB —
Usercollection (account linking by email).
Preconditions
NEXT_PUBLIC_GOOGLE_CLIENT_IDis set on the frontend (frontend/src/auth/services/google-oauth.tsline 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
- 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. - The frontend lazy-loads
https://accounts.google.com/gsi/clientif it is not yet onwindow.google. google.accounts.oauth2.initTokenClient({ client_id, scope: 'openid email profile' })is initialised, and.requestAccessToken()opens the Google popup.- On success the popup returns an ID token (a Google-signed JWT containing
email,email_verified,name,given_name,family_name,picture,sub). - Frontend calls
signUpWithGoogle({ googleToken, role, referralCode })(frontend/src/auth/context/jwt/action.ts:281-304), which POSTsPOST /api/auth/google/signup. - Backend
authController.googleSignUp(:781-872) callsgoogleOAuthService.verifyGoogleToken(googleToken). The verifier usesgoogle-auth-libraryto validate the JWT signature, expiry, audience (client_id), and issuer. - Duplicate check:
User.findOne({ email: googleUser.email })— if the email already exists, returns409 USER_EXISTSso the user can use sign-in instead. - New user creation with
passwordomitted,isEmailVerified: true,status: "active",profile.avatar = googleUser.picture, and the chosenrolefrom the request. - Referral attribution (
authController.ts:817-838): same logic as the email path — incrementreferrer.referralStats.totalReferrals, emitreferral-signuponuser-${referrer._id}. - Generate access + refresh tokens, push refresh into
user.refreshTokens[], respond with{ user, tokens }. - Frontend stores tokens in
localStorageand redirects to the dashboard.
Sign-in
- User clicks the Google icon on
/auth/jwt/sign-in. - Same GSI flow as sign-up — Google returns an ID token.
- Frontend calls
signInWithGoogle(googleToken)→POST /api/auth/google/signin. - Backend verifies the token, then looks up
User.findOne({ email: googleUser.email, status: "active" })(authController.ts:1194). Note thestatus: "active"filter: the query only matches active accounts. If no active user matches, returns404 USER_NOT_FOUND("please sign up first"). The frontend surfaces a localized prompt. - On hit:
existingUser.lastLoginAt = now; ifprofile.avataris empty and Google has a picture, it is back-filled (authController.ts:905-907). - 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
googleIdfield stored today, so matching is a one-way trust ongoogleUser.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 generic404 USER_NOT_FOUNDas 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:30 → authController.googleSignUp |
POST |
/api/auth/google/signin |
authRoutes.ts:31 → authController.googleSignIn |
Database writes
userscollection: on sign-up, full insert (no password). On sign-in, onlylastLoginAt, possiblyprofile.avatar, and a new refresh token appended.
Socket events emitted
referral-signup→user-${referrerId}when sign-up includes a validreferralCode.
Side effects
- No email is sent (Google handles trust). No
TempVerificationis 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) →
googleOAuthServicereturnsnull→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 bystatus: "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 === falseon the Google token → currently not enforced; the backend trusts any successful Google response. For an extra-strict mode, gate ongoogleUser.email_verified === trueingoogleOAuthService.
[!warning] Single backup file
frontend/src/auth/services/google-oauth.ts.backupis 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