plan: add Migration-InspectFlow.md (dep swap + Flyway baseline + rollback)
@@ -0,0 +1,563 @@
|
||||
# Migration Guide: InspectFlow → plate-auth
|
||||
|
||||
**Status:** Draft v1
|
||||
**Date:** 2026-06-24
|
||||
**Owner:** Patrick
|
||||
**Audience:** InspectFlow maintainer performing the dependency swap from in-tree auth code to `plate-auth-starter`
|
||||
**Target plate-auth version:** `0.1.0` (after `0.0.1` validation tag)
|
||||
|
||||
> This guide is **InspectFlow-specific**. For greenfield consumers, see [Integration-Guide.md](Integration-Guide.md).
|
||||
|
||||
---
|
||||
|
||||
## 1. What this migration does
|
||||
|
||||
Replaces InspectFlow's hand-rolled auth code with a dependency on `plate-auth-starter` + `@platesoft/auth`. After this migration:
|
||||
|
||||
- ~25 backend classes under `de.platesoft.inspectflow.{security,filter,service,controller,entity,repository,dto,enums}` are **deleted** and become a single Maven dependency.
|
||||
- ~5–8 frontend files under `frontend/{auth.ts, app/api/[...path]/route.ts, app/api/auth/[...nextauth]/route.ts, lib/hmac.ts}` are **deleted** and become a single npm dependency + a 5-line `auth.ts`.
|
||||
- Flyway migrations `V26..V31` are **kept** but marked as baseline rows in the new `flyway_schema_history_auth` table so plate-auth's `V1..V5` are not re-applied.
|
||||
- InspectFlow keeps `Company` entity + `OrgValidator` SPI implementation + onboarding logic — **T3 stays in-house**.
|
||||
|
||||
**Goal:** zero behavioral change for end-users. All E2E tests stay green. ([Sprint-0-Testplan.md § 6](Sprint-0-Testplan.md))
|
||||
|
||||
---
|
||||
|
||||
## 2. Prerequisites
|
||||
|
||||
Before starting:
|
||||
|
||||
- [ ] InspectFlow on **Spring Boot 4.1.0** (bump from 4.0.7 if needed — Sprint 14.7 prerequisite, see [Open-Questions.md](Open-Questions.md) Q08)
|
||||
- [ ] plate-auth `0.0.1` validation tag has been published and you can resolve it from Gitea
|
||||
- [ ] All Sprint 14.6 work merged to `main` (clean baseline)
|
||||
- [ ] Database backup taken
|
||||
- [ ] Sparkboard has **already** consumed plate-auth `0.0.1` successfully (proves the library works against a real consumer)
|
||||
- [ ] Feature branch: `feature/plate-auth-migration`
|
||||
|
||||
If any of these is false, **stop** and resolve first.
|
||||
|
||||
---
|
||||
|
||||
## 3. Migration overview
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
S0[Sprint 14.6 baseline] --> A[Step A: Add deps]
|
||||
A --> B[Step B: Delete moved backend classes]
|
||||
B --> C[Step C: Rename config keys]
|
||||
C --> D[Step D: Add SPI implementations]
|
||||
D --> E[Step E: Flyway baseline rows]
|
||||
E --> F[Step F: Frontend swap]
|
||||
F --> G[Step G: Run E2E suite]
|
||||
G -->|green| H[Step H: Merge + delete dead code]
|
||||
G -->|red| R[Rollback]
|
||||
```
|
||||
|
||||
Estimated effort: **1–2 days** for an experienced maintainer assuming the library works as advertised. Most time is verification, not coding.
|
||||
|
||||
---
|
||||
|
||||
## 4. Step A — Add dependencies
|
||||
|
||||
### 4.1 Backend `pom.xml`
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>de.platesoft</groupId>
|
||||
<artifactId>plate-auth-starter</artifactId>
|
||||
<version>0.1.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Add Gitea Maven repo + credentials if not already present (see [Integration-Guide.md § 3.1](Integration-Guide.md)).
|
||||
|
||||
### 4.2 Frontend `package.json`
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
pnpm add @platesoft/auth@0.1.0
|
||||
```
|
||||
|
||||
`.npmrc` must contain the `@platesoft:` scope mapping.
|
||||
|
||||
### 4.3 Verify build still passes
|
||||
|
||||
```bash
|
||||
cd backend && mvn clean compile
|
||||
cd frontend && pnpm install
|
||||
```
|
||||
|
||||
No code changes yet — both halves should still build cleanly with the new deps idle alongside the existing in-tree code.
|
||||
|
||||
---
|
||||
|
||||
## 5. Step B — Delete moved backend classes
|
||||
|
||||
These classes are now provided by `plate-auth-starter`. Delete the InspectFlow-local copy after verifying the package and class name match exactly.
|
||||
|
||||
### 5.1 Classes to delete
|
||||
|
||||
| InspectFlow path | Replaced by |
|
||||
|------------------|-------------|
|
||||
| [`backend/src/main/java/de/platesoft/inspectflow/service/JwtService.java`](backend/src/main/java/de/platesoft/inspectflow/service/JwtService.java:1) | `de.platesoft.auth.service.JwtService` |
|
||||
| [`backend/src/main/java/de/platesoft/inspectflow/filter/JwtAuthenticationFilter.java`](backend/src/main/java/de/platesoft/inspectflow/filter/JwtAuthenticationFilter.java:1) | `de.platesoft.auth.filter.JwtAuthenticationFilter` |
|
||||
| [`backend/src/main/java/de/platesoft/inspectflow/config/SecurityConfig.java`](backend/src/main/java/de/platesoft/inspectflow/config/SecurityConfig.java:1) | Auto-config in `PlateAuthAutoConfiguration` (see § 5.4 below) |
|
||||
| `…/inspectflow/service/ExchangeService.java` | `de.platesoft.auth.service.ExchangeService` |
|
||||
| `…/inspectflow/service/HmacEnvelope.java` | `de.platesoft.auth.crypto.HmacEnvelope` |
|
||||
| `…/inspectflow/service/MembershipService.java` | `de.platesoft.auth.service.MembershipService` |
|
||||
| `…/inspectflow/service/InvitationService.java` | `de.platesoft.auth.service.InvitationService` |
|
||||
| `…/inspectflow/service/AccessRequestService.java` | `de.platesoft.auth.service.AccessRequestService` |
|
||||
| `…/inspectflow/service/AuthService.java` (login/signup/reset) | `de.platesoft.auth.service.AuthService` |
|
||||
| `…/inspectflow/controller/AuthController.java` | `de.platesoft.auth.controller.AuthController` |
|
||||
| `…/inspectflow/controller/MembershipController.java` | `de.platesoft.auth.controller.MembershipController` |
|
||||
| `…/inspectflow/controller/InvitationController.java` | `de.platesoft.auth.controller.InvitationController` |
|
||||
| `…/inspectflow/controller/AccessRequestController.java` | `de.platesoft.auth.controller.AccessRequestController` |
|
||||
| `…/inspectflow/controller/AdminAuditController.java` | `de.platesoft.auth.controller.AdminAuditController` |
|
||||
| `…/inspectflow/entity/User.java` | `de.platesoft.auth.entity.User` |
|
||||
| `…/inspectflow/entity/UserIdentity.java` | `de.platesoft.auth.entity.UserIdentity` |
|
||||
| `…/inspectflow/entity/Membership.java` | `de.platesoft.auth.entity.Membership` (with polymorphic FK — see § 6.3) |
|
||||
| `…/inspectflow/entity/Invitation.java` | `de.platesoft.auth.entity.Invitation` |
|
||||
| `…/inspectflow/entity/AccessRequest.java` | `de.platesoft.auth.entity.AccessRequest` |
|
||||
| `…/inspectflow/entity/LoginEvent.java` | `de.platesoft.auth.entity.LoginEvent` |
|
||||
| `…/inspectflow/repository/UserRepository.java` | `de.platesoft.auth.repository.UserRepository` |
|
||||
| `…/inspectflow/repository/MembershipRepository.java` | `de.platesoft.auth.repository.MembershipRepository` |
|
||||
| `…/inspectflow/repository/InvitationRepository.java` | `de.platesoft.auth.repository.InvitationRepository` |
|
||||
| `…/inspectflow/repository/AccessRequestRepository.java` | `de.platesoft.auth.repository.AccessRequestRepository` |
|
||||
| `…/inspectflow/repository/LoginEventRepository.java` | `de.platesoft.auth.repository.LoginEventRepository` |
|
||||
| `…/inspectflow/dto/...` (auth-related) | `de.platesoft.auth.dto.*` |
|
||||
|
||||
### 5.2 Classes to KEEP (T3 — InspectFlow-specific)
|
||||
|
||||
These stay in InspectFlow because they encode the Company domain or onboarding logic:
|
||||
|
||||
| Path | Reason |
|
||||
|------|--------|
|
||||
| `de.platesoft.inspectflow.entity.Company` | Concrete org table — InspectFlow's domain |
|
||||
| `de.platesoft.inspectflow.repository.CompanyRepository` | Same |
|
||||
| `de.platesoft.inspectflow.service.CompanyService` | Same |
|
||||
| `de.platesoft.inspectflow.service.OrgContextResolver` | Adapter — refactor to implement plate-auth's `OrgValidator` SPI |
|
||||
| `de.platesoft.inspectflow.service.OnboardingService` | App-specific onboarding (create default machines, etc.) |
|
||||
|
||||
### 5.3 Find-and-fix imports
|
||||
|
||||
After deleting the classes in § 5.1, the rest of InspectFlow that references them (controllers, services that aren't auth-related) will fail to compile. Rewrite imports:
|
||||
|
||||
```bash
|
||||
# Find all consumers of the deleted packages
|
||||
cd backend
|
||||
grep -rln "de.platesoft.inspectflow.service.JwtService" src/main/java/
|
||||
grep -rln "de.platesoft.inspectflow.entity.User" src/main/java/
|
||||
# … repeat per deleted class
|
||||
|
||||
# Replace package prefix
|
||||
find src/main/java -name "*.java" -exec sed -i '' \
|
||||
-e 's|de.platesoft.inspectflow.service.JwtService|de.platesoft.auth.service.JwtService|g' \
|
||||
-e 's|de.platesoft.inspectflow.entity.User|de.platesoft.auth.entity.User|g' \
|
||||
-e 's|de.platesoft.inspectflow.entity.Membership|de.platesoft.auth.entity.Membership|g' \
|
||||
{} +
|
||||
```
|
||||
|
||||
> **Take small steps.** Replace one package at a time and recompile. Big-bang sed will leave you with a sea of red.
|
||||
|
||||
### 5.4 SecurityConfig
|
||||
|
||||
The starter ships its own `SecurityFilterChain` bean named `plateAuthSecurityChain`. InspectFlow's current [`SecurityConfig.java`](backend/src/main/java/de/platesoft/inspectflow/config/SecurityConfig.java:21) declares its own beans for `passwordEncoder`, `authenticationManager`, and a `filterChain`. Three of these are auto-provided by the starter:
|
||||
|
||||
- `passwordEncoder` → `PlateAuthAutoConfiguration` provides `BCryptPasswordEncoder` with cost 10. Delete InspectFlow's.
|
||||
- `authenticationManager` → same. Delete InspectFlow's.
|
||||
- `filterChain` → starter's `plateAuthSecurityChain` covers `/auth/**` + JWT for the rest. **Keep** an InspectFlow-specific chain for paths that need custom rules (e.g. `/api/admin/**` requires `hasRole('ADMIN')`).
|
||||
|
||||
```java
|
||||
// InspectFlow keeps a focused chain:
|
||||
@Bean
|
||||
@Order(0) // before plateAuthSecurityChain
|
||||
public SecurityFilterChain inspectflowAdminChain(HttpSecurity http) throws Exception {
|
||||
return http
|
||||
.securityMatcher("/api/admin/**")
|
||||
.authorizeHttpRequests(a -> a.anyRequest().hasRole("ADMIN"))
|
||||
.csrf(c -> c.disable())
|
||||
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.build();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Step C — Rename config keys
|
||||
|
||||
InspectFlow has historically scattered auth config across `inspectflow.*`, `jwt.*`, and `nextauth.*`. plate-auth consolidates everything under `plate.auth.*`.
|
||||
|
||||
### 6.1 Backend: `application.yml`
|
||||
|
||||
Before:
|
||||
|
||||
```yaml
|
||||
jwt:
|
||||
secret: ${JWT_SECRET}
|
||||
access-expiration-minutes: 15
|
||||
refresh-expiration-days: 30
|
||||
nextauth:
|
||||
exchange-secret: ${EXCHANGE_SECRET}
|
||||
exchange-max-age-seconds: 60
|
||||
inspectflow:
|
||||
registration-enabled: false
|
||||
cors:
|
||||
allowed-origins: [...]
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```yaml
|
||||
plate:
|
||||
auth:
|
||||
jwt:
|
||||
secret: ${JWT_SECRET}
|
||||
access-expiration: PT15M
|
||||
refresh-expiration: P30D
|
||||
issuer: inspectflow
|
||||
exchange:
|
||||
secret: ${EXCHANGE_SECRET}
|
||||
max-age: PT60S
|
||||
nonce-ttl: PT5M
|
||||
registration:
|
||||
enabled: false
|
||||
cors:
|
||||
allowed-origins: [...]
|
||||
providers:
|
||||
google:
|
||||
enabled: true
|
||||
client-id: ${GOOGLE_CLIENT_ID}
|
||||
client-secret: ${GOOGLE_CLIENT_SECRET}
|
||||
```
|
||||
|
||||
**Format changes:**
|
||||
|
||||
- Numeric `*-minutes` / `*-seconds` / `*-days` → ISO-8601 `Duration` format (`PT15M`, `PT60S`, `P30D`). Spring Boot parses both `15m` and `PT15M`, but the starter validates the ISO form.
|
||||
|
||||
### 6.2 Frontend: `.env.local` / `.env.production`
|
||||
|
||||
| Old | New |
|
||||
|-----|-----|
|
||||
| `JWT_SECRET` | (unused — backend only now) |
|
||||
| `EXCHANGE_SECRET` | `PLATE_AUTH_EXCHANGE_SECRET` |
|
||||
| `BACKEND_URL` | `PLATE_AUTH_BACKEND_URL` |
|
||||
| `NEXTAUTH_SECRET` | `NEXTAUTH_SECRET` (unchanged) |
|
||||
| `NEXTAUTH_URL` | `NEXTAUTH_URL` (unchanged) |
|
||||
| `GOOGLE_CLIENT_ID` | `GOOGLE_CLIENT_ID` (unchanged) |
|
||||
| `GOOGLE_CLIENT_SECRET` | `GOOGLE_CLIENT_SECRET` (unchanged) |
|
||||
|
||||
Update `docker-compose.yml`, TrueNAS config, `.env.example`, and `.env.prod.example`.
|
||||
|
||||
### 6.3 Membership entity column changes
|
||||
|
||||
Old InspectFlow `Membership`:
|
||||
|
||||
```java
|
||||
@Entity
|
||||
public class Membership {
|
||||
@ManyToOne private User user;
|
||||
@ManyToOne private Company company; // ← direct FK
|
||||
private Role role;
|
||||
}
|
||||
```
|
||||
|
||||
New plate-auth `Membership`:
|
||||
|
||||
```java
|
||||
@Entity
|
||||
public class Membership {
|
||||
@ManyToOne private User user;
|
||||
private String orgType; // "COMPANY"
|
||||
private Long orgId; // company.id
|
||||
private Role role;
|
||||
}
|
||||
```
|
||||
|
||||
**Migration:** the existing `memberships.company_id` column must be rewritten to `(org_type='COMPANY', org_id=company_id)`. Handled by the Flyway data-migration step in § 7.3.
|
||||
|
||||
---
|
||||
|
||||
## 7. Step D — Add SPI implementations
|
||||
|
||||
InspectFlow plugs back into plate-auth via SPI beans. Add a new config class:
|
||||
|
||||
```java
|
||||
// backend/src/main/java/de/platesoft/inspectflow/auth/PlateAuthBindings.java
|
||||
@Configuration
|
||||
public class PlateAuthBindings {
|
||||
|
||||
@Bean
|
||||
public OrgValidator orgValidator(CompanyRepository companies) {
|
||||
return (orgType, orgId) ->
|
||||
"COMPANY".equals(orgType) && companies.existsById(orgId);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public OrgDisplayNameResolver orgDisplayNameResolver(CompanyRepository companies) {
|
||||
return (orgType, orgId) -> companies.findById(orgId)
|
||||
.map(Company::getName)
|
||||
.orElse("Unbekannt");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public InvitationMailer invitationMailer(InspectFlowMailService mail) {
|
||||
return mail::sendInvitation; // delegate to existing mailer
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AccessRequestMailer accessRequestMailer(InspectFlowMailService mail) {
|
||||
return mail::sendAccessRequestNotification;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public OnboardingHook onboardingHook(OnboardingService onboarding) {
|
||||
return onboarding::onUserCreated; // delegate to existing onboarding
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This replaces all five default SPI beans. The starter logs "User-provided OrgValidator detected, default disabled" at INFO on boot — verify this in your first run.
|
||||
|
||||
---
|
||||
|
||||
## 8. Step E — Flyway baseline rows
|
||||
|
||||
InspectFlow's existing `flyway_schema_history` contains rows for `V26..V31` (your existing auth migrations). plate-auth ships its own migrations `V1..V5` and runs them against `flyway_schema_history_auth` (a separate table). The schema already has the tables — running `V1..V5` would fail.
|
||||
|
||||
### 8.1 Migration approach
|
||||
|
||||
Add a new InspectFlow-local migration `V32__C_seed_plate_auth_history.sql`:
|
||||
|
||||
```sql
|
||||
-- Pre-populate flyway_schema_history_auth so plate-auth Flyway sees the tables as already migrated.
|
||||
-- The schema was created by InspectFlow's V26..V31; plate-auth's V1..V5 would otherwise re-apply them.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS flyway_schema_history_auth (
|
||||
installed_rank int NOT NULL,
|
||||
version varchar(50),
|
||||
description varchar(200) NOT NULL,
|
||||
type varchar(20) NOT NULL,
|
||||
script varchar(1000) NOT NULL,
|
||||
checksum int,
|
||||
installed_by varchar(100) NOT NULL,
|
||||
installed_on timestamp NOT NULL DEFAULT now(),
|
||||
execution_time int NOT NULL,
|
||||
success boolean NOT NULL,
|
||||
PRIMARY KEY (installed_rank)
|
||||
);
|
||||
|
||||
INSERT INTO flyway_schema_history_auth
|
||||
(installed_rank, version, description, type, script, checksum, installed_by, execution_time, success)
|
||||
VALUES
|
||||
(1, '1', 'Create users', 'SQL', 'V1__C_create_users.sql', NULL, 'inspectflow-migration', 0, true),
|
||||
(2, '2', 'Create user_identities','SQL', 'V2__C_create_user_identities.sql', NULL, 'inspectflow-migration', 0, true),
|
||||
(3, '3', 'Create memberships', 'SQL', 'V3__C_create_memberships.sql', NULL, 'inspectflow-migration', 0, true),
|
||||
(4, '4', 'Create invitations', 'SQL', 'V4__C_create_invitations.sql', NULL, 'inspectflow-migration', 0, true),
|
||||
(5, '5', 'Create login_events', 'SQL', 'V5__C_create_login_events.sql', NULL, 'inspectflow-migration', 0, true)
|
||||
ON CONFLICT DO NOTHING;
|
||||
```
|
||||
|
||||
> **Checksum caveat.** Setting `checksum = NULL` makes Flyway skip checksum validation. If you want strict mode, run plate-auth on a scratch DB once, copy the checksums from its `flyway_schema_history_auth`, and paste them here.
|
||||
|
||||
### 8.2 Data migration: memberships.company_id → (org_type, org_id)
|
||||
|
||||
Same migration file or separate `V33__C_rewrite_memberships_to_polymorphic.sql`:
|
||||
|
||||
```sql
|
||||
-- Add new columns
|
||||
ALTER TABLE memberships ADD COLUMN IF NOT EXISTS org_type varchar(64);
|
||||
ALTER TABLE memberships ADD COLUMN IF NOT EXISTS org_id bigint;
|
||||
|
||||
-- Backfill from existing company_id
|
||||
UPDATE memberships SET org_type = 'COMPANY', org_id = company_id WHERE company_id IS NOT NULL;
|
||||
|
||||
-- Index + non-null constraint
|
||||
CREATE INDEX IF NOT EXISTS idx_memberships_org ON memberships(org_type, org_id);
|
||||
ALTER TABLE memberships ALTER COLUMN org_type SET NOT NULL;
|
||||
ALTER TABLE memberships ALTER COLUMN org_id SET NOT NULL;
|
||||
|
||||
-- Drop the old FK column (after verification!)
|
||||
-- ALTER TABLE memberships DROP CONSTRAINT memberships_company_id_fkey;
|
||||
-- ALTER TABLE memberships DROP COLUMN company_id;
|
||||
```
|
||||
|
||||
> **Caution.** Do **not** drop `company_id` in the same migration. Keep it as a shadow column until the next minor release and verify no code still reads it.
|
||||
|
||||
---
|
||||
|
||||
## 9. Step F — Frontend swap
|
||||
|
||||
### 9.1 Delete files
|
||||
|
||||
- [`frontend/auth.ts`](frontend/auth.ts:1) → rewrite (5 lines, see § 9.3)
|
||||
- `frontend/app/api/auth/[...nextauth]/route.ts` → keep, no change needed
|
||||
- `frontend/app/api/[...path]/route.ts` → rewrite (3 lines, see § 9.4)
|
||||
- `frontend/lib/hmac.ts` (if exists) → delete; replaced by `@platesoft/auth/edge`
|
||||
- `frontend/lib/auth-config.ts` (if exists) → delete; replaced by `createAuthConfig` factory
|
||||
|
||||
### 9.2 Keep files
|
||||
|
||||
- `frontend/middleware.ts` — InspectFlow keeps its own (custom redirect logic for unauth users)
|
||||
- `frontend/components/login-form.tsx` etc. if you have custom UI — leave them, ignore `@platesoft/auth/react`
|
||||
|
||||
### 9.3 New `frontend/auth.ts`
|
||||
|
||||
```typescript
|
||||
import { createAuthConfig } from "@platesoft/auth/server";
|
||||
import NextAuth from "next-auth";
|
||||
|
||||
export const { handlers, auth, signIn, signOut } = NextAuth(
|
||||
createAuthConfig({
|
||||
backendUrl: process.env.PLATE_AUTH_BACKEND_URL!,
|
||||
exchangeSecret: process.env.PLATE_AUTH_EXCHANGE_SECRET!,
|
||||
nextAuthSecret: process.env.NEXTAUTH_SECRET!,
|
||||
providers: {
|
||||
google: {
|
||||
clientId: process.env.GOOGLE_CLIENT_ID!,
|
||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
||||
},
|
||||
},
|
||||
pages: {
|
||||
signIn: "/login",
|
||||
},
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
### 9.4 New `frontend/app/api/[...path]/route.ts`
|
||||
|
||||
```typescript
|
||||
import { createProxyHandlers } from "@platesoft/auth/edge";
|
||||
|
||||
export const runtime = "edge";
|
||||
|
||||
export const { GET, POST, PUT, PATCH, DELETE } = createProxyHandlers({
|
||||
backendUrl: process.env.PLATE_AUTH_BACKEND_URL!,
|
||||
exchangeSecret: process.env.PLATE_AUTH_EXCHANGE_SECRET!,
|
||||
});
|
||||
```
|
||||
|
||||
That's it. Anything else InspectFlow needs (custom error pages, custom callbacks) is wired into NextAuth options that `createAuthConfig` exposes as overridable.
|
||||
|
||||
---
|
||||
|
||||
## 10. Step G — Run the E2E suite
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
pnpm exec playwright install --with-deps # if not already
|
||||
pnpm exec playwright test
|
||||
```
|
||||
|
||||
All 30+ scenarios in [`frontend/e2e/`](frontend/e2e/) must pass green. The critical ones for this migration:
|
||||
|
||||
- `auth-flow.unauth.spec.ts` — anonymous redirect, login form
|
||||
- `companies.spec.ts` — membership reads work
|
||||
- `admin-archive.spec.ts`, `admin-timeline.spec.ts`, `admin-migration.spec.ts` — admin role gate
|
||||
- `command-palette.spec.ts` — JWT-protected ops
|
||||
- Any "login as admin / login as user" setup script in `auth.setup.ts`
|
||||
|
||||
If any test fails, **stop**. Diagnose via:
|
||||
|
||||
1. Check backend logs for `OrgValidator` / SPI warnings.
|
||||
2. Check `login_events` table for the failing user.
|
||||
3. Diff the failing request between old + new code paths (proxy headers, exchange envelope shape).
|
||||
|
||||
---
|
||||
|
||||
## 11. Step H — Merge and clean up
|
||||
|
||||
After all tests pass:
|
||||
|
||||
1. Squash-merge `feature/plate-auth-migration` to `main`.
|
||||
2. Tag InspectFlow as `vX.Y+1.0` (minor bump — internal refactor with config breaking change for ops).
|
||||
3. Update `docs/` — add a section in [`docs/SPRINT-14-OVERVIEW.md`](docs/SPRINT-14-OVERVIEW.md) noting the carve-out + library version.
|
||||
4. **One sprint later**, drop `memberships.company_id` column in a follow-up migration (give yourself time to revert if production reveals issues).
|
||||
5. Subscribe to plate-auth `0.2.0` for the v0.2 feature wave (refresh rotation, magic-link, multi-key secrets).
|
||||
|
||||
---
|
||||
|
||||
## 12. Rollback procedure
|
||||
|
||||
If the migration goes sideways:
|
||||
|
||||
### Code rollback
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
git revert feature/plate-auth-migration # or just deploy the previous tag
|
||||
```
|
||||
|
||||
### Database rollback
|
||||
|
||||
The data migration in § 8.2 is **additive** (`memberships.org_type`, `org_id` are new columns). The old `company_id` column is preserved. Rolling back the code means the old `Membership` entity reads `company_id` again and ignores the new columns — safe.
|
||||
|
||||
The Flyway baseline rows in `flyway_schema_history_auth` are harmless if the code is reverted — the table simply exists with 5 rows and no consumer.
|
||||
|
||||
### Secret rollback
|
||||
|
||||
If you have already rotated `PLATE_AUTH_EXCHANGE_SECRET` and `EXCHANGE_SECRET` was different in the old code, restore the old env vars when reverting the code.
|
||||
|
||||
### Total downtime
|
||||
|
||||
The migration is designed for **zero-downtime deploy**:
|
||||
|
||||
- New columns added before code switch (additive).
|
||||
- New code reads new columns; old code reads old.
|
||||
- Stop the old replica, start the new — one request is in flight, at worst.
|
||||
|
||||
If you cannot do zero-downtime, plan a 5-minute maintenance window.
|
||||
|
||||
---
|
||||
|
||||
## 13. What changes for end-users
|
||||
|
||||
**Nothing.** Logins, sessions, sign-ups, invitations, access requests, admin views — all behave identically. The change is internal: same APIs, same UX, different code path.
|
||||
|
||||
If a user reports a regression, treat it as a bug — file an InspectFlow ticket **and** a plate-auth ticket (since the regression is likely in the library, not in InspectFlow's residual code).
|
||||
|
||||
---
|
||||
|
||||
## 14. Common pitfalls (migration-specific)
|
||||
|
||||
| Symptom | Likely cause | Fix |
|
||||
|---------|--------------|-----|
|
||||
| Boot fails with `Field 'orgType' of Membership is null` | Forgot the data migration in § 8.2 | Run V33 manually |
|
||||
| Flyway: "checksum mismatch on V1" | Baseline rows in § 8.1 have wrong checksums | Set `flyway.validate-on-migrate=false` temporarily, or recompute checksums |
|
||||
| `OrgValidator` always returns false | Wrong `orgType` literal — case sensitive (`"COMPANY"` not `"company"`) | Check § 7 SPI binding |
|
||||
| Frontend cannot reach backend → 502 | `BACKEND_URL` rename to `PLATE_AUTH_BACKEND_URL` not propagated to all envs | Grep `BACKEND_URL` across repo + deploy configs |
|
||||
| E2E `companies.spec.ts` fails | `OrgDisplayNameResolver` SPI not registered → display name shows `Unbekannt` | Add bean in § 7 |
|
||||
| Admin chain order wrong → users get 403 on `/api/admin/...` | `@Order(0)` missing on `inspectflowAdminChain` | Add explicit order; default starter chain is `@Order(100)` |
|
||||
|
||||
---
|
||||
|
||||
## 15. Sign-off checklist
|
||||
|
||||
Before declaring migration complete:
|
||||
|
||||
- [ ] All Playwright E2E tests pass
|
||||
- [ ] Manual smoke test: log in via Google, password, accept invite, request access, approve as admin
|
||||
- [ ] `/admin/audit` shows `login_events` rows from both old and new code paths (timestamps span the migration)
|
||||
- [ ] No `WARN`/`ERROR` log entries from `de.platesoft.auth.*` in first 24h post-deploy
|
||||
- [ ] Database row counts before/after migration match (users, memberships, invitations, access_requests, login_events)
|
||||
- [ ] `memberships.org_type` is non-null on all rows (use SQL audit)
|
||||
- [ ] Deleted backend classes (§ 5.1) are actually deleted (no zombies)
|
||||
- [ ] Deleted frontend files (§ 9.1) are actually deleted
|
||||
|
||||
---
|
||||
|
||||
## 16. Cross-references
|
||||
|
||||
- [Home.md](Home.md)
|
||||
- [Vision.md](Vision.md)
|
||||
- [Architecture.md](Architecture.md)
|
||||
- [Roadmap.md](Roadmap.md)
|
||||
- [Sprint-0-Assessment.md](Sprint-0-Assessment.md)
|
||||
- [Sprint-0-Plan.md](Sprint-0-Plan.md)
|
||||
- [Sprint-0-Testplan.md](Sprint-0-Testplan.md)
|
||||
- [Open-Questions.md](Open-Questions.md)
|
||||
- [Integration-Guide.md](Integration-Guide.md)
|
||||
|
||||
---
|
||||
|
||||
**End of Migration-InspectFlow.md (v1).**
|
||||
Reference in New Issue
Block a user