# Auth, Devices, and Tokens

This page describes the authentication, device management, and authorisation
design for quicproquo. It introduces account and device identities, gates
server operations by authenticated identity, enforces rate and size limits, and
binds MLS identity keys to accounts.

This design cuts across milestones M4 through M6. For the broader production
readiness plan, see [Production Readiness WBS](production-readiness.md).

---

## Goals

1. **Introduce accounts and devices** with authenticated access to `NodeService`.
2. **Gate operations by identity:** enqueue/fetch/fetchWait require a valid token
   bound to the caller's account and device.
3. **Enforce rate and size limits** per account, per device, and per IP.
4. **Bind MLS identity keys to accounts:** a KeyPackage upload must be associated
   with the uploading account, preventing impersonation.
5. **Keep wire changes minimal and versioned:** the `Auth` struct is additive
   and uses a version field for backward compatibility.

---

## Data Model (Server)

### Accounts

| Field | Type | Description |
|-------|------|-------------|
| `account_id` | UUID | Unique account identifier |
| `created_at` | Timestamp | Account creation time |
| `status` | Enum | `active`, `suspended`, `deleted` |

### Devices

| Field | Type | Description |
|-------|------|-------------|
| `device_id` | UUID | Unique device identifier |
| `account_id` | UUID | Owning account (foreign key) |
| `device_pubkey` | Ed25519 public key (32 bytes) | Device signing key |
| `created_at` | Timestamp | Device registration time |
| `status` | Enum | `active`, `revoked` |

### Sessions / Tokens

| Field | Type | Description |
|-------|------|-------------|
| `session_id` | UUID | Unique session identifier |
| `account_id` | UUID | Owning account |
| `device_id` | UUID | Originating device |
| `access_token` | Opaque bytes | Short-lived bearer token |
| `refresh_token` | Opaque bytes | Long-lived token for renewal |
| `expires_at` | Timestamp | Access token expiry |
| `created_at` | Timestamp | Session creation time |

### Identity Binding

| Field | Type | Description |
|-------|------|-------------|
| `account_id` | UUID | Owning account |
| `mls_identity_key` | Ed25519 public key (32 bytes) | MLS credential public key |
| `verified_fp` | SHA-256 fingerprint (32 bytes) | Fingerprint of the bound key |

The identity binding table ensures that only the account that registered an
Ed25519 public key can upload KeyPackages for that key. This prevents a
compromised or malicious client from uploading KeyPackages under another
account's identity.

---

## Wire / API Changes

### Auth Struct

A new `Auth` struct is added to all `NodeService` RPC methods:

```capnp
struct Auth {
  version     @0 :UInt16;     # 0 = legacy (no auth), 1 = token-based
  accessToken @1 :Data;       # opaque bearer token
  deviceId    @2 :Data;       # optional UUID (16 bytes) for audit/rate limit
}
```

The `Auth` struct is included as a parameter in `enqueue`, `fetch`, `fetchWait`,
`uploadKeyPackage`, and `fetchKeyPackage`.

### Versioning

| Version | Meaning |
|---------|---------|
| 0 | Legacy mode: no authentication. Server can allow-list in development but defaults to rejecting in production. |
| 1 | Token-based authentication. `accessToken` is required and validated. |

The server rejects any `version` value higher than its current maximum. This
ensures that a newer client connecting to an older server fails cleanly rather
than silently skipping auth.

### Optional Device ID

The `deviceId` field is optional. When present, the server uses it for:

- Per-device rate limiting (in addition to per-account limits).
- Audit logging (which device performed which operation).
- Future: device revocation without revoking the entire account.

---

## Server Enforcement

### Token Validation

1. Extract `Auth` struct from the incoming RPC.
2. If `version == 0` and server is in production mode, reject with
   `AUTHENTICATION_REQUIRED`.
3. If `version == 1`, validate `accessToken`:
   - Token must exist in the session store.
   - Token must not be expired (`expires_at > now`).
   - Associated account must have `status == active`.
   - Associated device (if `deviceId` present) must have `status == active`.
4. Map validated token to `(account_id, device_id)` for downstream authorisation.

### Identity Matching

- **uploadKeyPackage:** The `identityKey` in the RPC must match an identity
  binding for the authenticated account. Reject with `IDENTITY_MISMATCH` if the
  key is not bound to the caller's account.
- **fetchKeyPackage:** No identity restriction (any authenticated client can
  fetch any identity's KeyPackage -- this is required for the MLS add-member flow).
- **enqueue:** If `channelId` is present, the caller's identity must be in the
  channel membership. If `channelId` is absent (legacy mode), the operation is
  allowed for any authenticated client.
- **fetch / fetchWait:** The `recipientKey` must correspond to an identity bound
  to the caller's account.

### Rate Limits

| Limit | Scope | Default |
|-------|-------|---------|
| Request rate | Per IP | 50 requests/second |
| Request rate | Per account | 50 requests/second |
| Request rate | Per device | 50 requests/second |
| Payload size | Per RPC call | 5 MB |
| KeyPackage TTL | Per package | 24 hours |
| KeyPackage uploads | Per account | Configurable (prevents store exhaustion) |

Rate limit counters use a sliding window. When a limit is exceeded, the server
responds with `RATE_LIMITED` and includes a `Retry-After` hint.

### Audit Logging

The following events are logged at audit level:

- Authentication success (account, device, IP).
- Authentication failure (reason, IP).
- Token issuance and refresh (account, device).
- KeyPackage upload (account, identity key fingerprint).
- Enqueue (account, channel, recipient).
- Fetch / fetchWait (account, recipient).
- Rate limit exceeded (scope, account/IP, current rate).

All audit log entries include a timestamp and correlation ID. Sensitive fields
(token values, ciphertext, private keys) are never logged.

---

## Client Changes

### Login / Register Flow

1. **Register:** Client generates an Ed25519 identity keypair, sends the public
   key to the server. Server creates an account, binds the identity key, and
   returns an `(access_token, refresh_token)` pair.
2. **Login:** Client presents credentials (initially: signed challenge from
   device key). Server validates and issues tokens.
3. **Token storage:** Access and refresh tokens stored in the client state file
   (same location as identity keypair). The state file should be
   permission-restricted (`0600`).
4. **Token refresh:** Client detects `TOKEN_EXPIRED` errors and uses the refresh
   token to obtain a new access token without re-authenticating.

### RPC Integration

Every RPC call includes the `Auth` struct:

```rust
// Pseudocode for client RPC calls
let auth = Auth {
    version: 1,
    access_token: state.access_token.clone(),
    device_id: Some(state.device_id),
};
node_service.enqueue(auth, recipient_key, channel_id, payload).await?;
```

### Identity Binding

At registration, the client's Ed25519 public key is bound to the new account.
The client must refuse to upload KeyPackages if the local identity key does not
match the bound key -- this prevents accidental identity confusion after key
rotation.

---

## Compatibility

### Wire Version Field

The `Auth` struct includes its own `version` field, independent of the delivery
message version. This allows auth changes to evolve separately from the delivery
protocol.

### Legacy Support

- `version == 0`: No auth. Server behaviour is configurable:
  - **Development:** Allow legacy calls (default for `cargo run`).
  - **Production:** Reject legacy calls (default for Docker deployment).
- `version == 1`: Full auth. This is the target for M4+.

### N-1 Integration Tests

Compatibility testing covers:

- New client (v1 auth) against new server -- expected: full auth flow works.
- Old client (v0 legacy) against new server in dev mode -- expected: legacy
  calls succeed.
- Old client (v0 legacy) against new server in prod mode -- expected: clean
  rejection with `AUTHENTICATION_REQUIRED`.
- New client (v1 auth) against old server -- expected: server ignores unknown
  `Auth` struct fields; operations succeed if server does not enforce auth.

---

## Implementation Sequence

1. Extend Cap'n Proto schemas with the `Auth` struct and add it to all
   `NodeService` methods.
2. Implement token validation middleware in server RPC handlers; add an in-memory
   token store (upgradeable to SQLite at M6).
3. Bind `identityKey` to account on upload; enforce on fetch/enqueue.
4. Add tests: unit tests for token validation; integration tests for auth
   success and failure paths.
5. Add rate limiting middleware with configurable thresholds.
6. Add audit logging for all auth-related events.

---

## Cross-references

- [Milestones](milestones.md) -- M4 and M6 deliverables
- [Production Readiness WBS](production-readiness.md) -- Phase 3 (Auth/Device/Server Hardening)
- [1:1 Channel Design](dm-channels.md) -- channel-level authz
- [Wire Format: NodeService Schema](../wire-format/node-service-schema.md) -- RPC schema
- [Coding Standards](../contributing/coding-standards.md) -- security-by-design requirements
