From 0c017896056772add63755c81827bd4579f63e7c Mon Sep 17 00:00:00 2001 From: Gabriel Radureau Date: Tue, 5 May 2026 19:36:25 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20docs:=20AUTH.md=20synthesis=20(P?= =?UTF-8?q?hase=20A=20complete,=20Phase=20B=20partial)=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Gabriel Radureau Co-committed-by: Gabriel Radureau --- documentation/AUTH.md | 132 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 documentation/AUTH.md diff --git a/documentation/AUTH.md b/documentation/AUTH.md new file mode 100644 index 0000000..816e776 --- /dev/null +++ b/documentation/AUTH.md @@ -0,0 +1,132 @@ +# Authentication System + +## Overview + +The dance-lessons-coach authentication system provides a passwordless magic-link flow as the primary mechanism, with legacy username+password support during the transition period. OpenID Connect (OIDC) integration is in progress for Phase B. See [ADR-0028](../adr/0028-passwordless-auth-migration.md) for the migration strategy. + +## Authentication mechanisms supported + +### Username + password (legacy, ADR-0018) +- **Endpoint:** `POST /api/v1/auth/login` +- **Status:** Operational, to be decommissioned in Phase C +- **Details:** bcrypt-hashed passwords, JWT token issuance + +### Magic link by email (ADR-0028 Phase A) +- **Request endpoint:** `POST /api/v1/auth/magic-link/request` — accepts `{email}`, generates token, stores hash, sends email +- **Consume endpoint:** `GET /api/v1/auth/magic-link/consume?token=<...>` — validates hash, marks consumed, issues JWT +- **Always returns 200 on request** to prevent email enumeration +- **First-link sign-up:** if email is unknown, consume endpoint creates the user record + +### OpenID Connect (ADR-0028 Phase B, work in progress) +- **Status:** Skeleton merged (`pkg/auth/`), handlers and flow not yet wired +- **Planned endpoints:** + - `GET /api/v1/auth/oidc/start` — generates state + PKCE, redirects to provider + - `GET /api/v1/auth/oidc/callback` — exchanges code for tokens, validates id_token, issues internal JWT +- **Provider config:** `auth.oidc.providers.*` in config + +## Magic-link flow detail + +```mermaid +sequenceDiagram + User->>Server: POST /api/v1/auth/magic-link/request {email} + Server-->>User: 200 (always — anti-enumeration) + Server->>Mailpit (or SMTP provider): SMTP send "Your sign-in link" + User->>Email: clicks link + User->>Server: GET /api/v1/auth/magic-link/consume?token= + Server->>DB: verify hash, mark consumed, ensure user exists + Server-->>User: 200 {token: } +``` + +## Configuration + +### Email (ADR-0029) +| Config key | Env var | Default | Description | +|------------|---------|---------|-------------| +| `auth.email.from` | `DLC_AUTH_EMAIL_FROM` | `noreply@dance-lessons-coach.local` | Sender address | +| `auth.email.smtp_host` | `DLC_AUTH_EMAIL_SMTP_HOST` | `localhost` | SMTP host | +| `auth.email.smtp_port` | `DLC_AUTH_EMAIL_SMTP_PORT` | `1025` | SMTP port | +| `auth.email.smtp_use_tls` | `DLC_AUTH_EMAIL_SMTP_USE_TLS` | `false` | Use TLS | +| `auth.email.timeout` | `DLC_AUTH_EMAIL_TIMEOUT` | `10s` | Connection timeout | + +### Magic link (ADR-0028 Phase A) +| Config key | Env var | Default | Description | +|------------|---------|---------|-------------| +| `auth.magic_link.ttl` | `DLC_AUTH_MAGIC_LINK_TTL` | `15m` | Token lifetime | +| `auth.magic_link.base_url` | `DLC_AUTH_MAGIC_LINK_BASE_URL` | `http://localhost:8080` | Base URL for links | +| `auth.magic_link.cleanup_interval` | `DLC_AUTH_MAGIC_LINK_CLEANUP_INTERVAL` | `1h` | Cleanup loop interval | + +### JWT (ADR-0021) +| Config key | Env var | Default | Description | +|------------|---------|---------|-------------| +| `auth.jwt.ttl` | `DLC_AUTH_JWT_TTL` | `1h` | Token time-to-live | +| `auth.jwt.secret_retention.retention_factor` | `DLC_AUTH_JWT_SECRET_RETENTION_FACTOR` | `2.0` | Retention multiplier | +| `auth.jwt.secret_retention.max_retention` | `DLC_AUTH_JWT_SECRET_MAX_RETENTION` | `72h` | Maximum retention | +| `auth.jwt.secret_retention.cleanup_interval` | `DLC_AUTH_JWT_SECRET_CLEANUP_INTERVAL` | `1h` | Secret cleanup interval | + +### OIDC (Phase B, prep) +| Config key | Env var | Default | Description | +|------------|---------|---------|-------------| +| `auth.oidc.providers..issuer_url` | `DLC_AUTH_OIDC_ISSUER_URL` | - | Provider issuer URL | +| `auth.oidc.providers..client_id` | `DLC_AUTH_OIDC_CLIENT_ID` | - | Client ID | +| `auth.oidc.providers..client_secret` | `DLC_AUTH_OIDC_CLIENT_SECRET` | - | Client secret | + +## Token model + +Magic-link tokens use **SHA-256 hex hashing at rest** — only the hash is stored in the database (`token_hash` column, 64 chars). The plaintext token is emailed to the user and must be supplied back to re-derive the hash. This means a database leak reveals no usable tokens. See `pkg/user/magic_link.go` for the rationale. + +```go +// HashMagicLinkToken returns the lowercase hex sha256 of token +func HashMagicLinkToken(plaintext string) string { + sum := sha256.Sum256([]byte(plaintext)) + return hex.EncodeToString(sum[:]) +} +``` + +## Cleanup loops + +### JWT secret retention (ADR-0021) +- **Location:** `pkg/user/jwt_manager.go` — `StartCleanupLoop` +- **Interval:** Configurable via `auth.jwt.secret_retention.cleanup_interval` (default: 1h) +- **Behavior:** Removes secrets older than retention period (TTL x retention_factor, capped at max_retention) +- **Safety:** Never removes the current primary secret + +### Magic-link expired tokens (ADR-0028 Phase A) +- **Location:** `pkg/user/magic_link_cleanup.go` — `StartCleanupLoop` +- **Interval:** Configurable via `auth.magic_link.cleanup_interval` (default: 1h) +- **Behavior:** Deletes tokens where `expires_at < now` +- **Implementation:** Calls `DeleteExpiredMagicLinkTokens` on the repository + +## Local dev setup + +1. **Start services:** + ```bash + docker compose up -d # starts Postgres + Mailpit + ``` +2. **Inspect emails:** http://localhost:8025 (Mailpit UI) +3. **HTTPS for OIDC (Phase B):** + ```bash + make cert # generates certs/dev-cert.pem + certs/dev-key.pem via mkcert + ``` + See [MKCERT.md](MKCERT.md) for details. + +## Cross-references + +### Architecture Decision Records +| ADR | Description | +|-----|-------------| +| [ADR-0018](../adr/0018-user-management-auth-system.md) | Original username/password auth system | +| [ADR-0021](../adr/0021-jwt-secret-retention-policy.md) | JWT secret retention and cleanup | +| [ADR-0028](../adr/0028-passwordless-auth-migration.md) | Passwordless migration (Phase A complete, Phase B in progress) | +| [ADR-0029](../adr/0029-email-infrastructure-mailpit.md) | Email infrastructure (Mailpit) | +| [ADR-0030](../adr/0030-bdd-email-parallel-strategy.md) | BDD parallel email assertions | + +### Documentation +| Document | Description | +|----------|-------------| +| [EMAIL.md](EMAIL.md) | SMTP setup and Mailpit usage | +| [MKCERT.md](MKCERT.md) | Local HTTPS certificate setup | +| [PHASE_B_ROADMAP.md](PHASE_B_ROADMAP.md) | Remaining OIDC work | + +--- + +*Developer onboarding doc — see ADR-0028 for implementation details.*