# Security PIN Flow

This document explains how to configure and use a per-user 6-digit Security PIN for sensitive actions.
All API payloads follow **JSON:API**.

## 1) Setup Security PIN (first time)

Endpoint:
`POST /api/v1/auth/pin/setup`

Auth:
- Requires logged-in session (`AUTH` cookie / access token cookie)

Request:
```json
{
  "data": {
    "type": "auth_pin_setup",
    "attributes": {
      "pin": "123456",
      "confirm_pin": "123456"
    }
  }
}
```

Success (`200`):
```json
{
  "data": {
    "type": "auth_pin_setup",
    "attributes": {
      "configured": true
    }
  }
}
```

If PIN already exists (`409`):
```json
{
  "errors": [
    {
      "status": "409",
      "title": "PIN already configured",
      "detail": "security PIN is already set"
    }
  ]
}
```

## 2) Verify PIN before sensitive action

Endpoint:
`POST /api/v1/auth/pin/verify`

Auth:
- Requires logged-in session

Request:
```json
{
  "data": {
    "type": "auth_pin_verify",
    "attributes": {
      "pin": "123456",
      "action": "a546de2f-f1e6-4c2c-ab03-9b4bf39f4d74"
    }
  }
}
```

Success (`200`):
```json
{
  "data": {
    "type": "auth_pin_verify",
    "attributes": {
      "verified": true,
      "action_token": "<one-time-token>",
      "action_token_ttl_ms": 300000
    }
  }
}
```

Use the returned token on the sensitive API call:
- Header: `X-PIN-Verification-Token: <one-time-token>`

Example sensitive request:
```http
DELETE /api/v1/users/delete/{uuid}
X-PIN-Verification-Token: <one-time-token>
Cookie: wucher_at=...
```

Important:
- Token is action-bound (`a546de2f-f1e6-4c2c-ab03-9b4bf39f4d74` only for that action).
- Token is one-time use.
- Token expires by `AUTH_SECURITY_PIN_ACTION_TOKEN_TTL`.
- Maximum failed attempts follow `AUTH_SECURITY_PIN_MAX_ATTEMPTS` (default `5`).
- When max attempts are reached, API returns `429` and PIN becomes blocked.
- Block is permanent until PIN reset succeeds (`AUTH_SECURITY_PIN_LOCK_DURATION=0` means no auto-unlock timeout).

## 3) Change Security PIN

Endpoint:
`POST /api/v1/auth/pin/change`

Auth:
- Requires logged-in session

Request:
```json
{
  "data": {
    "type": "auth_pin_change",
    "attributes": {
      "current_pin": "123456",
      "new_pin": "654321",
      "confirm_pin": "654321"
    }
  }
}
```

Success (`200`):
```json
{
  "data": {
    "type": "auth_pin_change",
    "attributes": {
      "updated": true
    }
  }
}
```

## 4) Reset Security PIN Flow

### 4.1 Request reset link (blocked PIN button)
Endpoint:
`POST /api/v1/auth/pin/request-reset`

Auth:
- Requires logged-in session

Request:
```json
{
  "data": {
    "type": "auth_pin_request_reset",
    "attributes": {
      "password": "<current-account-password>"
    }
  }
}
```

Success (`200`):
```json
{
  "data": {
    "type": "auth_pin_request_reset",
    "attributes": {
      "message": "If allowed, a security PIN reset link has been sent to your email."
    }
  }
}
```

If PIN not blocked (`409`):
```json
{
  "errors": [
    {
      "status": "409",
      "title": "PIN not blocked",
      "detail": "security PIN is not blocked"
    }
  ]
}
```

Invalid password (`401`):
```json
{
  "errors": [
    {
      "status": "401",
      "title": "Unauthorized",
      "detail": "invalid password"
    }
  ]
}
```

Rate limited (`429`):
```json
{
  "errors": [
    {
      "status": "429",
      "title": "Too many requests",
      "detail": "please wait before requesting another reset link"
    }
  ]
}
```

### 4.2 Request reset link (public forgot endpoint)
Endpoint:
`POST /api/v1/auth/pin/forgot`

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

Response is always generic (`200`):
```json
{
  "data": {
    "type": "auth_forgot_pin",
    "attributes": {
      "message": "If the account exists, a security PIN reset link has been sent."
    }
  }
}
```

### 4.3 Reset PIN with password or Microsoft re-auth
Endpoint:
`POST /api/v1/auth/pin/reset`

Notes:
- `method` is optional. Default is `password`.
- For `method=password`, `token` is required and must be the raw single-use reset token from the email reset link, then send `password`.
- For `method=microsoft`, `token` is not required. Send `reauth_token` from Microsoft re-auth directly.

Request with password re-auth:
```json
{
  "data": {
    "type": "auth_reset_pin",
    "attributes": {
      "token": "<raw-token-from-email-link>",
      "password": "<current-account-password>",
      "new_pin": "123456",
      "confirm_pin": "123456"
    }
  }
}
```

Request with Microsoft re-auth:
```json
{
  "data": {
    "type": "auth_reset_pin",
    "attributes": {
      "method": "microsoft",
      "reauth_token": "<reauth-token-from-/auth/microsoft/reauth?purpose=pin_reset_request>",
      "new_pin": "123456",
      "confirm_pin": "123456"
    }
  }
}
```

Success (`200`):
```json
{
  "data": {
    "type": "auth_reset_pin",
    "attributes": {
      "message": "Security PIN has been reset successfully.",
      "reset_success": true,
      "redirect_url": "https://app.example.com"
    }
  }
}
```

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

Invalid password (`401`):
```json
{
  "errors": [
    {
      "status": "401",
      "title": "Unauthorized",
      "detail": "invalid password"
    }
  ]
}
```

## 5) Sensitive endpoints currently protected by PIN token

The following routes require `X-PIN-Verification-Token`:
- `POST /api/v1/auth/invite` (`action=2a59e8b4-5a72-4be3-b8fc-4f49941181c3`)
- `POST /api/v1/users/create` (`action=2a59e8b4-5a72-4be3-b8fc-4f49941181c3`)
- `PATCH /api/v1/users/update/:uuid` (`action=428fdf48-3f99-46ea-bc47-8fb2e312be08`)
- `DELETE /api/v1/users/delete/:uuid` (`action=a546de2f-f1e6-4c2c-ab03-9b4bf39f4d74`)
- `POST /api/v1/roles/create` (`action=7fb1d693-8692-4e8f-be86-fd0e29e96bc3`)
- `PATCH /api/v1/roles/update/:uuid` (`action=18ca5bbf-ce9d-4e24-8ace-793de56c1de0`)
- `DELETE /api/v1/roles/delete/:uuid` (`action=b87fca76-783d-45e7-9022-b2d9c95f6da7`)
- `POST /api/v1/roles/:uuid/permissions` (`action=48e523f8-d86f-4b24-b0e3-e213e4d33db4`)
- `DELETE /api/v1/roles/:uuid/permissions/:permission_uuid` (`action=f2bf59f8-0783-40fc-94ce-a79e40f84c44`)
- `POST /api/v1/roles/:uuid/permissions/assign-bulk` (`action=48e523f8-d86f-4b24-b0e3-e213e4d33db4`)
- `POST /api/v1/roles/:uuid/permissions/remove-bulk` (`action=f2bf59f8-0783-40fc-94ce-a79e40f84c44`)

## 6) Security controls

- PIN must be exactly 6 digits.
- PIN is encrypted at rest (`users.security_pin_encrypted`), not stored in plaintext.
- Forgot/reset tokens are random, hashed, single-use, and expiring.
- Verify attempts are rate-limited per user with lockout (`AUTH_SECURITY_PIN_MAX_ATTEMPTS`, `AUTH_SECURITY_PIN_LOCK_DURATION`).
- Forgot endpoint response is generic to reduce account enumeration risk.

## 7) Environment variables

- `AUTH_SECURITY_PIN_RESET_URL`
- `AUTH_SECURITY_PIN_RESET_TTL`
- `AUTH_FORGOT_SECURITY_PIN_EMAIL_COOLDOWN`
- `AUTH_FORGOT_SECURITY_PIN_MIN_DURATION`
- `AUTH_SECURITY_PIN_MAX_ATTEMPTS`
- `AUTH_SECURITY_PIN_LOCK_DURATION`
- `AUTH_SECURITY_PIN_ACTION_TOKEN_TTL`
