API reference
Credentials & pairing
The endpoints that exchange a pairing code for a device token and accept encrypted FCM uploads.
Device pairing
POST /pairing/device/code (session auth)
body: { label?: string }
→ { code: 'RUST-7A3B', expiresAt }
POST /pairing/device/activate (no auth)
body: { code }
→ { token: <48 bytes hex>, userId }
DELETE /pairing/device/:id (session auth)
→ { ok: true }
Codes expire 10 minutes after issue. Max 3 active codes per user; oldest is invalidated on the 4th.
FCM credential upload
PUT /credentials/fcm (bearer auth)
body: <opaque FCM bundle JSON>
→ { ok: true, steamId, uploadedAt }
What the API does on receipt:
- Decode the bundle just enough to extract its Steam ID.
- Reject (
400 mismatched_steam_id) if it doesn't match the bearer token's user's Steam ID. - Encrypt the entire bundle with AES-256-GCM using
CREDS_ENCRYPTION_KEY. - Upsert into
FcmCredential(encrypted blob + key version + timestamp). - Return
{ ok: true }.
Reading credentials (internal)
The relay and FCM worker never call HTTP — they read from Postgres directly with the same CREDS_ENCRYPTION_KEY env. The decrypted blob lives in memory only during one Rust+ connection attempt or one FCM session.
Key rotation
keyVersion on FcmCredential lets us migrate to a new encryption key without re-uploading every user's credentials:
- Set both
CREDS_ENCRYPTION_KEY(new) andCREDS_ENCRYPTION_KEY_PREV(old) in env. - On decrypt, try new key first, fall back to old.
- On every successful decrypt, re-encrypt with the new key and bump
keyVersion. - Once
keyVersionis uniform, dropCREDS_ENCRYPTION_KEY_PREV.
This is documented in src/lib/crypto.ts.