@enbox/dwn-server 0.1.2 → 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.
Files changed (70) hide show
  1. package/dist/esm/src/admin/admin-api.js +2 -2
  2. package/dist/esm/src/admin/admin-api.js.map +1 -1
  3. package/dist/esm/src/admin/admin-auth.js +1 -1
  4. package/dist/esm/src/admin/admin-auth.js.map +1 -1
  5. package/dist/esm/src/admin/admin-passkey-store.js.map +1 -1
  6. package/dist/esm/src/admin/admin-session.js.map +1 -1
  7. package/dist/esm/src/admin/admin-store.d.ts +2 -2
  8. package/dist/esm/src/admin/admin-store.d.ts.map +1 -1
  9. package/dist/esm/src/admin/admin-store.js +1 -1
  10. package/dist/esm/src/admin/admin-store.js.map +1 -1
  11. package/dist/esm/src/admin/audit-log.js.map +1 -1
  12. package/dist/esm/src/admin/webhook-manager.js.map +1 -1
  13. package/dist/esm/src/connect/connect-server.d.ts +1 -1
  14. package/dist/esm/src/connect/connect-server.d.ts.map +1 -1
  15. package/dist/esm/src/connect/connect-server.js.map +1 -1
  16. package/dist/esm/src/connect/sql-ttl-cache.d.ts +1 -1
  17. package/dist/esm/src/connect/sql-ttl-cache.d.ts.map +1 -1
  18. package/dist/esm/src/connect/sql-ttl-cache.js.map +1 -1
  19. package/dist/esm/src/connection/connection-manager.d.ts +9 -9
  20. package/dist/esm/src/connection/connection-manager.d.ts.map +1 -1
  21. package/dist/esm/src/connection/connection-manager.js.map +1 -1
  22. package/dist/esm/src/connection/socket-connection.d.ts +13 -13
  23. package/dist/esm/src/connection/socket-connection.d.ts.map +1 -1
  24. package/dist/esm/src/connection/socket-connection.js.map +1 -1
  25. package/dist/esm/src/delivery-service.d.ts.map +1 -1
  26. package/dist/esm/src/delivery-service.js +15 -7
  27. package/dist/esm/src/delivery-service.js.map +1 -1
  28. package/dist/esm/src/dwn-server.js.map +1 -1
  29. package/dist/esm/src/http-api.js +18 -18
  30. package/dist/esm/src/http-api.js.map +1 -1
  31. package/dist/esm/src/plugins/event-log-nats.js +16 -16
  32. package/dist/esm/src/plugins/event-log-nats.js.map +1 -1
  33. package/dist/esm/src/rate-limiter.js.map +1 -1
  34. package/dist/esm/src/registration/jwt-provider-auth-plugin.js.map +1 -1
  35. package/dist/esm/src/registration/open-auth-handler.js.map +1 -1
  36. package/dist/esm/src/registration/proof-of-work-manager.d.ts +5 -5
  37. package/dist/esm/src/registration/proof-of-work-manager.d.ts.map +1 -1
  38. package/dist/esm/src/registration/proof-of-work-manager.js +12 -10
  39. package/dist/esm/src/registration/proof-of-work-manager.js.map +1 -1
  40. package/dist/esm/src/registration/registration-manager.js +6 -6
  41. package/dist/esm/src/registration/registration-manager.js.map +1 -1
  42. package/dist/esm/src/registration/registration-store.d.ts +1 -1
  43. package/dist/esm/src/registration/registration-store.d.ts.map +1 -1
  44. package/dist/esm/src/registration/registration-store.js.map +1 -1
  45. package/dist/esm/src/server-migration-runner.js.map +1 -1
  46. package/dist/esm/src/ws-api.js.map +1 -1
  47. package/package.json +5 -5
  48. package/src/admin/admin-api.ts +5 -5
  49. package/src/admin/admin-auth.ts +1 -1
  50. package/src/admin/admin-passkey-store.ts +1 -1
  51. package/src/admin/admin-session.ts +2 -2
  52. package/src/admin/admin-store.ts +3 -3
  53. package/src/admin/audit-log.ts +1 -1
  54. package/src/admin/webhook-manager.ts +1 -1
  55. package/src/connect/connect-server.ts +1 -1
  56. package/src/connect/sql-ttl-cache.ts +1 -1
  57. package/src/connection/connection-manager.ts +9 -9
  58. package/src/connection/socket-connection.ts +13 -13
  59. package/src/delivery-service.ts +16 -7
  60. package/src/dwn-server.ts +3 -3
  61. package/src/http-api.ts +15 -15
  62. package/src/plugins/event-log-nats.ts +14 -14
  63. package/src/rate-limiter.ts +1 -1
  64. package/src/registration/jwt-provider-auth-plugin.ts +3 -3
  65. package/src/registration/open-auth-handler.ts +5 -5
  66. package/src/registration/proof-of-work-manager.ts +17 -15
  67. package/src/registration/registration-manager.ts +6 -6
  68. package/src/registration/registration-store.ts +1 -1
  69. package/src/server-migration-runner.ts +1 -1
  70. package/src/ws-api.ts +1 -1
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 !== undefined) {
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 (!requestObjectJwt) {
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
  }
@@ -910,16 +910,16 @@ export class HttpApi {
910
910
  log.info(`Retrieving ID token for state: ${state}...`);
911
911
 
912
912
  const idToken = await this.connectServer.getConnectResponse(state);
913
- if (!idToken) {
914
- return Response.json({
915
- ok : false,
916
- status : { code: 404, message: 'Not Found' },
917
- }, { status: 404 });
918
- } else {
913
+ if (idToken) {
919
914
  const body = typeof idToken === 'string' ? idToken : JSON.stringify(idToken);
920
915
  return new Response(body, {
921
916
  headers: { 'content-type': 'application/jwt' },
922
917
  });
918
+ } else {
919
+ return Response.json({
920
+ ok : false,
921
+ status : { code: 404, message: 'Not Found' },
922
+ }, { status: 404 });
923
923
  }
924
924
  }
925
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 !== this.#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 !== undefined) {
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 !== undefined) {
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 !== undefined) {
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);
@@ -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(async () => this.periodicallyRefreshChallengeNonce(), this.challengeRefreshFrequencyInSeconds * 1000);
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(async () => this.periodicallyRefreshProofOfWorkDifficulty(), this.difficultyReevaluationFrequencyInSeconds * 1000);
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 !== undefined) {
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 !== undefined) {
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 !== undefined
158
- ? JSON.stringify(validationResult.metadata)
159
- : undefined,
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,