# NAN-470 backend probe — 401 on Name field submit — 2026-05-21

## Q1 — Mobile: which hook/endpoint submits the Name field?

- Hook: `src/features/account/presentation/hooks/useEditProfileViewModel.ts:31`
  - `useProfileMutation` calls `container.account.repository.updateProfile({ fullName })`
  - `onError` handler: `extractMessage(err, 'Failed to update')` — no special 401 handling (`useEditProfileViewModel.ts:37-38`)
- HTTP method + path: `PUT /api/profiles/me`
- Service file: `src/features/account/service/api/NanoStreetAccountAdapter.ts:46-48`
  - Body sent: `{ display_name: string }`
- DTO: `display_name` only — no first/last name split

---

## Q2 — Backend: endpoint handler

- Route handler: `backend/services/main/app/routers/profiles.py:52-74` — `update_my_profile`
- Auth dependency: **present** — `Depends(get_current_user)` at `profiles.py:55`
- 401 raised: **yes** — `get_current_user` → `verify_token` raises `HTTPException(status_code=401, detail="Invalid authentication credentials")` at `backend/services/main/app/core/auth.py:184-186`
- Validation beyond non-empty/length: **strip only, no regex** — `UpdateProfileRequest` field validator at `backend/services/main/app/models/profiles.py:60-67` strips whitespace and returns `None` for blank strings; `Field(min_length=1, max_length=50)` enforces length after strip. No character-class restriction, no regex.
- Pydantic request model: `backend/services/main/app/models/profiles.py:50-81` — `UpdateProfileRequest`
  - `display_name: Optional[Annotated[str, Field(min_length=1, max_length=50)]]`

---

## Q3 — Auth flow: 401 path

- `get_current_user`: `backend/services/main/app/core/auth.py:176-194`
  - On expired/invalid JWT: raises `HTTPException(status_code=401, detail="Invalid authentication credentials")`
- Mobile HTTP client interceptor: `src/infrastructure/http/HttpClient.ts:108-128`
  - On 401: calls `refreshToken()` once, retries the original request with new token
  - If `refreshToken()` returns `null` OR the retry also yields 401: throws `HttpError(401)` with message `"HTTP 401 at PUT /api/profiles/me"`

---

## Q4 — Mobile 401 handling in the submitting hook

- Error handling: **generic catch → toast** — `useEditProfileViewModel.ts:37-38`
  - `extractMessage(err, 'Failed to update')` is called for all errors regardless of status code
  - No special 401 branch, no second refresh attempt, no "session expired" redirect
- User-facing error message on final 401:
  - If first refresh succeeds → request retries silently, user sees success (correct path)
  - If refresh fails (token truly expired, revoked, or refresh call itself 401s) → raw `"HTTP 401 at PUT /api/profiles/me"` surfaces as toast text

---

## Conclusion

**Bug is: auth-handling on mobile.**

Backend is correct — `PUT /api/profiles/me` returns 401 with clear detail on bad token, and has no character validation that could explain a false-positive rejection. The HTTP client does attempt one refresh + retry (correct), but when that retry also fails, the raw `HttpError` message (`"HTTP 401 at PUT /api/profiles/me"`) bubbles through `extractMessage` to the toast — users see a confusing technical string instead of "Session expired, please log in again."

**Minimal fix path:** In `NanoStreetAccountAdapter.ts` (or in the `useEditProfileViewModel` `onError` handler), catch `HttpError` where `status === 401` after the client's refresh-retry has already been exhausted, and throw/surface a user-friendly `AppError` with message like `"Your session expired — please log in again"`. No backend changes needed.
