feat(auth): implement sign-in service and session persistence - #35
Conversation
Build the service layer on top of the shared auth model (TG-16): - errors.ts: deterministic error model. AuthResult<T> discriminated union plus ok()/fail() constructors so every operation returns a typed outcome instead of throwing. Closed AuthErrorCode set and InvalidCredentialsError/AuthNetworkError sentinels the authenticator can raise. - storage.ts: secure session persistence behind a SessionStore abstraction. BrowserSessionStore JSON-encodes under a namespaced key, validates structure on read (discarding tampered/corrupt values), and fails closed. Defaults to sessionStorage with an in-memory fallback for SSR/tests. - service.ts: AuthService with signIn (validate -> authenticate -> persist), restore (load -> derive state, clearing expired/invalid sessions), and signOut (idempotent clear). Authenticator, store and clock are injectable. - service.test.ts: 21 vitest cases covering success and every error code, expiry clearing, idempotent sign-out, and the storage layer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Task-Id: 01KZXZVXG719GBK1115B05E0B8 Prompt-Version: 1c9c10e027a2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the authentication service layer for TG-16, building on the shared auth model (
types.ts/validation.ts) introduced by the parent epic branch. Adds three service operations — submit credentials, restore an existing session, and sign out — plus secure session persistence and deterministic, typed error handling.New modules under
soccer-app/src/auth/:errors.ts— deterministic error model.AuthResult<T>is a discriminated union ({ ok: true, value }|{ ok: false, error }) withok()/fail()constructors, so every operation returns a typed outcome rather than throwing for expected failures. A closedAuthErrorCodeset (invalid_input,invalid_credentials,network_error,no_session,session_expired,storage_error,unknown) plusInvalidCredentialsError/AuthNetworkErrorsentinels the authenticator raises to signal rejection vs. transport failure.storage.ts— secure session persistence behind aSessionStoreabstraction.BrowserSessionStoreJSON-encodes the session under a single namespaced key (soccer-app.auth.session), validates structure on read and discards tampered/corrupt payloads, and fails closed (never throws on read). Defaults tosessionStorage(cleared when the tab closes) with an in-memory fallback (MemoryKeyValueStore) for SSR/tests.createDefaultSessionStore()probes storage availability before using it.service.ts—AuthServicewith the stable contract:signIn(credentials)— local validation (valid email, ≥8-char password) before hitting the authenticator; persists and returns the session on success; maps authenticator exceptions to deterministic codes; rejects malformed sessions.restore()— loads the persisted session and derives its state; returnssignedInwhen active,session_expired(and clears the stale session) when expired,no_sessionwhen absent,storage_erroron read failure.signOut()— idempotent clear of the persisted session.now) are all injectable for testability.service.test.ts— 21 vitest cases;index.tsre-exports the new public API.The service is framework-agnostic (no React) so it can be consumed by the UI sibling issues.
Issue
TG-16: Implement sign-in service and session persistence (parent epic: "Add user sign-in functionality").
Build & test results
All commands run from repo root unless noted;
npm installwas run insoccer-app/first (node_modules was absent).mise run build(tsc -b && vite build) — PASS, built in ~150ms, 20 modules transformed.mise run lint(oxlint) — PASS, no warnings or errors.npm test(vitest run) — PASS, 2 files, 43 tests (21 new service tests + 22 existing model tests).Decisions
AuthResulterrors for deterministic, exhaustive handling; unexpected causes are preserved onerror.causeand mapped tounknown.sessionStoragedefault: chosen overlocalStorageso a session does not linger on shared machines; overridable via dependency injection. Persistence is structurally validated on read so a tampered value can never surface as an active session.User/UserSession/AuthStatemodel, so this dovetails with sibling issues.Agent notes
types.ts,validation.ts) provided clean, pure primitives (deriveAuthState,isValidSession) that the service composes directly — the service layer stayed thin and fully unit-testable via injected authenticator/store/clock.node_moduleswas not present, somise run buildinitially failed withtsc: not found; runningnpm installinsoccer-app/resolved it.soccer-app/; tasks are driven viamise(build/lint/dev). Tests are vitest, colocated as*.test.ts. Lint isoxlintwith a stricttsconfig(noUnusedLocals,verbatimModuleSyntax,erasableSyntaxOnly). Commit style is conventional commits with aCo-Authored-Bytrailer.createAuthService+createDefaultSessionStore; a realCredentialAuthenticator(fetch-based) can be dropped in without touching the service. Consider a shared test helper formakeSession/validUser(currently duplicated across the two test files).By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of the project license.