feat: update jersey/checkout pages to support session auth (#198) #200
No reviewers
Labels
No labels
domain:backend
domain:devops
domain:frontend
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
ldraney/westside-app!200
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "198-jersey-checkout-session-auth"
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?
Summary
Add dual-auth support to jersey and checkout pages so logged-in parents can access them via
?player_id=with Keycloak session auth, while preserving the existing?token=email-link flow unchanged.Changes
src/routes/(app)/jersey/+page.svelte— importready,isAuthenticated,getTokenfrom keycloak.js; addplayerIdstate andauthModederived signal; branch onMount to handle?player_id=with Bearer auth or?token=with raw fetch; updatehandleSelectto build checkout URL and headers based on auth mode; redirect to/signinif session mode and not authenticatedsrc/routes/(app)/checkout/+page.svelte— same dual-auth pattern: addplayerId/authMode; branch onMount for session vs token mode; updatehandleSubmitto use Bearer auth in session mode; redirect to/signinwhen unauthenticated with?player_id=Test Plan
/jersey?player_id=5while logged in — jersey page loads using Keycloak session auth/jersey?token=abc123— existing token flow works unchanged/jersey?player_id=5while NOT logged in — redirects to/signin/checkout?player_id=5&category=jerseywhile logged in — checkout works with session auth/checkout?token=abc123&category=jersey— existing token flow works unchangednpm run buildpasses (verified)Review Checklist
getToken()/signinnpm run buildpasses with no new warningsRelated Notes
QA Review -- PR #200
Scope Check
jersey/+page.svelteandcheckout/+page.svelte(matches ticket #198 file targets)npm run buildpasses per PR descriptionCode Quality
Auth mode detection -- clean
$derivedsignal:token ? 'token' : (playerId ? 'session' : null). Token takes precedence if both params present (correct -- email links must always work).Keycloak ready gate -- both files
await readybeforeisAuthenticated()in session mode. Matches the established pattern inbilling/+page.svelte.Bearer token injection -- uses
getToken()which handles auto-refresh. Correct.Session storage -- correctly guarded with
if (token)to avoid pollution in session mode.Template guards --
{:else if !authMode}correctly replaces{:else if !token}for the "Invalid Link" error state.Function guards --
handleSelectandhandleSubmitcheck!authModeinstead of!token. Correct.Nits (non-blocking)
Minor inconsistency: Jersey page has a reusable
sessionFetchOpts()helper but also inlines Bearer header logic inhandleSelect. Checkout page inlines everywhere. Not a bug -- just a style inconsistency. Could DRY up later.sessionFetchOptsparameter name: Theoptionsparameter shadows the outeroptionsstate variable. No runtime issue (different scope), but could be renamed tofetchOptionsfor clarity in a future pass.Acceptance Criteria Verification
/jersey?player_id=5while logged in -- uses Bearer auth viagetToken()/jersey?token=abc123-- existing flow unchanged (raw fetch, no Keycloak)/jersey?player_id=5while NOT logged in --goto('/signin')/checkout?player_id=5&category=jerseywhile logged in -- session auth with Bearer/checkout?token=abc123&category=jersey-- existing flow unchangedVERDICT: APPROVED
Clean, focused, additive change. Both auth paths are correctly branched with no regression risk to the existing token flow. Nits are cosmetic only.
PR #200 Review
DOMAIN REVIEW
Tech stack: SvelteKit (Svelte 5 runes:
$state,$derived), vanillafetch, Keycloak-js OIDC client.Auth flow analysis: The dual-auth pattern is correctly implemented. Token mode (email links with
?token=) is preserved exactly as-is. Session mode (logged-in users with?player_id=) correctly awaits Keycloak init, checksisAuthenticated(), and redirects to/signinwhen unauthenticated. Bearer tokens are attached to session-mode API calls viagetToken().Backwards compatibility: Confirmed. The
tokenpath through bothonMountflows is unchanged. The$derivedauthMode signal correctly prioritizestokenoverplayerId(token takes precedence if both are present), which is the right call -- an email link should always use the token path.SvelteKit patterns:
$state,$derived,onMountlifecycle are all used correctly. Reactive derivation ofauthModefromtoken/playerIdis clean.Scope: Only the 2 target files are modified. No scope creep.
BLOCKERS
None. No secrets committed, no unvalidated input leading to injection, auth logic is correct.
NITS
NIT 1: DRY -- manual Bearer construction vs existing
apiFetch$lib/api.jsexportsapiFetch()which already handlesawait ready,isAuthenticated()check,getToken(), and Bearer header construction. It is used in 12 other pages in this codebase. The PR instead manually constructs Bearer headers in 3 separate locations across 2 files:jersey/+page.svelteline ~116:sessionFetchOpts()helper (GET player-info)jersey/+page.svelteline ~256: inline Bearer construction (POST checkout)checkout/+page.svelteline ~113: inline Bearer construction (POST create-session)For session-mode calls,
apiFetchwould work directly:apiFetch('/jersey/player-info?player_id=' + playerId)apiFetch('/jersey/checkout?player_id=' + playerId, { method: 'POST', body: JSON.stringify(body) })This would eliminate all 3 manual Bearer constructions and align with the rest of the codebase. The token-mode paths legitimately need raw
fetch(token-in-URL, no Bearer), so those stay as-is.NIT 2: Internal inconsistency in jersey page
The jersey page defines a
sessionFetchOpts()helper for the GET request but does NOT use it for the POST request inhandleSelect(), instead duplicating the Bearer logic inline. If the manual approach is kept (over NIT 1), at minimumsessionFetchOpts()should be reused inhandleSelect().NIT 3:
getToken()null not handled as short-circuitIf
getToken()returnsnull(token refresh failed), the code still proceeds with the fetch -- just without an Authorization header. This will 401 from the API. ThegetToken()implementation inkeycloak.jscallslogin()on refresh failure (triggering a redirect) AND returns null, creating a race between the redirect and the fetch. Consider short-circuiting: ifbearerTokenis null, return early instead of sending an unauthenticated request.In jersey
sessionFetchOpts():In checkout
handleSubmit():NIT 4:
player_idnot URI-encodedplayerIdfrom URL search params is interpolated directly into API URLs withoutencodeURIComponent(). Low risk since player_id is expected to be numeric, but the checkout page already encodescategorywithencodeURIComponent(category || 'jersey'), so this is inconsistent. For defense-in-depth:SOP COMPLIANCE
198-jersey-checkout-session-authmatches issue #198PROCESS OBSERVATIONS
getToken()null race (NIT 3), which would manifest as a flash of error before redirect -- not data loss.apiFetchif auth patterns change. This is the most impactful nit for long-term maintainability.VERDICT: APPROVED
The auth logic is correct, backwards compatibility is preserved, and scope is tight. The nits (especially NIT 1 on DRY and NIT 3 on getToken null handling) are worth addressing but are not blockers.