@xtr-dev/rondevu-server 0.5.11 → 0.5.13
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 +9 -21
- package/dist/index.js +39 -19
- package/dist/index.js.map +2 -2
- package/migrations/0009_public_key_auth.sql +74 -0
- package/migrations/fresh_schema.sql +20 -21
- package/package.json +2 -1
- package/src/config.ts +1 -47
- package/src/crypto.ts +70 -304
- package/src/index.ts +2 -3
- package/src/rpc.ts +95 -271
- package/src/storage/d1.ts +77 -236
- package/src/storage/factory.ts +4 -17
- package/src/storage/memory.ts +49 -152
- package/src/storage/mysql.ts +71 -188
- package/src/storage/postgres.ts +72 -188
- package/src/storage/sqlite.ts +70 -195
- package/src/storage/types.ts +32 -88
- package/src/worker.ts +4 -9
package/src/storage/memory.ts
CHANGED
|
@@ -3,13 +3,9 @@ import {
|
|
|
3
3
|
Offer,
|
|
4
4
|
IceCandidate,
|
|
5
5
|
CreateOfferRequest,
|
|
6
|
-
Credential,
|
|
7
|
-
GenerateCredentialsRequest,
|
|
8
6
|
} from './types.ts';
|
|
9
7
|
import { generateOfferHash } from './hash-id.ts';
|
|
10
8
|
|
|
11
|
-
const YEAR_IN_MS = 365 * 24 * 60 * 60 * 1000;
|
|
12
|
-
|
|
13
9
|
interface RateLimit {
|
|
14
10
|
count: number;
|
|
15
11
|
resetTime: number;
|
|
@@ -25,26 +21,21 @@ interface NonceEntry {
|
|
|
25
21
|
* Best for development, testing, or ephemeral deployments
|
|
26
22
|
*/
|
|
27
23
|
export class MemoryStorage implements Storage {
|
|
28
|
-
private masterEncryptionKey: string;
|
|
29
|
-
|
|
30
24
|
// Primary storage
|
|
31
|
-
private credentials = new Map<string, Credential>();
|
|
32
25
|
private offers = new Map<string, Offer>();
|
|
33
26
|
private iceCandidates = new Map<string, IceCandidate[]>(); // offerId → candidates
|
|
34
27
|
private rateLimits = new Map<string, RateLimit>();
|
|
35
28
|
private nonces = new Map<string, NonceEntry>();
|
|
36
29
|
|
|
37
30
|
// Secondary indexes for efficient lookups
|
|
38
|
-
private
|
|
31
|
+
private offersByPublicKey = new Map<string, Set<string>>(); // publicKey → offer IDs
|
|
39
32
|
private offersByTag = new Map<string, Set<string>>(); // tag → offer IDs
|
|
40
|
-
private offersByAnswerer = new Map<string, Set<string>>(); // answerer
|
|
33
|
+
private offersByAnswerer = new Map<string, Set<string>>(); // answerer publicKey → offer IDs
|
|
41
34
|
|
|
42
35
|
// Auto-increment counter for ICE candidates
|
|
43
36
|
private iceCandidateIdCounter = 0;
|
|
44
37
|
|
|
45
|
-
constructor(
|
|
46
|
-
this.masterEncryptionKey = masterEncryptionKey;
|
|
47
|
-
}
|
|
38
|
+
constructor() {}
|
|
48
39
|
|
|
49
40
|
// ===== Offer Management =====
|
|
50
41
|
|
|
@@ -57,7 +48,7 @@ export class MemoryStorage implements Storage {
|
|
|
57
48
|
|
|
58
49
|
const offer: Offer = {
|
|
59
50
|
id,
|
|
60
|
-
|
|
51
|
+
publicKey: request.publicKey,
|
|
61
52
|
tags: request.tags,
|
|
62
53
|
sdp: request.sdp,
|
|
63
54
|
createdAt: now,
|
|
@@ -68,11 +59,11 @@ export class MemoryStorage implements Storage {
|
|
|
68
59
|
// Store offer
|
|
69
60
|
this.offers.set(id, offer);
|
|
70
61
|
|
|
71
|
-
// Update
|
|
72
|
-
if (!this.
|
|
73
|
-
this.
|
|
62
|
+
// Update publicKey index
|
|
63
|
+
if (!this.offersByPublicKey.has(request.publicKey)) {
|
|
64
|
+
this.offersByPublicKey.set(request.publicKey, new Set());
|
|
74
65
|
}
|
|
75
|
-
this.
|
|
66
|
+
this.offersByPublicKey.get(request.publicKey)!.add(id);
|
|
76
67
|
|
|
77
68
|
// Update tag indexes
|
|
78
69
|
for (const tag of request.tags) {
|
|
@@ -88,9 +79,9 @@ export class MemoryStorage implements Storage {
|
|
|
88
79
|
return created;
|
|
89
80
|
}
|
|
90
81
|
|
|
91
|
-
async
|
|
82
|
+
async getOffersByPublicKey(publicKey: string): Promise<Offer[]> {
|
|
92
83
|
const now = Date.now();
|
|
93
|
-
const offerIds = this.
|
|
84
|
+
const offerIds = this.offersByPublicKey.get(publicKey);
|
|
94
85
|
if (!offerIds) return [];
|
|
95
86
|
|
|
96
87
|
const offers: Offer[] = [];
|
|
@@ -112,9 +103,9 @@ export class MemoryStorage implements Storage {
|
|
|
112
103
|
return offer;
|
|
113
104
|
}
|
|
114
105
|
|
|
115
|
-
async deleteOffer(offerId: string,
|
|
106
|
+
async deleteOffer(offerId: string, ownerPublicKey: string): Promise<boolean> {
|
|
116
107
|
const offer = this.offers.get(offerId);
|
|
117
|
-
if (!offer || offer.
|
|
108
|
+
if (!offer || offer.publicKey !== ownerPublicKey) {
|
|
118
109
|
return false;
|
|
119
110
|
}
|
|
120
111
|
|
|
@@ -142,8 +133,9 @@ export class MemoryStorage implements Storage {
|
|
|
142
133
|
|
|
143
134
|
async answerOffer(
|
|
144
135
|
offerId: string,
|
|
145
|
-
|
|
146
|
-
answerSdp: string
|
|
136
|
+
answererPublicKey: string,
|
|
137
|
+
answerSdp: string,
|
|
138
|
+
matchedTags?: string[]
|
|
147
139
|
): Promise<{ success: boolean; error?: string }> {
|
|
148
140
|
const offer = await this.getOfferById(offerId);
|
|
149
141
|
|
|
@@ -151,34 +143,35 @@ export class MemoryStorage implements Storage {
|
|
|
151
143
|
return { success: false, error: 'Offer not found or expired' };
|
|
152
144
|
}
|
|
153
145
|
|
|
154
|
-
if (offer.
|
|
146
|
+
if (offer.answererPublicKey) {
|
|
155
147
|
return { success: false, error: 'Offer already answered' };
|
|
156
148
|
}
|
|
157
149
|
|
|
158
150
|
// Update offer with answer
|
|
159
151
|
const now = Date.now();
|
|
160
|
-
offer.
|
|
152
|
+
offer.answererPublicKey = answererPublicKey;
|
|
161
153
|
offer.answerSdp = answerSdp;
|
|
162
154
|
offer.answeredAt = now;
|
|
155
|
+
offer.matchedTags = matchedTags;
|
|
163
156
|
|
|
164
157
|
// Update answerer index
|
|
165
|
-
if (!this.offersByAnswerer.has(
|
|
166
|
-
this.offersByAnswerer.set(
|
|
158
|
+
if (!this.offersByAnswerer.has(answererPublicKey)) {
|
|
159
|
+
this.offersByAnswerer.set(answererPublicKey, new Set());
|
|
167
160
|
}
|
|
168
|
-
this.offersByAnswerer.get(
|
|
161
|
+
this.offersByAnswerer.get(answererPublicKey)!.add(offerId);
|
|
169
162
|
|
|
170
163
|
return { success: true };
|
|
171
164
|
}
|
|
172
165
|
|
|
173
|
-
async getAnsweredOffers(
|
|
166
|
+
async getAnsweredOffers(offererPublicKey: string): Promise<Offer[]> {
|
|
174
167
|
const now = Date.now();
|
|
175
|
-
const offerIds = this.
|
|
168
|
+
const offerIds = this.offersByPublicKey.get(offererPublicKey);
|
|
176
169
|
if (!offerIds) return [];
|
|
177
170
|
|
|
178
171
|
const offers: Offer[] = [];
|
|
179
172
|
for (const id of offerIds) {
|
|
180
173
|
const offer = this.offers.get(id);
|
|
181
|
-
if (offer && offer.
|
|
174
|
+
if (offer && offer.answererPublicKey && offer.expiresAt > now) {
|
|
182
175
|
offers.push(offer);
|
|
183
176
|
}
|
|
184
177
|
}
|
|
@@ -186,9 +179,9 @@ export class MemoryStorage implements Storage {
|
|
|
186
179
|
return offers.sort((a, b) => (b.answeredAt || 0) - (a.answeredAt || 0));
|
|
187
180
|
}
|
|
188
181
|
|
|
189
|
-
async getOffersAnsweredBy(
|
|
182
|
+
async getOffersAnsweredBy(answererPublicKey: string): Promise<Offer[]> {
|
|
190
183
|
const now = Date.now();
|
|
191
|
-
const offerIds = this.offersByAnswerer.get(
|
|
184
|
+
const offerIds = this.offersByAnswerer.get(answererPublicKey);
|
|
192
185
|
if (!offerIds) return [];
|
|
193
186
|
|
|
194
187
|
const offers: Offer[] = [];
|
|
@@ -206,7 +199,7 @@ export class MemoryStorage implements Storage {
|
|
|
206
199
|
|
|
207
200
|
async discoverOffers(
|
|
208
201
|
tags: string[],
|
|
209
|
-
|
|
202
|
+
excludePublicKey: string | null,
|
|
210
203
|
limit: number,
|
|
211
204
|
offset: number
|
|
212
205
|
): Promise<Offer[]> {
|
|
@@ -232,8 +225,8 @@ export class MemoryStorage implements Storage {
|
|
|
232
225
|
if (
|
|
233
226
|
offer &&
|
|
234
227
|
offer.expiresAt > now &&
|
|
235
|
-
!offer.
|
|
236
|
-
(!
|
|
228
|
+
!offer.answererPublicKey &&
|
|
229
|
+
(!excludePublicKey || offer.publicKey !== excludePublicKey)
|
|
237
230
|
) {
|
|
238
231
|
offers.push(offer);
|
|
239
232
|
}
|
|
@@ -246,7 +239,7 @@ export class MemoryStorage implements Storage {
|
|
|
246
239
|
|
|
247
240
|
async getRandomOffer(
|
|
248
241
|
tags: string[],
|
|
249
|
-
|
|
242
|
+
excludePublicKey: string | null
|
|
250
243
|
): Promise<Offer | null> {
|
|
251
244
|
if (tags.length === 0) return null;
|
|
252
245
|
|
|
@@ -270,8 +263,8 @@ export class MemoryStorage implements Storage {
|
|
|
270
263
|
if (
|
|
271
264
|
offer &&
|
|
272
265
|
offer.expiresAt > now &&
|
|
273
|
-
!offer.
|
|
274
|
-
(!
|
|
266
|
+
!offer.answererPublicKey &&
|
|
267
|
+
(!excludePublicKey || offer.publicKey !== excludePublicKey)
|
|
275
268
|
) {
|
|
276
269
|
matchingOffers.push(offer);
|
|
277
270
|
}
|
|
@@ -288,7 +281,7 @@ export class MemoryStorage implements Storage {
|
|
|
288
281
|
|
|
289
282
|
async addIceCandidates(
|
|
290
283
|
offerId: string,
|
|
291
|
-
|
|
284
|
+
publicKey: string,
|
|
292
285
|
role: 'offerer' | 'answerer',
|
|
293
286
|
candidates: any[]
|
|
294
287
|
): Promise<number> {
|
|
@@ -304,7 +297,7 @@ export class MemoryStorage implements Storage {
|
|
|
304
297
|
const candidate: IceCandidate = {
|
|
305
298
|
id: ++this.iceCandidateIdCounter,
|
|
306
299
|
offerId,
|
|
307
|
-
|
|
300
|
+
publicKey,
|
|
308
301
|
role,
|
|
309
302
|
candidate: candidates[i],
|
|
310
303
|
createdAt: baseTimestamp + i,
|
|
@@ -329,7 +322,7 @@ export class MemoryStorage implements Storage {
|
|
|
329
322
|
|
|
330
323
|
async getIceCandidatesForMultipleOffers(
|
|
331
324
|
offerIds: string[],
|
|
332
|
-
|
|
325
|
+
publicKey: string,
|
|
333
326
|
since?: number
|
|
334
327
|
): Promise<Map<string, IceCandidate[]>> {
|
|
335
328
|
const result = new Map<string, IceCandidate[]>();
|
|
@@ -347,8 +340,8 @@ export class MemoryStorage implements Storage {
|
|
|
347
340
|
|
|
348
341
|
// Determine which role's candidates to return
|
|
349
342
|
// If user is offerer, return answerer candidates and vice versa
|
|
350
|
-
const isOfferer = offer.
|
|
351
|
-
const isAnswerer = offer.
|
|
343
|
+
const isOfferer = offer.publicKey === publicKey;
|
|
344
|
+
const isAnswerer = offer.answererPublicKey === publicKey;
|
|
352
345
|
|
|
353
346
|
if (!isOfferer && !isAnswerer) continue;
|
|
354
347
|
|
|
@@ -366,97 +359,6 @@ export class MemoryStorage implements Storage {
|
|
|
366
359
|
return result;
|
|
367
360
|
}
|
|
368
361
|
|
|
369
|
-
// ===== Credential Management =====
|
|
370
|
-
|
|
371
|
-
async generateCredentials(request: GenerateCredentialsRequest): Promise<Credential> {
|
|
372
|
-
const now = Date.now();
|
|
373
|
-
const expiresAt = request.expiresAt || (now + YEAR_IN_MS);
|
|
374
|
-
|
|
375
|
-
const { generateCredentialName, generateSecret, encryptSecret } = await import('../crypto.ts');
|
|
376
|
-
|
|
377
|
-
let name: string;
|
|
378
|
-
|
|
379
|
-
if (request.name) {
|
|
380
|
-
if (this.credentials.has(request.name)) {
|
|
381
|
-
throw new Error('Username already taken');
|
|
382
|
-
}
|
|
383
|
-
name = request.name;
|
|
384
|
-
} else {
|
|
385
|
-
let attempts = 0;
|
|
386
|
-
const maxAttempts = 100;
|
|
387
|
-
|
|
388
|
-
while (attempts < maxAttempts) {
|
|
389
|
-
name = generateCredentialName();
|
|
390
|
-
if (!this.credentials.has(name)) break;
|
|
391
|
-
attempts++;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
if (attempts >= maxAttempts) {
|
|
395
|
-
throw new Error(`Failed to generate unique credential name after ${maxAttempts} attempts`);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const secret = generateSecret();
|
|
400
|
-
|
|
401
|
-
// Encrypt secret before storing
|
|
402
|
-
const encryptedSecret = await encryptSecret(secret, this.masterEncryptionKey);
|
|
403
|
-
|
|
404
|
-
const credential: Credential = {
|
|
405
|
-
name: name!,
|
|
406
|
-
secret: encryptedSecret,
|
|
407
|
-
createdAt: now,
|
|
408
|
-
expiresAt,
|
|
409
|
-
lastUsed: now,
|
|
410
|
-
};
|
|
411
|
-
|
|
412
|
-
this.credentials.set(name!, credential);
|
|
413
|
-
|
|
414
|
-
// Return plaintext secret to user
|
|
415
|
-
return {
|
|
416
|
-
...credential,
|
|
417
|
-
secret, // Return plaintext, not encrypted
|
|
418
|
-
};
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
async getCredential(name: string): Promise<Credential | null> {
|
|
422
|
-
const credential = this.credentials.get(name);
|
|
423
|
-
if (!credential || credential.expiresAt <= Date.now()) {
|
|
424
|
-
return null;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
try {
|
|
428
|
-
const { decryptSecret } = await import('../crypto.ts');
|
|
429
|
-
const decryptedSecret = await decryptSecret(credential.secret, this.masterEncryptionKey);
|
|
430
|
-
|
|
431
|
-
return {
|
|
432
|
-
...credential,
|
|
433
|
-
secret: decryptedSecret,
|
|
434
|
-
};
|
|
435
|
-
} catch (error) {
|
|
436
|
-
console.error(`Failed to decrypt secret for credential '${name}':`, error);
|
|
437
|
-
return null;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
async updateCredentialUsage(name: string, lastUsed: number, expiresAt: number): Promise<void> {
|
|
442
|
-
const credential = this.credentials.get(name);
|
|
443
|
-
if (credential) {
|
|
444
|
-
credential.lastUsed = lastUsed;
|
|
445
|
-
credential.expiresAt = expiresAt;
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
async deleteExpiredCredentials(now: number): Promise<number> {
|
|
450
|
-
let count = 0;
|
|
451
|
-
for (const [name, credential] of this.credentials) {
|
|
452
|
-
if (credential.expiresAt < now) {
|
|
453
|
-
this.credentials.delete(name);
|
|
454
|
-
count++;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
return count;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
362
|
// ===== Rate Limiting =====
|
|
461
363
|
|
|
462
364
|
async checkRateLimit(identifier: string, limit: number, windowMs: number): Promise<boolean> {
|
|
@@ -512,12 +414,11 @@ export class MemoryStorage implements Storage {
|
|
|
512
414
|
|
|
513
415
|
async close(): Promise<void> {
|
|
514
416
|
// Clear all data
|
|
515
|
-
this.credentials.clear();
|
|
516
417
|
this.offers.clear();
|
|
517
418
|
this.iceCandidates.clear();
|
|
518
419
|
this.rateLimits.clear();
|
|
519
420
|
this.nonces.clear();
|
|
520
|
-
this.
|
|
421
|
+
this.offersByPublicKey.clear();
|
|
521
422
|
this.offersByTag.clear();
|
|
522
423
|
this.offersByAnswerer.clear();
|
|
523
424
|
}
|
|
@@ -528,15 +429,11 @@ export class MemoryStorage implements Storage {
|
|
|
528
429
|
return this.offers.size;
|
|
529
430
|
}
|
|
530
431
|
|
|
531
|
-
async
|
|
532
|
-
const offerIds = this.
|
|
432
|
+
async getOfferCountByPublicKey(publicKey: string): Promise<number> {
|
|
433
|
+
const offerIds = this.offersByPublicKey.get(publicKey);
|
|
533
434
|
return offerIds ? offerIds.size : 0;
|
|
534
435
|
}
|
|
535
436
|
|
|
536
|
-
async getCredentialCount(): Promise<number> {
|
|
537
|
-
return this.credentials.size;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
437
|
async getIceCandidateCount(offerId: string): Promise<number> {
|
|
541
438
|
const candidates = this.iceCandidates.get(offerId);
|
|
542
439
|
return candidates ? candidates.length : 0;
|
|
@@ -545,12 +442,12 @@ export class MemoryStorage implements Storage {
|
|
|
545
442
|
// ===== Helper Methods =====
|
|
546
443
|
|
|
547
444
|
private removeOfferFromIndexes(offer: Offer): void {
|
|
548
|
-
// Remove from
|
|
549
|
-
const
|
|
550
|
-
if (
|
|
551
|
-
|
|
552
|
-
if (
|
|
553
|
-
this.
|
|
445
|
+
// Remove from publicKey index
|
|
446
|
+
const publicKeyOffers = this.offersByPublicKey.get(offer.publicKey);
|
|
447
|
+
if (publicKeyOffers) {
|
|
448
|
+
publicKeyOffers.delete(offer.id);
|
|
449
|
+
if (publicKeyOffers.size === 0) {
|
|
450
|
+
this.offersByPublicKey.delete(offer.publicKey);
|
|
554
451
|
}
|
|
555
452
|
}
|
|
556
453
|
|
|
@@ -566,12 +463,12 @@ export class MemoryStorage implements Storage {
|
|
|
566
463
|
}
|
|
567
464
|
|
|
568
465
|
// Remove from answerer index
|
|
569
|
-
if (offer.
|
|
570
|
-
const answererOffers = this.offersByAnswerer.get(offer.
|
|
466
|
+
if (offer.answererPublicKey) {
|
|
467
|
+
const answererOffers = this.offersByAnswerer.get(offer.answererPublicKey);
|
|
571
468
|
if (answererOffers) {
|
|
572
469
|
answererOffers.delete(offer.id);
|
|
573
470
|
if (answererOffers.size === 0) {
|
|
574
|
-
this.offersByAnswerer.delete(offer.
|
|
471
|
+
this.offersByAnswerer.delete(offer.answererPublicKey);
|
|
575
472
|
}
|
|
576
473
|
}
|
|
577
474
|
}
|