# Password Reset Reference

This document explains the full password reset and forced re-auth flow implemented in the project.

## Scope

This covers:

- reset token validation and reset submission
- JWT invalidation after reset
- behavior of `/auth/me` with stale tokens
- multi-tab synchronization in the frontend

---

## High-Level Behavior

After a successful password reset:

1. Backend updates the password hash.
2. Backend increments the user's `token_version`.
3. Backend clears the `token` auth cookie in the reset response.
4. Existing JWTs in other tabs/devices become invalid because their embedded `token_version` no longer matches.
5. Frontend broadcasts a logout sync event so other tabs immediately redirect to login.

---

## Backend Components

### 1) User model versioning

File: `backend/models/user.py`

- Added `token_version: int = 0`.
- `from_dict()` defaults missing value to `0` for backward compatibility.
- `to_dict()` persists `token_version`.

### 2) JWT issuance includes token version

File: `backend/api/auth_api.py` (`/auth/login`)

JWT payload now includes:

- `email`
- `user_id`
- `token_version`
- `exp`

### 3) `/auth/me` rejects stale tokens

File: `backend/api/auth_api.py` (`/auth/me`)

Flow:

- decode JWT
- load user from DB
- compare `payload.token_version` (default 0) with `user.token_version`
- if mismatch, return:
  - status: `401`
  - code: `INVALID_TOKEN`

### 4) reset-password invalidates sessions

File: `backend/api/auth_api.py` (`/auth/reset-password`)

On success:

- hash and store new password
- clear `reset_token` and `reset_token_created`
- increment `user.token_version`
- persist user
- clear `token` cookie in response (`expires=0`, `httponly=True`, `secure=True`, `samesite='Strict'`)

### 5) shared auth utility enforcement

File: `backend/api/utils.py` (`get_current_user_id`)

Protected endpoints that use this helper also enforce token version:

- decode JWT
- load user by `user_id`
- compare JWT `token_version` vs DB `token_version`
- return `None` if mismatch

---

## Frontend Components

### 1) Reset password page

File: `frontend/vue-app/src/components/auth/ResetPassword.vue`

On successful `/api/auth/reset-password`:

- calls `logoutUser()` from auth store
- still shows success modal
- Sign In action navigates to login

### 2) Cross-tab logout sync

File: `frontend/vue-app/src/stores/auth.ts`

Implemented:

- logout broadcast key: `authSyncEvent`
- `logoutUser()`:
  - applies local logged-out state
  - writes logout event to localStorage
- `initAuthSync()`:
  - listens to `storage` events
  - if logout event arrives, applies logged-out state and redirects to `/auth/login` when outside `/auth/*`
- `checkAuth()` now funnels failed `/api/auth/me` checks through `logoutUser()`

### 3) Sync bootstrap

File: `frontend/vue-app/src/main.ts`

- calls `initAuthSync()` at app startup.

### 4) Global `401 Unauthorized` handling

Files:

- `frontend/vue-app/src/common/api.ts`
- `frontend/vue-app/src/main.ts`

Implemented:

- `installUnauthorizedFetchInterceptor()` wraps global `fetch`
- if any response is `401`, frontend:
  - calls `logoutUser()`
  - redirects to `/auth` (unless already on `/auth/*`)

This ensures protected pages consistently return users to auth landing when a session is invalid.

---

## Sequence Diagram (Reset Success)

```mermaid
sequenceDiagram
  participant U as User (Tab A)
  participant FE as ResetPassword.vue
  participant BE as auth_api.py
  participant DB as users_db
  participant LS as localStorage
  participant T2 as Browser Tab B

  U->>FE: Submit new password + token
  FE->>BE: POST /api/auth/reset-password
  BE->>DB: Validate reset token + expiry
  BE->>DB: Update password hash
  BE->>DB: token_version = token_version + 1
  BE-->>FE: 200 + clear auth cookie

  FE->>LS: logoutUser() writes authSyncEvent
  LS-->>T2: storage event(authSyncEvent: logout)
  T2->>T2: clear auth state
  T2->>T2: redirect /auth/login
```

---

## Sequence Diagram (Stale Token Check)

```mermaid
sequenceDiagram
  participant T as Any Tab with old JWT
  participant BE as /auth/me
  participant DB as users_db

  T->>BE: GET /auth/me (old JWT token_version=N)
  BE->>DB: Load user (current token_version=N+1)
  BE-->>T: 401 { code: INVALID_TOKEN }
```

---

## Example API Calls

### Validate reset token

`GET /api/auth/validate-reset-token?token=<token>`

Possible failures:

- `400 MISSING_TOKEN`
- `400 INVALID_TOKEN`
- `400 TOKEN_TIMESTAMP_MISSING`
- `400 TOKEN_EXPIRED`

### Reset password

`POST /api/auth/reset-password`

Request body:

```json
{
  "token": "<reset-token>",
  "password": "newStrongPassword123"
}
```

Success:

- `200 { "message": "Password has been reset" }`
- response also clears auth cookie

### Auth check after reset with stale JWT

`GET /api/auth/me`

Expected:

- `401 { "error": "Invalid token", "code": "INVALID_TOKEN" }`

---

## SSE vs Cross-Tab Sync

Current design intentionally does **not** rely on SSE to enforce logout correctness.

Why:

- Security correctness is guaranteed by cookie clearing + token_version invalidation.
- SSE can improve UX but is not required for correctness.
- Cross-tab immediate UX is handled client-side via localStorage `storage` events.

---

## Test Coverage

Backend:

- `backend/tests/test_auth_api.py`
- includes regression test ensuring old JWT fails `/auth/me` after reset.

Frontend:

- `frontend/vue-app/src/stores/__tests__/auth.childmode.spec.ts`
- includes cross-tab storage logout behavior.
- `frontend/vue-app/src/common/__tests__/api.interceptor.spec.ts`
- verifies global `401` interceptor logout and redirect behavior.

---

## Troubleshooting Checklist

- If stale sessions still appear valid:
  - verify `token_version` exists in user records
  - confirm `/auth/login` includes `token_version` claim
  - confirm `/auth/me` compares JWT vs DB token_version
  - confirm `/auth/reset-password` increments token_version
- If other tabs do not redirect:
  - verify `initAuthSync()` is called in `main.ts`
  - verify `logoutUser()` is called on reset success
  - check browser supports storage events across tabs for same origin
