Initial commit: nick docs

This commit is contained in:
moojttaba
2026-05-23 20:35:34 +03:30
commit 0da235ae27
90 changed files with 18268 additions and 0 deletions

View File

@@ -0,0 +1,144 @@
---
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
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 found 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`, 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, looks up `User.findOne({ email: googleUser.email })`. If no user, 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.
> [!tip] Account linking is implicit by email
> A user who originally signed up via email + password can sign in with Google as long as the email matches — no extra "link account" step. The backend simply reuses the existing user document. There is **no** separate `googleId` field stored today, so this is a one-way trust on `googleUser.email`.
## 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
BE->>DB: User.findOne({ email })
alt Sign-up: user 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: user missing
BE-->>FE: 404 USER_NOT_FOUND
else Sign-in: ok
BE->>DB: user.lastLoginAt = now; back-fill avatar if blank
end
BE->>BE: generate access + refresh; push refresh
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`.
- **User already exists during sign-up** → `409`; frontend prompts to use sign-in instead.
- **User missing during sign-in** → `404`; frontend redirects to sign-up.
- **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`