@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/types.ts
CHANGED
|
@@ -3,15 +3,16 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export interface Offer {
|
|
5
5
|
id: string;
|
|
6
|
-
|
|
6
|
+
publicKey: string; // Owner's Ed25519 public key
|
|
7
7
|
tags: string[]; // Tags for discovery (match ANY)
|
|
8
8
|
sdp: string;
|
|
9
9
|
createdAt: number;
|
|
10
10
|
expiresAt: number;
|
|
11
11
|
lastSeen: number;
|
|
12
|
-
|
|
12
|
+
answererPublicKey?: string;
|
|
13
13
|
answerSdp?: string;
|
|
14
14
|
answeredAt?: number;
|
|
15
|
+
matchedTags?: string[]; // Tags the answerer searched for to find this offer
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
/**
|
|
@@ -21,7 +22,7 @@ export interface Offer {
|
|
|
21
22
|
export interface IceCandidate {
|
|
22
23
|
id: number;
|
|
23
24
|
offerId: string;
|
|
24
|
-
|
|
25
|
+
publicKey: string; // Sender's Ed25519 public key
|
|
25
26
|
role: 'offerer' | 'answerer';
|
|
26
27
|
candidate: any; // Full candidate object as JSON - don't enforce structure
|
|
27
28
|
createdAt: number;
|
|
@@ -32,32 +33,12 @@ export interface IceCandidate {
|
|
|
32
33
|
*/
|
|
33
34
|
export interface CreateOfferRequest {
|
|
34
35
|
id?: string;
|
|
35
|
-
|
|
36
|
+
publicKey: string; // Owner's Ed25519 public key
|
|
36
37
|
tags: string[]; // Tags for discovery
|
|
37
38
|
sdp: string;
|
|
38
39
|
expiresAt: number;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
/**
|
|
42
|
-
* Represents a credential (random name + secret pair)
|
|
43
|
-
* Replaces the old username/publicKey system for simpler authentication
|
|
44
|
-
*/
|
|
45
|
-
export interface Credential {
|
|
46
|
-
name: string; // Random name (e.g., "brave-tiger-7a3f")
|
|
47
|
-
secret: string; // Random secret (API key style)
|
|
48
|
-
createdAt: number;
|
|
49
|
-
expiresAt: number; // 365 days from creation/last use
|
|
50
|
-
lastUsed: number;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Request to generate new credentials
|
|
55
|
-
*/
|
|
56
|
-
export interface GenerateCredentialsRequest {
|
|
57
|
-
name?: string; // Optional: claim specific username (must be unique, 4-32 chars)
|
|
58
|
-
expiresAt?: number; // Optional: override default expiry
|
|
59
|
-
}
|
|
60
|
-
|
|
61
42
|
/**
|
|
62
43
|
* Storage interface for rondevu signaling system
|
|
63
44
|
* Implementations can use different backends (SQLite, D1, etc.)
|
|
@@ -85,11 +66,11 @@ export interface Storage {
|
|
|
85
66
|
createOffers(offers: CreateOfferRequest[]): Promise<Offer[]>;
|
|
86
67
|
|
|
87
68
|
/**
|
|
88
|
-
* Retrieves all offers from a specific
|
|
89
|
-
* @param
|
|
90
|
-
* @returns Array of offers from the
|
|
69
|
+
* Retrieves all offers from a specific owner
|
|
70
|
+
* @param publicKey Owner's Ed25519 public key
|
|
71
|
+
* @returns Array of offers from the owner
|
|
91
72
|
*/
|
|
92
|
-
|
|
73
|
+
getOffersByPublicKey(publicKey: string): Promise<Offer[]>;
|
|
93
74
|
|
|
94
75
|
/**
|
|
95
76
|
* Retrieves a specific offer by ID
|
|
@@ -101,10 +82,10 @@ export interface Storage {
|
|
|
101
82
|
/**
|
|
102
83
|
* Deletes an offer (with ownership verification)
|
|
103
84
|
* @param offerId Offer identifier
|
|
104
|
-
* @param
|
|
85
|
+
* @param ownerPublicKey Public key of the owner (for verification)
|
|
105
86
|
* @returns true if deleted, false if not found or not owned
|
|
106
87
|
*/
|
|
107
|
-
deleteOffer(offerId: string,
|
|
88
|
+
deleteOffer(offerId: string, ownerPublicKey: string): Promise<boolean>;
|
|
108
89
|
|
|
109
90
|
/**
|
|
110
91
|
* Deletes all expired offers
|
|
@@ -116,43 +97,44 @@ export interface Storage {
|
|
|
116
97
|
/**
|
|
117
98
|
* Answers an offer (locks it to the answerer)
|
|
118
99
|
* @param offerId Offer identifier
|
|
119
|
-
* @param
|
|
100
|
+
* @param answererPublicKey Answerer's public key
|
|
120
101
|
* @param answerSdp WebRTC answer SDP
|
|
102
|
+
* @param matchedTags Optional tags the answerer searched for to find this offer
|
|
121
103
|
* @returns Success status and optional error message
|
|
122
104
|
*/
|
|
123
|
-
answerOffer(offerId: string,
|
|
105
|
+
answerOffer(offerId: string, answererPublicKey: string, answerSdp: string, matchedTags?: string[]): Promise<{
|
|
124
106
|
success: boolean;
|
|
125
107
|
error?: string;
|
|
126
108
|
}>;
|
|
127
109
|
|
|
128
110
|
/**
|
|
129
111
|
* Retrieves all answered offers for a specific offerer
|
|
130
|
-
* @param
|
|
112
|
+
* @param offererPublicKey Offerer's public key
|
|
131
113
|
* @returns Array of answered offers
|
|
132
114
|
*/
|
|
133
|
-
getAnsweredOffers(
|
|
115
|
+
getAnsweredOffers(offererPublicKey: string): Promise<Offer[]>;
|
|
134
116
|
|
|
135
117
|
/**
|
|
136
118
|
* Retrieves all offers answered by a specific user (where they are the answerer)
|
|
137
|
-
* @param
|
|
119
|
+
* @param answererPublicKey Answerer's public key
|
|
138
120
|
* @returns Array of offers the user has answered
|
|
139
121
|
*/
|
|
140
|
-
getOffersAnsweredBy(
|
|
122
|
+
getOffersAnsweredBy(answererPublicKey: string): Promise<Offer[]>;
|
|
141
123
|
|
|
142
124
|
// ===== Discovery =====
|
|
143
125
|
|
|
144
126
|
/**
|
|
145
127
|
* Discovers offers by tags with pagination
|
|
146
|
-
* Returns available offers (where
|
|
128
|
+
* Returns available offers (where answerer_public_key IS NULL) matching ANY of the provided tags
|
|
147
129
|
* @param tags Array of tags to match (OR logic)
|
|
148
|
-
* @param
|
|
130
|
+
* @param excludePublicKey Optional public key to exclude from results (self-exclusion)
|
|
149
131
|
* @param limit Maximum number of offers to return
|
|
150
132
|
* @param offset Number of offers to skip
|
|
151
133
|
* @returns Array of available offers matching tags
|
|
152
134
|
*/
|
|
153
135
|
discoverOffers(
|
|
154
136
|
tags: string[],
|
|
155
|
-
|
|
137
|
+
excludePublicKey: string | null,
|
|
156
138
|
limit: number,
|
|
157
139
|
offset: number
|
|
158
140
|
): Promise<Offer[]>;
|
|
@@ -160,12 +142,12 @@ export interface Storage {
|
|
|
160
142
|
/**
|
|
161
143
|
* Gets a random available offer matching any of the provided tags
|
|
162
144
|
* @param tags Array of tags to match (OR logic)
|
|
163
|
-
* @param
|
|
145
|
+
* @param excludePublicKey Optional public key to exclude (self-exclusion)
|
|
164
146
|
* @returns Random available offer, or null if none found
|
|
165
147
|
*/
|
|
166
148
|
getRandomOffer(
|
|
167
149
|
tags: string[],
|
|
168
|
-
|
|
150
|
+
excludePublicKey: string | null
|
|
169
151
|
): Promise<Offer | null>;
|
|
170
152
|
|
|
171
153
|
// ===== ICE Candidate Management =====
|
|
@@ -173,14 +155,14 @@ export interface Storage {
|
|
|
173
155
|
/**
|
|
174
156
|
* Adds ICE candidates for an offer
|
|
175
157
|
* @param offerId Offer identifier
|
|
176
|
-
* @param
|
|
158
|
+
* @param publicKey Public key posting the candidates
|
|
177
159
|
* @param role Role of the user (offerer or answerer)
|
|
178
160
|
* @param candidates Array of candidate objects (stored as plain JSON)
|
|
179
161
|
* @returns Number of candidates added
|
|
180
162
|
*/
|
|
181
163
|
addIceCandidates(
|
|
182
164
|
offerId: string,
|
|
183
|
-
|
|
165
|
+
publicKey: string,
|
|
184
166
|
role: 'offerer' | 'answerer',
|
|
185
167
|
candidates: any[]
|
|
186
168
|
): Promise<number>;
|
|
@@ -201,48 +183,16 @@ export interface Storage {
|
|
|
201
183
|
/**
|
|
202
184
|
* Retrieves ICE candidates for multiple offers (batch operation)
|
|
203
185
|
* @param offerIds Array of offer identifiers
|
|
204
|
-
* @param
|
|
186
|
+
* @param publicKey Public key requesting the candidates
|
|
205
187
|
* @param since Optional timestamp - only return candidates after this time
|
|
206
188
|
* @returns Map of offer ID to ICE candidates
|
|
207
189
|
*/
|
|
208
190
|
getIceCandidatesForMultipleOffers(
|
|
209
191
|
offerIds: string[],
|
|
210
|
-
|
|
192
|
+
publicKey: string,
|
|
211
193
|
since?: number
|
|
212
194
|
): Promise<Map<string, IceCandidate[]>>;
|
|
213
195
|
|
|
214
|
-
// ===== Credential Management =====
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Generates a new credential (random name + secret)
|
|
218
|
-
* @param request Credential generation request
|
|
219
|
-
* @returns Created credential record
|
|
220
|
-
*/
|
|
221
|
-
generateCredentials(request: GenerateCredentialsRequest): Promise<Credential>;
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Gets a credential by name
|
|
225
|
-
* @param name Credential name
|
|
226
|
-
* @returns Credential record if found, null otherwise
|
|
227
|
-
*/
|
|
228
|
-
getCredential(name: string): Promise<Credential | null>;
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Updates credential usage timestamp and expiry
|
|
232
|
-
* Called after successful signature verification
|
|
233
|
-
* @param name Credential name
|
|
234
|
-
* @param lastUsed Last used timestamp
|
|
235
|
-
* @param expiresAt New expiry timestamp
|
|
236
|
-
*/
|
|
237
|
-
updateCredentialUsage(name: string, lastUsed: number, expiresAt: number): Promise<void>;
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Deletes all expired credentials
|
|
241
|
-
* @param now Current timestamp
|
|
242
|
-
* @returns Number of credentials deleted
|
|
243
|
-
*/
|
|
244
|
-
deleteExpiredCredentials(now: number): Promise<number>;
|
|
245
|
-
|
|
246
196
|
// ===== Rate Limiting =====
|
|
247
197
|
|
|
248
198
|
/**
|
|
@@ -265,7 +215,7 @@ export interface Storage {
|
|
|
265
215
|
|
|
266
216
|
/**
|
|
267
217
|
* Check if nonce has been used and mark it as used (atomic operation)
|
|
268
|
-
* @param nonceKey Unique nonce identifier (format: "nonce:{
|
|
218
|
+
* @param nonceKey Unique nonce identifier (format: "nonce:{publicKey}:{nonce}")
|
|
269
219
|
* @param expiresAt Timestamp when nonce expires (should be timestamp + timestampMaxAge)
|
|
270
220
|
* @returns true if nonce is new (allowed), false if already used (replay attack)
|
|
271
221
|
*/
|
|
@@ -292,17 +242,11 @@ export interface Storage {
|
|
|
292
242
|
getOfferCount(): Promise<number>;
|
|
293
243
|
|
|
294
244
|
/**
|
|
295
|
-
* Gets number of offers for a specific
|
|
296
|
-
* @param
|
|
297
|
-
* @returns Offer count for
|
|
298
|
-
*/
|
|
299
|
-
getOfferCountByUsername(username: string): Promise<number>;
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* Gets total number of credentials in storage
|
|
303
|
-
* @returns Total credential count
|
|
245
|
+
* Gets number of offers for a specific owner
|
|
246
|
+
* @param publicKey Owner's public key
|
|
247
|
+
* @returns Offer count for owner
|
|
304
248
|
*/
|
|
305
|
-
|
|
249
|
+
getOfferCountByPublicKey(publicKey: string): Promise<number>;
|
|
306
250
|
|
|
307
251
|
/**
|
|
308
252
|
* Gets number of ICE candidates for a specific offer
|
package/src/worker.ts
CHANGED
|
@@ -7,7 +7,6 @@ import { buildWorkerConfig, runCleanup } from './config.ts';
|
|
|
7
7
|
*/
|
|
8
8
|
export interface Env {
|
|
9
9
|
DB: D1Database;
|
|
10
|
-
MASTER_ENCRYPTION_KEY: string;
|
|
11
10
|
OFFER_DEFAULT_TTL?: string;
|
|
12
11
|
OFFER_MAX_TTL?: string;
|
|
13
12
|
OFFER_MIN_TTL?: string;
|
|
@@ -19,11 +18,7 @@ export interface Env {
|
|
|
19
18
|
|
|
20
19
|
export default {
|
|
21
20
|
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
|
22
|
-
|
|
23
|
-
return new Response('MASTER_ENCRYPTION_KEY must be 64-char hex string', { status: 500 });
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const storage = new D1Storage(env.DB, env.MASTER_ENCRYPTION_KEY);
|
|
21
|
+
const storage = new D1Storage(env.DB);
|
|
27
22
|
const config = buildWorkerConfig(env);
|
|
28
23
|
const app = createApp(storage, config);
|
|
29
24
|
|
|
@@ -31,14 +26,14 @@ export default {
|
|
|
31
26
|
},
|
|
32
27
|
|
|
33
28
|
async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext): Promise<void> {
|
|
34
|
-
const storage = new D1Storage(env.DB
|
|
29
|
+
const storage = new D1Storage(env.DB);
|
|
35
30
|
const now = Date.now();
|
|
36
31
|
|
|
37
32
|
try {
|
|
38
33
|
const result = await runCleanup(storage, now);
|
|
39
|
-
const total = result.offers + result.
|
|
34
|
+
const total = result.offers + result.rateLimits + result.nonces;
|
|
40
35
|
if (total > 0) {
|
|
41
|
-
console.log(`Cleanup: ${result.offers} offers, ${result.
|
|
36
|
+
console.log(`Cleanup: ${result.offers} offers, ${result.rateLimits} rate limits, ${result.nonces} nonces`);
|
|
42
37
|
}
|
|
43
38
|
} catch (error) {
|
|
44
39
|
console.error('Cleanup error:', error);
|