---
phase: 08-social-sign-in
plan: 03
type: execute
wave: 3
depends_on:
  - 08-01
  - 08-02
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-10, AUTH-11, AUTH-12, AUTH-13]
requirements:
  - AUTH-10
  - AUTH-11
  - AUTH-12
  - AUTH-13

must_haves:
  truths:
    - "D-01: Apple verified email auto-links to an existing Xtablo user."
    - "D-02: Apple missing or unverified email is rejected before local account creation/linking."
    - "D-03: Apple provider subject wins after link, even if email changes."
    - "D-12: Successful Apple sign-in redirects to `/`."
    - "D-13: Apple display name and avatar URL are stored when supplied."
    - "D-14: Apple name is persisted immediately when present on the first callback payload."
    - "D-15: Apple private relay emails are accepted when Apple marks them verified."
    - "D-16: Apple email changes update user_identities.email while provider subject remains durable."
    - "D-17: Apple email changes update users.email when possible and handle unique conflicts explicitly."
    - "AUTH-12: Apple 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: "Apple client-secret signing, nonce validation, and Apple verifier config"
    - path: "backend/internal/web/handlers_social.go"
      provides: "Apple start and callback handlers"
---

## Phase Goal

Deliver a complete Sign in with Apple vertical slice using the same local account-linking and session path as Google.

<objective>
Add Apple OAuth/OIDC flow:

1. Parse Apple provider config without logging private key material.
2. Generate Apple ES256 client-secret JWTs for token exchange.
3. Add `/auth/apple/start` and Apple callback handling.
4. Validate state and nonce, verify Apple ID token, persist first-login name when present, link/create local users, and issue local Xtablo session.
</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 Apple config, client-secret signing, and verifier support</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/oauth.go
    - backend/internal/auth/oauth_test.go
    - backend/cmd/web/main.go
    - backend/internal/web/auth_deps.go
    - backend/go.mod
  </read_first>
  <action>
    Add `github.com/go-jose/go-jose/v4` if needed for Apple ES256 signing. Extend provider config with `APPLE_CLIENT_ID`, `APPLE_TEAM_ID`, `APPLE_KEY_ID`, `APPLE_PRIVATE_KEY`, and `APPLE_REDIRECT_URL`. `Configured()` must require all Apple fields. Implement Apple client-secret generation with issuer `APPLE_TEAM_ID`, subject/audience for Apple, `kid` header from `APPLE_KEY_ID`, and an expiration bounded to Apple's allowed lifetime. Normalize multiline/private-key env formats without printing the key.

    Extend the verifier abstraction for Apple ID tokens and nonce validation. Unit tests must inspect JWT claims and headers without using real Apple credentials.
  </action>
  <verify>
    <automated>cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend && go test ./internal/auth -run 'Test.*Apple.*(Config|ClientSecret|Nonce|Verifier)' -count=1</automated>
  </verify>
  <acceptance_criteria>
    - Apple config is disabled when any required Apple env var is empty.
    - Apple client-secret tests assert `iss`, `sub`, `aud`, `exp`, and `kid`.
    - Apple private key material is never logged or included in error responses.
    - Nonce mismatch tests fail callback verification before account linking.
  </acceptance_criteria>
  <done>
    Apple provider primitives are configured and tested without real Apple network calls.
  </done>
</task>

<task type="auto">
  <name>Task 2: Implement Apple start/callback and first-login profile handling</name>
  <files>
    backend/internal/web/router.go
    backend/internal/web/handlers_social.go
    backend/internal/web/handlers_social_test.go
  </files>
  <read_first>
    - backend/internal/web/router.go
    - backend/internal/web/handlers_social.go
    - backend/internal/web/handlers_social_test.go
    - backend/internal/auth/oauth.go
    - backend/internal/db/queries/user_identities.sql
    - backend/internal/auth/session.go
  </read_first>
  <action>
    Register `GET /auth/apple/start` and the Apple callback route selected by implementation. Prefer a callback shape that works with Apple `form_post`; if using POST, account for existing CSRF middleware by routing or validating provider state before processing, without weakening CSRF for local form posts. The start handler sets state and nonce cookies and redirects to Apple authorization with scopes `name email` and the chosen response mode.

    Callback processing validates state and nonce, exchanges the authorization code with Apple using the generated client secret, verifies ID token issuer/audience/expiry/subject/email/email_verified, accepts Apple private relay emails when verified, persists Apple name from the first authorization payload when present, links/creates local users through the shared transaction path, creates the existing Xtablo session cookie, and redirects to `/`.
  </action>
  <verify>
    <automated>cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend && go test ./internal/web -run 'Test.*Apple.*(Start|Callback|Nonce|Relay|Name|Session|Conflict)' -count=1</automated>
  </verify>
  <acceptance_criteria>
    - `GET /auth/apple/start` returns a redirect and sets state/nonce cookies when configured.
    - Apple callback with invalid state or nonce creates no user/session.
    - Apple callback with unverified/missing email renders `This provider did not return a verified email. Try another sign-in method.`
    - Apple private relay email marked verified creates or links a local user.
    - Apple first-login name is stored as display name when present.
    - Apple callback for an existing subject signs in the linked user despite email changes.
    - Apple callback issues the Xtablo session cookie and redirects to `/`.
  </acceptance_criteria>
  <done>
    Apple sign-in is a tested end-to-end local session flow.
  </done>
</task>

</tasks>

<threat_model>
| Threat ID | Severity | Component | Mitigation |
|-----------|----------|-----------|------------|
| T-08-09 | high | Apple callback | Validate OAuth state and ID-token nonce before token claims affect local accounts |
| T-08-10 | high | Apple ID token | Verify issuer, audience, expiry, subject, email, email_verified, and nonce |
| T-08-11 | high | Apple client secret | Keep private key server-side, never log it, and bound JWT expiration |
| T-08-12 | medium | Apple first-login name | Treat name as display metadata only, never as identity proof |
| T-08-13 | medium | CSRF middleware interaction | Do not bypass local CSRF protection beyond the provider callback route that is protected by OAuth state |
</threat_model>

<verification>
Run:

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

<success_criteria>
1. Login/signup can start Apple sign-in once UI plan links to `/auth/apple/start`.
2. Apple callback validates state/nonce, exchanges code, verifies ID token, creates/links users, and issues the local session.
3. Apple tests cover verified relay email acceptance, first-login name persistence, subject precedence, and email conflict handling.
</success_criteria>

