feat(auth): force new Bunyip-JIT users through name onboarding #151

Open
YousifShkara wants to merge 1 commit from feat/force-profile-setup-on-jit into main
Owner

upsert_user_from_oidc (JIT path for OIDC users) seeds first/last name from a synthetic email-local-part placeholder because Bunyip's at+jwt doesn't carry name claims. That leaves new users with names like "User abc12" in their topbar until they discover the Profile page and fix it. Now the SPA can gate the rest of the app behind a forced onboarding screen.

Schema (migration 033): adds users.profile_completed_at TIMESTAMPTZ (nullable). NULL means "needs onboarding", a timestamp means "name was confirmed at that moment". Existing rows backfill to NOW() so the rollout doesn't surprise-trap anyone already using mokosh.

JIT semantics: the INSERT in upsert_user_from_oidc does not set the column, so freshly JIT-created Bunyip users land with profile_completed_at = NULL. The Google JIT path (PMS-138 fallback) is unchanged: it never goes through upsert_user_from_oidc and arrives with proper names from the Google profile, so existing-row backfill keeps it covered. Legacy-login signups are also covered by the backfill.

Activation: update_user (the handler behind PUT /api/v1/auth/me) now appends profile_completed_at = COALESCE(profile_completed_at, NOW()) to the SET clause whenever the request body carries both first_name and last_name as non-empty (trimmed). COALESCE preserves any prior completion timestamp, so editing your name from the Settings page after onboarding doesn't reset the activation clock.

Shape exposed: CurrentUser and UserResponse both gain a derived profile_completed: bool (profile_completed_at.is_some()). /api/v1/auth/me ships it; the SPA reads it and decides whether to redirect to /onboarding/profile. The at+jwt middleware path that constructs a lazy CurrentUser without a DB hit defaults profile_completed: true to avoid trapping users during the brief window between authentication and the SPA's first /me reconciliation.

Backwards-compat: CurrentUser deserialises with #[serde(default = "default_true")] so an older serialised payload reaching a newer client (or vice versa) treats users as completed by default.

Paired with the SPA-side change on mokosh-apps (feat/force-name-onboarding), which is the actual gate UI.

`upsert_user_from_oidc` (JIT path for OIDC users) seeds first/last name from a synthetic email-local-part placeholder because Bunyip's at+jwt doesn't carry name claims. That leaves new users with names like "User abc12" in their topbar until they discover the Profile page and fix it. Now the SPA can gate the rest of the app behind a forced onboarding screen. Schema (migration 033): adds `users.profile_completed_at TIMESTAMPTZ` (nullable). NULL means "needs onboarding", a timestamp means "name was confirmed at that moment". Existing rows backfill to `NOW()` so the rollout doesn't surprise-trap anyone already using mokosh. JIT semantics: the INSERT in `upsert_user_from_oidc` does not set the column, so freshly JIT-created Bunyip users land with `profile_completed_at = NULL`. The Google JIT path (PMS-138 fallback) is unchanged: it never goes through `upsert_user_from_oidc` and arrives with proper names from the Google profile, so existing-row backfill keeps it covered. Legacy-login signups are also covered by the backfill. Activation: `update_user` (the handler behind `PUT /api/v1/auth/me`) now appends `profile_completed_at = COALESCE(profile_completed_at, NOW())` to the SET clause whenever the request body carries both `first_name` and `last_name` as non-empty (trimmed). COALESCE preserves any prior completion timestamp, so editing your name from the Settings page after onboarding doesn't reset the activation clock. Shape exposed: `CurrentUser` and `UserResponse` both gain a derived `profile_completed: bool` (`profile_completed_at.is_some()`). `/api/v1/auth/me` ships it; the SPA reads it and decides whether to redirect to `/onboarding/profile`. The at+jwt middleware path that constructs a lazy `CurrentUser` without a DB hit defaults `profile_completed: true` to avoid trapping users during the brief window between authentication and the SPA's first `/me` reconciliation. Backwards-compat: `CurrentUser` deserialises with `#[serde(default = "default_true")]` so an older serialised payload reaching a newer client (or vice versa) treats users as completed by default. Paired with the SPA-side change on mokosh-apps (`feat/force-name-onboarding`), which is the actual gate UI.
feat(auth): force new Bunyip-JIT users through name onboarding
All checks were successful
Build OCI container / Build and push mokosh-api image (push) Successful in 3m30s
E2E (staging) / Playwright against staging (pull_request) Successful in 32s
Check / fmt + clippy + compile + tests (pull_request) Successful in 1m20s
b4a15711e5
`upsert_user_from_oidc` (JIT path for OIDC users) seeds first/last name from a synthetic email-local-part placeholder because Bunyip's at+jwt doesn't carry name claims. That leaves new users with names like "User abc12" in their topbar until they discover the Profile page and fix it. Now the SPA can gate the rest of the app behind a forced onboarding screen.

Schema (migration 033): adds `users.profile_completed_at TIMESTAMPTZ` (nullable). NULL means "needs onboarding", a timestamp means "name was confirmed at that moment". Existing rows backfill to `NOW()` so the rollout doesn't surprise-trap anyone already using mokosh.

JIT semantics: the INSERT in `upsert_user_from_oidc` does not set the column, so freshly JIT-created Bunyip users land with `profile_completed_at = NULL`. The Google JIT path (PMS-138 fallback) is unchanged: it never goes through `upsert_user_from_oidc` and arrives with proper names from the Google profile, so existing-row backfill keeps it covered. Legacy-login signups are also covered by the backfill.

Activation: `update_user` (the handler behind `PUT /api/v1/auth/me`) now appends `profile_completed_at = COALESCE(profile_completed_at, NOW())` to the SET clause whenever the request body carries both `first_name` and `last_name` as non-empty (trimmed). COALESCE preserves any prior completion timestamp, so editing your name from the Settings page after onboarding doesn't reset the activation clock.

Shape exposed: `CurrentUser` and `UserResponse` both gain a derived `profile_completed: bool` (`profile_completed_at.is_some()`). `/api/v1/auth/me` ships it; the SPA reads it and decides whether to redirect to `/onboarding/profile`. The at+jwt middleware path that constructs a lazy `CurrentUser` without a DB hit defaults `profile_completed: true` to avoid trapping users during the brief window between authentication and the SPA's first `/me` reconciliation.

Backwards-compat: `CurrentUser` deserialises with `#[serde(default = "default_true")]` so an older serialised payload reaching a newer client (or vice versa) treats users as completed by default.

Paired with the SPA-side change on mokosh-apps (`feat/force-name-onboarding`), which is the actual gate UI.
All checks were successful
Build OCI container / Build and push mokosh-api image (push) Successful in 3m30s
E2E (staging) / Playwright against staging (pull_request) Successful in 32s
Check / fmt + clippy + compile + tests (pull_request) Successful in 1m20s
This pull request has changes conflicting with the target branch.
  • crates/mokosh-types/src/auth.rs
  • src/modules/auth/at_jwt.rs
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/force-profile-setup-on-jit:feat/force-profile-setup-on-jit
git switch feat/force-profile-setup-on-jit
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
psa-systems/mokosh-server!151
No description provided.