Skip to content

feat(auth): implement sign-in service and session persistence - #35

Open
ayushtr-aws wants to merge 1 commit into
bgagent/01KZXZN46CAW1WYDYS07APWMT7/shared-orchestration-context-the-parent-epic-severfrom
bgagent/01KZXZVXG719GBK1115B05E0B8/shared-orchestration-context-the-parent-epic-sever
Open

feat(auth): implement sign-in service and session persistence#35
ayushtr-aws wants to merge 1 commit into
bgagent/01KZXZN46CAW1WYDYS07APWMT7/shared-orchestration-context-the-parent-epic-severfrom
bgagent/01KZXZVXG719GBK1115B05E0B8/shared-orchestration-context-the-parent-epic-sever

Conversation

@ayushtr-aws

Copy link
Copy Markdown
Owner

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 }) with ok()/fail() constructors, so every operation returns a typed outcome rather than throwing for expected failures. A closed AuthErrorCode set (invalid_input, invalid_credentials, network_error, no_session, session_expired, storage_error, unknown) plus InvalidCredentialsError / AuthNetworkError sentinels the authenticator raises to signal rejection vs. transport failure.
  • storage.ts — secure session persistence behind a SessionStore abstraction. BrowserSessionStore JSON-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 to sessionStorage (cleared when the tab closes) with an in-memory fallback (MemoryKeyValueStore) for SSR/tests. createDefaultSessionStore() probes storage availability before using it.
  • service.tsAuthService with 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; returns signedIn when active, session_expired (and clears the stale session) when expired, no_session when absent, storage_error on read failure.
    • signOut() — idempotent clear of the persisted session.
    • Authenticator, store, and clock (now) are all injectable for testability.
  • service.test.ts — 21 vitest cases; index.ts re-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 install was run in soccer-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

  • Result type over exceptions: expected failures are returned as typed AuthResult errors for deterministic, exhaustive handling; unexpected causes are preserved on error.cause and mapped to unknown.
  • sessionStorage default: chosen over localStorage so 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.
  • Naming/shapes follow the shared orchestration context and the existing User/UserSession/AuthState model, so this dovetails with sibling issues.

Agent notes

  • What went well: the parent commit's model (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.
  • What was difficult: nothing major. node_modules was not present, so mise run build initially failed with tsc: not found; running npm install in soccer-app/ resolved it.
  • Repo conventions discovered: Vite + React 19 + TypeScript app lives in soccer-app/; tasks are driven via mise (build/lint/dev). Tests are vitest, colocated as *.test.ts. Lint is oxlint with a strict tsconfig (noUnusedLocals, verbatimModuleSyntax, erasableSyntaxOnly). Commit style is conventional commits with a Co-Authored-By trailer.
  • Suggestions for future tasks: the UI sign-in form (sibling issue) can consume createAuthService + createDefaultSessionStore; a real CredentialAuthenticator (fetch-based) can be dropped in without touching the service. Consider a shared test helper for makeSession/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.

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant