@enbox/dwn-server 0.1.1 → 0.1.3
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/dist/esm/src/admin/admin-api.js +2 -2
- package/dist/esm/src/admin/admin-api.js.map +1 -1
- package/dist/esm/src/admin/admin-auth.js +1 -1
- package/dist/esm/src/admin/admin-auth.js.map +1 -1
- package/dist/esm/src/admin/admin-passkey-store.js.map +1 -1
- package/dist/esm/src/admin/admin-session.js.map +1 -1
- package/dist/esm/src/admin/admin-store.d.ts +2 -2
- package/dist/esm/src/admin/admin-store.d.ts.map +1 -1
- package/dist/esm/src/admin/admin-store.js +1 -1
- package/dist/esm/src/admin/admin-store.js.map +1 -1
- package/dist/esm/src/admin/audit-log.js.map +1 -1
- package/dist/esm/src/admin/webhook-manager.js.map +1 -1
- package/dist/esm/src/connect/connect-server.d.ts +1 -1
- package/dist/esm/src/connect/connect-server.d.ts.map +1 -1
- package/dist/esm/src/connect/connect-server.js.map +1 -1
- package/dist/esm/src/connect/sql-ttl-cache.d.ts +1 -1
- package/dist/esm/src/connect/sql-ttl-cache.d.ts.map +1 -1
- package/dist/esm/src/connect/sql-ttl-cache.js.map +1 -1
- package/dist/esm/src/connection/connection-manager.d.ts +9 -9
- package/dist/esm/src/connection/connection-manager.d.ts.map +1 -1
- package/dist/esm/src/connection/connection-manager.js.map +1 -1
- package/dist/esm/src/connection/socket-connection.d.ts +13 -13
- package/dist/esm/src/connection/socket-connection.d.ts.map +1 -1
- package/dist/esm/src/connection/socket-connection.js.map +1 -1
- package/dist/esm/src/delivery-service.d.ts.map +1 -1
- package/dist/esm/src/delivery-service.js +15 -7
- package/dist/esm/src/delivery-service.js.map +1 -1
- package/dist/esm/src/dwn-server.js.map +1 -1
- package/dist/esm/src/http-api.d.ts.map +1 -1
- package/dist/esm/src/http-api.js +35 -21
- package/dist/esm/src/http-api.js.map +1 -1
- package/dist/esm/src/plugins/event-log-nats.js +16 -16
- package/dist/esm/src/plugins/event-log-nats.js.map +1 -1
- package/dist/esm/src/rate-limiter.js.map +1 -1
- package/dist/esm/src/registration/jwt-provider-auth-plugin.js.map +1 -1
- package/dist/esm/src/registration/open-auth-handler.js.map +1 -1
- package/dist/esm/src/registration/proof-of-work-manager.d.ts +5 -5
- package/dist/esm/src/registration/proof-of-work-manager.d.ts.map +1 -1
- package/dist/esm/src/registration/proof-of-work-manager.js +12 -10
- package/dist/esm/src/registration/proof-of-work-manager.js.map +1 -1
- package/dist/esm/src/registration/registration-manager.js +6 -6
- package/dist/esm/src/registration/registration-manager.js.map +1 -1
- package/dist/esm/src/registration/registration-store.d.ts +1 -1
- package/dist/esm/src/registration/registration-store.d.ts.map +1 -1
- package/dist/esm/src/registration/registration-store.js.map +1 -1
- package/dist/esm/src/server-migration-runner.js.map +1 -1
- package/dist/esm/src/ws-api.js.map +1 -1
- package/package.json +5 -5
- package/src/admin/admin-api.ts +5 -5
- package/src/admin/admin-auth.ts +1 -1
- package/src/admin/admin-passkey-store.ts +1 -1
- package/src/admin/admin-session.ts +2 -2
- package/src/admin/admin-store.ts +3 -3
- package/src/admin/audit-log.ts +1 -1
- package/src/admin/webhook-manager.ts +1 -1
- package/src/connect/connect-server.ts +1 -1
- package/src/connect/sql-ttl-cache.ts +1 -1
- package/src/connection/connection-manager.ts +9 -9
- package/src/connection/socket-connection.ts +13 -13
- package/src/delivery-service.ts +16 -7
- package/src/dwn-server.ts +3 -3
- package/src/http-api.ts +33 -18
- package/src/plugins/event-log-nats.ts +14 -14
- package/src/rate-limiter.ts +1 -1
- package/src/registration/jwt-provider-auth-plugin.ts +3 -3
- package/src/registration/open-auth-handler.ts +5 -5
- package/src/registration/proof-of-work-manager.ts +17 -15
- package/src/registration/registration-manager.ts +6 -6
- package/src/registration/registration-store.ts +1 -1
- package/src/server-migration-runner.ts +1 -1
- package/src/ws-api.ts +1 -1
|
@@ -38,22 +38,22 @@ export class SocketConnection {
|
|
|
38
38
|
/** Timestamp when the connection was established (for admin introspection). */
|
|
39
39
|
public readonly connectedAt: number = Date.now();
|
|
40
40
|
|
|
41
|
-
private heartbeatInterval: ReturnType<typeof setInterval>;
|
|
42
|
-
private subscriptions: Map<JsonRpcId, JsonRpcSubscription> = new Map();
|
|
43
|
-
private flowControllers: Map<JsonRpcId, FlowController> = new Map();
|
|
41
|
+
private readonly heartbeatInterval: ReturnType<typeof setInterval>;
|
|
42
|
+
private readonly subscriptions: Map<JsonRpcId, JsonRpcSubscription> = new Map();
|
|
43
|
+
private readonly flowControllers: Map<JsonRpcId, FlowController> = new Map();
|
|
44
44
|
private isAlive: boolean;
|
|
45
45
|
|
|
46
46
|
constructor(
|
|
47
|
-
private socket: ServerWebSocket<WsData>,
|
|
48
|
-
private dwn: Dwn,
|
|
49
|
-
private onCloseCallback?: () => void,
|
|
50
|
-
private maxInFlight: number = DEFAULT_MAX_IN_FLIGHT,
|
|
51
|
-
private activityLog?: ActivityLog,
|
|
52
|
-
private adminStore?: AdminStore,
|
|
53
|
-
private registrationStore?: RegistrationStore,
|
|
54
|
-
private serverConfig?: DwnServerConfig,
|
|
55
|
-
private tenantRateLimiter?: RateLimiter,
|
|
56
|
-
private messageProcessedHooks?: MessageProcessedHook[],
|
|
47
|
+
private readonly socket: ServerWebSocket<WsData>,
|
|
48
|
+
private readonly dwn: Dwn,
|
|
49
|
+
private readonly onCloseCallback?: () => void,
|
|
50
|
+
private readonly maxInFlight: number = DEFAULT_MAX_IN_FLIGHT,
|
|
51
|
+
private readonly activityLog?: ActivityLog,
|
|
52
|
+
private readonly adminStore?: AdminStore,
|
|
53
|
+
private readonly registrationStore?: RegistrationStore,
|
|
54
|
+
private readonly serverConfig?: DwnServerConfig,
|
|
55
|
+
private readonly tenantRateLimiter?: RateLimiter,
|
|
56
|
+
private readonly messageProcessedHooks?: MessageProcessedHook[],
|
|
57
57
|
){
|
|
58
58
|
// Bun handles ping/pong automatically at the protocol level, but we still
|
|
59
59
|
// want an application-level heartbeat to detect dead connections.
|
package/src/delivery-service.ts
CHANGED
|
@@ -8,6 +8,15 @@ import log from 'loglevel';
|
|
|
8
8
|
import { createJsonRpcRequest } from '@enbox/dwn-clients';
|
|
9
9
|
import { DwnInterfaceName, DwnMethodName, getRuleSetAtPath, Message } from '@enbox/dwn-sdk-js';
|
|
10
10
|
|
|
11
|
+
/** Strips trailing `/` characters without regex (avoids ReDoS scanners). */
|
|
12
|
+
function stripTrailingSlashes(value: string): string {
|
|
13
|
+
let end = value.length;
|
|
14
|
+
while (end > 0 && value.codePointAt(end - 1) === 47) { // 47 === '/'
|
|
15
|
+
end--;
|
|
16
|
+
}
|
|
17
|
+
return end === value.length ? value : value.slice(0, end);
|
|
18
|
+
}
|
|
19
|
+
|
|
11
20
|
// ---------------------------------------------------------------------------
|
|
12
21
|
// Types
|
|
13
22
|
// ---------------------------------------------------------------------------
|
|
@@ -77,7 +86,7 @@ export class DeliveryService implements MessageProcessedHook {
|
|
|
77
86
|
this.#dwn = dwn;
|
|
78
87
|
this.#didResolver = didResolver;
|
|
79
88
|
this.#config = config;
|
|
80
|
-
this.#selfBaseUrl = config.baseUrl
|
|
89
|
+
this.#selfBaseUrl = stripTrailingSlashes(config.baseUrl);
|
|
81
90
|
}
|
|
82
91
|
|
|
83
92
|
/**
|
|
@@ -224,7 +233,7 @@ export class DeliveryService implements MessageProcessedHook {
|
|
|
224
233
|
}
|
|
225
234
|
|
|
226
235
|
const ruleSet = getRuleSetAtPath(descriptor.protocolPath, protocolDefinition.structure);
|
|
227
|
-
if (!ruleSet
|
|
236
|
+
if (!ruleSet?.$delivery) {
|
|
228
237
|
return;
|
|
229
238
|
}
|
|
230
239
|
|
|
@@ -536,17 +545,17 @@ export class DeliveryService implements MessageProcessedHook {
|
|
|
536
545
|
const epValue = service.serviceEndpoint;
|
|
537
546
|
|
|
538
547
|
if (typeof epValue === 'string') {
|
|
539
|
-
endpoints.push({ url: epValue
|
|
548
|
+
endpoints.push({ url: stripTrailingSlashes(epValue), isFull: true });
|
|
540
549
|
} else if (Array.isArray(epValue)) {
|
|
541
550
|
for (const entry of epValue) {
|
|
542
551
|
if (typeof entry === 'string') {
|
|
543
|
-
endpoints.push({ url: entry
|
|
552
|
+
endpoints.push({ url: stripTrailingSlashes(entry), isFull: true });
|
|
544
553
|
} else if (entry && typeof entry === 'object') {
|
|
545
554
|
// Map entry: { url: "...", dataRetention?: "full" | "cache" }
|
|
546
555
|
const mapEntry = entry as { url?: string; dataRetention?: string };
|
|
547
556
|
if (typeof mapEntry.url === 'string') {
|
|
548
557
|
endpoints.push({
|
|
549
|
-
url : mapEntry.url
|
|
558
|
+
url : stripTrailingSlashes(mapEntry.url),
|
|
550
559
|
isFull : mapEntry.dataRetention !== 'cache',
|
|
551
560
|
});
|
|
552
561
|
}
|
|
@@ -559,7 +568,7 @@ export class DeliveryService implements MessageProcessedHook {
|
|
|
559
568
|
if (Array.isArray(nodes)) {
|
|
560
569
|
for (const node of nodes) {
|
|
561
570
|
if (typeof node === 'string') {
|
|
562
|
-
endpoints.push({ url: node
|
|
571
|
+
endpoints.push({ url: stripTrailingSlashes(node), isFull: true });
|
|
563
572
|
}
|
|
564
573
|
}
|
|
565
574
|
}
|
|
@@ -569,7 +578,7 @@ export class DeliveryService implements MessageProcessedHook {
|
|
|
569
578
|
const mapEntry = epValue as { url?: string; dataRetention?: string };
|
|
570
579
|
if (typeof mapEntry.url === 'string') {
|
|
571
580
|
endpoints.push({
|
|
572
|
-
url : mapEntry.url
|
|
581
|
+
url : stripTrailingSlashes(mapEntry.url),
|
|
573
582
|
isFull : mapEntry.dataRetention !== 'cache',
|
|
574
583
|
});
|
|
575
584
|
}
|
package/src/dwn-server.ts
CHANGED
|
@@ -101,9 +101,9 @@ export class DwnServer {
|
|
|
101
101
|
#auditLog: AuditLog | undefined;
|
|
102
102
|
#passkeyStore: AdminPasskeyStore | undefined;
|
|
103
103
|
#sessionManager: AdminSessionManager | undefined;
|
|
104
|
-
#externalHooks: MessageProcessedHook[];
|
|
105
|
-
#externalRegistrationManager: RegistrationManager | undefined;
|
|
106
|
-
#externalOpenAuthHandler: OpenAuthHandler | undefined;
|
|
104
|
+
readonly #externalHooks: MessageProcessedHook[];
|
|
105
|
+
readonly #externalRegistrationManager: RegistrationManager | undefined;
|
|
106
|
+
readonly #externalOpenAuthHandler: OpenAuthHandler | undefined;
|
|
107
107
|
|
|
108
108
|
/**
|
|
109
109
|
* @param options.dwn - Dwn instance to use as an override.
|
package/src/http-api.ts
CHANGED
|
@@ -800,11 +800,11 @@ export class HttpApi {
|
|
|
800
800
|
return Response.json({ success: true }, { status: 200 });
|
|
801
801
|
} catch (error) {
|
|
802
802
|
const dwnServerError = error as DwnServerError;
|
|
803
|
-
if (dwnServerError.code
|
|
804
|
-
return Response.json(dwnServerError, { status: 400 });
|
|
805
|
-
} else {
|
|
803
|
+
if (dwnServerError.code === undefined) {
|
|
806
804
|
log.info('Error handling registration request:', error);
|
|
807
805
|
return Response.json({ success: false }, { status: 500 });
|
|
806
|
+
} else {
|
|
807
|
+
return Response.json(dwnServerError, { status: 400 });
|
|
808
808
|
}
|
|
809
809
|
}
|
|
810
810
|
}
|
|
@@ -850,18 +850,18 @@ export class HttpApi {
|
|
|
850
850
|
log.info(`Retrieving Connect Request object of ID: ${requestId}...`);
|
|
851
851
|
|
|
852
852
|
const requestObjectJwt = await this.connectServer.getConnectRequest(requestId);
|
|
853
|
-
if (
|
|
854
|
-
return Response.json({
|
|
855
|
-
ok : false,
|
|
856
|
-
status : { code: 404, message: 'Not Found' },
|
|
857
|
-
}, { status: 404 });
|
|
858
|
-
} else {
|
|
853
|
+
if (requestObjectJwt) {
|
|
859
854
|
const body = typeof requestObjectJwt === 'string'
|
|
860
855
|
? requestObjectJwt
|
|
861
856
|
: JSON.stringify(requestObjectJwt);
|
|
862
857
|
return new Response(body, {
|
|
863
858
|
headers: { 'content-type': 'application/jwt' },
|
|
864
859
|
});
|
|
860
|
+
} else {
|
|
861
|
+
return Response.json({
|
|
862
|
+
ok : false,
|
|
863
|
+
status : { code: 404, message: 'Not Found' },
|
|
864
|
+
}, { status: 404 });
|
|
865
865
|
}
|
|
866
866
|
}
|
|
867
867
|
}
|
|
@@ -869,9 +869,24 @@ export class HttpApi {
|
|
|
869
869
|
// POST /connect/callback
|
|
870
870
|
if (method === 'POST' && path === '/connect/callback') {
|
|
871
871
|
log.info('Storing Identity Provider (wallet) pushed response with ID token...');
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
872
|
+
|
|
873
|
+
// The agent's submitConnectResponse sends application/x-www-form-urlencoded
|
|
874
|
+
// but the server was previously parsing as JSON, causing a 500 error.
|
|
875
|
+
// Support both content types for robustness.
|
|
876
|
+
const contentType = req.headers.get('content-type') ?? '';
|
|
877
|
+
let idToken: string | undefined;
|
|
878
|
+
let state: string | undefined;
|
|
879
|
+
|
|
880
|
+
if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
881
|
+
const text = await req.text();
|
|
882
|
+
const params = new URLSearchParams(text);
|
|
883
|
+
idToken = params.get('id_token') ?? undefined;
|
|
884
|
+
state = params.get('state') ?? undefined;
|
|
885
|
+
} else {
|
|
886
|
+
const body = await req.json();
|
|
887
|
+
idToken = body.id_token;
|
|
888
|
+
state = body.state;
|
|
889
|
+
}
|
|
875
890
|
|
|
876
891
|
if (idToken !== undefined && state != undefined) {
|
|
877
892
|
await this.connectServer.setConnectResponse(state, idToken);
|
|
@@ -895,16 +910,16 @@ export class HttpApi {
|
|
|
895
910
|
log.info(`Retrieving ID token for state: ${state}...`);
|
|
896
911
|
|
|
897
912
|
const idToken = await this.connectServer.getConnectResponse(state);
|
|
898
|
-
if (
|
|
899
|
-
return Response.json({
|
|
900
|
-
ok : false,
|
|
901
|
-
status : { code: 404, message: 'Not Found' },
|
|
902
|
-
}, { status: 404 });
|
|
903
|
-
} else {
|
|
913
|
+
if (idToken) {
|
|
904
914
|
const body = typeof idToken === 'string' ? idToken : JSON.stringify(idToken);
|
|
905
915
|
return new Response(body, {
|
|
906
916
|
headers: { 'content-type': 'application/jwt' },
|
|
907
917
|
});
|
|
918
|
+
} else {
|
|
919
|
+
return Response.json({
|
|
920
|
+
ok : false,
|
|
921
|
+
status : { code: 404, message: 'Not Found' },
|
|
922
|
+
}, { status: 404 });
|
|
908
923
|
}
|
|
909
924
|
}
|
|
910
925
|
}
|
|
@@ -161,13 +161,13 @@ function tenantToSubjectToken(tenant: string): string {
|
|
|
161
161
|
* Must be a default export with a no-arg constructor.
|
|
162
162
|
*/
|
|
163
163
|
export default class NatsEventLog implements EventLog {
|
|
164
|
-
#config: NatsEventLogConfig;
|
|
164
|
+
readonly #config: NatsEventLogConfig;
|
|
165
165
|
#nc: NatsConnection | undefined;
|
|
166
166
|
#js: JetStreamClient | undefined;
|
|
167
167
|
#jsm: JetStreamManager | undefined;
|
|
168
168
|
|
|
169
169
|
/** Active subscription consumers, keyed by consumer name. */
|
|
170
|
-
#activeConsumers: Map<string, { messages?: ConsumerMessages; stopped: boolean }> = new Map();
|
|
170
|
+
readonly #activeConsumers: Map<string, { messages?: ConsumerMessages; stopped: boolean }> = new Map();
|
|
171
171
|
|
|
172
172
|
/**
|
|
173
173
|
* Epoch for this EventLog instance. Stable as long as the JetStream stream exists.
|
|
@@ -252,13 +252,13 @@ export default class NatsEventLog implements EventLog {
|
|
|
252
252
|
let reason: ProgressGapReason;
|
|
253
253
|
if (cursor.streamId !== expectedStreamId) {
|
|
254
254
|
reason = 'stream_mismatch';
|
|
255
|
-
} else if (cursor.epoch
|
|
256
|
-
reason = 'epoch_mismatch';
|
|
257
|
-
} else {
|
|
255
|
+
} else if (cursor.epoch === this.#epoch) {
|
|
258
256
|
// Check if position is within replay bounds using BigInt
|
|
259
257
|
// for safe handling of NATS sequences beyond Number.MAX_SAFE_INTEGER.
|
|
260
258
|
const bounds = await this.getReplayBounds(tenant);
|
|
261
|
-
if (bounds
|
|
259
|
+
if (bounds === undefined) {
|
|
260
|
+
return; // No events — vacuously valid.
|
|
261
|
+
} else {
|
|
262
262
|
const cursorSeq = BigInt(cursor.position);
|
|
263
263
|
const oldestSeq = BigInt(bounds.oldest.position);
|
|
264
264
|
if (cursorSeq < oldestSeq - 1n) {
|
|
@@ -266,9 +266,9 @@ export default class NatsEventLog implements EventLog {
|
|
|
266
266
|
} else {
|
|
267
267
|
return; // Valid.
|
|
268
268
|
}
|
|
269
|
-
} else {
|
|
270
|
-
return; // No events — vacuously valid.
|
|
271
269
|
}
|
|
270
|
+
} else {
|
|
271
|
+
reason = 'epoch_mismatch';
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
const bounds = await this.getReplayBounds(tenant);
|
|
@@ -315,11 +315,11 @@ export default class NatsEventLog implements EventLog {
|
|
|
315
315
|
ack_policy : AckPolicy.None, // ordered consumers use AckNone
|
|
316
316
|
};
|
|
317
317
|
|
|
318
|
-
if (cursor
|
|
318
|
+
if (cursor === undefined) {
|
|
319
|
+
consumerOpts.deliver_policy = DeliverPolicy.All;
|
|
320
|
+
} else {
|
|
319
321
|
consumerOpts.deliver_policy = DeliverPolicy.StartSequence;
|
|
320
322
|
consumerOpts.opt_start_seq = Number(cursor.position) + 1;
|
|
321
|
-
} else {
|
|
322
|
-
consumerOpts.deliver_policy = DeliverPolicy.All;
|
|
323
323
|
}
|
|
324
324
|
|
|
325
325
|
const consumer = await this.#jsm!.consumers.add(this.#config.streamName, consumerOpts);
|
|
@@ -405,11 +405,11 @@ export default class NatsEventLog implements EventLog {
|
|
|
405
405
|
inactive_threshold : 60_000_000_000, // 60 seconds in nanos
|
|
406
406
|
};
|
|
407
407
|
|
|
408
|
-
if (cursor
|
|
408
|
+
if (cursor === undefined) {
|
|
409
|
+
consumerOpts.deliver_policy = DeliverPolicy.New;
|
|
410
|
+
} else {
|
|
409
411
|
consumerOpts.deliver_policy = DeliverPolicy.StartSequence;
|
|
410
412
|
consumerOpts.opt_start_seq = Number(cursor.position) + 1;
|
|
411
|
-
} else {
|
|
412
|
-
consumerOpts.deliver_policy = DeliverPolicy.New;
|
|
413
413
|
}
|
|
414
414
|
|
|
415
415
|
await this.#jsm!.consumers.add(this.#config.streamName, consumerOpts);
|
package/src/rate-limiter.ts
CHANGED
|
@@ -25,7 +25,7 @@ type Bucket = {
|
|
|
25
25
|
export class RateLimiter {
|
|
26
26
|
#refillRate: number;
|
|
27
27
|
#maxTokens: number;
|
|
28
|
-
#buckets: Map<string, Bucket> = new Map();
|
|
28
|
+
readonly #buckets: Map<string, Bucket> = new Map();
|
|
29
29
|
#cleanupInterval: ReturnType<typeof setInterval> | undefined;
|
|
30
30
|
|
|
31
31
|
/** Stale buckets older than 5 minutes are purged. */
|
|
@@ -36,9 +36,9 @@ export type JwtProviderAuthPluginOptions = {
|
|
|
36
36
|
* {@link ProviderAuthPlugin} instead.
|
|
37
37
|
*/
|
|
38
38
|
export class JwtProviderAuthPlugin implements ProviderAuthPlugin {
|
|
39
|
-
#getKey: Uint8Array | jose.JWTVerifyGetKey;
|
|
40
|
-
#issuer: string | undefined;
|
|
41
|
-
#audience: string | undefined;
|
|
39
|
+
readonly #getKey: Uint8Array | jose.JWTVerifyGetKey;
|
|
40
|
+
readonly #issuer: string | undefined;
|
|
41
|
+
readonly #audience: string | undefined;
|
|
42
42
|
|
|
43
43
|
private constructor(
|
|
44
44
|
getKey: Uint8Array | jose.JWTVerifyGetKey,
|
|
@@ -28,14 +28,14 @@ const MAX_PENDING_CODES = 10_000;
|
|
|
28
28
|
const CLEANUP_INTERVAL_MS = 60_000;
|
|
29
29
|
|
|
30
30
|
export class OpenAuthHandler {
|
|
31
|
-
#secret: Uint8Array;
|
|
32
|
-
#issuer: string;
|
|
31
|
+
readonly #secret: Uint8Array;
|
|
32
|
+
readonly #issuer: string;
|
|
33
33
|
/** Pending authorization codes. Maps code → { redirectUri, expiresAt }. */
|
|
34
|
-
#pendingCodes: Map<string, { redirectUri: string; expiresAt: number }>;
|
|
34
|
+
readonly #pendingCodes: Map<string, { redirectUri: string; expiresAt: number }>;
|
|
35
35
|
/** Registration token TTL in seconds. Default: 1 year. */
|
|
36
|
-
#tokenTtlSeconds: number;
|
|
36
|
+
readonly #tokenTtlSeconds: number;
|
|
37
37
|
/** Periodic cleanup timer for expired codes. */
|
|
38
|
-
#cleanupTimer: ReturnType<typeof setInterval>;
|
|
38
|
+
readonly #cleanupTimer: ReturnType<typeof setInterval>;
|
|
39
39
|
|
|
40
40
|
private constructor(secret: Uint8Array, issuer: string, tokenTtlSeconds: number) {
|
|
41
41
|
this.#secret = secret;
|
|
@@ -20,14 +20,14 @@ export class ProofOfWorkManager {
|
|
|
20
20
|
|
|
21
21
|
// There is opportunity to improve implementation here.
|
|
22
22
|
// TODO: https://github.com/enboxorg/enbox/issues/101
|
|
23
|
-
private proofOfWorkOfLastMinute: Map<string, number> = new Map(); // proofOfWorkId -> timestamp of proof-of-work
|
|
23
|
+
private readonly proofOfWorkOfLastMinute: Map<string, number> = new Map(); // proofOfWorkId -> timestamp of proof-of-work
|
|
24
24
|
|
|
25
25
|
// Seed to generate the challenge nonce from, this allows all DWN instances in a cluster to generate the same challenge.
|
|
26
|
-
private challengeSeed?: string;
|
|
27
|
-
private difficultyIncreaseMultiplier: number;
|
|
26
|
+
private readonly challengeSeed?: string;
|
|
27
|
+
private readonly difficultyIncreaseMultiplier: number;
|
|
28
28
|
private currentMaximumAllowedHashValueAsBigInt: bigint;
|
|
29
|
-
private initialMaximumAllowedHashValueAsBigInt: bigint;
|
|
30
|
-
private desiredSolveCountPerMinute: number;
|
|
29
|
+
private readonly initialMaximumAllowedHashValueAsBigInt: bigint;
|
|
30
|
+
private readonly desiredSolveCountPerMinute: number;
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* How often the challenge nonce is refreshed.
|
|
@@ -183,17 +183,19 @@ export class ProofOfWorkManager {
|
|
|
183
183
|
} catch (error) {
|
|
184
184
|
console.error(`Encountered error while refreshing challenge nonce: ${error}`);
|
|
185
185
|
} finally {
|
|
186
|
-
setTimeout(
|
|
186
|
+
setTimeout(() => this.periodicallyRefreshChallengeNonce(), this.challengeRefreshFrequencyInSeconds * 1000);
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
private periodicallyRefreshProofOfWorkDifficulty (): void {
|
|
190
|
+
private async periodicallyRefreshProofOfWorkDifficulty (): Promise<void> {
|
|
191
191
|
try {
|
|
192
|
-
this.refreshMaximumAllowedHashValue();
|
|
192
|
+
await this.refreshMaximumAllowedHashValue();
|
|
193
193
|
} catch (error) {
|
|
194
194
|
console.error(`Encountered error while updating proof of work difficulty: ${error}`);
|
|
195
195
|
} finally {
|
|
196
|
-
setTimeout(
|
|
196
|
+
setTimeout(() => {
|
|
197
|
+
void this.periodicallyRefreshProofOfWorkDifficulty();
|
|
198
|
+
}, this.difficultyReevaluationFrequencyInSeconds * 1000);
|
|
197
199
|
}
|
|
198
200
|
}
|
|
199
201
|
|
|
@@ -208,7 +210,12 @@ export class ProofOfWorkManager {
|
|
|
208
210
|
|
|
209
211
|
private refreshChallengeNonce(): void {
|
|
210
212
|
// If challenge seed is supplied, use it to deterministically generate the challenge nonces.
|
|
211
|
-
if (this.challengeSeed
|
|
213
|
+
if (this.challengeSeed === undefined) {
|
|
214
|
+
const newChallengeNonce = ProofOfWork.generateNonce();
|
|
215
|
+
|
|
216
|
+
this.challengeNonces.previousChallengeNonce = this.challengeNonces.currentChallengeNonce;
|
|
217
|
+
this.challengeNonces.currentChallengeNonce = newChallengeNonce;
|
|
218
|
+
} else {
|
|
212
219
|
const currentRefreshIntervalId = Math.floor(Date.now() / (this.challengeRefreshFrequencyInSeconds * 1000));
|
|
213
220
|
const previousRefreshIntervalId = currentRefreshIntervalId - 1;
|
|
214
221
|
const nextRefreshIntervalId = currentRefreshIntervalId + 1;
|
|
@@ -218,11 +225,6 @@ export class ProofOfWorkManager {
|
|
|
218
225
|
const nextChallengeNonce = ProofOfWork.hashAsHexString([this.challengeSeed, nextRefreshIntervalId.toString(), this.challengeSeed]);
|
|
219
226
|
|
|
220
227
|
this.challengeNonces = { previousChallengeNonce, currentChallengeNonce, nextChallengeNonce };
|
|
221
|
-
} else {
|
|
222
|
-
const newChallengeNonce = ProofOfWork.generateNonce();
|
|
223
|
-
|
|
224
|
-
this.challengeNonces.previousChallengeNonce = this.challengeNonces.currentChallengeNonce;
|
|
225
|
-
this.challengeNonces.currentChallengeNonce = newChallengeNonce;
|
|
226
228
|
}
|
|
227
229
|
}
|
|
228
230
|
|
|
@@ -108,13 +108,13 @@ export class RegistrationManager implements TenantGate {
|
|
|
108
108
|
public async handleRegistrationRequest(registrationRequest: RegistrationRequest): Promise<void> {
|
|
109
109
|
if (registrationRequest.providerAuth?.registrationToken !== undefined) {
|
|
110
110
|
await this.handleProviderAuthRegistration(registrationRequest);
|
|
111
|
-
} else if (registrationRequest.proofOfWork
|
|
112
|
-
await this.handleProofOfWorkRegistration(registrationRequest);
|
|
113
|
-
} else {
|
|
111
|
+
} else if (registrationRequest.proofOfWork === undefined) {
|
|
114
112
|
throw new DwnServerError(
|
|
115
113
|
DwnServerErrorCode.RegistrationRequestMissingCredentials,
|
|
116
114
|
'Registration request must include either providerAuth or proofOfWork credentials.',
|
|
117
115
|
);
|
|
116
|
+
} else {
|
|
117
|
+
await this.handleProofOfWorkRegistration(registrationRequest);
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
|
|
@@ -154,9 +154,9 @@ export class RegistrationManager implements TenantGate {
|
|
|
154
154
|
termsOfServiceHash : registrationRequest.registrationData.termsOfServiceHash,
|
|
155
155
|
accountId : validationResult.accountId,
|
|
156
156
|
registrationType : 'provider-auth',
|
|
157
|
-
metadata : validationResult.metadata
|
|
158
|
-
?
|
|
159
|
-
:
|
|
157
|
+
metadata : validationResult.metadata === undefined
|
|
158
|
+
? undefined
|
|
159
|
+
: JSON.stringify(validationResult.metadata),
|
|
160
160
|
});
|
|
161
161
|
}
|
|
162
162
|
|
|
@@ -12,7 +12,7 @@ export class RegistrationStore {
|
|
|
12
12
|
private static readonly registeredTenantTableName = 'registeredTenants';
|
|
13
13
|
private static readonly tenantQuotasTableName = 'tenantQuotas';
|
|
14
14
|
|
|
15
|
-
private db: Kysely<RegistrationDatabase>;
|
|
15
|
+
private readonly db: Kysely<RegistrationDatabase>;
|
|
16
16
|
|
|
17
17
|
private constructor (sqlDialect: Dialect) {
|
|
18
18
|
this.db = new Kysely<RegistrationDatabase>({ dialect: sqlDialect });
|
|
@@ -11,7 +11,7 @@ import { Migrator } from 'kysely';
|
|
|
11
11
|
* dialect, producing the concrete Kysely {@link Migration} objects.
|
|
12
12
|
*/
|
|
13
13
|
class ServerMigrationProvider implements MigrationProvider {
|
|
14
|
-
#dialect: Dialect;
|
|
14
|
+
readonly #dialect: Dialect;
|
|
15
15
|
#factories: ReadonlyArray<readonly [name: string, factory: ServerMigrationFactory]>;
|
|
16
16
|
|
|
17
17
|
constructor(
|
package/src/ws-api.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { InMemoryConnectionManager } from './connection/connection-manager.js';
|
|
|
14
14
|
|
|
15
15
|
export class WsApi {
|
|
16
16
|
dwn: Dwn;
|
|
17
|
-
#connectionManager: ConnectionManager;
|
|
17
|
+
readonly #connectionManager: ConnectionManager;
|
|
18
18
|
|
|
19
19
|
constructor(
|
|
20
20
|
httpApi: HttpApi, dwn: Dwn, connectionManager?: ConnectionManager,
|