Files
client/specs/features/002-authentication.md
2026-03-07 19:46:19 +01:00

4.0 KiB

Spec 002: Authentication (Forest-Server Frontend)

Status: Phase 2 Complete (20 tests passing)

Overview

Forage is a server-side rendered frontend for forest-server. All user management (register, login, sessions, tokens) is handled by forest-server's UsersService via gRPC. Forage stores access/refresh tokens in HTTP-only cookies and proxies auth operations to the forest-server backend.

Architecture

Browser <--HTML/cookies--> forage-server (axum) <--gRPC--> forest-server (UsersService)
  • No local user database in forage
  • forest-server owns all auth state (users, sessions, passwords)
  • forage-server stores access_token + refresh_token in HTTP-only cookies
  • forage-server has a gRPC client to forest-server's UsersService

Behavioral Contract

gRPC Client (forage-core)

A typed client wrapping forest-server's UsersService:

  • register(username, email, password) -> Result<AuthTokens>
  • login(identifier, password) -> Result<AuthTokens>
  • refresh_token(refresh_token) -> Result<AuthTokens>
  • logout(refresh_token) -> Result<()>
  • get_user(access_token) -> Result<User>
  • list_personal_access_tokens(access_token, user_id) -> Result<Vec<Token>>
  • create_personal_access_token(access_token, user_id, name, scopes, expires) -> Result<(Token, raw_key)>
  • delete_personal_access_token(access_token, token_id) -> Result<()>
  • forage_access cookie: access_token, HttpOnly, Secure, SameSite=Lax, Path=/
  • forage_refresh cookie: refresh_token, HttpOnly, Secure, SameSite=Lax, Path=/
  • On every authenticated request: extract access_token from cookie
  • If access_token expired but refresh_token valid: auto-refresh, set new cookies
  • If both expired: redirect to /login

Routes

Public Pages

  • GET /signup -> signup form (200), redirect to /dashboard if authenticated
  • POST /signup -> call Register RPC, set cookies, redirect to /dashboard (302)
  • GET /login -> login form (200), redirect to /dashboard if authenticated
  • POST /login -> call Login RPC, set cookies, redirect to /dashboard (302)
  • POST /logout -> call Logout RPC, clear cookies, redirect to / (302)

Authenticated Pages

  • GET /dashboard -> home page showing user info + orgs (200), or redirect to /login
  • GET /settings/tokens -> list PATs (200)
  • POST /settings/tokens -> create PAT, show raw key once (200)
  • POST /settings/tokens/:id/delete -> delete PAT, redirect to /settings/tokens (302)

Error Handling

  • gRPC errors mapped to user-friendly messages in form re-renders
  • Invalid credentials: "Invalid username/email or password" (no enumeration)
  • Duplicate email/username on register: "Already registered"
  • Network error to forest-server: 502 Bad Gateway page

Edge Cases

  • Forest-server unreachable: show error page, don't crash
  • Expired access token with valid refresh: auto-refresh transparently
  • Both tokens expired: redirect to login, clear cookies
  • Malformed cookie values: treat as unauthenticated
  • Concurrent requests during token refresh: only refresh once

Purity Boundary

Pure Core (forage-core)

  • ForestClient trait (mockable for tests)
  • Token cookie helpers (build Set-Cookie headers, parse cookies)
  • Form validation (email format, password length)

Effectful Shell (forage-server)

  • Actual gRPC calls to forest-server
  • HTTP cookie read/write
  • Route handlers and template rendering
  • Auth middleware (extractor)

Test Strategy

Unit Tests (forage-core)

  • Cookie header building: correct flags, encoding
  • Form validation: email format, password length
  • Token expiry detection

Integration Tests (forage-server)

  • All routes render correct templates (using mock ForestClient)
  • POST /signup calls register, sets cookies on success
  • POST /login calls login, sets cookies on success
  • GET /dashboard without cookies -> redirect to /login
  • GET /dashboard with valid token -> 200 with user content
  • POST /logout clears cookies
  • Error paths: bad credentials, server down

The mock ForestClient allows testing all UI flows without a running forest-server.