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).
+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;
|
package de.plate.sparkboard.onboarding;
|
||||||
|
|
||||||
import de.platesoft.auth.spi.OnboardingHook;
|
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.MembershipService;
|
||||||
import de.platesoft.auth.membership.Role;
|
import de.platesoft.auth.membership.Role;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@@ -147,9 +147,9 @@ public class SparkboardOnboardingHook implements OnboardingHook {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterFirstLogin(OnboardingContext ctx) {
|
public void onFirstSignIn(AuthenticatedUser user) {
|
||||||
Role role = admins.isAdminEmail(ctx.email()) ? Role.ADMIN : Role.MEMBER;
|
Role role = admins.isAdminEmail(user.email()) ? Role.ADMIN : Role.MEMBER;
|
||||||
memberships.upsert(ctx.userId(), ORG_TYPE, FAMILY_SPARK_ID, role);
|
memberships.upsert(user.id(), ORG_TYPE, FAMILY_SPARK_ID, role);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -304,7 +304,7 @@ sequenceDiagram
|
|||||||
BE->>BE: plate-auth checks allowlist
|
BE->>BE: plate-auth checks allowlist
|
||||||
BE->>DB: SELECT / INSERT auth_identities
|
BE->>DB: SELECT / INSERT auth_identities
|
||||||
Note over BE,DB: First login? → fire OnboardingHook
|
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->>DB: INSERT memberships (user_id, 'SPARK_ORG', FAMILY_SPARK_ID, role)
|
||||||
BE-->>FE: { userId, accessToken (15m JWT), refreshToken }
|
BE-->>FE: { userId, accessToken (15m JWT), refreshToken }
|
||||||
FE->>FE: NextAuth session populated
|
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:
|
Sparkboard's [`SparkboardOnboardingHook`](Architecture.md#5-single-org-mode--the-onboardinghook-spi) is the **entire** Java auth integration. The full impl sketch:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
// backend/src/main/java/de/plate/sparkboard/auth/SparkboardOnboardingHook.java
|
// backend/src/main/java/de/plate/sparkboard/onboarding/SparkboardOnboardingHook.java
|
||||||
package de.plate.sparkboard.auth;
|
package de.plate.sparkboard.onboarding;
|
||||||
|
|
||||||
import de.platesoft.auth.spi.OnboardingHook;
|
import de.platesoft.auth.spi.OnboardingHook;
|
||||||
import de.platesoft.auth.model.AuthenticatedUser;
|
import de.platesoft.auth.model.AuthenticatedUser;
|
||||||
@@ -66,7 +66,7 @@ public class SparkboardOnboardingHook implements OnboardingHook {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFirstLogin(AuthenticatedUser user) {
|
public void onFirstSignIn(AuthenticatedUser user) {
|
||||||
var role = adminProps.admins().contains(user.email()) ? "ADMIN" : "MEMBER";
|
var role = adminProps.admins().contains(user.email()) ? "ADMIN" : "MEMBER";
|
||||||
memberships.upsert(user.id(), SPARK_ORG_TYPE, FAMILY_SPARK_ID, role);
|
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. |
|
| `SecurityFilterChain apiChain` | Requires authentication for `/api/**`. JWT bearer via Resource Server. |
|
||||||
| `JwtDecoder` | Decodes Sparkboard JWTs using `plate.auth.jwt.secret`. |
|
| `JwtDecoder` | Decodes Sparkboard JWTs using `plate.auth.jwt.secret`. |
|
||||||
| `@CurrentUser` argument resolver | Injects an `AuthenticatedUser` into controllers. |
|
| `@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`. |
|
| 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:
|
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`.
|
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.
|
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.
|
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")`.
|
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.
|
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.
|
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;
|
package de.plate.sparkboard.onboarding;
|
||||||
|
|
||||||
import de.platesoft.auth.spi.OnboardingHook;
|
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.MembershipService;
|
||||||
import de.platesoft.auth.membership.Role;
|
import de.platesoft.auth.membership.Role;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@@ -232,10 +232,10 @@ import java.util.UUID;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The one and only Sparkboard customisation of plate-auth's SPI.
|
* 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.
|
* 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).
|
* but we do not depend on that promise being bug-free).
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@@ -255,9 +255,9 @@ public class SparkboardOnboardingHook implements OnboardingHook {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterFirstLogin(OnboardingContext ctx) {
|
public void onFirstSignIn(AuthenticatedUser user) {
|
||||||
Role role = admins.isAdminEmail(ctx.email()) ? Role.ADMIN : Role.MEMBER;
|
Role role = admins.isAdminEmail(user.email()) ? Role.ADMIN : Role.MEMBER;
|
||||||
memberships.upsert(ctx.userId(), ORG_TYPE, FAMILY_SPARK_ID, role);
|
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
|
### 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`.
|
- **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.
|
- **Then:** `MembershipService.upsert(userId, "SPARK_ORG", FAMILY_SPARK_ID, "ADMIN")` called once.
|
||||||
|
|
||||||
### UT-07 — SparkboardOnboardingHook MEMBER path
|
### 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
|
### 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.)
|
- **Then:** `MembershipService.upsert` called twice with identical arguments; no exception. (`upsert` semantics, not duplicate insert.)
|
||||||
|
|
||||||
### UT-09 — SparkboardAdminProperties binding
|
### UT-09 — SparkboardAdminProperties binding
|
||||||
|
|||||||
Reference in New Issue
Block a user