@xtr-dev/rondevu-server 0.1.5 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@xtr-dev/rondevu-server)](https://www.npmjs.com/package/@xtr-dev/rondevu-server)
4
4
 
5
- 🌐 **Topic-based peer discovery and WebRTC signaling**
5
+ 🌐 **DNS-like WebRTC signaling with username claiming and service discovery**
6
6
 
7
- Scalable peer-to-peer connection establishment with topic-based discovery, stateless authentication, and complete WebRTC signaling.
7
+ Scalable WebRTC signaling server with cryptographic username claiming, service publishing, and privacy-preserving discovery.
8
8
 
9
9
  **Related repositories:**
10
10
  - [@xtr-dev/rondevu-client](https://github.com/xtr-dev/rondevu-client) - TypeScript client library ([npm](https://www.npmjs.com/package/@xtr-dev/rondevu-client))
@@ -15,14 +15,28 @@ Scalable peer-to-peer connection establishment with topic-based discovery, state
15
15
 
16
16
  ## Features
17
17
 
18
- - **Topic-Based Discovery**: Tag offers with topics (e.g., torrent infohashes) for efficient peer finding
18
+ - **Username Claiming**: Cryptographic username ownership with Ed25519 signatures (365-day validity, auto-renewed on use)
19
+ - **Service Publishing**: Package-style naming with semantic versioning (com.example.chat@1.0.0)
20
+ - **Privacy-Preserving Discovery**: UUID-based service index prevents enumeration
21
+ - **Public/Private Services**: Control service visibility
19
22
  - **Stateless Authentication**: AES-256-GCM encrypted credentials, no server-side sessions
20
- - **Protected Offers**: Optional secret field for access-controlled peer connections
21
- - **Bloom Filters**: Client-side peer exclusion for efficient discovery
22
- - **Multi-Offer Support**: Create multiple offers per peer simultaneously
23
23
  - **Complete WebRTC Signaling**: Offer/answer exchange and ICE candidate relay
24
24
  - **Dual Storage**: SQLite (Node.js/Docker) and Cloudflare D1 (Workers) backends
25
25
 
26
+ ## Architecture
27
+
28
+ ```
29
+ Username Claiming → Service Publishing → Service Discovery → WebRTC Connection
30
+
31
+ alice claims "alice" with Ed25519 signature
32
+
33
+ alice publishes com.example.chat@1.0.0 → receives UUID abc123
34
+
35
+ bob queries alice's services → gets UUID abc123
36
+
37
+ bob connects to UUID abc123 → WebRTC connection established
38
+ ```
39
+
26
40
  ## Quick Start
27
41
 
28
42
  **Node.js:**
@@ -32,7 +46,7 @@ npm install && npm start
32
46
 
33
47
  **Docker:**
34
48
  ```bash
35
- docker build -t rondevu . && docker run -p 3000:3000 -e STORAGE_PATH=:memory: rondevu
49
+ docker build -t rondevu . && docker run -p 3000:3000 -e STORAGE_PATH=:memory: -e AUTH_SECRET=$(openssl rand -hex 32) rondevu
36
50
  ```
37
51
 
38
52
  **Cloudflare Workers:**
@@ -53,86 +67,182 @@ Health check endpoint with version
53
67
  #### `POST /register`
54
68
  Register a new peer and receive credentials (peerId + secret)
55
69
 
56
- **Request (optional):**
70
+ Generates a cryptographically random 128-bit peer ID.
71
+
72
+ **Response:**
57
73
  ```json
58
74
  {
59
- "peerId": "my-custom-peer-id"
75
+ "peerId": "f17c195f067255e357232e34cf0735d9",
76
+ "secret": "DdorTR8QgSn9yngn+4qqR8cs1aMijvX..."
60
77
  }
61
78
  ```
62
79
 
63
- **Notes:**
64
- - `peerId` (optional): Custom peer ID (1-128 characters). If not provided, a random ID will be generated.
65
- - Returns 409 Conflict if the custom peer ID is already in use.
80
+ ### Username Management
81
+
82
+ #### `POST /usernames/claim`
83
+ Claim a username with cryptographic proof
84
+
85
+ **Request:**
86
+ ```json
87
+ {
88
+ "username": "alice",
89
+ "publicKey": "base64-encoded-ed25519-public-key",
90
+ "signature": "base64-encoded-signature",
91
+ "message": "claim:alice:1733404800000"
92
+ }
93
+ ```
66
94
 
67
95
  **Response:**
68
96
  ```json
69
97
  {
70
- "peerId": "f17c195f067255e357232e34cf0735d9",
71
- "secret": "DdorTR8QgSn9yngn+4qqR8cs1aMijvX..."
98
+ "username": "alice",
99
+ "claimedAt": 1733404800000,
100
+ "expiresAt": 1765027200000
72
101
  }
73
102
  ```
74
103
 
75
- #### `GET /topics?limit=50&offset=0`
76
- List all topics with active peer counts (paginated)
104
+ **Validation:**
105
+ - Username format: `^[a-z0-9][a-z0-9-]*[a-z0-9]$` (3-32 characters)
106
+ - Signature must be valid Ed25519 signature
107
+ - Timestamp must be within 5 minutes (replay protection)
108
+ - Expires after 365 days, auto-renewed on use
77
109
 
78
- **Query Parameters:**
79
- - `limit` (optional): Maximum number of topics to return (default: 50, max: 200)
80
- - `offset` (optional): Number of topics to skip (default: 0)
110
+ #### `GET /usernames/:username`
111
+ Check username availability and claim status
81
112
 
82
113
  **Response:**
83
114
  ```json
84
115
  {
85
- "topics": [
86
- {"topic": "movie-xyz", "activePeers": 42},
87
- {"topic": "torrent-abc", "activePeers": 15}
88
- ],
89
- "total": 123,
90
- "limit": 50,
91
- "offset": 0
116
+ "username": "alice",
117
+ "available": false,
118
+ "claimedAt": 1733404800000,
119
+ "expiresAt": 1765027200000,
120
+ "publicKey": "..."
92
121
  }
93
122
  ```
94
123
 
95
- #### `GET /offers/by-topic/:topic?limit=50&bloom=...`
96
- Find offers by topic with optional bloom filter exclusion
97
-
98
- **Query Parameters:**
99
- - `limit` (optional): Maximum offers to return (default: 50, max: 200)
100
- - `bloom` (optional): Base64-encoded bloom filter to exclude known peers
124
+ #### `GET /usernames/:username/services`
125
+ List all services for a username (privacy-preserving)
101
126
 
102
127
  **Response:**
103
128
  ```json
104
129
  {
105
- "topic": "movie-xyz",
106
- "offers": [
130
+ "username": "alice",
131
+ "services": [
107
132
  {
108
- "id": "offer-id",
109
- "peerId": "peer-id",
110
- "sdp": "v=0...",
111
- "topics": ["movie-xyz", "hd-content"],
112
- "expiresAt": 1234567890,
113
- "lastSeen": 1234567890,
114
- "hasSecret": true, // Indicates if secret is required to answer
115
- "info": "Looking for peers in EU region" // Public info field (optional)
133
+ "uuid": "abc123",
134
+ "isPublic": false
135
+ },
136
+ {
137
+ "uuid": "def456",
138
+ "isPublic": true,
139
+ "serviceFqn": "com.example.public@1.0.0",
140
+ "metadata": { "description": "Public service" }
116
141
  }
117
- ],
118
- "total": 42,
119
- "returned": 10
142
+ ]
143
+ }
144
+ ```
145
+
146
+ ### Service Management
147
+
148
+ #### `POST /services`
149
+ Publish a service (requires authentication and username signature)
150
+
151
+ **Headers:**
152
+ - `Authorization: Bearer {peerId}:{secret}`
153
+
154
+ **Request:**
155
+ ```json
156
+ {
157
+ "username": "alice",
158
+ "serviceFqn": "com.example.chat@1.0.0",
159
+ "sdp": "v=0...",
160
+ "ttl": 300000,
161
+ "isPublic": false,
162
+ "metadata": { "description": "Chat service" },
163
+ "signature": "base64-encoded-signature",
164
+ "message": "publish:alice:com.example.chat@1.0.0:1733404800000"
165
+ }
166
+ ```
167
+
168
+ **Response:**
169
+ ```json
170
+ {
171
+ "serviceId": "uuid-v4",
172
+ "uuid": "uuid-v4-for-index",
173
+ "offerId": "offer-hash-id",
174
+ "expiresAt": 1733405100000
175
+ }
176
+ ```
177
+
178
+ **Service FQN Format:**
179
+ - Service name: Reverse domain notation (e.g., `com.example.chat`)
180
+ - Version: Semantic versioning (e.g., `1.0.0`, `2.1.3-beta`)
181
+ - Complete FQN: `service-name@version` (e.g., `com.example.chat@1.0.0`)
182
+
183
+ **Validation:**
184
+ - Service name pattern: `^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$`
185
+ - Length: 3-128 characters
186
+ - Version pattern: `^[0-9]+\.[0-9]+\.[0-9]+(-[a-z0-9.-]+)?$`
187
+
188
+ #### `GET /services/:uuid`
189
+ Get service details by UUID
190
+
191
+ **Response:**
192
+ ```json
193
+ {
194
+ "serviceId": "...",
195
+ "username": "alice",
196
+ "serviceFqn": "com.example.chat@1.0.0",
197
+ "offerId": "...",
198
+ "sdp": "v=0...",
199
+ "isPublic": false,
200
+ "metadata": { ... },
201
+ "createdAt": 1733404800000,
202
+ "expiresAt": 1733405100000
203
+ }
204
+ ```
205
+
206
+ #### `DELETE /services/:serviceId`
207
+ Unpublish a service (requires authentication and ownership)
208
+
209
+ **Headers:**
210
+ - `Authorization: Bearer {peerId}:{secret}`
211
+
212
+ **Request:**
213
+ ```json
214
+ {
215
+ "username": "alice"
120
216
  }
121
217
  ```
122
218
 
123
- **Notes:**
124
- - `hasSecret`: Boolean flag indicating whether a secret is required to answer this offer. The actual secret is never exposed in public endpoints.
125
- - `info`: Optional public metadata field (max 128 characters) visible to all peers.
219
+ ### Service Discovery
126
220
 
127
- #### `GET /peers/:peerId/offers`
128
- View all offers from a specific peer
221
+ #### `POST /index/:username/query`
222
+ Query a service by FQN
129
223
 
130
- ### Authenticated Endpoints
224
+ **Request:**
225
+ ```json
226
+ {
227
+ "serviceFqn": "com.example.chat@1.0.0"
228
+ }
229
+ ```
230
+
231
+ **Response:**
232
+ ```json
233
+ {
234
+ "uuid": "abc123",
235
+ "allowed": true
236
+ }
237
+ ```
131
238
 
132
- All authenticated endpoints require `Authorization: Bearer {peerId}:{secret}` header.
239
+ ### Offer Management (Low-level)
133
240
 
134
241
  #### `POST /offers`
135
- Create one or more offers
242
+ Create one or more offers (requires authentication)
243
+
244
+ **Headers:**
245
+ - `Authorization: Bearer {peerId}:{secret}`
136
246
 
137
247
  **Request:**
138
248
  ```json
@@ -140,19 +250,12 @@ Create one or more offers
140
250
  "offers": [
141
251
  {
142
252
  "sdp": "v=0...",
143
- "topics": ["movie-xyz", "hd-content"],
144
- "ttl": 300000,
145
- "secret": "my-secret-password", // Optional: protect offer (max 128 chars)
146
- "info": "Looking for peers in EU region" // Optional: public info (max 128 chars)
253
+ "ttl": 300000
147
254
  }
148
255
  ]
149
256
  }
150
257
  ```
151
258
 
152
- **Notes:**
153
- - `secret` (optional): Protect the offer with a secret. Answerers must provide the correct secret to connect.
154
- - `info` (optional): Public metadata visible to all peers (max 128 characters). Useful for describing the offer or connection requirements.
155
-
156
259
  #### `GET /offers/mine`
157
260
  List all offers owned by authenticated peer
158
261
 
@@ -168,14 +271,10 @@ Answer an offer (locks it to answerer)
168
271
  **Request:**
169
272
  ```json
170
273
  {
171
- "sdp": "v=0...",
172
- "secret": "my-secret-password" // Required if offer is protected
274
+ "sdp": "v=0..."
173
275
  }
174
276
  ```
175
277
 
176
- **Notes:**
177
- - `secret` (optional): Required if the offer was created with a secret. Must match the offer's secret.
178
-
179
278
  #### `GET /offers/answers`
180
279
  Poll for answers to your offers
181
280
 
@@ -201,13 +300,62 @@ Environment variables:
201
300
  | `PORT` | `3000` | Server port (Node.js/Docker) |
202
301
  | `CORS_ORIGINS` | `*` | Comma-separated allowed origins |
203
302
  | `STORAGE_PATH` | `./rondevu.db` | SQLite database path (use `:memory:` for in-memory) |
204
- | `VERSION` | `0.4.0` | Server version (semver) |
205
- | `AUTH_SECRET` | Random 32-byte hex | Secret key for credential encryption |
303
+ | `VERSION` | `2.0.0` | Server version (semver) |
304
+ | `AUTH_SECRET` | Random 32-byte hex | Secret key for credential encryption (required for production) |
206
305
  | `OFFER_DEFAULT_TTL` | `300000` | Default offer TTL in ms (5 minutes) |
207
306
  | `OFFER_MIN_TTL` | `60000` | Minimum offer TTL in ms (1 minute) |
208
307
  | `OFFER_MAX_TTL` | `3600000` | Maximum offer TTL in ms (1 hour) |
209
308
  | `MAX_OFFERS_PER_REQUEST` | `10` | Maximum offers per create request |
210
- | `MAX_TOPICS_PER_OFFER` | `20` | Maximum topics per offer |
309
+
310
+ ## Database Schema
311
+
312
+ ### usernames
313
+ - `username` (PK): Claimed username
314
+ - `public_key`: Ed25519 public key (base64)
315
+ - `claimed_at`: Claim timestamp
316
+ - `expires_at`: Expiry timestamp (365 days)
317
+ - `last_used`: Last activity timestamp
318
+ - `metadata`: Optional JSON metadata
319
+
320
+ ### services
321
+ - `id` (PK): Service ID (UUID)
322
+ - `username` (FK): Owner username
323
+ - `service_fqn`: Fully qualified name (com.example.chat@1.0.0)
324
+ - `offer_id` (FK): WebRTC offer ID
325
+ - `is_public`: Public/private flag
326
+ - `metadata`: JSON metadata
327
+ - `created_at`, `expires_at`: Timestamps
328
+
329
+ ### service_index (privacy layer)
330
+ - `uuid` (PK): Random UUID for discovery
331
+ - `service_id` (FK): Links to service
332
+ - `username`, `service_fqn`: Denormalized for performance
333
+
334
+ ## Security
335
+
336
+ ### Username Claiming
337
+ - **Algorithm**: Ed25519 signatures
338
+ - **Message Format**: `claim:{username}:{timestamp}`
339
+ - **Replay Protection**: Timestamp must be within 5 minutes
340
+ - **Key Management**: Private keys never leave the client
341
+
342
+ ### Service Publishing
343
+ - **Ownership Verification**: Every publish requires username signature
344
+ - **Message Format**: `publish:{username}:{serviceFqn}:{timestamp}`
345
+ - **Auto-Renewal**: Publishing a service extends username expiry
346
+
347
+ ### Privacy
348
+ - **Private Services**: Only UUID exposed, FQN hidden
349
+ - **Public Services**: FQN and metadata visible
350
+ - **No Enumeration**: Cannot list all services without knowing FQN
351
+
352
+ ## Migration from V1
353
+
354
+ V2 is a **breaking change** that removes topic-based discovery. See [MIGRATION.md](../MIGRATION.md) for detailed migration guide.
355
+
356
+ **Key Changes:**
357
+ - ❌ Removed: Topic-based discovery, bloom filters, public peer listings
358
+ - ✅ Added: Username claiming, service publishing, UUID-based privacy
211
359
 
212
360
  ## License
213
361