Skip to content

Auth layer calls /auth/login and /auth/refresh-token endpoints that the backend does not implement #5

Description

@P3az3

Labels / Complexity: bug · High — 600 points

Problem

The frontend auth layer is wired to an email/password + refresh-token API that the backend does not implement. lib/api/auth.ts calls endpoints such as /auth/login, /auth/register, /auth/refresh-token, /auth/social-login, /auth/me, and /users/forgot-password. The actual backend (MilestoneX-Backend) exposes none of these; its auth module contains only GET /auth/challenge (src/auth/auth-challenge.controller.ts), POST /auth/verify (src/auth/auth-verify.controller.ts), and POST /auth/logout (src/auth/auth-logout.controller.ts), and POST /auth/verify returns { accessToken, tokenType: 'Bearer' } rather than the frontend's expected LoginResponse { user, token, refreshToken? }:

// lib/api/auth.ts
login: async (credentials: LoginCredentials) => {
  const response = await apiClient.post<ApiResponse<LoginResponse>>("/auth/login", credentials);
  ...
},
refreshToken: async () => {
  const response = await apiClient.post<ApiResponse<LoginResponse>>("/auth/refresh-token");
  ...
},

Consequence: the wallet challenge-response flow that is the backend's only sign-in method is never invoked from the frontend (a search finds no call to /auth/challenge or /auth/verify). Login, signup, social login, and token refresh therefore fail against the real backend, and the entire sessionExpiry.ts refresh mechanism is built around a /auth/refresh-token endpoint that does not exist. The frontend has two incompatible auth worlds — NextAuth OAuth in lib/auth/authOptions.ts and a dead email/password client — and neither produces a backend accessToken the API actually accepts.

Root cause

// lib/api/auth.ts
const response = await apiClient.post<ApiResponse<LoginResponse>>("/auth/login", credentials);
// ← no such endpoint; the backend only offers wallet challenge-response
// src/auth/auth-verify.controller.ts (backend) — the only real sign-in response shape
return { accessToken, tokenType: 'Bearer' };

Why this is architecturally hard

  1. The fix must implement the Stellar wallet challenge-response on the frontend: request /auth/challenge, sign the returned challenge with the connected wallet (the app already manages wallets in store/walletStore.ts and lib/stellar/), then POST /auth/verify with { walletAddress, signedChallenge, challenge }. This replaces, not patches, the email/password login/register methods.
  2. The backend issues a single 15-minute accessToken with no refresh token, so lib/auth/sessionTimeout.ts, lib/auth/sessionExpiry.ts, and store/authStore.ts — all built around a refreshToken — must be reworked to handle re-authentication via a fresh wallet signature instead of a refresh call.
  3. types/api.ts defines LoginCredentials/RegisterRequest/LoginResponse (user, token, refreshToken) that do not match the backend's { accessToken, tokenType }; the User type is email-based while the backend user is wallet-based (walletAddress, displayName, role). Aligning types means touching every component that reads user.email.

Proposed design

Add a wallet-auth client that calls /auth/challenge then /auth/verify, store the returned accessToken as the bearer token, and derive the user from the backend's walletAddress/role (or from GET /users/me). Replace the LoginResponse/LoginCredentials types with backend-accurate shapes and rework session expiry to prompt a re-sign instead of a refresh. Keep OAuth (authOptions.ts) as a separate, clearly-marked concern until the backend adds social login.

Downstream impact

This is the highest-leverage cross-repo issue in the batch: the frontend must implement the backend's exact auth ABI (GET /auth/challenge, POST /auth/verify, { accessToken, tokenType }). The sibling MilestoneX-Backend repo is the authority for that contract; the frontend cannot ship a working login without matching it. No generated bindings; plain REST + JWT claim shape.

Acceptance criteria

Auth flow

  • A user can sign in by signing a /auth/challenge challenge and completing /auth/verify, and the returned accessToken is used for subsequent API calls.
  • The dead /auth/login, /auth/register, /auth/refresh-token, /auth/social-login, and /auth/me calls are removed or replaced with backend-backed equivalents.
  • Session expiry re-triggers wallet re-authentication rather than calling a nonexistent refresh endpoint.

Types

  • LoginResponse and the User type match the backend's { accessToken, tokenType } and wallet-based user shape.

Tests

  • A test (or manual walkthrough) proves sign-in produces a token the backend accepts on a protected endpoint.

Out of scope

Adding social/OAuth login to the backend is a separate backend issue; this one makes wallet challenge-response the frontend's working auth path.

Getting started

Files in scope: lib/api/auth.ts, lib/auth/sessionTimeout.ts, lib/auth/sessionExpiry.ts, store/authStore.ts, types/api.ts.

npm run type-check
npm run lint

Good first files to read: lib/api/auth.ts, store/walletStore.ts, lib/stellar/connection.ts, and (in the backend) src/auth/auth-verify.controller.ts.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions