---
phase: 08-social-sign-in
plan: 02
type: execute
wave: 2
depends_on:
  - 08-01
files_modified:
  - backend/go.mod
  - backend/go.sum
  - backend/internal/auth/oauth.go
  - backend/internal/auth/oauth_test.go
  - backend/internal/web/auth_deps.go
  - backend/cmd/web/main.go
  - backend/internal/web/router.go
  - backend/internal/web/handlers_social.go
  - backend/internal/web/handlers_social_test.go
autonomous: true
requirements_addressed: [AUTH-08, AUTH-09, AUTH-12, AUTH-13]
requirements:
  - AUTH-08
  - AUTH-09
  - AUTH-12
  - AUTH-13

must_haves:
  truths:
    - "D-01: Google verified email auto-links to an existing Xtablo user."
    - "D-02: Google missing or unverified email is rejected before local account creation/linking."
    - "D-03: Google provider subject wins after link, even if email changes."
    - "D-12: Successful Google sign-in redirects to `/`."
    - "D-13: Google display name and avatar URL are stored when supplied."
    - "D-16: Google email changes update user_identities.email while provider subject remains durable."
    - "D-17: Google email changes update users.email when possible and handle unique conflicts explicitly."
    - "AUTH-12: Google callback issues the existing Xtablo session cookie."
    - "AUTH-13: Existing local auth middleware and CSRF behavior continue to pass."
  artifacts:
    - path: "backend/internal/auth/oauth.go"
      provides: "provider config, state/nonce cookie helpers, verifier interfaces, normalized claims"
    - path: "backend/internal/web/handlers_social.go"
      provides: "Google start and callback handlers"
---

## Phase Goal

Deliver a complete Google sign-in vertical slice using local Xtablo users and sessions.

<objective>
Add provider primitives and Google OAuth/OIDC flow:

1. Add provider config parsing and missing-config detection.
2. Add state/nonce helpers with short-lived HTTP-only cookies.
3. Add normalized provider claims and verifier interfaces for mockable tests.
4. Add `/auth/google/start` and `/auth/google/callback`.
5. Exchange authorization code, verify Google ID token, link/create local user, create Xtablo session, and redirect to `/`.
</objective>

<execution_context>
@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.codex/get-shit-done/workflows/execute-plan.md
</execution_context>

<context>
@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/08-social-sign-in/08-CONTEXT.md
@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/08-social-sign-in/08-RESEARCH.md
@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.planning/phases/08-social-sign-in/08-PATTERNS.md
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add OAuth provider primitives and Google config wiring</name>
  <files>
    backend/go.mod
    backend/go.sum
    backend/internal/auth/oauth.go
    backend/internal/auth/oauth_test.go
    backend/internal/web/auth_deps.go
    backend/cmd/web/main.go
  </files>
  <read_first>
    - backend/internal/auth/csrf.go
    - backend/internal/auth/session.go
    - backend/internal/web/handlers_auth.go
    - backend/cmd/web/main.go
    - backend/go.mod
  </read_first>
  <action>
    Add direct dependencies `golang.org/x/oauth2` and `github.com/coreos/go-oidc/v3/oidc`. Create `auth.ProviderConfig` and `auth.OAuthConfig` with Google fields: client ID, client secret, redirect URL, scopes, auth URL/token URL/issuer values. Add `Configured()` methods that return false when required Google env vars are missing. Add state and nonce helpers that create cryptographically random values, set short-lived HTTP-only cookies, validate returned values with constant-time comparison where applicable, and clear cookies on callback.

    Add a verifier interface so handler tests can use fake Google claims without contacting real Google. Normalized claims must include provider, subject, email, email_verified, display_name, avatar_url, and nonce.

    In `cmd/web/main.go`, read `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, and `GOOGLE_REDIRECT_URL`, pass config through `web.AuthDeps`, and do not log secret values.
  </action>
  <verify>
    <automated>cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend && go test ./internal/auth -run 'Test.*(OAuth|State|Nonce|ProviderConfig)' -count=1</automated>
  </verify>
  <acceptance_criteria>
    - `backend/internal/auth/oauth.go` exposes a Google config whose `Configured()` is false with empty env values.
    - OAuth state and nonce tests fail for mismatched values and pass for matching values.
    - No tests call real Google endpoints.
    - `cmd/web/main.go` reads Google env vars without logging `GOOGLE_CLIENT_SECRET`.
  </acceptance_criteria>
  <done>
    Google config and provider primitives are testable without network access.
  </done>
</task>

<task type="auto">
  <name>Task 2: Implement Google start/callback handlers and linking flow</name>
  <files>
    backend/internal/web/router.go
    backend/internal/web/handlers_social.go
    backend/internal/web/handlers_social_test.go
    backend/internal/db/queries/user_identities.sql
    backend/internal/db/sqlc/
  </files>
  <read_first>
    - backend/internal/web/router.go
    - backend/internal/web/handlers_auth.go
    - backend/internal/auth/session.go
    - backend/internal/db/queries/user_identities.sql
    - backend/internal/db/queries/users.sql
    - backend/internal/web/handlers_auth_test.go
  </read_first>
  <action>
    Register `GET /auth/google/start` and `GET /auth/google/callback` outside the protected route group. The start handler must reject missing Google config with a safe response, set state/nonce cookies, and redirect to Google's authorization URL with `openid email profile` scope. The callback must validate state, exchange the authorization code server-side, verify the ID token through the configured verifier, reject missing/unverified email with `This provider did not return a verified email. Try another sign-in method.`, and run provider linking in a DB transaction.

    Transaction order: get identity by provider+subject; if found, update metadata/email and sign in that user; if not found, require verified email, look up local user by email, link existing user or create a social-only user with NULL password hash, then insert identity. If provider subject is already linked and email changes, update `user_identities.email`; update `users.email` only when no unique conflict occurs. Never relink the subject to another user because of an email change.

    Create a local session using existing `auth.Store.Create` and `auth.SetSessionCookie`, then redirect to `/`.
  </action>
  <verify>
    <automated>cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend && go test ./internal/web -run 'Test.*Google.*(Start|Callback|Link|Session|Unverified|Conflict)' -count=1</automated>
  </verify>
  <acceptance_criteria>
    - `GET /auth/google/start` returns a redirect and sets state/nonce cookies when configured.
    - Google callback with invalid state is rejected and creates no user/session.
    - Google callback with unverified or missing email renders `This provider did not return a verified email. Try another sign-in method.`
    - Google callback with verified email matching a password user inserts `user_identities` and preserves password login.
    - Google callback with a new verified email creates a social-only user and sets the Xtablo session cookie.
    - Google callback for an existing provider subject signs in the linked user even when the returned email changed.
    - Email update conflict does not relink the provider subject to another user.
  </acceptance_criteria>
  <done>
    Google sign-in is a tested end-to-end local session flow.
  </done>
</task>

</tasks>

<threat_model>
| Threat ID | Severity | Component | Mitigation |
|-----------|----------|-----------|------------|
| T-08-04 | high | Google callback | Validate OAuth state before token exchange or linking |
| T-08-05 | high | Google ID token | Verify issuer, audience, expiry, subject, email, and email_verified through OIDC verifier |
| T-08-06 | high | Account linking | Link on verified email only when provider subject is not already known |
| T-08-07 | medium | Logging | Do not log auth code, ID token, access token, or client secret |
| T-08-08 | medium | Session fixation | Always issue the normal local session cookie after successful callback |
</threat_model>

<verification>
Run:

```
cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend && go test ./internal/auth ./internal/web -run 'Test.*(OAuth|Google|Login|Signup|Session)' -count=1
```
</verification>

<success_criteria>
1. Login/signup can start Google sign-in once UI plan links to `/auth/google/start`.
2. Google callback validates state, exchanges code, verifies ID token, creates/links users, and issues the local session.
3. Google tests cover verified email linking, unverified email rejection, provider-subject precedence, and email conflict handling.
</success_criteria>

