fix(launcher): close the cross-browser stale-JWT gap on the app launcher (BUNYIP-229) #256
No reviewers
Labels
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
psa-systems/bunyip!256
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/BUNYIP-229-stale-jwt-cross-browser"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Reproduced on staging post-BUNYIP-226 with yousif+7: signup in Browser A, name-save in A, verify email by opening the link in Browser B, refresh A → /dashboard rendered Mokosh Locked again. BUNYIP-226's refresh-at-grant-site pattern only covered the same-browser shape: in this cross-browser flow the BUNYIP-221 grant fires in Browser B's session (no cookie there, refresh skipped) while Browser A holds the stale JWT, and the path Browser A takes after refresh (onboarding_get → /dashboard forward) had no rotation trigger.
Two layered fixes:
onboarding_get now calls auth_api::refresh before redirecting to /dashboard when needs_onboarding returns false. Any user landing on /onboarding after the dual gate has been satisfied gets their JWT rotated before the next hop, so the dashboard reads the fresh claims on first paint. Mirrors the BUNYIP-226 pattern on the GET-side forwarder. Refresh failure is non-fatal.
/v1/applications and /v1/applications/{slug} now read has_member_access from the DB user row instead of trusting the JWT claim cache. The launcher is the surface where the staleness symptom is visible, and the JWT can go stale via any of: cross-browser email verify firing the grant, an admin tier flip, a scheduled trial expiry, the BUNYIP-225 sibling-sub cleanup. A one-row DB read per dashboard load is cheap and works regardless of which handler caused the flip - the "always works" backstop so the next state-flipping path nobody remembered to wire to refresh() doesn't reintroduce this surprise. Anonymous callers and DB read failures fall back to the existing claim path.
#BUNYIP-229
Reproduced on staging post-BUNYIP-226 with yousif+7: signup in Browser A, name-save in A, verify email by opening the link in Browser B, refresh A → /dashboard rendered Mokosh Locked again. BUNYIP-226's refresh-at-grant-site pattern only covered the same-browser shape: in this cross-browser flow the BUNYIP-221 grant fires in Browser B's session (no cookie there, refresh skipped) while Browser A holds the stale JWT, and the path Browser A takes after refresh (onboarding_get → /dashboard forward) had no rotation trigger. Two layered fixes: 1. onboarding_get now calls auth_api::refresh before redirecting to /dashboard when needs_onboarding returns false. Any user landing on /onboarding after the dual gate has been satisfied gets their JWT rotated before the next hop, so the dashboard reads the fresh claims on first paint. Mirrors the BUNYIP-226 pattern on the GET-side forwarder. Refresh failure is non-fatal. 2. /v1/applications and /v1/applications/{slug} now read has_member_access from the DB user row instead of trusting the JWT claim cache. The launcher is the surface where the staleness symptom is visible, and the JWT can go stale via any of: cross-browser email verify firing the grant, an admin tier flip, a scheduled trial expiry, the BUNYIP-225 sibling-sub cleanup. A one-row DB read per dashboard load is cheap and works regardless of which handler caused the flip - the "always works" backstop so the next state-flipping path nobody remembered to wire to refresh() doesn't reintroduce this surprise. Anonymous callers and DB read failures fall back to the existing claim path. #BUNYIP-229