# Auth

Three auth modes, configured via `AUTH_MODE` env var. All three issue the
same JWT after authentication — downstream middleware is auth-mode-agnostic.

## Builtin Auth (default)

Username/password authentication with bcrypt hashing.

### Register

```
POST /auth/register
```

```json
{
  "username": "jdoe",
  "password": "...",
  "email": "jdoe@example.com",
  "display_name": "Jane Doe"
}
```

Gated by `allow_registration` policy. A unique `handle` is
auto-generated from `username` (collision-safe with `-2`, `-3` suffixes).

**Response (201):** Same token shape as Login. If the user requires admin
approval (`is_active` = false), returns `201` with:

```json
{ "message": "Account created but requires admin approval", "user_id": "uuid" }
```

### Login

```
POST /auth/login
```

```json
{ "username": "jdoe", "password": "..." }
```

**Response (200):**

```json
{
  "access_token": "eyJ...",
  "refresh_token": "opaque-string",
  "token_type": "Bearer",
  "expires_in": 900,
  "user": {
    "id": "uuid",
    "username": "jdoe",
    "email": "jdoe@example.com",
    "display_name": "Jane Doe",
    "handle": "jdoe",
    "role": "user",
    "auth_source": "builtin"
  }
}
```

### Refresh

```
POST /auth/refresh
```

```json
{ "refresh_token": "opaque-string" }
```

Returns new `access_token` and `refresh_token`. Old refresh token is
invalidated (rotation).

### Logout

```
POST /auth/logout
```

```json
{ "refresh_token": "opaque-string" }
```

Revokes the refresh token. Returns `{ "message": "logged out" }`.

## mTLS Auth

Mutual TLS via reverse proxy headers. The proxy terminates TLS and
forwards cert DN fields in headers. The backend auto-provisions
users on first connection.

**Configured by:** `AUTH_MODE=mtls` + `MTLS_HEADER_*` env vars.

No explicit login/register endpoints. User identity is extracted from
the TLS certificate on every request. `password_hash` is NULL for
mTLS users.

**Headers parsed:**
- `MTLS_HEADER_CN` (default: `X-SSL-Client-CN`) — common name → username
- `MTLS_HEADER_DN` (default: `X-SSL-Client-DN`) — distinguished name → metadata
- `MTLS_HEADER_VERIFY` (default: `X-SSL-Client-Verify`) — must be `SUCCESS`
- `MTLS_HEADER_FINGERPRINT` (default: `X-SSL-Client-Fingerprint`) — stable identity

On first request with a valid cert, the system creates a user with
`auth_source=mtls` and `external_id=fingerprint`.

## OIDC Auth

OpenID Connect authorization code flow. Tested with Keycloak but
compatible with any OIDC-compliant IdP.

**Configured by:** `AUTH_MODE=oidc` + `OIDC_*` env vars.

### OIDC Login (redirect)

```
GET /auth/oidc/login
```

Redirects to the IdP authorization endpoint. Stores `state` + `nonce`
in `oidc_auth_state` table (cleaned up after callback).

### OIDC Callback

```
GET /auth/oidc/callback?code=...&state=...
```

Exchanges authorization code for tokens, validates ID token signature
via JWKS, extracts claims. Auto-provisions user on first login with
`auth_source=oidc` and `external_id=sub`. Returns HTML fragment that
passes tokens to the frontend via URL fragment.

**Claim mapping:**
- `sub` → `external_id`
- `preferred_username` → `username` + `handle`
- `email` → `email`
- `name` → `display_name`
- `groups` → synced to internal groups with `source=oidc`

**Split-horizon issuer:** `OIDC_EXTERNAL_ISSUER_URL` can differ from
`OIDC_ISSUER_URL` when the IdP's internal address (Docker/K8s service)
differs from its external address.

**OIDC env vars:**

| Var | Required | Description |
|-----|----------|-------------|
| `OIDC_ISSUER_URL` | Yes | IdP issuer URL for discovery |
| `OIDC_EXTERNAL_ISSUER_URL` | No | External issuer (if different from internal) |
| `OIDC_CLIENT_ID` | Yes | Client ID |
| `OIDC_CLIENT_SECRET` | Yes | Client secret |
| `OIDC_REDIRECT_URL` | No | Callback URL (auto-derived if not set) |

## Login Page Adaptation

The `/login` page adapts by `AUTH_MODE`:

| Mode | UI |
|------|-----|
| `builtin` | Username/password form + register link |
| `mtls` | Certificate status display |
| `oidc` | "Sign in with SSO" button |

## User Identity Fields

All auth modes produce users with:

| Field | Description |
|-------|-------------|
| `auth_source` | `builtin`, `mtls`, or `oidc` |
| `external_id` | IdP subject (OIDC) or cert fingerprint (mTLS). NULL for builtin. |
| `handle` | Unique @mention handle. Auto-generated, collision-safe. |

`handle` is the canonical identifier for @mentions (replacing username
in the resolution chain since v0.24.0).
