# Unified Auth Sessions Backend Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Add a backend API that returns a unified list of authenticated Fabro sessions the server currently knows about.

**Architecture:** The server will expose `/api/v1/auth/sessions` as the canonical session list API. V1 combines the current browser cookie session with active CLI refresh-token chains, using a normalized response shape that the frontend can render without knowing storage details. Browser sessions remain synthetic and non-revocable until durable browser-session storage exists.

**Tech Stack:** Rust, Axum, OpenAPI/progenitor, SlateDB-backed `fabro-store`, `cargo nextest`.

**Frontend dependency:** This plan defines the API contract consumed by [2026-05-10-auth-sessions-frontend.md](2026-05-10-auth-sessions-frontend.md). Complete this backend plan before implementing the frontend plan.

---

## Contract

Add this endpoint pair:

- `GET /api/v1/auth/sessions`
- `DELETE /api/v1/auth/sessions/{id}`

Add these response types to `docs/public/api-reference/fabro-api.yaml`:

```yaml
AuthSessionsResponse:
  type: object
  required: [sessions]
  properties:
    sessions:
      type: array
      items:
        $ref: "#/components/schemas/AuthSession"

AuthSession:
  type: object
  required:
    - id
    - kind
    - current
    - provider
    - login
    - label
    - createdAt
    - lastSeenAt
    - expiresAt
    - revocable
  properties:
    id:
      type: string
    kind:
      type: string
      enum: [browser, cli]
    current:
      type: boolean
    provider:
      type: string
      example: github
    login:
      type: string
    label:
      type: string
    userAgent:
      type: string
      nullable: true
    createdAt:
      type: string
      format: date-time
    lastSeenAt:
      type: string
      format: date-time
    expiresAt:
      type: string
      format: date-time
    revocable:
      type: boolean
```

Use these IDs:

- Current browser session: `browser:current`
- CLI refresh-token chain: `cli:<chain_id>`

## Tasks

### Task 1: Add Store Support For Active CLI Sessions

**Files:**
- Modify: `lib/crates/fabro-store/src/slate/auth_tokens.rs`

- [x] Add a public store method that scans refresh tokens and returns active CLI session rows for one identity.
- [x] Treat a token as active when `used == false` and `expires_at > now`.
- [x] Group by `chain_id` and return the newest active token per chain by `last_used_at`.
- [x] Filter by `identity` equality with the authenticated user identity.
- [x] Add unit tests covering active, expired, used, duplicate-chain, and other-identity tokens.
- [x] Run:

```bash
cargo nextest run -p fabro-store auth_tokens
```

Expected: `PASS`.

### Task 2: Define The OpenAPI Contract

**Files:**
- Modify: `docs/public/api-reference/fabro-api.yaml`
- Generated by build: `lib/crates/fabro-api/src/generated.rs`

- [x] Add `GET /api/v1/auth/sessions` under the existing Auth section.
- [x] Add `DELETE /api/v1/auth/sessions/{id}` under the same section.
- [x] Add `AuthSessionsResponse` and `AuthSession` schemas exactly matching the contract above.
- [x] Run:

```bash
cargo build -p fabro-api
```

Expected: build succeeds and generated Rust API types include the new schemas.

### Task 3: Implement List Sessions Handler

**Files:**
- Modify: `lib/crates/fabro-server/src/web_auth.rs`

- [x] Add response structs local to `web_auth.rs` that serialize to the OpenAPI response shape.
- [x] Register `GET /auth/sessions` in `api_routes()`.
- [x] Require an authenticated user with the same auth path as `auth_me`.
- [x] Read the current `SessionCookie` from headers and emit one browser session:

```text
id: browser:current
kind: browser
current: true
provider: session_provider(session.auth_method)
login: session.login
label: This browser
userAgent: null
createdAt: session.iat as RFC3339 UTC
lastSeenAt: session.iat as RFC3339 UTC
expiresAt: session.exp as RFC3339 UTC
revocable: false
```

- [x] Query `RefreshTokenStore` for active CLI chains matching the authenticated identity.
- [x] Emit each CLI chain as:

```text
id: cli:<chain_id>
kind: cli
current: false
provider: github
login: token.login
label: Fabro CLI
userAgent: token.user_agent
createdAt: token.issued_at
lastSeenAt: token.last_used_at
expiresAt: token.expires_at
revocable: true
```

- [x] Sort sessions with current sessions first, then descending `lastSeenAt`.

### Task 4: Implement Revoke Session Handler

**Files:**
- Modify: `lib/crates/fabro-server/src/web_auth.rs`

- [x] Register `DELETE /auth/sessions/{id}` in `api_routes()`.
- [x] Require an authenticated user.
- [x] If `id == "browser:current"`, return a non-success JSON error because browser sessions are not durable in v1.
- [x] If `id` starts with `cli:`, parse the remainder as a UUID.
- [x] Look up active CLI chains for the authenticated identity and only delete the chain if it belongs to that identity.
- [x] Revoke valid CLI sessions with `RefreshTokenStore::delete_chain(chain_id)`.
- [x] Return `204 No Content` after successful revocation.
- [x] Return `404` for unknown session IDs and `400` for malformed IDs.

### Task 5: Add Server Tests

**Files:**
- Modify or add tests under `lib/crates/fabro-server/tests/it/api/`

- [x] Add a test that authenticated browser requests receive one `browser` session.
- [x] Add a test that active CLI refresh-token chains for the same identity appear in the unified list.
- [x] Add a test that expired, used, and different-identity CLI tokens are excluded.
- [x] Add a test that deleting `cli:<chain_id>` removes that token chain.
- [x] Add a test that deleting `browser:current` returns a non-success response.
- [x] Add a test that unauthenticated list and delete requests return `401`.
- [x] Run:

```bash
cargo nextest run -p fabro-server
```

Expected: `PASS`.

## Final Validation

Run:

```bash
cargo build -p fabro-api
cargo nextest run -p fabro-store
cargo nextest run -p fabro-server
```

Expected: all commands pass.

## Assumptions

- V1 does not add durable browser-session storage.
- The current browser session is represented by the signed browser cookie only.
- CLI sessions are represented by active refresh-token chains.
- The API contract intentionally hides token hashes and raw refresh-token details.
