---
phase: 28-profile-and-logto-integration
plan: 03
type: execute
wave: 2
depends_on: [01, 02]
files_modified:
  - src/client/routes/__root.tsx
  - src/server/routes/auth.ts
autonomous: false
requirements: []
user_setup:
  - type: external_config
    name: Logto Sign-In Branding
    instructions: |
      In Logto Console > Sign-in & account > Branding:
      1. Upload GearBox logo (dark variant for light backgrounds)
      2. Set brand color to #374151 (gray-700)
      3. Add custom CSS to match GearBox styling (rounded corners, font, button styles)
      4. Use CSS attribute selectors: div[class$=container], button[class$=button]
  - type: external_config
    name: Logto Social Connectors (D-09)
    instructions: |
      In Logto Console > Connectors > Social connectors:
      1. Add Google connector — requires Google Cloud Console OAuth 2.0 credentials
      2. Add GitHub connector — requires GitHub Developer Settings OAuth App
      3. Enable both in Sign-in & account > Sign-up & sign-in > Social sign-in
  - type: external_config
    name: Logto Email Verification (D-10)
    instructions: |
      In Logto Console > Sign-in & account > Sign-up & sign-in:
      - Require email verification at signup
  - type: external_config
    name: Logto Password Policy (D-11)
    instructions: |
      In Logto Console > Sign-in & account > Password policy:
      - Minimum length: 8
      - Require: uppercase, lowercase, number
  - type: external_config
    name: Custom Domain (D-08, optional)
    instructions: |
      Configure reverse proxy (nginx/Caddy) to serve Logto under auth.gearbox.de.
      Update OIDC_ISSUER env var to https://auth.gearbox.de/oidc.
      Update OIDC_REDIRECT_URI to use the new domain.

must_haves:
  truths:
    - Navigation includes link to /profile page
    - /me endpoint returns createdAt field for member-since display
    - Logto sign-in page shows GearBox branding (manual verification)
    - Google and GitHub social sign-in connectors are enabled (manual verification)
    - Email verification is required at signup (manual verification)
  artifacts:
    - src/client/routes/__root.tsx (updated with profile nav link)
    - src/server/routes/auth.ts (updated /me endpoint)
  key_links:
    - Navigation profile link points to /profile route from Plan 02
    - /me endpoint provides createdAt used by profile page account info section
---

<objective>
Wire navigation to /profile, extend /me endpoint with member-since data, and configure Logto branding/social connectors/policies per D-07, D-08, D-09, D-10, D-11.

Purpose: Make the profile page discoverable via navigation, provide the createdAt data needed by the profile page, and ensure Logto is configured with GearBox branding and security policies so users never feel they've left the app (D-07).
Output: Updated navigation, extended /me endpoint, Logto configuration checkpoints
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/28-profile-and-logto-integration/28-CONTEXT.md
@.planning/phases/28-profile-and-logto-integration/28-RESEARCH.md

@src/client/routes/__root.tsx
@src/server/routes/auth.ts
@src/client/hooks/useAuth.ts
</context>

<threat_model>
## Threat Model

| ID | Threat | Severity | Mitigation |
|----|--------|----------|------------|
| T-28-10 | createdAt leaks information about user registration patterns | LOW | Only return for authenticated user's own data (already behind /me auth) |
</threat_model>

<tasks>

<task type="auto">
  <name>Task 1: Add profile navigation link and extend /me endpoint</name>
  <files>src/client/routes/__root.tsx, src/server/routes/auth.ts, src/client/hooks/useAuth.ts</files>
  <read_first>
    - src/client/routes/__root.tsx (current navigation layout — find where settings/logout links are)
    - src/server/routes/auth.ts (current /me endpoint — see what it returns)
    - src/client/hooks/useAuth.ts (AuthState interface — needs createdAt field)
    - src/db/schema.ts (users table — createdAt column)
  </read_first>
  <action>
**Update `src/server/routes/auth.ts` — extend /me endpoint:**

In the GET `/me` handler, after `getOrCreateUser(db, auth.sub)`, also query the full user record to get `createdAt`:

```typescript
app.get("/me", async (c) => {
  const auth = await getAuth(c);
  if (auth) {
    const db = c.get("db");
    const user = await getOrCreateUser(db, auth.sub);
    // Get full user record for createdAt
    const [fullUser] = await db.select().from(users).where(eq(users.id, user.id));
    return c.json({
      user: {
        id: user.id,
        email: auth.email,
        createdAt: fullUser?.createdAt?.toISOString() ?? null,
      },
      authenticated: true,
    });
  }
  return c.json({ user: null, authenticated: false });
});
```

Add necessary imports: `import { eq } from "drizzle-orm"` and `import { users } from "../../db/schema.ts"`.

**Update `src/client/hooks/useAuth.ts` — extend AuthState interface:**

Add `createdAt` to the user type:
```typescript
interface AuthState {
  user: { id: string; email?: string; createdAt?: string } | null;
  authenticated: boolean;
}
```

**Update `src/client/routes/__root.tsx` — add profile link:**

Find the navigation section where settings/logout links exist (look for `/settings` or `useLogout`). Add a "Profile" link next to or near the settings link:

```tsx
<Link to="/profile" className="...">Profile</Link>
```

Use the same styling as the existing settings link. If the nav uses icons, use the "User" or "CircleUser" icon from the curated Lucide icon set (check `lib/iconData` for available icons). If no icon-based nav, use text link.

Only show the Profile link when `auth?.authenticated` is true (same guard as existing settings/logout links).
  </action>
  <acceptance_criteria>
    - src/server/routes/auth.ts `/me` endpoint response includes `createdAt` field
    - src/server/routes/auth.ts imports `users` from schema and `eq` from drizzle-orm
    - src/client/hooks/useAuth.ts AuthState interface includes `createdAt?: string`
    - src/client/routes/__root.tsx contains a Link to `/profile`
    - The profile link is only visible when authenticated
  </acceptance_criteria>
  <verify>
    <automated>grep -q "createdAt" src/server/routes/auth.ts && grep -q "createdAt" src/client/hooks/useAuth.ts && grep -q "/profile" src/client/routes/__root.tsx</automated>
  </verify>
  <done>/me returns createdAt, AuthState type includes it, navigation has profile link visible to authenticated users</done>
</task>

<task type="checkpoint:human-action">
  <name>Task 2: Configure Logto branding, social connectors, and security policies</name>
  <files>NONE (Logto Console configuration only)</files>
  <read_first>
    - .planning/phases/28-profile-and-logto-integration/28-RESEARCH.md (section 6 — branding details)
    - .planning/phases/28-profile-and-logto-integration/28-CONTEXT.md (D-07, D-08, D-09, D-10, D-11)
  </read_first>
  <action>
This task requires manual configuration in the Logto admin console. Claude cannot perform these actions.

**D-07: Sign-in page branding** — In Logto Console > Sign-in & account > Branding:
1. Upload GearBox logo (PNG/SVG, dark version for white background)
2. Set brand color to `#374151` (gray-700)
3. Add custom CSS to match GearBox styling. Key selectors:
   - `div[class$=container]` — set `font-family` to match system font stack
   - `button[class$=primary]` — set `background-color: #374151`, `border-radius: 0.5rem`
   - `input[class$=input]` — set `border-color: #e5e7eb` (gray-200), `border-radius: 0.5rem`
4. Verify by visiting /login — page should feel like GearBox, not generic Logto

**D-08: Custom domain** (optional, if DNS supports it):
1. Configure reverse proxy to serve Logto under `auth.gearbox.de`
2. Update `OIDC_ISSUER` env var to `https://auth.gearbox.de/oidc`
3. Update `OIDC_REDIRECT_URI` to use the custom domain

**D-09: Social connectors** — In Logto Console > Connectors > Social:
1. **Google**: Create OAuth 2.0 credentials in Google Cloud Console. Configure Google connector in Logto with client ID and secret.
2. **GitHub**: Create OAuth App in GitHub Developer Settings. Configure GitHub connector in Logto with client ID and secret.
3. Enable both in Sign-in & account > Sign-up & sign-in > Social sign-in section.

**D-10: Email verification** — In Logto Console > Sign-in & account > Sign-up & sign-in:
- Set email verification to "Required" for new signups

**D-11: Password policy** — In Logto Console > Sign-in & account > Password policy:
- Minimum length: 8
- Require: uppercase letter
- Require: lowercase letter
- Require: number
  </action>
  <acceptance_criteria>
    - Visiting /login shows GearBox-branded login page (logo, colors)
    - Google and GitHub social sign-in buttons appear on the login page
    - Creating a new account requires email verification
    - Attempting to set a password shorter than 8 chars or without mixed case is rejected
  </acceptance_criteria>
  <verify>
    <automated>echo "Manual verification required — Logto Console configuration"</automated>
  </verify>
  <done>Logto sign-in page shows GearBox branding with logo and matching colors, Google and GitHub social sign-in are available, email verification is required, password policy enforces 8+ chars with mixed case and number</done>
</task>

</tasks>

<verification>
1. `bun run lint` — no lint errors
2. `bun run build` — build succeeds
3. Navigation shows profile link when authenticated
4. /me endpoint returns createdAt in response
5. Manual: Logto login page shows GearBox branding
6. Manual: Social sign-in buttons visible
</verification>

<success_criteria>
- Profile page is discoverable via navigation
- /me endpoint provides createdAt for member-since display
- Logto sign-in page is branded to match GearBox (D-07)
- Google and GitHub social connectors are configured (D-09)
- Email verification required at signup (D-10)
- Password policy enforces strength requirements (D-11)
</success_criteria>
