# Authentication & Authorization

This document covers the full auth model for the Packr registry: session/token auth, the two-layer bypass system (internal-secret vs super-admin-token), and the package ownership model.

---

## Auth layers

Packr has three levels of inbound auth:

| Layer | Credential | Who uses it |
|-------|-----------|-------------|
| **Session / JWT** | Bearer token in `Authorization` header or `packr_session` cookie | Human users (dashboard, CLI) |
| **Internal secret** | `X-Internal-Secret` header | Services calling the registry on behalf of the system (Stripe webhook, dashboard OAuth callback) |
| **Super-admin token** | `X-Super-Admin-Token` header | Human emergency override — deceased/locked-out maintainer recovery |

### Session / JWT

`AdminAuthMiddleware` accepts two credential types:

1. **Session token (HMAC)** — created at OAuth login, stored in dashboard cookie or returned to CLI. Short-lived (7 days).
2. **JWT (device flow)** — created by `packr-cli login`. Valid for 30 days. The JWT carries `org_id`, `user_id`, permissions, and scopes.

Both are passed as `Authorization: Bearer <token>`. On success, the user's internal integer ID is placed in the request context via `auth.GetUserID(ctx)`.

### Internal secret (`X-Internal-Secret`)

Service-to-service calls. The shared secret is set via `INTERNAL_API_SECRET` env var and configured on both the Go registry and the Next.js dashboard.

**Use cases:**
- Dashboard OAuth callback creating a registry session (`POST /api/v1/admin/sessions`)
- Stripe webhook updating a subscription (`POST /api/v1/admin/subscriptions`)
- Device flow callback authorizing a pending code (`POST /api/v1/admin/device/callback`)
- System-managed ownership changes (e.g. an automated pipeline adding a CI user as co-owner)

**What it bypasses:** The ownership check on `add-owner`, `remove-owner`, and `transfer` endpoints.

**What it does NOT bypass:** The super-admin-only `reset-owners` endpoint. Internal secret is for automated systems; it should not hold blanket override authority over package ownership.

### Super-admin token (`X-Super-Admin-Token`)

Human emergency override. The token is set via `SUPER_ADMIN_TOKEN` env var.

**Use cases:**
- A package maintainer is deceased, unreachable, or their account is locked.
- An abandoned package needs to be transferred to an active maintainer.
- A CI system accidentally published with the wrong owner; the original dev is unavailable.

**What it bypasses:** All ownership checks including the `reset-owners` endpoint. This is intentionally broader than the internal secret.

**What it does NOT bypass:** Basic request validity (package must exist, new owner list must be non-empty, etc.).

**Distinction from `X-Internal-Secret`:**

| Aspect | `X-Internal-Secret` | `X-Super-Admin-Token` |
|--------|---------------------|----------------------|
| Intended user | Automated services | Human operators |
| Session required | No | No |
| Bypasses ownership | Yes (add/remove/transfer) | Yes (add/remove/transfer + reset) |
| Can access `reset-owners` | No | Yes |
| Rotation | On infrastructure deploy | On security incident |

---

## Package ownership model

### Schema

`package_owners(package_id, user_id, added_at)` is a many-to-many join table. One package may have many owners; one user may own many packages.

### Ownership lifecycle

1. **First publish** — the user who publishes becomes the sole owner automatically.
2. **Add co-owner** — any existing owner can add further owners via `POST .../owners`.
3. **Remove co-owner** — any existing owner can remove another (but not the last one).
4. **Transfer** — `POST .../transfer` removes the caller and adds the target. Atomic.
5. **Force-reset** — `POST .../owners/reset` (super-admin only) atomically replaces the entire owner list. Audit-logs the prior state.

### `super_admin` user flag

In addition to the header-based override, a user account can have `super_admin=true` in the `users` table. This grants the same authority as `X-Super-Admin-Token` to any request authenticated with that user's token or session.

#### Preferred: env-driven first-boot bootstrap

For a fresh registry — or one whose OAuth providers were misconfigured and the previous admin is locked out — set three env vars at deploy time:

```bash
BOOTSTRAP_ADMIN_USER=kmandrup
BOOTSTRAP_ADMIN_PASSWORD='A10-char+Strong!Pass'   # passes validate.Password
BOOTSTRAP_GRANT_SUPER_ADMIN=true
```

At first boot the server creates the user, promotes it to `super_admin`, and emits an audit event (`user.created` with actor `system:bootstrap`). On every subsequent boot, the user row is left untouched and a one-time WARNING logs that `BOOTSTRAP_ADMIN_PASSWORD` is still set so the operator remembers to unset it from their secrets store. After the row exists, the env vars are inert.

Partial config is fatal at startup — `BOOTSTRAP_ADMIN_USER` without `BOOTSTRAP_ADMIN_PASSWORD` (or vice versa) blocks the boot with a message naming both vars. Username and password go through the same validators as `POST /-/v1/login`, so a malformed env var fails the boot instead of failing on first login.

#### Fallback: direct DB access

For an already-deployed registry where bootstrap is too late (the user row already exists, or the operator prefers SQL), set the flag with one statement:

```sql
-- SQLite / Postgres
UPDATE users SET super_admin = 1 WHERE name = 'kmandrup';
```

Or via a future admin API endpoint (not yet exposed in the HTTP layer — use direct DB access when the bootstrap path is unavailable).

---

## Audit logging

All owner mutations emit an audit event:

| Action | When |
|--------|------|
| `package.owner.added` | co-owner added |
| `package.owner.removed` | co-owner removed |
| `package.owners.reset` | force-reset (details include prior owner list) |
| `package.transferred` | ownership transferred |

View recent audit events:

```bash
curl -H "Authorization: Bearer $TOKEN" \
  https://api.packr.blueforge.studio/api/v1/admin/audit
```

---

## Environment variable reference

| Var | Description |
|-----|-------------|
| `JWT_SECRET` | Signs JWTs from `packr-cli login`. Required for persistent tokens. |
| `SESSION_SECRET` | Signs session tokens (dashboard + CLI). Required for persistent sessions. |
| `INTERNAL_API_SECRET` | Service-to-service shared secret. Required for dashboard login. |
| `SUPER_ADMIN_TOKEN` | Human emergency override. Keep in a password manager. |

Generate all secrets with `openssl rand -hex 32`.
