diff --git a/Roadmap.md b/Roadmap.md new file mode 100644 index 0000000..371406f --- /dev/null +++ b/Roadmap.md @@ -0,0 +1,232 @@ +# Roadmap + +**Status:** Draft v1 +**Date:** 2026-06-24 +**Owner:** Patrick (plate-software) + +--- + +## Versioning policy + +plate-auth follows **SemVer**, but with two amendments suited to a closed-source internal library: + +| Version part | Bump trigger | Consumer impact | +|---|---|---| +| MAJOR (`1.x.x`) | Wire contract change OR removed public API | Coordinated upgrade across all consumers | +| MINOR (`x.1.x`) | New feature, new SPI, new endpoint, deprecation | Drop-in upgrade — read CHANGELOG | +| PATCH (`x.x.1`) | Bug fix, dependency bump, internal refactor | Drop-in upgrade — no read needed | + +**Amendment 1 — lockstep frontend/backend versions.** +`@platesoft/auth@0.x.y` and `de.platesoft:plate-auth-starter:0.x.y` always ship together with identical +version numbers. The wire contract is the union of both sides; you cannot upgrade one without the peer. + +**Amendment 2 — `0.x` is internal-experimental.** +Until `1.0.0` we may break minor versions if a consumer experience demands it. All breakage is documented +in the CHANGELOG with a migration recipe. Once `1.0.0` ships, SemVer is strict. + +--- + +## Release horizon + +```mermaid +gantt + title plate-auth release horizon + dateFormat YYYY-MM-DD + axisFormat %b %Y + + section v0.1 — Ship + Sprint 0 (extraction) :s0, 2026-06-24, 14d + v0.1.0 published :milestone, m1, after s0, 0d + InspectFlow migrates :ifm, after m1, 7d + Sparkboard adopts :sba, after m1, 21d + + section v0.2 — Polish + SPI cleanup + Email opt-in :s2, after ifm, 21d + v0.2.0 published :milestone, m2, after s2, 0d + + section v0.3 — Operate + Multi-replica nonce store :s3, after m2, 14d + Refresh rotation hardening :s3b, after s3, 14d + v0.3.0 published :milestone, m3, after s3b, 0d + + section v0.4 — Open + Document SPI, write integration tests :s4, after m3, 30d + 1.0.0-RC candidate :milestone, m4, after s4, 0d +``` + +Dates are indicative — Sprint 0 has a hard target (Sparkboard cannot start until v0.1 ships). +Everything after Sprint 0 is "as priorities allow." + +--- + +## v0.1.0 — Ship (Sprint 0 deliverable) + +**Theme:** *Extract InspectFlow 14.1-14.6 verbatim. No new features. No clever refactors.* + +### Backend (`plate-auth-starter`) + +- ✅ Move `de.platesoft.inspectflow.*` auth code under `de.platesoft.auth.*` +- ✅ `PlateAuthAutoConfiguration` with `@ConditionalOnMissingBean` overrides +- ✅ `PlateAuthProperties` (`plate.auth.*` config namespace) +- ✅ Flyway migrations under `classpath:db/migration/auth/V1..V6` +- ✅ SPI: `OrgValidator`, `OrgDisplayNameResolver`, `InvitationMailer`, + `AccessRequestMailer`, `OnboardingHook` +- ✅ Endpoints: `/api/auth/**`, `/api/invitations/**`, `/api/access-requests/**`, `/api/admin/**` +- ✅ Spring Boot 4.1.0 + Java 25 LTS baseline +- ✅ Published to Gitea Package Registry + +### Frontend (`@platesoft/auth`) + +- ✅ `createAuthConfig({ providers, exchange, secret })` → NextAuth v5 `NextAuthConfig` +- ✅ Google + Microsoft Entra ID + Email magic-link providers (opt-in via options) +- ✅ `exchange/envelope.ts` (Web Crypto HMAC-SHA256, Edge-runtime compatible) +- ✅ `createProxyHandlers({ backendUrl })` with hop-by-hop header stripping + `duplex:"half"` +- ✅ `useAccessToken()`, `useMemberships()` client hooks +- ✅ Published to Gitea Package Registry npm + +### Validation + +- ✅ InspectFlow's current auth replaced by the library — full E2E test pass +- ✅ Sparkboard greenfield setup completes in <30min using only `Integration-Guide.md` +- ✅ Plan Reviewer (architect mode) approves Sprint-0-Plan.md before code starts + +**Out of scope for v0.1:** +- Multi-replica nonce store (in-memory is fine for InspectFlow + early Sparkboard) +- WebAuthn / passkeys +- Per-tenant JWT secret rotation +- Externalized audit-log sink (Loki / OpenSearch) + +--- + +## v0.2.0 — Polish + +**Theme:** *Make the consumer experience pleasant. Smooth the SPI corners InspectFlow + Sparkboard found.* + +Triggered by feedback from the two consumers during their first month using v0.1. + +### Likely contents + +- `InvitationMailer` default implementation that *actually* sends mail via `JavaMailSender` + (currently no-op logger) +- `AccessRequestMailer` admin-notification template +- Configurable invitation expiration window (currently hardcoded 7d in `InvitationService`) +- `useAccessToken()` Edge-runtime safety pass — current impl uses `getSession()` which is App-Router-only +- Email magic-link default UX page (consumers currently have to build their own callback page) +- Better error responses (RFC 7807 Problem Details) for `/api/auth/*` endpoints +- TypeScript improvements: stricter `ExchangeEnvelope` shape, exported types for `Membership`, `Invitation` +- ADR-style docs auto-published to wiki + +### Possibly v0.2 (depends on consumer demand) + +- WebAuthn / passkey provider (NextAuth supports it; we add the backend plumbing) +- Per-app branding on the exchange endpoint (custom user-agent string for audit) + +--- + +## v0.3.0 — Operate + +**Theme:** *Run plate-auth across multiple replicas + harden refresh tokens.* + +Triggered when the first consumer hits horizontal scaling. + +### Backend + +- **Nonce store via Redis / Postgres** instead of in-memory `ConcurrentHashMap` — current impl breaks + replay protection across replicas (an envelope can be replayed up to N-1 times where N = replica count). +- **Refresh token rotation table** — currently refresh tokens are stateless JWTs which means revocation + requires a denylist. Move to opaque refresh tokens with a `refresh_tokens` table for revocation + + family-tracking + reuse detection. +- **Configurable token signing key rotation** — support a JWK Set with `kid` header so we can rotate the + HMAC secret without all-user logout. +- **Pluggable login-event sink** — emit `LoginEvent` to a `LoginEventSink` SPI so consumers can ship to + Loki / OpenSearch / Sentry instead of (or in addition to) Postgres. + +### Frontend + +- Session sliding-expiration vs absolute-expiration toggle +- Optional `requireRoles(['ADMIN'])` middleware helper + +--- + +## v0.4.0 — Open + +**Theme:** *Document the SPI thoroughly. Write the integration tests we wish we had at v0.1.* + +This is the "ready to be used by a developer who doesn't know me" version. + +- Complete SPI Javadoc + TSDoc +- Integration test suite that any consumer can run against their override of `OrgValidator` +- Example consumer apps (minimal Sparkboard-like + minimal InspectFlow-like) checked into the plate-auth + repo under `examples/` +- `Architecture.md` upgraded with a "how to add a new auth provider" recipe +- Public-facing wiki polish — diagrams, terminology glossary, FAQ + +If at this point both consumers are happy + we want to start product #3, we cut **`1.0.0-RC1`**. + +--- + +## 1.0.0 criteria + +We do not ship `1.0.0` until: + +1. ✅ At least two consumers have been running v0.x in production for ≥3 months +2. ✅ At least one minor upgrade (v0.x → v0.y) has been performed without breakage +3. ✅ The SPI has been used to override at least three different extension points across consumers + (proves the extension surface is real) +4. ✅ A security review (external or internal) has signed off on the threat model +5. ✅ No "must rename / must refactor" backlog items are open + +If we hit `1.0.0` we promise **strict SemVer** thereafter — breaking changes only on major bumps, +deprecation notices ≥1 minor cycle in advance. + +--- + +## Deprecation policy + +- **Deprecation lives in code as `@Deprecated(since="0.x", forRemoval=true)` + CHANGELOG entry.** +- **One full minor cycle of warning before removal.** Something deprecated in `0.3` is removed no earlier + than `0.5`. +- **Migration recipe is mandatory.** Every deprecation includes a `// MIGRATION:` Javadoc/TSDoc comment + pointing at the replacement. + +--- + +## What is explicitly *not* on the roadmap + +Items that have been considered and rejected (for now): + +| Idea | Why not | +|---|---| +| SAML support | No customer has asked. Adds significant code + audit surface. | +| SCIM provisioning | Same — no demand. Revisit if a B2B customer shows up. | +| Multi-org-type-per-user (e.g. user has memberships to a Company *and* a Club) | Schema supports it but no consumer wants it. Don't build until asked. | +| Public OIDC server (plate-auth as IdP) | We are consumers, not providers. Different threat model. Don't bend the architecture. | +| Audit log UI components | UI is per-product. Library emits structured events; consumers visualize. | +| iOS / Android SDKs | Native apps can call `/api/auth/exchange` directly. SDKs add maintenance load with no obvious win. | + +If consumer demand justifies any of these, they get added to the roadmap with their own discovery doc. + +--- + +## Hypothesis-driven scoping + +Each release answers (or refutes) a hypothesis. If the hypothesis is refuted, the next release adapts. + +| Version | Hypothesis | How we validate | +|---|---|---| +| v0.1 | Extraction is non-lossy — InspectFlow keeps 100% of features after migration | Run full InspectFlow E2E suite post-migration | +| v0.1 | Sparkboard can start day 1 with the library | Time-to-first-signin <30min from a clean repo | +| v0.2 | The SPI is enough — no consumer needs to fork | Track GitHub-style "forks" / shadow-overrides across consumers | +| v0.3 | We hit the multi-replica nonce limitation in practice | First time a consumer scales beyond 1 replica + reports replay anomaly | +| v0.4 | Library is documented enough to onboard a stranger | Bring in a fresh contributor, time them on adding a feature | + +When a hypothesis is refuted (e.g. consumers *do* fork the library at v0.2), the next minor turns into a +"why do they fork" investigation before adding more features. + +--- + +## Cross-references + +- Sprint 0 detail: [`Sprint-0-Plan.md`](Sprint-0-Plan.md) +- Architectural rationale: [`Architecture.md`](Architecture.md) +- Open design questions: [`Open-Questions.md`](Open-Questions.md)