plan(s0): chunk 4 - security checklist + rollout + acceptance
+204
@@ -706,3 +706,207 @@ commit.
|
||||
---
|
||||
|
||||
*Plan continues — security review, rollout, acceptance.*
|
||||
|
||||
---
|
||||
|
||||
## 9. Security review checklist
|
||||
|
||||
The library wraps authentication, so the security review bar is higher than for a typical extraction.
|
||||
v0.1.0 cannot tag until every item below is verified.
|
||||
|
||||
### 9.1 Secrets
|
||||
|
||||
- [ ] `plate.auth.jwt.secret` and `plate.auth.exchange.secret` are `@NotBlank @Size(min=32)`. App boot
|
||||
fails if missing or too short. Verified by `PlateAuthPropertiesValidationTest`.
|
||||
- [ ] No default value for either secret anywhere — not in `application.yml`, not in test resources,
|
||||
not in `@Value("${...:default}")` fallback.
|
||||
- [ ] Secrets are read only from env vars / external config. Never logged. JwtService never logs the secret.
|
||||
- [ ] Test fixtures generate per-test secrets via `UUID.randomUUID()` — no fixed test secrets in repo.
|
||||
- [ ] `.gitignore` excludes `.env*` (except `.env.example` template).
|
||||
|
||||
### 9.2 HMAC exchange
|
||||
|
||||
- [ ] HMAC algorithm = SHA-256, fixed, not configurable in v0.1.
|
||||
- [ ] Signature compare uses constant-time comparison
|
||||
(`MessageDigest.isEqual` on backend, `crypto.subtle.timingSafeEqual` equivalent or `Buffer.compare`+length-check on frontend).
|
||||
- [ ] Envelope `iat` checked against `now - maxAge`. Default `maxAge=60s`. Configurable.
|
||||
- [ ] Nonce dedup: every envelope's `nonce` is stored for `nonceTtl` (default 5min). Replay within that
|
||||
window is rejected with HTTP 401.
|
||||
- [ ] In-memory nonce store is documented as **single-replica only**. Multi-replica replay protection
|
||||
deferred to v0.3 (per Roadmap).
|
||||
|
||||
### 9.3 JWT
|
||||
|
||||
- [ ] HMAC SHA-256 signing.
|
||||
- [ ] Issuer claim is validated against `plate.auth.jwt.issuer` (default `"plate-auth"`).
|
||||
- [ ] Expiration validated. Tokens without `exp` rejected.
|
||||
- [ ] No claims contain PII beyond email + user id + role.
|
||||
- [ ] Refresh-token rotation: a successful refresh issues a new refresh token (and ideally invalidates
|
||||
the old). v0.1 keeps the InspectFlow-current behavior — tracked for hardening in v0.3.
|
||||
|
||||
### 9.4 SQL + persistence
|
||||
|
||||
- [ ] All repository queries are JPA Criteria or `@Query` with named parameters — no string concat.
|
||||
- [ ] No `entityManager.createNativeQuery("..." + userInput + "...")` anywhere in moved services.
|
||||
- [ ] Migrations are SQL files only (no Java-callbacks doing reflection-fueled stuff).
|
||||
- [ ] Envers `RevInfoListener` populates `actor_user_id` from `SecurityContextHolder` (defensive null check).
|
||||
|
||||
### 9.5 Input validation
|
||||
|
||||
- [ ] All controllers use `@Valid` on `@RequestBody` DTOs.
|
||||
- [ ] Email fields validated `@Email`.
|
||||
- [ ] Token fields (invitation, refresh) length-checked to expected size.
|
||||
- [ ] `OrgType` and other enums use Jackson default behavior (unknown values → 400).
|
||||
- [ ] No raw `Map<String,Object>` payloads accepted on auth endpoints.
|
||||
|
||||
### 9.6 Error responses
|
||||
|
||||
- [ ] Failed login returns generic "invalid credentials" message — no leak of "user exists" vs
|
||||
"user doesn't exist."
|
||||
- [ ] `LoginEvent.outcome=FAILURE` is recorded even on unknown-user attempts.
|
||||
- [ ] 401 / 403 responses include no stack trace, no SQL fragment, no internal class names.
|
||||
- [ ] Logging: `log.warn("Login failed for {}", email)` — no password. `log.debug("...")` in JwtService
|
||||
never logs the secret or full token.
|
||||
|
||||
### 9.7 Audit
|
||||
|
||||
- [ ] Every state-changing op in `MembershipService`, `InvitationService`, `AccessRequestService`,
|
||||
`AuthService` results in an Envers revision with `actor_user_id` set.
|
||||
- [ ] `LoginEventService.recordSuccess` and `.recordFailure` cover all four `outcome` enum values.
|
||||
- [ ] `AdminAuditController` enforces `hasRole('ADMIN')` via method-security annotations.
|
||||
|
||||
### 9.8 OAuth providers
|
||||
|
||||
- [ ] Google `clientId` and `clientSecret` only configured via env, never default.
|
||||
- [ ] Microsoft Entra ID provider is `@ConditionalOnProperty("plate.auth.providers.microsoft.enabled")`.
|
||||
Default disabled. If enabled without configured creds → fail-fast at startup.
|
||||
- [ ] Email magic-link provider is `@ConditionalOnProperty("plate.auth.providers.email-magic-link.enabled")`.
|
||||
Same fail-fast.
|
||||
- [ ] `allowDangerousEmailAccountLinking` is **false** in NextAuth config — verified by snapshot test of
|
||||
`createAuthConfig` output.
|
||||
|
||||
### 9.9 Frontend
|
||||
|
||||
- [ ] `NEXTAUTH_SECRET` documented as required. Library will not start if missing (NextAuth itself
|
||||
enforces).
|
||||
- [ ] `NEXTAUTH_EXCHANGE_SECRET` is never sent to the browser. Used server-side only in
|
||||
`signIn` callback. Validated by reading the bundled output of `@platesoft/auth/config`.
|
||||
- [ ] Proxy strips hop-by-hop headers (per RFC 7230 + custom `Authorization` override).
|
||||
- [ ] Proxy never echoes the bearer token in error responses.
|
||||
- [ ] Edge-runtime compatibility validated by running tests in `@edge-runtime/jest-environment` or
|
||||
vitest equivalent.
|
||||
|
||||
### 9.10 Dependencies
|
||||
|
||||
- [ ] All deps have CVE scan clean at release tag (Gitea Actions runs Snyk or OWASP dep-check).
|
||||
- [ ] No transitive dep with known CVE > medium severity.
|
||||
- [ ] Renovate (or equivalent) configured to keep deps current post-release.
|
||||
|
||||
---
|
||||
|
||||
## 10. Rollout plan
|
||||
|
||||
### 10.1 Step 0 — internal validation tag
|
||||
|
||||
1. Cut `v0.0.1` from main after all workstreams W1–W7 complete + tests green.
|
||||
2. In a throwaway repo, consume `de.platesoft:plate-auth-starter:0.0.1` + `@platesoft/auth@0.0.1`.
|
||||
3. Implement an `OrgValidator` that returns `true` for any input. Boot the app.
|
||||
4. Hit `/api/auth/config` — should return Google provider info.
|
||||
5. Sign in via Google. Verify exchange + JWT issued + `/api/auth/me` works.
|
||||
6. Cleanup: revoke + redeploy. Mark `v0.0.1` as "validated."
|
||||
|
||||
### 10.2 Step 1 — Sparkboard adoption
|
||||
|
||||
Sparkboard is the easier consumer because it has no auth code yet.
|
||||
|
||||
1. Add the Maven dep + npm dep at the chosen `0.1.0` version.
|
||||
2. Implement Sparkboard's `OrgValidator` against their `workspaces` table.
|
||||
3. Add env vars: `PLATE_AUTH_JWT_SECRET`, `PLATE_AUTH_EXCHANGE_SECRET`,
|
||||
`GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `NEXTAUTH_SECRET`, `NEXTAUTH_EXCHANGE_SECRET`,
|
||||
`NEXT_PUBLIC_BACKEND_URL`.
|
||||
4. Add `app/api/auth/[...nextauth]/route.ts` per [`Integration-Guide.md`](Integration-Guide.md).
|
||||
5. Boot, sign in, ship.
|
||||
6. Time-from-zero-to-first-signin = key v0.1 success metric.
|
||||
|
||||
### 10.3 Step 2 — InspectFlow migration
|
||||
|
||||
InspectFlow is harder because it must replace in-tree code without losing data.
|
||||
|
||||
1. Follow [`Migration-InspectFlow.md`](Migration-InspectFlow.md) end-to-end.
|
||||
2. Critical path:
|
||||
- Add `plate-auth-starter` dep
|
||||
- Remove old `de.platesoft.inspectflow.{filter,service,controller}` auth classes
|
||||
- Rename `inspectflow.*` config props → `plate.auth.*`
|
||||
- Insert baseline rows into `flyway_schema_history_auth` for V1..V5 (data already exists from V26..V31)
|
||||
- Add `OrgValidator` impl wrapping `CompanyRepository.existsById(...)`
|
||||
- Add `OnboardingHook` impl that calls existing `OnboardingService`
|
||||
- Frontend: replace `lib/auth-config.ts` with a thin wrapper over `createAuthConfig`
|
||||
- Run full E2E suite — must pass
|
||||
3. Deploy to staging.
|
||||
4. Smoke test: sign-in for at least one user of each provider (Google, Microsoft if used, Email if used,
|
||||
password if registration was enabled).
|
||||
5. Deploy to production behind feature flag if available; otherwise off-hours deploy with rollback plan.
|
||||
|
||||
### 10.4 Rollback strategy
|
||||
|
||||
If v0.1.0 ships and InspectFlow's adoption breaks something we missed:
|
||||
|
||||
1. **Frontend rollback:** revert the `package.json` dep + re-vendor the old `auth-config.ts`. No DB
|
||||
change required.
|
||||
2. **Backend rollback:** revert the `pom.xml` dep, redeploy. Database is unaffected (entities are the
|
||||
same — just the package names of the classes mapping to them changed). Hibernate `@Table(name="...")`
|
||||
keeps the SQL schema stable.
|
||||
3. **Worst case:** restore from pre-deploy database backup + redeploy old commit. Acceptable downtime
|
||||
window: ≤30min off-hours.
|
||||
|
||||
### 10.5 Acceptance criteria
|
||||
|
||||
v0.1.0 is "done" when:
|
||||
|
||||
| # | Criterion | How verified |
|
||||
|---|---|---|
|
||||
| A1 | Maven artifact published at `de.platesoft:plate-auth-starter:0.1.0` | `mvn dependency:get` from a fresh repo succeeds |
|
||||
| A2 | npm artifact published at `@platesoft/auth@0.1.0` | `npm view @platesoft/auth@0.1.0` from a fresh repo succeeds |
|
||||
| A3 | InspectFlow runs full E2E suite green using the library | CI green on InspectFlow's migration PR |
|
||||
| A4 | Sparkboard signs in a user with only Integration-Guide instructions | Stopwatch < 30min from clean repo |
|
||||
| A5 | All 16 security checklist items in § 9 verified | Security Review document (mode `security-reviewer`) APPROVED |
|
||||
| A6 | Plan Reviewer APPROVED the plan | Plan-Review document committed to wiki repo |
|
||||
| A7 | All 10 wiki docs published + cross-references resolve | Visual review by Patrick |
|
||||
| A8 | CHANGELOG.md released at 0.1.0 | Tag pushed, release notes visible on Gitea |
|
||||
|
||||
---
|
||||
|
||||
## 11. Items deferred to v0.2+
|
||||
|
||||
Tracked here so they don't get lost when Sprint 0 closes:
|
||||
|
||||
- Per-tenant JWT issuer config (multi-app sharing a single Postgres)
|
||||
- Refresh-token rotation table + family-tracking
|
||||
- `LoginEventSink` SPI for external audit-log shipping
|
||||
- Multi-replica nonce store via Redis or Postgres `UPSERT ... ON CONFLICT`
|
||||
- WebAuthn / passkey support
|
||||
- RFC 7807 Problem Details on error responses
|
||||
- Configurable invitation expiration (currently hardcoded 7d)
|
||||
- Default `JavaMailSender`-backed `InvitationMailer`
|
||||
- Better Edge-runtime test coverage
|
||||
- TypeScript export of `Membership`, `Invitation`, `AccessRequest` types
|
||||
- Exported Zod / valibot schemas for envelope and DTOs
|
||||
|
||||
These will form the v0.2 backlog. They are not blockers for v0.1.
|
||||
|
||||
---
|
||||
|
||||
## 12. Cross-references
|
||||
|
||||
- Assessment: [`Sprint-0-Assessment.md`](Sprint-0-Assessment.md)
|
||||
- Test plan: [`Sprint-0-Testplan.md`](Sprint-0-Testplan.md)
|
||||
- Architecture reference: [`Architecture.md`](Architecture.md)
|
||||
- Open questions blocking final plan: [`Open-Questions.md`](Open-Questions.md)
|
||||
- Consumer integration: [`Integration-Guide.md`](Integration-Guide.md)
|
||||
- InspectFlow migration recipe: [`Migration-InspectFlow.md`](Migration-InspectFlow.md)
|
||||
|
||||
---
|
||||
|
||||
*End of plan.*
|
||||
|
||||
**Status:** Submitted for Plan Reviewer (architect mode) review. Patrick GO required before code starts.
|
||||
|
||||
Reference in New Issue
Block a user