Skip to content

feat(auth): POST /api/auth/login + me-fields + secondary SPA form#119

Merged
themightychris merged 1 commit into
mainfrom
feat/login-migration-phase-b
May 31, 2026
Merged

feat(auth): POST /api/auth/login + me-fields + secondary SPA form#119
themightychris merged 1 commit into
mainfrom
feat/login-migration-phase-b

Conversation

@themightychris
Copy link
Copy Markdown
Member

Summary

Phase B of the login-migration impl. Wires the phase-A verifier into a real login endpoint, surfaces `/api/auth/me`'s new fields, and adds the SPA's secondary password form.

Backend

  • `POST /api/auth/login` — verifies via the phase-A `verifyLegacyPassword`, rotates the credential on success (rehash if needed; always refresh `lastUsedAt`), mints a session with `loginMethod: 'legacy_password'`. Anti-enumeration timing floor via `dummyVerify`. Uniform 401 `invalid_credentials` on every failure.
  • `PrivateStore.putLegacyPassword` — write path for rotated credentials, threaded through the cross-store transaction's staged-mutation set.
  • JWT `loginMethod` claim — optional on access + refresh tokens. Preserved across refresh. `github-oauth.ts` passes `'github'` on success.
  • `GET /api/auth/me` — adds `hasGitHubLink` + `lastLoginMethod`.

SPA

  • `LoginPlaceholder.tsx` — collapsed "🔑 Or sign in with your Code for Philly password" disclosure below the GitHub CTA. Form expands on click, submits to `api.auth.login`, renders inline 401/429 errors. "Returning member" copy updated to match the new spec.
  • `api.auth.login` helper.

What's NOT in this PR

  • "Forgot your password?" affordance — deferred to phase C (needs `PasswordToken` private record + email-notifier integration).
  • `POST /api/auth/link-github` + `/account` banner — deferred to phase D.

Test plan

  • 12 new API tests: SHA-1/argon2 happy paths, email resolution, GitHub-linked user, rehash-on-login, no-rotate-when-current, all 401 paths, `/api/auth/me` post-login fields, anonymous `/api/auth/me`
  • 4 new web tests: collapsed disclosure renders, expands on click, submit gated until both fields filled, inline 401 error
  • `npm run type-check && npm run lint` clean

🤖 Generated with Claude Code

Phase B of the login-migration impl. Wires the phase-A verifier into
a real login endpoint, surfaces /api/auth/me's new fields, and adds
the SPA's secondary password form.

Backend:

  POST /api/auth/login (new) — accepts { usernameOrEmail, password },
    resolves to a Person by slug or email, verifies via the phase-A
    verifyLegacyPassword, rotates the credential on success (rehash
    if needsRehash; always refresh lastUsedAt), mints a session with
    loginMethod: 'legacy_password'. Anti-enumeration timing floor via
    dummyVerify on missing-user/credential paths. Uniform 401
    invalid_credentials on any failure. Rate-limit covered by the
    existing /api/auth/* 10/min/IP bucket.

  PrivateStore.putLegacyPassword (new) — write path for rotated
    credentials. Threaded through the cross-store transaction's
    staged-mutation set so future callers (password-reset confirm in
    phase C) get atomic semantics for free.

  JWT loginMethod claim — optional on access + refresh tokens.
    issueSession takes the value; verifyAccess/verifyRefresh surface
    it via the claim object. The refresh route preserves the claim
    across token rotation. github-oauth.ts now passes 'github' when
    minting on the callback's success paths.

  GET /api/auth/me — adds hasGitHubLink (derived from
    Person.githubUserId !== null) and lastLoginMethod (pulled from
    the access claim; null for anonymous or pre-PR sessions).

  middleware.ts SessionContext — adds optional loginMethod surfaced
    from the verified claim.

SPA:

  apps/web/src/pages/LoginPlaceholder.tsx — adds a collapsed
    "🔑 Or sign in with your Code for Philly password" disclosure
    below the GitHub CTA. Expanded form posts to api.auth.login with
    inline 401/429 error rendering. "Returning member" copy updated
    to match the new spec. "Forgot your password?" deferred to
    phase C.

  apps/web/src/lib/api.ts — adds api.auth.login(usernameOrEmail,
    password).

Tests:

  apps/api/tests/auth-login.test.ts (new) — 12 cases: SHA-1/argon2
    happy paths, email resolution, GitHub-linked user, rehash-on-
    login proof, no-rotate-when-current, all four 401 paths
    (wrong password, unknown user, unknown email, missing body),
    /api/auth/me post-login (legacy_password + hasGitHubLink
    false/true), anonymous /api/auth/me.

  apps/web/tests/LoginPlaceholder.test.tsx (new) — 4 cases: GitHub
    button + collapsed disclosure render, disclosure expands to
    reveal fields, submit gated until both fields filled, inline
    401 error.

All existing tests still pass (357 API + 58 web + 75 shared expected
on full sweep).

Closes the immediate phase B; phase C (password reset) and phase D
(link-github) follow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@themightychris themightychris merged commit 8858125 into main May 31, 2026
1 check passed
@themightychris themightychris deleted the feat/login-migration-phase-b branch May 31, 2026 16:47
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