From 0a98ace6f76d47e44138c56cd2ef0df3885d95a3 Mon Sep 17 00:00:00 2001 From: Bas van den Aakster Date: Wed, 10 Dec 2025 22:19:06 +0100 Subject: [PATCH] docs: Update README for unified Ed25519 authentication - Remove POST /register endpoint documentation - Update all endpoints to show signature-based auth (username, signature, message) - Remove Authorization header examples (replaced with body/query params) - Add anonymous username documentation (anon-{timestamp}-{random}) - Update database schema to show username-based tables - Remove AUTH_SECRET from configuration - Update security section with Ed25519 authentication details --- README.md | 117 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 60 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 3452453..866e5aa 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,11 @@ Scalable WebRTC signaling server with cryptographic username claiming, service p ## Features - **Username Claiming**: Cryptographic username ownership with Ed25519 signatures (365-day validity, auto-renewed on use) +- **Anonymous Users**: Auto-generated anonymous usernames for quick testing without claiming - **Service Publishing**: Service:version@username naming (e.g., `chat:1.0.0@alice`) - **Service Discovery**: Random and paginated discovery for finding services without knowing usernames - **Semantic Versioning**: Compatible version matching (chat:1.0.0 matches any 1.x.x) -- **Stateless Authentication**: AES-256-GCM encrypted credentials, no server-side sessions +- **Signature-Based Authentication**: All authenticated requests use Ed25519 signatures - **Complete WebRTC Signaling**: Offer/answer exchange and ICE candidate relay - **Efficient Batch Polling**: Combined endpoint for answers and ICE candidates (50% fewer HTTP requests) - **Dual Storage**: SQLite (Node.js/Docker) and Cloudflare D1 (Workers) backends @@ -49,7 +50,7 @@ npm install && npm start **Docker:** ```bash -docker build -t rondevu . && docker run -p 3000:3000 -e STORAGE_PATH=:memory: -e AUTH_SECRET=$(openssl rand -hex 32) rondevu +docker build -t rondevu . && docker run -p 3000:3000 -e STORAGE_PATH=:memory: rondevu ``` **Cloudflare Workers:** @@ -85,19 +86,6 @@ Health check endpoint with version } ``` -#### `POST /register` -Register a new peer and receive credentials (peerId + secret) - -Generates a cryptographically random 128-bit peer ID. - -**Response:** -```json -{ - "peerId": "f17c195f067255e357232e34cf0735d9", - "secret": "DdorTR8QgSn9yngn+4qqR8cs1aMijvX..." -} -``` - ### User Management #### `GET /users/:username` @@ -151,14 +139,12 @@ Claim a username with cryptographic proof ### Service Management #### `POST /services` -Publish a service with offers (requires authentication and username signature) - -**Headers:** -- `Authorization: Bearer {peerId}:{secret}` +Publish a service with offers (requires username and signature) **Request:** ```json { + "username": "alice", "serviceFqn": "chat:1.0.0@alice", "offers": [ { "sdp": "v=0..." }, @@ -262,10 +248,12 @@ Returns array of unique available offers from different users. ``` #### `DELETE /services/:fqn` -Unpublish a service (requires authentication and ownership) +Unpublish a service (requires username, signature, and ownership) -**Headers:** -- `Authorization: Bearer {peerId}:{secret}` +**Query Parameters:** +- `username` - Your username +- `signature` - Base64-encoded Ed25519 signature +- `message` - Signed message (format: `deleteService:{username}:{serviceFqn}:{timestamp}`) **Response:** ```json @@ -279,13 +267,13 @@ Unpublish a service (requires authentication and ownership) #### `POST /services/:fqn/offers/:offerId/answer` Post answer SDP to specific offer -**Headers:** -- `Authorization: Bearer {peerId}:{secret}` - **Request:** ```json { - "sdp": "v=0..." + "username": "bob", + "sdp": "v=0...", + "signature": "base64-encoded-signature", + "message": "answerOffer:{username}:{offerId}:{timestamp}" } ``` @@ -300,15 +288,17 @@ Post answer SDP to specific offer #### `GET /services/:fqn/offers/:offerId/answer` Get answer SDP (offerer polls this) -**Headers:** -- `Authorization: Bearer {peerId}:{secret}` +**Query Parameters:** +- `username` - Your username +- `signature` - Base64-encoded Ed25519 signature +- `message` - Signed message (format: `getAnswer:{username}:{offerId}:{timestamp}`) **Response:** ```json { "sdp": "v=0...", "offerId": "offer-hash", - "answererId": "answerer-peer-id", + "answererUsername": "bob", "answeredAt": 1733404800000 } ``` @@ -318,10 +308,10 @@ Returns 404 if not yet answered. #### `GET /offers/answered` Get all answered offers (efficient batch polling for offerer) -**Headers:** -- `Authorization: Bearer {peerId}:{secret}` - -**Query params:** +**Query Parameters:** +- `username` - Your username +- `signature` - Base64-encoded Ed25519 signature +- `message` - Signed message (format: `getAnsweredOffers:{username}:{timestamp}`) - `since` - Optional timestamp to get only new answers **Response:** @@ -331,7 +321,7 @@ Get all answered offers (efficient batch polling for offerer) { "offerId": "offer-hash", "serviceId": "service-uuid", - "answererId": "answerer-peer-id", + "answererUsername": "bob", "sdp": "v=0...", "answeredAt": 1733404800000 } @@ -342,10 +332,10 @@ Get all answered offers (efficient batch polling for offerer) #### `GET /offers/poll` Combined polling for answers and ICE candidates (offerer) -**Headers:** -- `Authorization: Bearer {peerId}:{secret}` - -**Query params:** +**Query Parameters:** +- `username` - Your username +- `signature` - Base64-encoded Ed25519 signature +- `message` - Signed message (format: `pollOffers:{username}:{timestamp}`) - `since` - Optional timestamp to get only new data **Response:** @@ -355,7 +345,7 @@ Combined polling for answers and ICE candidates (offerer) { "offerId": "offer-hash", "serviceId": "service-uuid", - "answererId": "answerer-peer-id", + "answererUsername": "bob", "sdp": "v=0...", "answeredAt": 1733404800000 } @@ -365,7 +355,7 @@ Combined polling for answers and ICE candidates (offerer) { "candidate": { "candidate": "...", "sdpMid": "0", "sdpMLineIndex": 0 }, "role": "answerer", - "peerId": "peer-id", + "username": "bob", "createdAt": 1733404800000 } ] @@ -378,19 +368,19 @@ More efficient than polling answers and ICE separately - reduces HTTP requests b #### `POST /services/:fqn/offers/:offerId/ice-candidates` Add ICE candidates to specific offer -**Headers:** -- `Authorization: Bearer {peerId}:{secret}` - **Request:** ```json { + "username": "alice", "candidates": [ { "candidate": "candidate:...", "sdpMid": "0", "sdpMLineIndex": 0 } - ] + ], + "signature": "base64-encoded-signature", + "message": "addIceCandidates:{username}:{offerId}:{timestamp}" } ``` @@ -405,10 +395,10 @@ Add ICE candidates to specific offer #### `GET /services/:fqn/offers/:offerId/ice-candidates` Get ICE candidates for specific offer -**Headers:** -- `Authorization: Bearer {peerId}:{secret}` - -**Query params:** +**Query Parameters:** +- `username` - Your username +- `signature` - Base64-encoded Ed25519 signature +- `message` - Signed message (format: `getIceCandidates:{username}:{offerId}:{timestamp}`) - `since` - Optional timestamp to get only new candidates **Response:** @@ -439,12 +429,12 @@ Environment variables: | `PORT` | `3000` | Server port (Node.js/Docker) | | `CORS_ORIGINS` | `*` | Comma-separated allowed origins | | `STORAGE_PATH` | `./rondevu.db` | SQLite database path (use `:memory:` for in-memory) | -| `VERSION` | `0.4.0` | Server version (semver) | -| `AUTH_SECRET` | Random 32-byte hex | Secret key for credential encryption (required for production) | -| `OFFER_DEFAULT_TTL` | `300000` | Default offer TTL in ms (5 minutes) | +| `VERSION` | `0.5.0` | Server version (semver) | +| `OFFER_DEFAULT_TTL` | `60000` | Default offer TTL in ms (1 minute) | | `OFFER_MIN_TTL` | `60000` | Minimum offer TTL in ms (1 minute) | -| `OFFER_MAX_TTL` | `3600000` | Maximum offer TTL in ms (1 hour) | -| `MAX_OFFERS_PER_REQUEST` | `10` | Maximum offers per create request | +| `OFFER_MAX_TTL` | `86400000` | Maximum offer TTL in ms (24 hours) | +| `CLEANUP_INTERVAL` | `60000` | Cleanup interval in ms (1 minute) | +| `MAX_OFFERS_PER_REQUEST` | `100` | Maximum offers per create request | ## Database Schema @@ -454,6 +444,7 @@ Environment variables: - `claimed_at`: Claim timestamp - `expires_at`: Expiry timestamp (365 days) - `last_used`: Last activity timestamp +- `metadata`: Optional JSON metadata ### services - `id` (PK): Service ID (UUID) @@ -466,30 +457,42 @@ Environment variables: ### offers - `id` (PK): Offer ID (hash of SDP) -- `peer_id` (FK): Owner peer ID +- `username` (FK): Owner username - `service_id` (FK): Link to service - `service_fqn`: Denormalized service FQN - `sdp`: WebRTC offer SDP -- `answerer_peer_id`: Peer ID of answerer (null until answered) +- `answerer_username`: Username of answerer (null until answered) - `answer_sdp`: WebRTC answer SDP (null until answered) - `answered_at`: Timestamp when answered -- `created_at`, `expires_at`: Timestamps +- `created_at`, `expires_at`, `last_seen`: Timestamps ### ice_candidates - `id` (PK): Auto-increment ID - `offer_id` (FK): Link to offer -- `peer_id`: Peer who sent the candidate +- `username`: Username who sent the candidate - `role`: 'offerer' or 'answerer' - `candidate`: JSON-encoded candidate - `created_at`: Timestamp ## Security +### Ed25519 Signature Authentication +All authenticated requests require: +- **username**: Your claimed username +- **signature**: Base64-encoded Ed25519 signature of the message +- **message**: Signed message with format-specific structure + ### Username Claiming - **Algorithm**: Ed25519 signatures - **Message Format**: `claim:{username}:{timestamp}` - **Replay Protection**: Timestamp must be within 5 minutes - **Key Management**: Private keys never leave the client +- **Validity**: 365 days, auto-renewed on use + +### Anonymous Users +- **Format**: `anon-{timestamp}-{random}` (e.g., `anon-lx2w34-a3f501`) +- **Generation**: Auto-generated by client for testing +- **Behavior**: Same as regular usernames, can be claimed ### Service Publishing - **Ownership Verification**: Every publish requires username signature