📝 docs: ADR-0028 Phase B roadmap (B.3 / B.4 / B.5 outline) #71

Merged
arcodange merged 1 commits from vibe/batch4-task-b-phase-b-roadmap into main 2026-05-05 19:30:59 +02:00
Showing only changes of commit cc0187e456 - Show all commits

View File

@@ -0,0 +1,137 @@
# ADR-0028 Phase B Roadmap
**Document ID:** PHASE_B_ROADMAP
**Date:** 2026-05-05 evening
**Status:** In Progress
**Author:** AI Agent (vibe/batch4-task-b-phase-b-roadmap)
---
## Status as of 2026-05-05 evening
- [x] ADR-0028 Phase A complete (PRs #59-#63, #65)
- [x] Phase B.1 OIDC config (PR #64)
- [x] Phase B prep : pkg/auth skeleton (PR #69) + mkcert doc (PR #68)
- [ ] Phase B.3 not yet started
---
## Remaining work
Phase B delivers OpenID Connect Authorization Code flow with PKCE. Work is organized into **3 shippable phases**, each deliverable as an independent PR.
### Phase B.3 — OIDC client implementation
- **Goal:** Implement the core OIDC client methods in `pkg/auth/oidc.go`
- **Tasks:**
- `Discover()`: HTTP GET to `/.well-known/openid-configuration`, parse + cache discovery document
- `RefreshJWKS()`: HTTP GET to JWKS URI, parse RSA public keys, cache with TTL
- `ExchangeCode()`: POST to token endpoint with code + PKCE verifier, return TokenResponse
- `ValidateIDToken()`: Verify signature against JWKS, validate standard claims (iss, aud, exp, iat)
- **LOE:** ~200 lines of Go + unit tests
- **Dependencies:** None (uses standard library `crypto/rsa`, `encoding/jwt`)
- **Deliverable:** 1 PR
### Phase B.4 — OIDC HTTP handlers
- **Goal:** Add OIDC flow endpoints and wire them into the server
- **Tasks:**
- Create `pkg/user/api/oidc_handler.go`
- `GET /api/v1/auth/oidc/start`:
- Generate state (CSRF protection) + PKCE verifier + challenge
- Store state + verifier (cookie or short-lived in-memory store)
- Redirect to provider's authorization endpoint
- `GET /api/v1/auth/oidc/callback`:
- Validate state parameter matches stored state
- Exchange code for tokens (calls B.3 client)
- Validate id_token (calls B.3 client)
- Issue internal JWT (reuse existing JWT manager from ADR-0021)
- Return JWT in Set-Cookie + JSON body
- Wire routes in `pkg/server/server.go`
- **LOE:** ~150 lines of Go + unit tests + integration tests
- **Dependencies:** B.3 (client methods must be implemented)
- **Prerequisite:** Run `make cert` (mkcert, from PR #68) before starting dev
- **Deliverable:** 1 PR
### Phase B.5 — BDD coverage
- **Goal:** End-to-end OIDC testing
- **Tasks:**
- Create `features/auth/oidc.feature` with scenarios:
- Happy path: start → provider auth → callback → JWT issued
- Error: state mismatch
- Error: invalid code
- Error: expired id_token
- Use mock OIDC provider (local in-process) OR deterministic test against Authelia/Keycloak in docker-compose
- Follow ADR-0030 parallel BDD strategy for email assertions
- **LOE:** ~150 lines of Gherkin + step definitions
- **Dependencies:** B.3 + B.4 (endpoints must be operational)
- **Deliverable:** 1 PR
---
## Dependencies and order
```
B.3 (OIDC client)
B.4 (HTTP handlers) —— requires B.3
B.5 (BDD coverage) —— requires B.3 + B.4
```
**Note:** mkcert (PR #68) is ready. When starting B.4 development, run `make cert` once to generate local HTTPS certificates.
---
## Out of scope for Phase B (deferred)
| Item | Target Phase | Rationale |
|------|--------------|-----------|
| Decommission password auth | Phase C | Separate ADR after B is in production |
| Multi-provider (Authelia + Google) | Phase B.6 (if needed) | Single provider sufficient for MVP |
| JWKS rotation mid-flight retry | B.3 enhancement | Handle in initial implementation |
| Token refresh flow | Future | Not required for auth code flow MVP |
---
## Risk register
| Risk | Mitigation | Owner |
|------|------------|-------|
| JWKS rotation handling | Implement refresh + retry logic; key rotation must not break mid-flight validation | B.3 implementer |
| PKCE storage | Use signed cookie or short-lived in-memory store; document trade-offs in implementation PR | B.4 implementer |
| Testing without real provider | Use mock OIDC server for CI; local dev uses Authelia in docker-compose | B.5 implementer |
| State CSRF protection | Use cryptographically random state; store server-side with short TTL | B.4 implementer |
---
## Cross-references
- [ADR-0028: Passwordless authentication: magic link → OpenID Connect](../adr/0028-passwordless-auth-migration.md)
- [ADR-0029: Email infrastructure (Mailpit)](../adr/0029-email-infrastructure-mailpit.md)
- [ADR-0030: BDD email parallel strategy](../adr/0030-bdd-email-parallel-strategy.md)
- [PR #59: Email infrastructure](https://gitea.arcodange.lab/arcodange/dance-lessons-coach/pulls/59)
- [PR #60: BDD Mailpit helper](https://gitea.arcodange.lab/arcodange/dance-lessons-coach/pulls/60)
- [PR #61: magic_link_tokens table](https://gitea.arcodange.lab/arcodange/dance-lessons-coach/pulls/61)
- [PR #62: Magic link handlers](https://gitea.arcodange.lab/arcodange/dance-lessons-coach/pulls/62)
- [PR #63: Magic link BDD](https://gitea.arcodange.lab/arcodange/dance-lessons-coach/pulls/63)
- [PR #64: OIDC config skeleton](https://gitea.arcodange.lab/arcodange/dance-lessons-coach/pulls/64)
- [PR #65: Magic link cleanup loop](https://gitea.arcodange.lab/arcodange/dance-lessons-coach/pulls/65)
- [PR #68: mkcert documentation](https://gitea.arcodange.lab/arcodange/dance-lessons-coach/pulls/68)
- [PR #69: pkg/auth skeleton](https://gitea.arcodange.lab/arcodange/dance-lessons-coach/pulls/69)
---
## Appendix: File inventory
Existing (merged):
- `pkg/auth/oidc.go` — skeleton with TODO methods (PR #69)
- `pkg/auth/oidc_test.go` — placeholder tests (PR #69)
- `documentation/MKCERT.md` — mkcert setup guide (PR #68)
- `Makefile` — includes `make cert` target (PR #68)
To be created:
- `pkg/auth/oidc.go` — complete implementation (B.3)
- `pkg/user/api/oidc_handler.go` — HTTP handlers (B.4)
- `pkg/server/server.go` — route wiring (B.4)
- `features/auth/oidc.feature` — BDD scenarios (B.5)
- `pkg/auth/oidc_test.go` — expanded unit tests (B.3)
- `pkg/user/api/oidc_handler_test.go` — handler tests (B.4)