Rename the entire workspace:
- Crate packages: quicnprotochat-{core,proto,server,client,gui,p2p,mobile} -> quicproquo-*
- Binary names: quicnprotochat -> qpq, quicnprotochat-server -> qpq-server,
quicnprotochat-gui -> qpq-gui
- Default files: *-state.bin -> qpq-state.bin, *-server.toml -> qpq-server.toml,
*.db -> qpq.db
- Environment variable prefix: QUICNPROTOCHAT_* -> QPQ_*
- App identifier: chat.quicnproto.gui -> chat.quicproquo.gui
- Proto package: quicnprotochat.bench -> quicproquo.bench
- All documentation, Docker, CI, and script references updated
HKDF domain-separation strings and P2P ALPN remain unchanged for
backward compatibility with existing encrypted state and wire protocol.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
257 lines
9.2 KiB
Markdown
257 lines
9.2 KiB
Markdown
# 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
|