init push
This commit is contained in:
91
docs/password-reset.md
Normal file
91
docs/password-reset.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user