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