diff --git a/Architecture.md b/Architecture.md index 3090c32..28314d8 100644 --- a/Architecture.md +++ b/Architecture.md @@ -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. --- diff --git a/Integration-Guide.md b/Integration-Guide.md index a89195a..5886bbd 100644 --- a/Integration-Guide.md +++ b/Integration-Guide.md @@ -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`). --- diff --git a/Sprint-0-Plan.md b/Sprint-0-Plan.md index 3fc7bee..8f5d425 100644 --- a/Sprint-0-Plan.md +++ b/Sprint-0-Plan.md @@ -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 W1–W7 complete + tests green. +1. Cut `v0.0.1` from main after all workstreams W1–W7 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. diff --git a/Sprint-0-Testplan.md b/Sprint-0-Testplan.md index f87d23c..52b5cd2 100644 --- a/Sprint-0-Testplan.md +++ b/Sprint-0-Testplan.md @@ -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