docs: F3+F4 — rename onboarding hook to onFirstSignIn(AuthenticatedUser), fix UT-06 package

F3: rename SparkboardOnboardingHook.afterFirstLogin(OnboardingContext)
    → onFirstSignIn(AuthenticatedUser) across Architecture, Integration-Guide,
    Sprint-1-Plan-Part-2, Sprint-1-Testplan, Home.
    plate-auth's spelling wins; aligns with plate-auth Architecture +
    Sprint-0-Plan SPI signature.

F4: Sprint-1-Testplan UT-06 test class moved from
    de.plate.sparkboard.auth.SparkboardOnboardingHookTest →
    de.plate.sparkboard.onboarding.SparkboardOnboardingHookTest.
    Matches the package declared in Sprint-1-Plan-Part-2 +
    Integration-Guide §2.

Home.md hook link text also updated to onFirstSignIn().

Plan-Review.md intentionally left untouched (historical record).
Patrick Plate
2026-06-24 15:28:48 +02:00
parent 05f054bd09
commit f970504b93
4 changed files with 19 additions and 19 deletions
+5 -5
@@ -123,7 +123,7 @@ This is the **one SPI bean** Sparkboard implements. It's how a newly-signed-in G
package de.plate.sparkboard.onboarding;
import de.platesoft.auth.spi.OnboardingHook;
import de.platesoft.auth.spi.OnboardingContext;
import de.platesoft.auth.model.AuthenticatedUser;
import de.platesoft.auth.membership.MembershipService;
import de.platesoft.auth.membership.Role;
import org.springframework.stereotype.Component;
@@ -147,9 +147,9 @@ public class SparkboardOnboardingHook implements OnboardingHook {
}
@Override
public void afterFirstLogin(OnboardingContext ctx) {
Role role = admins.isAdminEmail(ctx.email()) ? Role.ADMIN : Role.MEMBER;
memberships.upsert(ctx.userId(), ORG_TYPE, FAMILY_SPARK_ID, role);
public void onFirstSignIn(AuthenticatedUser user) {
Role role = admins.isAdminEmail(user.email()) ? Role.ADMIN : Role.MEMBER;
memberships.upsert(user.id(), ORG_TYPE, FAMILY_SPARK_ID, role);
}
}
```
@@ -304,7 +304,7 @@ sequenceDiagram
BE->>BE: plate-auth checks allowlist
BE->>DB: SELECT / INSERT auth_identities
Note over BE,DB: First login? → fire OnboardingHook
BE->>BE: SparkboardOnboardingHook.afterFirstLogin
BE->>BE: SparkboardOnboardingHook.onFirstSignIn
BE->>DB: INSERT memberships (user_id, 'SPARK_ORG', FAMILY_SPARK_ID, role)
BE-->>FE: { userId, accessToken (15m JWT), refreshToken }
FE->>FE: NextAuth session populated
+5 -5
@@ -40,8 +40,8 @@ The other four:
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;
// backend/src/main/java/de/plate/sparkboard/onboarding/SparkboardOnboardingHook.java
package de.plate.sparkboard.onboarding;
import de.platesoft.auth.spi.OnboardingHook;
import de.platesoft.auth.model.AuthenticatedUser;
@@ -66,7 +66,7 @@ public class SparkboardOnboardingHook implements OnboardingHook {
}
@Override
public void onFirstLogin(AuthenticatedUser user) {
public void onFirstSignIn(AuthenticatedUser user) {
var role = adminProps.admins().contains(user.email()) ? "ADMIN" : "MEMBER";
memberships.upsert(user.id(), SPARK_ORG_TYPE, FAMILY_SPARK_ID, role);
}
@@ -198,7 +198,7 @@ Sparkboard writes zero lines of Spring Security config. plate-auth auto-config w
| `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. |
| Onboarding hook orchestrator | Calls `SparkboardOnboardingHook.onFirstSignIn(...)` on first plate-auth sign-in 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:
@@ -379,7 +379,7 @@ Both tables sit in the same Postgres database. Both are managed by Flyway. They
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)`.
8. **If this is the first sign-in**, plate-auth's onboarding orchestrator calls `SparkboardOnboardingHook.onFirstSignIn(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.
+6 -6
@@ -223,7 +223,7 @@ Activated in `SparkboardApplication` with `@ConfigurationPropertiesScan` or `@En
package de.plate.sparkboard.onboarding;
import de.platesoft.auth.spi.OnboardingHook;
import de.platesoft.auth.spi.OnboardingContext;
import de.platesoft.auth.model.AuthenticatedUser;
import de.platesoft.auth.membership.MembershipService;
import de.platesoft.auth.membership.Role;
import org.springframework.stereotype.Component;
@@ -232,10 +232,10 @@ import java.util.UUID;
/**
* The one and only Sparkboard customisation of plate-auth's SPI.
* Runs after a successful first login, before NextAuth's signIn
* Runs after a successful first sign-in, before NextAuth's signIn
* callback returns to the user.
*
* Idempotent: runs every login (plate-auth promises first-login-only
* Idempotent: runs every login (plate-auth promises first-sign-in-only
* but we do not depend on that promise being bug-free).
*/
@Component
@@ -255,9 +255,9 @@ public class SparkboardOnboardingHook implements OnboardingHook {
}
@Override
public void afterFirstLogin(OnboardingContext ctx) {
Role role = admins.isAdminEmail(ctx.email()) ? Role.ADMIN : Role.MEMBER;
memberships.upsert(ctx.userId(), ORG_TYPE, FAMILY_SPARK_ID, role);
public void onFirstSignIn(AuthenticatedUser user) {
Role role = admins.isAdminEmail(user.email()) ? Role.ADMIN : Role.MEMBER;
memberships.upsert(user.id(), ORG_TYPE, FAMILY_SPARK_ID, role);
}
}
```
+3 -3
@@ -98,9 +98,9 @@ Tests **not** in scope: plate-auth's own internals (those are covered by the pla
### UT-06 — SparkboardOnboardingHook ADMIN path
- **Class:** `de.plate.sparkboard.auth.SparkboardOnboardingHookTest`
- **Class:** `de.plate.sparkboard.onboarding.SparkboardOnboardingHookTest`
- **Given:** `sparkboard.admins[] = ["patrick@plate-software.de"]`, login event for `patrick@plate-software.de`.
- **When:** `hook.onFirstLogin(authenticatedUser)`
- **When:** `hook.onFirstSignIn(authenticatedUser)`
- **Then:** `MembershipService.upsert(userId, "SPARK_ORG", FAMILY_SPARK_ID, "ADMIN")` called once.
### UT-07 — SparkboardOnboardingHook MEMBER path
@@ -110,7 +110,7 @@ Tests **not** in scope: plate-auth's own internals (those are covered by the pla
### UT-08 — Hook idempotency
- **When:** `hook.onFirstLogin(user)` called twice with same user.
- **When:** `hook.onFirstSignIn(user)` called twice with same user.
- **Then:** `MembershipService.upsert` called twice with identical arguments; no exception. (`upsert` semantics, not duplicate insert.)
### UT-09 — SparkboardAdminProperties binding