docs: apply F1 (PermissiveOrgValidator default + per-call WARN) and F2 (V1..V6 migrations) across Architecture, Plan, Testplan, Integration-Guide

Patrick Plate
2026-06-24 15:23:58 +02:00
parent a4d3bb7a57
commit ab7be39839
4 changed files with 33 additions and 23 deletions
+5 -3
@@ -149,13 +149,15 @@ Consumers implement these to plug their domain into plate-auth:
| Interface | Required? | Default | Purpose |
|---|---|---|---|
| `OrgValidator` | **Yes** (if T2 used) | — | Validate `org_id` exists in consumer's `companies` / `workspaces` / etc. |
| `OrgDisplayNameResolver` | Optional | Returns `org_id.toString()` | Pretty-print org for invitations / emails. |
| `OrgValidator` | Optional (override strongly recommended for production) | `PermissiveOrgValidator` — returns `true` for any `(org_type, org_id)` and **logs a WARN on every call** ("OrgValidator default permissive — override `OrgValidator` bean before production") | Validate `(org_type, org_id)` exists in consumer's `companies` / `workspaces` / etc. |
| `OrgDisplayNameResolver` | Optional | Returns `type + ":" + org_id.toString()` | Pretty-print org for invitations / emails. |
| `InvitationMailer` | Optional | No-op logger | Send invitation emails. Default just logs the token URL. |
| `AccessRequestMailer` | Optional | No-op logger | Notify admins of new access requests. |
| `OnboardingHook` | Optional | No-op | Called on first successful sign-in — consumer's place to wire T3 onboarding. |
All extension points are `@ConditionalOnMissingBean` — register your own bean to override.
All extension points are `@ConditionalOnMissingBean` — register your own bean to override. The
`PermissiveOrgValidator` default exists so the starter boots green with zero consumer code; the
per-call WARN log makes it impossible to ignore that no real validation is happening.
---
+4 -3
@@ -110,7 +110,7 @@ That's it for the bare minimum. Run your app — the starter auto-configures.
| `ExchangeService` | HMAC envelope mint/consume |
| `JwtAuthenticationFilter` | Reads `Authorization: Bearer ...`, populates `SecurityContext` |
| `SecurityFilterChain` (named `plateAuthSecurityChain`) | Whitelists `/auth/**`, requires JWT elsewhere |
| `OrgValidator` (default = `PermissiveOrgValidator`) | Accepts any `(org_type, org_id)` — replace via SPI |
| `OrgValidator` (default = `PermissiveOrgValidator`) | Accepts any `(org_type, org_id)` and **logs a WARN on every call** (`"OrgValidator default permissive — override de.platesoft.auth.spi.OrgValidator bean before production"`) — replace via SPI before production |
| `InvitationMailer` (default = `LoggingMailer`) | Logs mails to console — replace via SPI |
| `AccessRequestMailer` (default = `LoggingMailer`) | Same |
| `OnboardingHook` (default = `NoopOnboardingHook`) | Does nothing — replace via SPI |
@@ -405,11 +405,12 @@ Run these manually to verify the integration is healthy:
- [ ] App boots without errors. Logs show `Started ... in X seconds`.
- [ ] `curl http://localhost:8080/auth/health` returns 200 OK.
- [ ] DB has 5 new tables under your schema. `flyway_schema_history_auth` has 5 rows.
- [ ] DB has 6 new tables / index objects under your schema. `flyway_schema_history_auth` has **6 rows** (V1..V6).
- [ ] `curl -d '{"email":"a","password":"b"}' http://localhost:8080/auth/login` returns 401 with structured JSON error (not a stack trace).
- [ ] Frontend `/login` page renders.
- [ ] Sign-in with Google works → redirect → `/api/hello` returns email.
- [ ] No `WARN` logs about missing SPI defaults (you should have replaced `PermissiveOrgValidator` and `LoggingMailer`).
- [ ] **WARN logs `"OrgValidator default permissive — override ..."` are gone** — meaning you registered a real `OrgValidator` bean. Until you do, the default fires on every membership check.
- [ ] No remaining `LoggingMailer` warnings (you should have replaced `InvitationMailer` and `AccessRequestMailer`).
---
+21 -14
@@ -36,8 +36,8 @@ Each step is numbered so the Code-mode worker can check off progress without amb
- Frontend artifact `@platesoft/auth@0.1.0` (npm, NextAuth v5 wiring)
- 5 SPIs (`OrgValidator`, `OrgDisplayNameResolver`, `InvitationMailer`, `AccessRequestMailer`,
`OnboardingHook`)
- Flyway migrations under `db/migration/auth/V1..V5` (one less than InspectFlow because V30 stays
in app — see § 5)
- Flyway migrations under `db/migration/auth/V1..V6` (6 migrations — see § 5 and
[`Architecture.md`](Architecture.md) § 8.1 for the canonical list)
- Gitea Actions pipeline that publishes both artifacts on a `v*` git tag
- Internal integration tests covering exchange + JWT + memberships
- All 10 wiki docs reviewed + approved by Plan Reviewer + GO from Patrick
@@ -372,9 +372,10 @@ through `PlateAuthProperties`. App fails fast at startup if `secret` is missing
```
2. **W4-2** Default implementations (annotated `@ConditionalOnMissingBean`, registered in
`PlateAuthAutoConfiguration`):
- `DefaultOrgValidator` — **does NOT exist as a default**; auto-config requires the consumer to
provide one if T2 endpoints are used. If absent, T2 endpoints fail-fast on first invocation with a
clear error: "Provide a `de.platesoft.auth.spi.OrgValidator` bean to use plate-auth multi-tenancy."
- `PermissiveOrgValidator` — **ships as the default**. `exists(...)` always returns `true` and
**logs a WARN on every call**: `"OrgValidator default permissive — override de.platesoft.auth.spi.OrgValidator bean before production"`.
Rationale: the starter must boot green with zero consumer code; the per-call WARN makes it
impossible to ship to production without noticing that real validation is missing.
- `DefaultOrgDisplayNameResolver` — returns `type + ":" + orgId.toString()`
- `LoggingInvitationMailer` — logs the accept URL at INFO level
- `LoggingAccessRequestMailer` — logs notifications at INFO level
@@ -389,8 +390,9 @@ through `PlateAuthProperties`. App fails fast at startup if `secret` is missing
- What happens if the consumer doesn't provide one (which default kicks in)
- Migration: if a previous version's signature changed, link to CHANGELOG
**Done when:** A consumer can register a single `OrgValidator` bean and have T2 fully functional. Default
mailers log instead of crashing.
**Done when:** Starter boots green with zero SPI beans (default `PermissiveOrgValidator` accepts
all `(org_type, org_id)` and emits per-call WARN). A consumer can register a single `OrgValidator`
bean to replace it and have T2 fully validated. Default mailers log instead of crashing.
---
@@ -554,11 +556,11 @@ for v0.1 (subject to Plan Reviewer concurrence):
> - Runs at startup *before* the application's own Flyway
> - Application's primary `Flyway` continues to manage `flyway_schema_history` for app migrations
>
> **Why:** plate-auth's `V1..V5` numbering is completely independent of any app's `V1..VN`.
> **Why:** plate-auth's `V1..V6` numbering is completely independent of any app's `V1..VN`.
> Both libraries can advance their own version space without collision. Consumers get a clean install
> from scratch, and InspectFlow's `Migration-InspectFlow.md` handles the in-place baseline.
If Plan Reviewer rejects this and prefers numbered-tail approach (e.g. plate-auth ships V1..V5 and
If Plan Reviewer rejects this and prefers numbered-tail approach (e.g. plate-auth ships V1..V6 and
relies on app migrations starting at V100), we revise to single-table strategy. Both approaches are
viable; the separate-table one is more isolating.
@@ -577,15 +579,19 @@ viable; the separate-table one is more isolating.
migrated; if you previously relied on it, keep it in your app's migration."
4. **W5-4** Copy V28 → `V3__create_invitations.sql`.
5. **W5-5** Copy V29 → `V4__create_access_requests.sql`.
6. **W5-6** Copy V31 → `V5__create_login_events_and_revinfo_actor.sql`.
6. **W5-6** Create `V5__add_microsoft_tenant_id_index.sql`. This is a standalone migration that
adds an index on `user_identities.microsoft_tenant_id` (the column itself lives in V1). It stays
separate from the login-events migration so the index can ship as a hotfix without renumbering.
7. **W5-7** Copy V31 → `V6__create_login_events_and_revinfo_actor.sql`.
(V30, `companies.microsoft_tenant_id`, stays in InspectFlow's migration set — T3.)
7. **W5-7** Add `MigrationContentTest` (integration test) that:
8. **W5-8** Add `MigrationContentTest` (integration test) that:
- Spins up Testcontainers Postgres
- Runs plate-auth Flyway against `flyway_schema_history_auth`
- Asserts all 5 versions applied successfully
- Asserts all 6 versions applied successfully (`V1..V6`)
- Asserts no SQL errors in clean install
**Done when:** Migration test passes against Testcontainers Postgres in CI.
**Done when:** Migration test passes against Testcontainers Postgres in CI with 6 rows in
`flyway_schema_history_auth`.
### 7.3 W5 — Auto-config the second Flyway bean
@@ -808,7 +814,8 @@ v0.1.0 cannot tag until every item below is verified.
### 10.1 Step 0 — internal validation tag
1. Cut `v0.0.1` from main after all workstreams W1W7 complete + tests green.
1. Cut `v0.0.1` from main after all workstreams W1W7 complete + tests green (6 Flyway migrations
applied, default `PermissiveOrgValidator` emits WARN on every call).
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.
+3 -3
@@ -188,8 +188,8 @@ Status legend: ⬜ Open · 🟡 In progress · ✅ Passed · ❌ Failed · ⏭
- **Class:** `de.platesoft.auth.spi.OrgContextResolverTest`
- **Given:** No user-provided `OrgValidator` bean; default `PermissiveOrgValidator` in effect.
- **When:** Resolving any `(org_type, org_id)`.
- **Then:** Returns `true` (default-accept), logs a WARN once on startup that no `OrgValidator` is configured.
- **When:** Resolving any `(org_type, org_id)` — called N times.
- **Then:** Returns `true` (default-accept) every time, and emits **one WARN log entry per call** with message containing `"OrgValidator default permissive — override de.platesoft.auth.spi.OrgValidator bean before production"`. Assert WARN count == N (not throttled, not one-shot).
---
@@ -202,7 +202,7 @@ Strategy: each integration test boots Spring with `@SpringBootTest(classes = Pla
- **Class:** `de.platesoft.auth.flyway.PlateAuthFlywayMigrationIT`
- **Given:** Empty Postgres 16 container.
- **When:** Boot starter with default config; Flyway runs.
- **Then:** Schema contains tables `users`, `user_identities`, `memberships`, `invitations`, `access_requests`, `login_events`. `flyway_schema_history_auth` table has 5 rows (V1..V5). All migrations are non-failed.
- **Then:** Schema contains tables `users`, `user_identities`, `memberships`, `invitations`, `access_requests`, `login_events`, plus the index `idx_user_identities_microsoft_tenant_id` from V5. `flyway_schema_history_auth` table has **6 rows (V1..V6)**. All migrations are non-failed.
### T-IT02 — Auto-config wires required beans