# Password Reset

## Endpoints

### `POST /api/v1/auth/forgot-password`

Request:
```json
{
  "data": {
    "type": "auth_forgot_password",
    "attributes": {
      "email": "user@example.com"
    }
  }
}
```

Response (`200` always for valid payload):
```json
{
  "data": {
    "type": "auth_forgot_password",
    "attributes": {
      "message": "If the account exists, a password reset link has been sent."
    }
  }
}
```

### `POST /api/v1/auth/reset-password`

Request:
```json
{
  "data": {
    "type": "auth_reset_password",
    "attributes": {
      "token": "<raw-token>",
      "new_password": "StrongPassphrase123!",
      "confirm_password": "StrongPassphrase123!"
    }
  }
}
```

Success response (`200`):
```json
{
  "data": {
    "type": "auth_reset_password",
    "attributes": {
      "message": "Password has been reset successfully. Please log in again."
    }
  }
}
```

Invalid/expired/used token response (`400`):
```json
{
  "errors": [
    {
      "status": "400",
      "title": "Invalid reset link",
      "detail": "This reset link is invalid or expired."
    }
  ]
}
```

## Environment Variables

- `AUTH_PASSWORD_RESET_URL` frontend reset page URL; backend appends `?token=...`
- `AUTH_PASSWORD_RESET_TTL` reset token TTL (default `20m`, enforced 15-30m range)
- `AUTH_FORGOT_PASSWORD_EMAIL_COOLDOWN` cooldown per email identifier (default `2m`)
- `AUTH_FORGOT_PASSWORD_MIN_DURATION` minimum forgot-password response duration (default `150ms`)
- `AUTH_FORGOT_PASSWORD_IP_RATE_MAX` per-IP max attempts in window (default `10`)
- `AUTH_FORGOT_PASSWORD_IP_RATE_WINDOW` per-IP window duration (default `15m`)

## Security Decisions

- Uses `crypto/rand` for reset token entropy.
- Stores only SHA-256 token hashes in DB (`password_reset_tokens.token_hash`).
- Raw token is only delivered via email link.
- Tokens are single-use (`used_at`) and expire (`expires_at`).
- New forgot-password request invalidates previous active reset tokens for that user.
- Successful reset invalidates all active reset tokens for the user.
- Successful reset revokes active sessions by incrementing the MySQL-backed user session version used in JWT claims.
- Forgot-password response is generic to prevent account enumeration.
- Forgot-password throttling is applied per IP and per email identifier.
