92 lines
2.3 KiB
Markdown
92 lines
2.3 KiB
Markdown
# 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.
|