docs(integration-guide): Sparkboard plate-auth wire-up walkthrough
+476
@@ -0,0 +1,476 @@
|
||||
# Integration Guide — Sparkboard ⇆ plate-auth
|
||||
|
||||
**Status:** Draft v1
|
||||
**Audience:** Anyone wiring a new plate-software app against plate-auth (Sparkboard is the worked example)
|
||||
**Last updated:** 2026-06-24
|
||||
**Companion to:** [plate-auth/Integration-Guide](https://git.plate-software.de/pplate/plate-auth/wiki/Integration-Guide)
|
||||
|
||||
> This page is the **Sparkboard-specific** walkthrough. It assumes you've read the generic [plate-auth Integration Guide](https://git.plate-software.de/pplate/plate-auth/wiki/Integration-Guide) and shows how the abstract pieces map to Sparkboard's concrete code.
|
||||
|
||||
---
|
||||
|
||||
## 1. What Sparkboard imports from plate-auth
|
||||
|
||||
Sparkboard depends on exactly two artifacts:
|
||||
|
||||
| Artifact | Version | Where it shows up |
|
||||
|----------|---------|-------------------|
|
||||
| `de.platesoft:plate-auth-starter` | `0.1.0` | `backend/pom.xml` |
|
||||
| `@platesoft/auth` | `0.1.0` | `frontend/package.json` |
|
||||
|
||||
That's it. There is no third artifact. There is no Sparkboard-specific fork. There is no "we patched plate-auth to do X."
|
||||
|
||||
If Sparkboard needs new behaviour that doesn't fit the SPI seams, the change goes **upstream into plate-auth**, gets released as v0.2.0 or v0.1.1, and Sparkboard bumps its dependency. Sparkboard never customises plate-auth in-tree.
|
||||
|
||||
---
|
||||
|
||||
## 2. The one SPI Sparkboard implements
|
||||
|
||||
Of plate-auth's 5 SPI seams (`OrgValidator`, `OrgDisplayNameResolver`, `InvitationMailer`, `AccessRequestMailer`, `OnboardingHook`), **Sparkboard implements only one**: `OnboardingHook`.
|
||||
|
||||
The other four:
|
||||
|
||||
| SPI | What plate-auth does by default | Why Sparkboard doesn't override |
|
||||
|-----|--------------------------------|---------------------------------|
|
||||
| `OrgValidator` | "Allow any org_type" | Sparkboard only has one org_type (`SPARK_ORG`) and one org instance; nothing to validate against. |
|
||||
| `OrgDisplayNameResolver` | Returns `"Untitled org"` | Sparkboard's single org is hard-coded as "Family Spark" via the seed migration. The resolver is never queried because no UI surfaces multi-org. |
|
||||
| `InvitationMailer` | No-op (log only) | Sparkboard has no invite UI in v1. |
|
||||
| `AccessRequestMailer` | No-op (log only) | Sparkboard has no access-request flow. |
|
||||
|
||||
Sparkboard's [`SparkboardOnboardingHook`](Architecture.md#5-single-org-mode--the-onboardinghook-spi) is the **entire** Java auth integration. The full impl sketch:
|
||||
|
||||
```java
|
||||
// backend/src/main/java/de/plate/sparkboard/auth/SparkboardOnboardingHook.java
|
||||
package de.plate.sparkboard.auth;
|
||||
|
||||
import de.platesoft.auth.spi.OnboardingHook;
|
||||
import de.platesoft.auth.model.AuthenticatedUser;
|
||||
import de.platesoft.auth.service.MembershipService;
|
||||
import org.springframework.stereotype.Component;
|
||||
import java.util.UUID;
|
||||
|
||||
@Component
|
||||
public class SparkboardOnboardingHook implements OnboardingHook {
|
||||
|
||||
public static final UUID FAMILY_SPARK_ID =
|
||||
UUID.fromString("00000000-0000-0000-0000-000000000001");
|
||||
public static final String SPARK_ORG_TYPE = "SPARK_ORG";
|
||||
|
||||
private final MembershipService memberships;
|
||||
private final SparkboardAdminProperties adminProps;
|
||||
|
||||
public SparkboardOnboardingHook(MembershipService memberships,
|
||||
SparkboardAdminProperties adminProps) {
|
||||
this.memberships = memberships;
|
||||
this.adminProps = adminProps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFirstLogin(AuthenticatedUser user) {
|
||||
var role = adminProps.admins().contains(user.email()) ? "ADMIN" : "MEMBER";
|
||||
memberships.upsert(user.id(), SPARK_ORG_TYPE, FAMILY_SPARK_ID, role);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
That's the entire SPI footprint. Sparkboard is a `@Component`, plate-auth's auto-config picks it up via Spring's `ObjectProvider<OnboardingHook>`, and the hook fires every login (idempotent thanks to `upsert`).
|
||||
|
||||
---
|
||||
|
||||
## 3. Backend wire-up — line-by-line
|
||||
|
||||
### 3.1 [`backend/pom.xml`](Sprint-1-Plan.md) excerpt
|
||||
|
||||
```xml
|
||||
<project>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>4.1.0</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<java.version>25</java.version>
|
||||
<plate-auth.version>0.1.0</plate-auth.version>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>gitea-platesoft</id>
|
||||
<url>https://git.plate-software.de/api/packages/platesoft/maven</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>de.platesoft</groupId>
|
||||
<artifactId>plate-auth-starter</artifactId>
|
||||
<version>${plate-auth.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-database-postgresql</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
```
|
||||
|
||||
> **Note:** plate-auth transitively brings Spring Security, Resource Server (JWT), and its own Flyway migrations. Sparkboard's `pom.xml` doesn't list those — that's the whole point of the starter.
|
||||
|
||||
### 3.2 [`backend/src/main/resources/application.yml`](Sprint-1-Plan.md)
|
||||
|
||||
The complete plate-auth-relevant config block:
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:postgresql://localhost:5432/sparkboard
|
||||
username: ${DB_USER}
|
||||
password: ${DB_PASSWORD}
|
||||
flyway:
|
||||
enabled: true
|
||||
locations: classpath:db/migration
|
||||
# Sparkboard's history table
|
||||
table: flyway_schema_history
|
||||
|
||||
# plate-auth section
|
||||
plate:
|
||||
auth:
|
||||
base-url: http://localhost:8080
|
||||
public-url: https://sparkboard.plate-software.de
|
||||
jwt:
|
||||
issuer: https://sparkboard.plate-software.de
|
||||
audience: sparkboard-frontend
|
||||
secret: ${PLATE_AUTH_JWT_SECRET}
|
||||
lifetime: PT12H
|
||||
oauth:
|
||||
google:
|
||||
client-id: ${GOOGLE_CLIENT_ID}
|
||||
client-secret: ${GOOGLE_CLIENT_SECRET}
|
||||
redirect-uri: ${PLATE_AUTH_GOOGLE_REDIRECT_URI:http://localhost:8080/api/auth/callback/google}
|
||||
allowlist:
|
||||
- patrick@plate-software.de
|
||||
- friend@example.com
|
||||
- kid1@example.com
|
||||
- kid2@example.com
|
||||
flyway:
|
||||
# plate-auth's separate history table — prevents collision with Sparkboard's
|
||||
history-table: flyway_schema_history_auth
|
||||
onboarding:
|
||||
auto-create-membership: false # we handle that in SparkboardOnboardingHook
|
||||
first-org-type: SPARK_ORG # informs plate-auth which org_type to use if it ever creates one
|
||||
exchange:
|
||||
secret: ${PLATE_AUTH_EXCHANGE_SECRET}
|
||||
|
||||
# Sparkboard-specific config
|
||||
sparkboard:
|
||||
admins:
|
||||
- patrick@plate-software.de
|
||||
- friend@example.com
|
||||
```
|
||||
|
||||
> **Key points:**
|
||||
> - `plate.auth.flyway.history-table` is set to `flyway_schema_history_auth` so the two Flyway histories never collide.
|
||||
> - `plate.auth.onboarding.auto-create-membership: false` — we explicitly opt-out of plate-auth's built-in membership creation because `SparkboardOnboardingHook` handles it.
|
||||
> - `sparkboard.admins[]` is a **Sparkboard** property, not a plate-auth property. It's read by `SparkboardAdminProperties`.
|
||||
|
||||
### 3.3 What auto-config wires for Sparkboard
|
||||
|
||||
Sparkboard writes zero lines of Spring Security config. plate-auth auto-config wires:
|
||||
|
||||
| Bean | What it does |
|
||||
|------|--------------|
|
||||
| `SecurityFilterChain authChain` | Permits `/api/auth/**`. |
|
||||
| `SecurityFilterChain apiChain` | Requires authentication for `/api/**`. JWT bearer via Resource Server. |
|
||||
| `JwtDecoder` | Decodes Sparkboard JWTs using `plate.auth.jwt.secret`. |
|
||||
| `@CurrentUser` argument resolver | Injects an `AuthenticatedUser` into controllers. |
|
||||
| Onboarding hook orchestrator | Calls `SparkboardOnboardingHook.onFirstLogin(...)` on first plate-auth login per user. |
|
||||
| Flyway migrations | Loads from `classpath:db/migration-auth/` into `flyway_schema_history_auth`. |
|
||||
|
||||
Sparkboard's controllers therefore look like this — no auth ceremony at all:
|
||||
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/api/ideas")
|
||||
public class IdeaController {
|
||||
|
||||
@PostMapping
|
||||
public IdeaDto create(@CurrentUser AuthenticatedUser user,
|
||||
@Valid @RequestBody CreateIdeaRequest req) {
|
||||
return service.create(req, user.id(), SparkboardOnboardingHook.FAMILY_SPARK_ID);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`@CurrentUser` is **the** integration point. If you see it, you know the request is authenticated and you have the user's identity.
|
||||
|
||||
---
|
||||
|
||||
## 4. Frontend wire-up — line-by-line
|
||||
|
||||
### 4.1 [`frontend/package.json`](Sprint-1-Plan.md) excerpt
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "sparkboard-frontend",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@platesoft/auth": "0.1.0",
|
||||
"next": "15.0.0",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"next-auth": "5.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 [`frontend/.npmrc`](Sprint-1-Plan.md)
|
||||
|
||||
```
|
||||
@platesoft:registry=https://git.plate-software.de/api/packages/platesoft/npm/
|
||||
//git.plate-software.de/api/packages/platesoft/npm/:_authToken=${GITEA_NPM_TOKEN}
|
||||
```
|
||||
|
||||
`GITEA_NPM_TOKEN` is set in `.env.local` (dev) and in Gitea Actions secrets (CI).
|
||||
|
||||
### 4.3 [`frontend/.env.local`](Sprint-1-Plan.md) excerpt
|
||||
|
||||
```
|
||||
NEXT_PUBLIC_BACKEND_URL=http://localhost:8080
|
||||
PLATE_AUTH_EXCHANGE_SECRET=<same as backend>
|
||||
NEXTAUTH_URL=http://localhost:3000
|
||||
NEXTAUTH_SECRET=<random 32 bytes base64>
|
||||
GOOGLE_CLIENT_ID=<your-client-id>
|
||||
GOOGLE_CLIENT_SECRET=<your-secret>
|
||||
GITEA_NPM_TOKEN=<your-token>
|
||||
```
|
||||
|
||||
### 4.4 [`frontend/lib/auth.ts`](Sprint-1-Plan.md)
|
||||
|
||||
The factory call. **Sparkboard writes ~15 lines of auth config**:
|
||||
|
||||
```typescript
|
||||
import { createAuthConfig } from "@platesoft/auth/next-auth";
|
||||
import Google from "next-auth/providers/google";
|
||||
|
||||
export const authConfig = createAuthConfig({
|
||||
providers: [Google({ clientId: process.env.GOOGLE_CLIENT_ID!,
|
||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET! })],
|
||||
exchange: {
|
||||
backendUrl: process.env.NEXT_PUBLIC_BACKEND_URL!,
|
||||
secret: process.env.PLATE_AUTH_EXCHANGE_SECRET!,
|
||||
},
|
||||
pages: { signIn: "/login" },
|
||||
});
|
||||
|
||||
export const { handlers, signIn, signOut, auth } = authConfig;
|
||||
```
|
||||
|
||||
That's the **complete** NextAuth v5 setup. No callbacks, no JWT customisation, no signIn handlers. The factory does all of it.
|
||||
|
||||
### 4.5 [`frontend/app/api/auth/[...nextauth]/route.ts`](Sprint-1-Plan.md)
|
||||
|
||||
```typescript
|
||||
export { GET, POST } from "@/lib/auth";
|
||||
```
|
||||
|
||||
(Yes, one line.)
|
||||
|
||||
### 4.6 [`frontend/app/api/backend/[...path]/route.ts`](Sprint-1-Plan.md)
|
||||
|
||||
The proxy. Sparkboard never lets the browser call the backend directly:
|
||||
|
||||
```typescript
|
||||
import { createProxyHandlers } from "@platesoft/auth/proxy";
|
||||
|
||||
const { GET, POST, PUT, DELETE, PATCH } = createProxyHandlers({
|
||||
backendUrl: process.env.NEXT_PUBLIC_BACKEND_URL!,
|
||||
});
|
||||
|
||||
export { GET, POST, PUT, DELETE, PATCH };
|
||||
```
|
||||
|
||||
### 4.7 [`frontend/middleware.ts`](Sprint-1-Plan.md)
|
||||
|
||||
```typescript
|
||||
export { middleware, config } from "@platesoft/auth/middleware";
|
||||
```
|
||||
|
||||
The middleware redirects unauthenticated requests away from protected routes to `/login`.
|
||||
|
||||
### 4.8 Optional: React components
|
||||
|
||||
If Sparkboard needs a "Sign in with Google" button, plate-auth ships one:
|
||||
|
||||
```tsx
|
||||
import { SignInButton } from "@platesoft/auth/components";
|
||||
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Sparkboard</h1>
|
||||
<SignInButton provider="google">Sign in with Google</SignInButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Or roll your own using `signIn` from `lib/auth.ts`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Database setup
|
||||
|
||||
### 5.1 Two histories, one schema
|
||||
|
||||
```
|
||||
postgres
|
||||
└── sparkboard (database)
|
||||
├── flyway_schema_history (Sparkboard's migrations)
|
||||
│ └── tracks: spark_org, ideas, ...
|
||||
└── flyway_schema_history_auth (plate-auth's migrations)
|
||||
└── tracks: auth_identities, memberships, ...
|
||||
```
|
||||
|
||||
Both tables sit in the same Postgres database. Both are managed by Flyway. They don't reference each other.
|
||||
|
||||
### 5.2 What plate-auth creates
|
||||
|
||||
- `auth_identities (user_id PK, email, provider, ...)` — user identities.
|
||||
- `memberships (user_id, org_type, org_id, role, ...)` — polymorphic membership pivot.
|
||||
- `invitations`, `access_requests` — empty for Sparkboard (no UI uses them).
|
||||
|
||||
### 5.3 What Sparkboard creates
|
||||
|
||||
- `spark_org (id PK, name, org_type, created_at)` — seeded with one row.
|
||||
- `ideas (id PK, author_id, org_id, title, body, status, created_at, updated_at)`.
|
||||
|
||||
### 5.4 No cross-history foreign keys
|
||||
|
||||
**Important:** Sparkboard's `ideas.author_id` is NOT declared as a FK to `auth_identities.user_id`.
|
||||
|
||||
**Why:**
|
||||
- The two Flyway histories run independently. If plate-auth's `V1__auth_identities.sql` hasn't run yet when Sparkboard's `V1__init.sql` runs, the FK declaration fails.
|
||||
- Referential integrity is enforced at the application layer (controllers always inject `@CurrentUser`, which guarantees a valid `user_id`).
|
||||
- Trade-off: theoretical risk of orphan `ideas` rows if a `auth_identities` row is deleted. For 4 humans, the risk is zero in practice.
|
||||
|
||||
---
|
||||
|
||||
## 6. Full sign-in flow walkthrough (Sparkboard concrete)
|
||||
|
||||
1. Patrick navigates to `https://sparkboard.plate-software.de`.
|
||||
2. Sparkboard middleware sees no session → redirects to `/login`.
|
||||
3. `/login` renders the `SignInButton`.
|
||||
4. Patrick clicks it → NextAuth-Google OAuth dance → callback to `/api/auth/callback/google`.
|
||||
5. NextAuth verifies the Google token, looks up the email `patrick@plate-software.de`.
|
||||
6. The signIn callback (configured by `createAuthConfig`) hashes the email with `PLATE_AUTH_EXCHANGE_SECRET` and POSTs an envelope to `POST /api/auth/exchange` on the backend.
|
||||
7. Backend's plate-auth verifies the HMAC, checks the allowlist (`patrick@plate-software.de` is in it), looks up or inserts an `auth_identities` row.
|
||||
8. **If this is the first login**, plate-auth's onboarding orchestrator calls `SparkboardOnboardingHook.onFirstLogin(user)`.
|
||||
9. Hook checks `sparkboard.admins[]` → `patrick@plate-software.de` is there → calls `MembershipService.upsert(userId, "SPARK_ORG", FAMILY_SPARK_ID, "ADMIN")`.
|
||||
10. plate-auth issues a JWT, signs it with `plate.auth.jwt.secret`, returns it in the exchange response.
|
||||
11. NextAuth stores it in the session cookie.
|
||||
12. Frontend redirects to `/ideas`.
|
||||
13. `/ideas` server component calls `listIdeas()` which calls `fetch("http://backend:8080/api/ideas", { headers: { Cookie } })`.
|
||||
14. Backend's `SecurityFilterChain` extracts the JWT from the session-cookie-carried bearer, verifies it via `JwtDecoder`, populates `@CurrentUser`.
|
||||
15. `IdeaController.list(@CurrentUser, FAMILY_SPARK_ID)` returns the ideas for Family Spark.
|
||||
|
||||
Steps 1, 12, 13 are pure Sparkboard. Steps 2–11, 14 are pure plate-auth. Sparkboard never touched a security filter or a JWT.
|
||||
|
||||
---
|
||||
|
||||
## 7. What to do when…
|
||||
|
||||
### 7.1 …a new family member arrives
|
||||
|
||||
1. Add their email to `plate.auth.allowlist[]` in `application.yml`.
|
||||
2. (Optional) add to `sparkboard.admins[]` if they should be ADMIN.
|
||||
3. `git push origin main` → Gitea Actions deploys.
|
||||
4. New member signs in via Google → `SparkboardOnboardingHook` auto-creates their membership.
|
||||
|
||||
No DB edits, no manual steps.
|
||||
|
||||
### 7.2 …plate-auth ships v0.2.0 with a new feature
|
||||
|
||||
1. Bump `plate-auth.version` in `pom.xml` and `@platesoft/auth` in `package.json`.
|
||||
2. Read plate-auth's CHANGELOG.
|
||||
3. Adjust any breaking changes (semver minor should mean no breaks, but read).
|
||||
4. Sparkboard tests should still pass; CI verifies.
|
||||
|
||||
### 7.3 …Sparkboard needs an auth feature plate-auth doesn't have
|
||||
|
||||
1. **Don't** fork plate-auth in-tree.
|
||||
2. File an issue on the plate-auth repo describing the use case.
|
||||
3. Either:
|
||||
- plate-auth adds a new SPI seam (Sparkboard implements it).
|
||||
- plate-auth ships the feature behind a `plate.auth.*` config flag (Sparkboard enables it).
|
||||
4. Sparkboard bumps to the new plate-auth version.
|
||||
|
||||
This protects both projects from divergence.
|
||||
|
||||
### 7.4 …an exchange envelope verification fails
|
||||
|
||||
Most likely cause: `PLATE_AUTH_EXCHANGE_SECRET` differs between frontend `.env.local` and backend `application.yml`. Both must be identical. Symptom: 401 immediately after Google OAuth callback.
|
||||
|
||||
### 7.5 …Flyway complains about migration history
|
||||
|
||||
Likely cause: someone added a Sparkboard migration to `db/migration-auth/` by accident. Sparkboard's migrations go in `db/migration/`. plate-auth's are inside its starter JAR; never put migration files for plate-auth in Sparkboard's repo.
|
||||
|
||||
---
|
||||
|
||||
## 8. Verification checklist (after first deploy)
|
||||
|
||||
Run these in order. If any fails, stop and fix before continuing.
|
||||
|
||||
- [ ] `docker compose ps` shows postgres, backend, frontend, caddy all healthy.
|
||||
- [ ] `psql -U sparkboard -c "\dt"` lists both `flyway_schema_history` AND `flyway_schema_history_auth`.
|
||||
- [ ] `psql` confirms `spark_org` has one row with id `00000000-0000-0000-0000-000000000001`.
|
||||
- [ ] `curl https://sparkboard.plate-software.de/api/health` returns 200.
|
||||
- [ ] Browser loads `https://sparkboard.plate-software.de/login` and shows "Sign in with Google".
|
||||
- [ ] Patrick signs in → reaches `/ideas` (empty list).
|
||||
- [ ] `psql -c "SELECT * FROM memberships;"` shows one row with `(patrick_user_id, 'SPARK_ORG', '00...01', 'ADMIN')`.
|
||||
- [ ] Patrick creates an idea → it appears on `/ideas`.
|
||||
|
||||
---
|
||||
|
||||
## 9. Common pitfalls (Sparkboard-specific)
|
||||
|
||||
| Pitfall | Symptom | Fix |
|
||||
|---------|---------|-----|
|
||||
| Forgetting `plate.auth.flyway.history-table` | Flyway error: duplicate migration version | Set it to `flyway_schema_history_auth` |
|
||||
| Using `plate.auth.onboarding.auto-create-membership: true` AND `SparkboardOnboardingHook` | Two memberships per user | Set the property to `false` |
|
||||
| Setting `org_id` to the wrong UUID in IdeaController | `GET /api/ideas` returns `[]` even though ideas exist | Verify `FAMILY_SPARK_ID = UUID.fromString("00000000-0000-0000-0000-000000000001")` |
|
||||
| Hardcoded `Authorization: Bearer ...` in fetch from server component | Works locally, breaks in prod | Always forward `cookies()` from `next/headers` instead |
|
||||
| Migrating with `idea.author_id` FK to `auth_identities` | Migration fails because plate-auth tables don't exist yet at migration time | Drop the FK; rely on app-layer integrity |
|
||||
| Different `PLATE_AUTH_EXCHANGE_SECRET` on frontend vs backend | 401 right after Google callback | Set both from the same secret store |
|
||||
| `sparkboard.admins[]` email case mismatch | All users get MEMBER role | Normalise to lowercase in `SparkboardOnboardingHook` |
|
||||
|
||||
---
|
||||
|
||||
## 10. Cross-references
|
||||
|
||||
- [Sprint-1-Plan](Sprint-1-Plan.md) chunks 1–4 — the work that produces these files
|
||||
- [Architecture](Architecture.md) — diagrams of the wire flow
|
||||
- [Sprint-1-Assessment](Sprint-1-Assessment.md) §10 — plate-auth integration footprint table
|
||||
- [Open-Questions](Open-Questions.md) — Q01 (single-org), Q03 (admin promotion), Q06 (security customisation)
|
||||
- plate-auth wiki:
|
||||
- [plate-auth/Integration-Guide](https://git.plate-software.de/pplate/plate-auth/wiki/Integration-Guide) — generic version
|
||||
- [plate-auth/Architecture](https://git.plate-software.de/pplate/plate-auth/wiki/Architecture) — what plate-auth ships
|
||||
- [plate-auth/Roadmap](https://git.plate-software.de/pplate/plate-auth/wiki/Roadmap) — what's coming in v0.2, v0.3
|
||||
|
||||
---
|
||||
|
||||
_End of Integration Guide. Status: **Draft v1, awaiting GO from Patrick.**_
|
||||
Reference in New Issue
Block a user