From 9002fe3f6dc79102ed19207f253eae5cb1fc0402 Mon Sep 17 00:00:00 2001 From: Bas van den Aakster Date: Fri, 12 Dec 2025 23:17:06 +0100 Subject: [PATCH] Make README more concise with ADVANCED.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructure documentation for better discoverability: Changes: - README.md: 624 → 259 lines (58% reduction) - ADVANCED.md: New comprehensive guide (502 lines) README.md now contains: ✅ Features and architecture overview ✅ Quick start commands ✅ RPC interface basics ✅ Core method examples ✅ Configuration quick reference ✅ Links to advanced docs ADVANCED.md contains: 📚 Complete RPC method reference (8 methods) 📚 Full configuration table 📚 Database schema documentation 📚 Security implementation details 📚 Migration guides Benefits: - Faster onboarding for API consumers - Essential examples in README - Detailed reference still accessible - Consistent documentation structure 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- ADVANCED.md | 502 ++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 497 +++++++-------------------------------------------- 2 files changed, 568 insertions(+), 431 deletions(-) create mode 100644 ADVANCED.md diff --git a/ADVANCED.md b/ADVANCED.md new file mode 100644 index 0000000..f5e218e --- /dev/null +++ b/ADVANCED.md @@ -0,0 +1,502 @@ +# Rondevu Server - Advanced Usage + +Comprehensive API reference, configuration guide, database schema, and security details. + +## Table of Contents + +- [RPC Methods](#rpc-methods) +- [Configuration](#configuration) +- [Database Schema](#database-schema) +- [Security](#security) +- [Migration Guide](#migration-guide) + +--- + +## RPC Methods + +### `getUser` +Check username availability + +**Parameters:** +- `username` - Username to check + +**Message format:** `getUser:{username}:{timestamp}` (no authentication required) + +**Example:** +```json +{ + "method": "getUser", + "message": "getUser:alice:1733404800000", + "signature": "base64-signature", + "params": { "username": "alice" } +} +``` + +**Response:** +```json +{ + "success": true, + "result": { + "username": "alice", + "available": false, + "claimedAt": 1733404800000, + "expiresAt": 1765027200000, + "publicKey": "base64-encoded-public-key" + } +} +``` + +### `claimUsername` +Claim a username with cryptographic proof + +**Parameters:** +- `username` - Username to claim +- `publicKey` - Base64-encoded Ed25519 public key + +**Message format:** `claim:{username}:{timestamp}` + +**Example:** +```json +{ + "method": "claimUsername", + "message": "claim:alice:1733404800000", + "signature": "base64-signature", + "params": { + "username": "alice", + "publicKey": "base64-encoded-public-key" + } +} +``` + +**Response:** +```json +{ + "success": true, + "result": { + "success": true, + "username": "alice" + } +} +``` + +### `getService` +Get service by FQN (direct lookup, random discovery, or paginated) + +**Parameters:** +- `serviceFqn` - Service FQN (e.g., `chat:1.0.0` or `chat:1.0.0@alice`) +- `limit` - (optional) Number of results for paginated mode +- `offset` - (optional) Offset for paginated mode + +**Message format:** `getService:{username}:{serviceFqn}:{timestamp}` + +**Modes:** +1. **Direct lookup** (with @username): Returns specific user's service +2. **Random** (without @username, no limit): Returns random service +3. **Paginated** (without @username, with limit): Returns multiple services + +**Example:** +```json +{ + "method": "getService", + "message": "getService:bob:chat:1.0.0:1733404800000", + "signature": "base64-signature", + "params": { + "serviceFqn": "chat:1.0.0@alice" + } +} +``` + +**Response:** +```json +{ + "success": true, + "result": { + "serviceId": "uuid", + "username": "alice", + "serviceFqn": "chat:1.0.0@alice", + "offerId": "offer-hash", + "sdp": "v=0...", + "createdAt": 1733404800000, + "expiresAt": 1733405100000 + } +} +``` + +### `publishService` +Publish a service with offers + +**Parameters:** +- `serviceFqn` - Service FQN with username (e.g., `chat:1.0.0@alice`) +- `offers` - Array of offers, each with `sdp` field +- `ttl` - (optional) Time to live in milliseconds + +**Message format:** `publishService:{username}:{serviceFqn}:{timestamp}` + +**Example:** +```json +{ + "method": "publishService", + "message": "publishService:alice:chat:1.0.0@alice:1733404800000", + "signature": "base64-signature", + "params": { + "serviceFqn": "chat:1.0.0@alice", + "offers": [ + { "sdp": "v=0..." }, + { "sdp": "v=0..." } + ], + "ttl": 300000 + } +} +``` + +**Response:** +```json +{ + "success": true, + "result": { + "serviceId": "uuid", + "username": "alice", + "serviceFqn": "chat:1.0.0@alice", + "offers": [ + { + "offerId": "offer-hash-1", + "sdp": "v=0...", + "createdAt": 1733404800000, + "expiresAt": 1733405100000 + } + ], + "createdAt": 1733404800000, + "expiresAt": 1733405100000 + } +} +``` + +### `deleteService` +Delete a service + +**Parameters:** +- `serviceFqn` - Service FQN with username + +**Message format:** `deleteService:{username}:{serviceFqn}:{timestamp}` + +**Example:** +```json +{ + "method": "deleteService", + "message": "deleteService:alice:chat:1.0.0@alice:1733404800000", + "signature": "base64-signature", + "params": { + "serviceFqn": "chat:1.0.0@alice" + } +} +``` + +**Response:** +```json +{ + "success": true, + "result": { "success": true } +} +``` + +### `answerOffer` +Answer a specific offer + +**Parameters:** +- `serviceFqn` - Service FQN +- `offerId` - Offer ID +- `sdp` - Answer SDP + +**Message format:** `answerOffer:{username}:{offerId}:{timestamp}` + +**Example:** +```json +{ + "method": "answerOffer", + "message": "answerOffer:bob:offer-hash:1733404800000", + "signature": "base64-signature", + "params": { + "serviceFqn": "chat:1.0.0@alice", + "offerId": "offer-hash", + "sdp": "v=0..." + } +} +``` + +**Response:** +```json +{ + "success": true, + "result": { + "success": true, + "offerId": "offer-hash" + } +} +``` + +### `getOfferAnswer` +Get answer for an offer (offerer polls this) + +**Parameters:** +- `serviceFqn` - Service FQN +- `offerId` - Offer ID + +**Message format:** `getOfferAnswer:{username}:{offerId}:{timestamp}` + +**Example:** +```json +{ + "method": "getOfferAnswer", + "message": "getOfferAnswer:alice:offer-hash:1733404800000", + "signature": "base64-signature", + "params": { + "serviceFqn": "chat:1.0.0@alice", + "offerId": "offer-hash" + } +} +``` + +**Response:** +```json +{ + "success": true, + "result": { + "sdp": "v=0...", + "offerId": "offer-hash", + "answererId": "bob", + "answeredAt": 1733404800000 + } +} +``` + +### `poll` +Combined polling for answers and ICE candidates + +**Parameters:** +- `since` - (optional) Timestamp to get only new data + +**Message format:** `poll:{username}:{timestamp}` + +**Example:** +```json +{ + "method": "poll", + "message": "poll:alice:1733404800000", + "signature": "base64-signature", + "params": { + "since": 1733404800000 + } +} +``` + +**Response:** +```json +{ + "success": true, + "result": { + "answers": [ + { + "offerId": "offer-hash", + "serviceId": "service-uuid", + "answererId": "bob", + "sdp": "v=0...", + "answeredAt": 1733404800000 + } + ], + "iceCandidates": { + "offer-hash": [ + { + "candidate": { "candidate": "...", "sdpMid": "0", "sdpMLineIndex": 0 }, + "role": "answerer", + "username": "bob", + "createdAt": 1733404800000 + } + ] + } + } +} +``` + +### `addIceCandidates` +Add ICE candidates to an offer + +**Parameters:** +- `serviceFqn` - Service FQN +- `offerId` - Offer ID +- `candidates` - Array of ICE candidates + +**Message format:** `addIceCandidates:{username}:{offerId}:{timestamp}` + +**Example:** +```json +{ + "method": "addIceCandidates", + "message": "addIceCandidates:alice:offer-hash:1733404800000", + "signature": "base64-signature", + "params": { + "serviceFqn": "chat:1.0.0@alice", + "offerId": "offer-hash", + "candidates": [ + { + "candidate": "candidate:...", + "sdpMid": "0", + "sdpMLineIndex": 0 + } + ] + } +} +``` + +**Response:** +```json +{ + "success": true, + "result": { + "count": 1, + "offerId": "offer-hash" + } +} +``` + +### `getIceCandidates` +Get ICE candidates for an offer + +**Parameters:** +- `serviceFqn` - Service FQN +- `offerId` - Offer ID +- `since` - (optional) Timestamp to get only new candidates + +**Message format:** `getIceCandidates:{username}:{offerId}:{timestamp}` + +**Example:** +```json +{ + "method": "getIceCandidates", + "message": "getIceCandidates:alice:offer-hash:1733404800000", + "signature": "base64-signature", + "params": { + "serviceFqn": "chat:1.0.0@alice", + "offerId": "offer-hash", + "since": 1733404800000 + } +} +``` + +**Response:** +```json +{ + "success": true, + "result": { + "candidates": [ + { + "candidate": { + "candidate": "candidate:...", + "sdpMid": "0", + "sdpMLineIndex": 0 + }, + "createdAt": 1733404800000 + } + ], + "offerId": "offer-hash" + } +} +``` + + +## Configuration + +Environment variables: + +| Variable | Default | Description | +|----------|---------|-------------| +| `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.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` | `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 + +### usernames +- `username` (PK): Claimed username +- `public_key`: Ed25519 public key (base64) +- `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) +- `username` (FK): Owner username +- `service_fqn`: Fully qualified name (chat:1.0.0@alice) +- `service_name`: Service name component (chat) +- `version`: Version component (1.0.0) +- `created_at`, `expires_at`: Timestamps +- UNIQUE constraint on (service_name, version, username) + +### offers +- `id` (PK): Offer ID (hash of SDP) +- `username` (FK): Owner username +- `service_id` (FK): Link to service +- `service_fqn`: Denormalized service FQN +- `sdp`: WebRTC offer SDP +- `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`, `last_seen`: Timestamps + +### ice_candidates +- `id` (PK): Auto-increment ID +- `offer_id` (FK): Link to offer +- `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: +- **message**: Signed message with format-specific structure +- **signature**: Base64-encoded Ed25519 signature of the message +- Username is extracted from the message + +### 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**: Can be generated by client for testing +- **Behavior**: Same as regular usernames, must be explicitly claimed like any username + +### Service Publishing +- **Ownership Verification**: Every publish requires username signature +- **Message Format**: `publishService:{username}:{serviceFqn}:{timestamp}` +- **Auto-Renewal**: Publishing a service extends username expiry + +### ICE Candidate Filtering +- Server filters candidates by role to prevent peers from receiving their own candidates +- Offerers receive only answerer candidates +- Answerers receive only offerer candidates + +## Migration from v0.4.x + +See [MIGRATION.md](../MIGRATION.md) for detailed migration guide. + +**Key Changes:** +- Moved from REST API to RPC interface with single `/rpc` endpoint +- All methods now use POST with JSON body +- Batch operations supported +- Authentication is per-method instead of per-endpoint middleware + +## License + +MIT diff --git a/README.md b/README.md index bf6ab03..f4036ea 100644 --- a/README.md +++ b/README.md @@ -103,521 +103,156 @@ All API calls are made to `POST /rpc` with JSON-RPC format. ```json { "success": true, - "result": { - "username": "alice", - "available": false, - "claimedAt": 1733404800000, - "expiresAt": 1765027200000, - "publicKey": "base64-encoded-public-key" - } + "result": { /* method-specific data */ } } ``` -**Batch responses:** -```json -[ - { - "success": true, - "result": { "username": "alice", "available": false } - }, - { - "success": true, - "result": { "success": true, "username": "bob" } - } -] -``` - **Error response:** ```json { "success": false, - "error": "Username already claimed by different public key" + "error": "Error message" } ``` -## RPC Methods +**Batch responses:** Array of responses matching request array order. -### `getUser` -Check username availability +## Core Methods -**Parameters:** -- `username` - Username to check +### Username Management -**Message format:** `getUser:{username}:{timestamp}` (no authentication required) - -**Example:** -```json +```typescript +// Check username availability +POST /rpc { "method": "getUser", - "message": "getUser:alice:1733404800000", - "signature": "base64-signature", "params": { "username": "alice" } } -``` -**Response:** -```json -{ - "success": true, - "result": { - "username": "alice", - "available": false, - "claimedAt": 1733404800000, - "expiresAt": 1765027200000, - "publicKey": "base64-encoded-public-key" - } -} -``` - -### `claimUsername` -Claim a username with cryptographic proof - -**Parameters:** -- `username` - Username to claim -- `publicKey` - Base64-encoded Ed25519 public key - -**Message format:** `claim:{username}:{timestamp}` - -**Example:** -```json +// Claim username (requires signature) +POST /rpc { "method": "claimUsername", "message": "claim:alice:1733404800000", "signature": "base64-signature", "params": { "username": "alice", - "publicKey": "base64-encoded-public-key" + "publicKey": "base64-public-key" } } ``` -**Response:** -```json -{ - "success": true, - "result": { - "success": true, - "username": "alice" - } -} -``` +### Service Publishing -### `getService` -Get service by FQN (direct lookup, random discovery, or paginated) - -**Parameters:** -- `serviceFqn` - Service FQN (e.g., `chat:1.0.0` or `chat:1.0.0@alice`) -- `limit` - (optional) Number of results for paginated mode -- `offset` - (optional) Offset for paginated mode - -**Message format:** `getService:{username}:{serviceFqn}:{timestamp}` - -**Modes:** -1. **Direct lookup** (with @username): Returns specific user's service -2. **Random** (without @username, no limit): Returns random service -3. **Paginated** (without @username, with limit): Returns multiple services - -**Example:** -```json -{ - "method": "getService", - "message": "getService:bob:chat:1.0.0:1733404800000", - "signature": "base64-signature", - "params": { - "serviceFqn": "chat:1.0.0@alice" - } -} -``` - -**Response:** -```json -{ - "success": true, - "result": { - "serviceId": "uuid", - "username": "alice", - "serviceFqn": "chat:1.0.0@alice", - "offerId": "offer-hash", - "sdp": "v=0...", - "createdAt": 1733404800000, - "expiresAt": 1733405100000 - } -} -``` - -### `publishService` -Publish a service with offers - -**Parameters:** -- `serviceFqn` - Service FQN with username (e.g., `chat:1.0.0@alice`) -- `offers` - Array of offers, each with `sdp` field -- `ttl` - (optional) Time to live in milliseconds - -**Message format:** `publishService:{username}:{serviceFqn}:{timestamp}` - -**Example:** -```json +```typescript +// Publish service (requires signature) +POST /rpc { "method": "publishService", "message": "publishService:alice:chat:1.0.0@alice:1733404800000", "signature": "base64-signature", "params": { "serviceFqn": "chat:1.0.0@alice", - "offers": [ - { "sdp": "v=0..." }, - { "sdp": "v=0..." } - ], + "offers": [{ "sdp": "webrtc-offer-sdp" }], "ttl": 300000 } } ``` -**Response:** -```json +### Service Discovery + +```typescript +// Get specific service +POST /rpc { - "success": true, - "result": { - "serviceId": "uuid", - "username": "alice", - "serviceFqn": "chat:1.0.0@alice", - "offers": [ - { - "offerId": "offer-hash-1", - "sdp": "v=0...", - "createdAt": 1733404800000, - "expiresAt": 1733405100000 - } - ], - "createdAt": 1733404800000, - "expiresAt": 1733405100000 - } + "method": "getService", + "params": { "serviceFqn": "chat:1.0.0@alice" } } -``` -### `deleteService` -Delete a service - -**Parameters:** -- `serviceFqn` - Service FQN with username - -**Message format:** `deleteService:{username}:{serviceFqn}:{timestamp}` - -**Example:** -```json +// Random discovery +POST /rpc { - "method": "deleteService", - "message": "deleteService:alice:chat:1.0.0@alice:1733404800000", - "signature": "base64-signature", + "method": "getService", + "params": { "serviceFqn": "chat:1.0.0" } +} + +// Paginated discovery +POST /rpc +{ + "method": "getService", "params": { - "serviceFqn": "chat:1.0.0@alice" + "serviceFqn": "chat:1.0.0", + "limit": 10, + "offset": 0 } } ``` -**Response:** -```json -{ - "success": true, - "result": { "success": true } -} -``` +### WebRTC Signaling -### `answerOffer` -Answer a specific offer - -**Parameters:** -- `serviceFqn` - Service FQN -- `offerId` - Offer ID -- `sdp` - Answer SDP - -**Message format:** `answerOffer:{username}:{offerId}:{timestamp}` - -**Example:** -```json +```typescript +// Answer offer (requires signature) +POST /rpc { "method": "answerOffer", - "message": "answerOffer:bob:offer-hash:1733404800000", + "message": "answer:bob:offer-id:1733404800000", "signature": "base64-signature", "params": { "serviceFqn": "chat:1.0.0@alice", - "offerId": "offer-hash", - "sdp": "v=0..." + "offerId": "offer-id", + "sdp": "webrtc-answer-sdp" } } -``` -**Response:** -```json -{ - "success": true, - "result": { - "success": true, - "offerId": "offer-hash" - } -} -``` - -### `getOfferAnswer` -Get answer for an offer (offerer polls this) - -**Parameters:** -- `serviceFqn` - Service FQN -- `offerId` - Offer ID - -**Message format:** `getOfferAnswer:{username}:{offerId}:{timestamp}` - -**Example:** -```json -{ - "method": "getOfferAnswer", - "message": "getOfferAnswer:alice:offer-hash:1733404800000", - "signature": "base64-signature", - "params": { - "serviceFqn": "chat:1.0.0@alice", - "offerId": "offer-hash" - } -} -``` - -**Response:** -```json -{ - "success": true, - "result": { - "sdp": "v=0...", - "offerId": "offer-hash", - "answererId": "bob", - "answeredAt": 1733404800000 - } -} -``` - -### `poll` -Combined polling for answers and ICE candidates - -**Parameters:** -- `since` - (optional) Timestamp to get only new data - -**Message format:** `poll:{username}:{timestamp}` - -**Example:** -```json -{ - "method": "poll", - "message": "poll:alice:1733404800000", - "signature": "base64-signature", - "params": { - "since": 1733404800000 - } -} -``` - -**Response:** -```json -{ - "success": true, - "result": { - "answers": [ - { - "offerId": "offer-hash", - "serviceId": "service-uuid", - "answererId": "bob", - "sdp": "v=0...", - "answeredAt": 1733404800000 - } - ], - "iceCandidates": { - "offer-hash": [ - { - "candidate": { "candidate": "...", "sdpMid": "0", "sdpMLineIndex": 0 }, - "role": "answerer", - "username": "bob", - "createdAt": 1733404800000 - } - ] - } - } -} -``` - -### `addIceCandidates` -Add ICE candidates to an offer - -**Parameters:** -- `serviceFqn` - Service FQN -- `offerId` - Offer ID -- `candidates` - Array of ICE candidates - -**Message format:** `addIceCandidates:{username}:{offerId}:{timestamp}` - -**Example:** -```json +// Add ICE candidates (requires signature) +POST /rpc { "method": "addIceCandidates", - "message": "addIceCandidates:alice:offer-hash:1733404800000", - "signature": "base64-signature", "params": { "serviceFqn": "chat:1.0.0@alice", - "offerId": "offer-hash", - "candidates": [ - { - "candidate": "candidate:...", - "sdpMid": "0", - "sdpMLineIndex": 0 - } - ] + "offerId": "offer-id", + "candidates": [{ /* RTCIceCandidateInit */ }] } } -``` -**Response:** -```json +// Poll for answers and ICE candidates (requires signature) +POST /rpc { - "success": true, - "result": { - "count": 1, - "offerId": "offer-hash" - } -} -``` - -### `getIceCandidates` -Get ICE candidates for an offer - -**Parameters:** -- `serviceFqn` - Service FQN -- `offerId` - Offer ID -- `since` - (optional) Timestamp to get only new candidates - -**Message format:** `getIceCandidates:{username}:{offerId}:{timestamp}` - -**Example:** -```json -{ - "method": "getIceCandidates", - "message": "getIceCandidates:alice:offer-hash:1733404800000", - "signature": "base64-signature", - "params": { - "serviceFqn": "chat:1.0.0@alice", - "offerId": "offer-hash", - "since": 1733404800000 - } -} -``` - -**Response:** -```json -{ - "success": true, - "result": { - "candidates": [ - { - "candidate": { - "candidate": "candidate:...", - "sdpMid": "0", - "sdpMLineIndex": 0 - }, - "createdAt": 1733404800000 - } - ], - "offerId": "offer-hash" - } + "method": "poll", + "params": { "since": 1733404800000 } } ``` ## Configuration -Environment variables: +Quick reference for common environment variables: | Variable | Default | Description | |----------|---------|-------------| | `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.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` | `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 +📚 See [ADVANCED.md](./ADVANCED.md#configuration) for complete configuration reference. -### usernames -- `username` (PK): Claimed username -- `public_key`: Ed25519 public key (base64) -- `claimed_at`: Claim timestamp -- `expires_at`: Expiry timestamp (365 days) -- `last_used`: Last activity timestamp -- `metadata`: Optional JSON metadata +## Documentation -### services -- `id` (PK): Service ID (UUID) -- `username` (FK): Owner username -- `service_fqn`: Fully qualified name (chat:1.0.0@alice) -- `service_name`: Service name component (chat) -- `version`: Version component (1.0.0) -- `created_at`, `expires_at`: Timestamps -- UNIQUE constraint on (service_name, version, username) - -### offers -- `id` (PK): Offer ID (hash of SDP) -- `username` (FK): Owner username -- `service_id` (FK): Link to service -- `service_fqn`: Denormalized service FQN -- `sdp`: WebRTC offer SDP -- `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`, `last_seen`: Timestamps - -### ice_candidates -- `id` (PK): Auto-increment ID -- `offer_id` (FK): Link to offer -- `username`: Username who sent the candidate -- `role`: 'offerer' or 'answerer' -- `candidate`: JSON-encoded candidate -- `created_at`: Timestamp +📚 **[ADVANCED.md](./ADVANCED.md)** - Comprehensive guide including: +- Complete RPC method reference with examples +- Full configuration options +- Database schema documentation +- Security implementation details +- Migration guides ## Security -### Ed25519 Signature Authentication -All authenticated requests require: -- **message**: Signed message with format-specific structure -- **signature**: Base64-encoded Ed25519 signature of the message -- Username is extracted from the message +All authenticated operations require Ed25519 signatures: +- **Message Format**: `{method}:{username}:{context}:{timestamp}` +- **Signature**: Base64-encoded Ed25519 signature of the message +- **Replay Protection**: Timestamps must be within 5 minutes +- **Username Ownership**: Verified via public key signature -### 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**: Can be generated by client for testing -- **Behavior**: Same as regular usernames, must be explicitly claimed like any username - -### Service Publishing -- **Ownership Verification**: Every publish requires username signature -- **Message Format**: `publishService:{username}:{serviceFqn}:{timestamp}` -- **Auto-Renewal**: Publishing a service extends username expiry - -### ICE Candidate Filtering -- Server filters candidates by role to prevent peers from receiving their own candidates -- Offerers receive only answerer candidates -- Answerers receive only offerer candidates - -## Migration from v0.4.x - -See [MIGRATION.md](../MIGRATION.md) for detailed migration guide. - -**Key Changes:** -- Moved from REST API to RPC interface with single `/rpc` endpoint -- All methods now use POST with JSON body -- Batch operations supported -- Authentication is per-method instead of per-endpoint middleware +See [ADVANCED.md](./ADVANCED.md#security) for detailed security documentation. ## License