@enbox/dwn-server 0.0.2 → 0.0.4
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/LICENSE +3 -2
- package/README.md +115 -215
- package/dist/esm/src/admin/activity-log.d.ts +44 -0
- package/dist/esm/src/admin/activity-log.d.ts.map +1 -0
- package/dist/esm/src/admin/activity-log.js +85 -0
- package/dist/esm/src/admin/activity-log.js.map +1 -0
- package/dist/esm/src/admin/admin-api.d.ts +61 -0
- package/dist/esm/src/admin/admin-api.d.ts.map +1 -0
- package/dist/esm/src/admin/admin-api.js +1047 -0
- package/dist/esm/src/admin/admin-api.js.map +1 -0
- package/dist/esm/src/admin/admin-auth.d.ts +9 -0
- package/dist/esm/src/admin/admin-auth.d.ts.map +1 -0
- package/dist/esm/src/admin/admin-auth.js +45 -0
- package/dist/esm/src/admin/admin-auth.js.map +1 -0
- package/dist/esm/src/admin/admin-store.d.ts +111 -0
- package/dist/esm/src/admin/admin-store.d.ts.map +1 -0
- package/dist/esm/src/admin/admin-store.js +376 -0
- package/dist/esm/src/admin/admin-store.js.map +1 -0
- package/dist/esm/src/admin/audit-log.d.ts +94 -0
- package/dist/esm/src/admin/audit-log.d.ts.map +1 -0
- package/dist/esm/src/admin/audit-log.js +220 -0
- package/dist/esm/src/admin/audit-log.js.map +1 -0
- package/dist/esm/src/admin/index.d.ts +10 -0
- package/dist/esm/src/admin/index.d.ts.map +1 -0
- package/dist/esm/src/admin/index.js +7 -0
- package/dist/esm/src/admin/index.js.map +1 -0
- package/dist/esm/src/admin/types.d.ts +306 -0
- package/dist/esm/src/admin/types.d.ts.map +1 -0
- package/dist/esm/src/admin/types.js +2 -0
- package/dist/esm/src/admin/types.js.map +1 -0
- package/dist/esm/src/admin/webhook-manager.d.ts +55 -0
- package/dist/esm/src/admin/webhook-manager.d.ts.map +1 -0
- package/dist/esm/src/admin/webhook-manager.js +184 -0
- package/dist/esm/src/admin/webhook-manager.js.map +1 -0
- package/dist/esm/src/config.d.ts +124 -9
- package/dist/esm/src/config.d.ts.map +1 -1
- package/dist/esm/src/config.js +155 -13
- package/dist/esm/src/config.js.map +1 -1
- package/dist/esm/src/connection/connection-manager.d.ts +32 -9
- package/dist/esm/src/connection/connection-manager.d.ts.map +1 -1
- package/dist/esm/src/connection/connection-manager.js +38 -5
- package/dist/esm/src/connection/connection-manager.js.map +1 -1
- package/dist/esm/src/connection/flow-controller.d.ts +53 -0
- package/dist/esm/src/connection/flow-controller.d.ts.map +1 -0
- package/dist/esm/src/connection/flow-controller.js +101 -0
- package/dist/esm/src/connection/flow-controller.js.map +1 -0
- package/dist/esm/src/connection/socket-connection.d.ts +54 -18
- package/dist/esm/src/connection/socket-connection.d.ts.map +1 -1
- package/dist/esm/src/connection/socket-connection.js +102 -40
- package/dist/esm/src/connection/socket-connection.js.map +1 -1
- package/dist/esm/src/delivery-service.d.ts +43 -0
- package/dist/esm/src/delivery-service.d.ts.map +1 -0
- package/dist/esm/src/delivery-service.js +574 -0
- package/dist/esm/src/delivery-service.js.map +1 -0
- package/dist/esm/src/dwn-error.d.ts +10 -1
- package/dist/esm/src/dwn-error.d.ts.map +1 -1
- package/dist/esm/src/dwn-error.js +9 -0
- package/dist/esm/src/dwn-error.js.map +1 -1
- package/dist/esm/src/dwn-server.d.ts +13 -6
- package/dist/esm/src/dwn-server.d.ts.map +1 -1
- package/dist/esm/src/dwn-server.js +199 -24
- package/dist/esm/src/dwn-server.js.map +1 -1
- package/dist/esm/src/http-api.d.ts +28 -13
- package/dist/esm/src/http-api.d.ts.map +1 -1
- package/dist/esm/src/http-api.js +649 -374
- package/dist/esm/src/http-api.js.map +1 -1
- package/dist/esm/src/index.d.ts +6 -2
- package/dist/esm/src/index.d.ts.map +1 -1
- package/dist/esm/src/index.js +4 -1
- package/dist/esm/src/index.js.map +1 -1
- package/dist/esm/src/json-rpc-api.js +2 -1
- package/dist/esm/src/json-rpc-api.js.map +1 -1
- package/dist/esm/src/json-rpc-handlers/dwn/process-message.d.ts.map +1 -1
- package/dist/esm/src/json-rpc-handlers/dwn/process-message.js +109 -7
- package/dist/esm/src/json-rpc-handlers/dwn/process-message.js.map +1 -1
- package/dist/esm/src/json-rpc-handlers/subscription/ack.d.ts +20 -0
- package/dist/esm/src/json-rpc-handlers/subscription/ack.d.ts.map +1 -0
- package/dist/esm/src/json-rpc-handlers/subscription/ack.js +41 -0
- package/dist/esm/src/json-rpc-handlers/subscription/ack.js.map +1 -0
- package/dist/esm/src/json-rpc-handlers/subscription/close.d.ts.map +1 -1
- package/dist/esm/src/json-rpc-handlers/subscription/close.js +1 -1
- package/dist/esm/src/json-rpc-handlers/subscription/close.js.map +1 -1
- package/dist/esm/src/json-rpc-handlers/subscription/index.d.ts +1 -0
- package/dist/esm/src/json-rpc-handlers/subscription/index.d.ts.map +1 -1
- package/dist/esm/src/json-rpc-handlers/subscription/index.js +1 -0
- package/dist/esm/src/json-rpc-handlers/subscription/index.js.map +1 -1
- package/dist/esm/src/lib/json-rpc-router.d.ts +25 -8
- package/dist/esm/src/lib/json-rpc-router.d.ts.map +1 -1
- package/dist/esm/src/lib/json-rpc-router.js.map +1 -1
- package/dist/esm/src/lib/sql-utils.d.ts +6 -0
- package/dist/esm/src/lib/sql-utils.d.ts.map +1 -0
- package/dist/esm/src/lib/sql-utils.js +8 -0
- package/dist/esm/src/lib/sql-utils.js.map +1 -0
- package/dist/esm/src/main.js +0 -6
- package/dist/esm/src/main.js.map +1 -1
- package/dist/esm/src/message-processed-hook.d.ts +35 -0
- package/dist/esm/src/message-processed-hook.d.ts.map +1 -0
- package/dist/esm/src/message-processed-hook.js +2 -0
- package/dist/esm/src/message-processed-hook.js.map +1 -0
- package/dist/esm/src/metrics.d.ts +14 -2
- package/dist/esm/src/metrics.d.ts.map +1 -1
- package/dist/esm/src/metrics.js +41 -1
- package/dist/esm/src/metrics.js.map +1 -1
- package/dist/esm/src/plugins/event-log-nats.d.ts +25 -0
- package/dist/esm/src/plugins/event-log-nats.d.ts.map +1 -0
- package/dist/esm/src/plugins/event-log-nats.js +379 -0
- package/dist/esm/src/plugins/event-log-nats.js.map +1 -0
- package/dist/esm/src/rate-limiter.d.ts +60 -0
- package/dist/esm/src/rate-limiter.d.ts.map +1 -0
- package/dist/esm/src/rate-limiter.js +116 -0
- package/dist/esm/src/rate-limiter.js.map +1 -0
- package/dist/esm/src/registration/jwt-provider-auth-plugin.d.ts +53 -0
- package/dist/esm/src/registration/jwt-provider-auth-plugin.d.ts.map +1 -0
- package/dist/esm/src/registration/jwt-provider-auth-plugin.js +90 -0
- package/dist/esm/src/registration/jwt-provider-auth-plugin.js.map +1 -0
- package/dist/esm/src/registration/open-auth-handler.d.ts +37 -0
- package/dist/esm/src/registration/open-auth-handler.d.ts.map +1 -0
- package/dist/esm/src/registration/open-auth-handler.js +214 -0
- package/dist/esm/src/registration/open-auth-handler.js.map +1 -0
- package/dist/esm/src/registration/proof-of-work-manager.d.ts +1 -1
- package/dist/esm/src/registration/proof-of-work-manager.d.ts.map +1 -1
- package/dist/esm/src/registration/proof-of-work-manager.js +3 -3
- package/dist/esm/src/registration/proof-of-work-manager.js.map +1 -1
- package/dist/esm/src/registration/provider-auth-plugin.d.ts +46 -0
- package/dist/esm/src/registration/provider-auth-plugin.d.ts.map +1 -0
- package/dist/esm/src/registration/provider-auth-plugin.js +29 -0
- package/dist/esm/src/registration/provider-auth-plugin.js.map +1 -0
- package/dist/esm/src/registration/registration-manager.d.ts +28 -5
- package/dist/esm/src/registration/registration-manager.d.ts.map +1 -1
- package/dist/esm/src/registration/registration-manager.js +83 -12
- package/dist/esm/src/registration/registration-manager.js.map +1 -1
- package/dist/esm/src/registration/registration-store.d.ts +83 -3
- package/dist/esm/src/registration/registration-store.d.ts.map +1 -1
- package/dist/esm/src/registration/registration-store.js +248 -11
- package/dist/esm/src/registration/registration-store.js.map +1 -1
- package/dist/esm/src/storage.d.ts +5 -5
- package/dist/esm/src/storage.d.ts.map +1 -1
- package/dist/esm/src/storage.js +105 -24
- package/dist/esm/src/storage.js.map +1 -1
- package/dist/esm/src/web5-connect/sql-ttl-cache.d.ts.map +1 -1
- package/dist/esm/src/web5-connect/sql-ttl-cache.js +11 -3
- package/dist/esm/src/web5-connect/sql-ttl-cache.js.map +1 -1
- package/dist/esm/src/web5-connect/web5-connect-server.d.ts.map +1 -1
- package/dist/esm/src/web5-connect/web5-connect-server.js +2 -2
- package/dist/esm/src/web5-connect/web5-connect-server.js.map +1 -1
- package/dist/esm/src/ws-api.d.ts +18 -4
- package/dist/esm/src/ws-api.d.ts.map +1 -1
- package/dist/esm/src/ws-api.js +12 -16
- package/dist/esm/src/ws-api.js.map +1 -1
- package/package.json +34 -53
- package/src/admin/activity-log.ts +100 -0
- package/src/admin/admin-api.ts +1308 -0
- package/src/admin/admin-auth.ts +56 -0
- package/src/admin/admin-store.ts +515 -0
- package/src/admin/audit-log.ts +327 -0
- package/src/admin/index.ts +34 -0
- package/src/admin/types.ts +352 -0
- package/src/admin/webhook-manager.ts +245 -0
- package/src/config.ts +190 -22
- package/src/connection/connection-manager.ts +67 -17
- package/src/connection/flow-controller.ts +117 -0
- package/src/connection/socket-connection.ts +144 -67
- package/src/delivery-service.ts +740 -0
- package/src/dwn-error.ts +11 -2
- package/src/dwn-server.ts +254 -39
- package/src/http-api.ts +736 -392
- package/src/index.ts +13 -2
- package/src/json-rpc-api.ts +2 -1
- package/src/json-rpc-handlers/dwn/process-message.ts +149 -15
- package/src/json-rpc-handlers/subscription/ack.ts +63 -0
- package/src/json-rpc-handlers/subscription/close.ts +5 -9
- package/src/json-rpc-handlers/subscription/index.ts +1 -0
- package/src/lib/json-rpc-router.ts +26 -11
- package/src/lib/sql-utils.ts +7 -0
- package/src/main.ts +0 -8
- package/src/message-processed-hook.ts +33 -0
- package/src/metrics.ts +57 -8
- package/src/plugins/event-log-nats.ts +466 -0
- package/src/process-handlers.ts +5 -5
- package/src/rate-limiter.ts +143 -0
- package/src/registration/jwt-provider-auth-plugin.ts +119 -0
- package/src/registration/open-auth-handler.ts +263 -0
- package/src/registration/proof-of-work-manager.ts +11 -10
- package/src/registration/provider-auth-plugin.ts +84 -0
- package/src/registration/registration-manager.ts +129 -31
- package/src/registration/registration-store.ts +332 -22
- package/src/storage.ts +136 -40
- package/src/web5-connect/sql-ttl-cache.ts +12 -5
- package/src/web5-connect/web5-connect-server.ts +9 -8
- package/src/ws-api.ts +39 -26
- package/dist/cjs/index.js +0 -6811
- package/dist/cjs/package.json +0 -1
- package/dist/esm/src/json-rpc-socket.d.ts +0 -39
- package/dist/esm/src/json-rpc-socket.d.ts.map +0 -1
- package/dist/esm/src/json-rpc-socket.js +0 -125
- package/dist/esm/src/json-rpc-socket.js.map +0 -1
- package/dist/esm/src/lib/http-server-shutdown-handler.d.ts +0 -10
- package/dist/esm/src/lib/http-server-shutdown-handler.d.ts.map +0 -1
- package/dist/esm/src/lib/http-server-shutdown-handler.js +0 -65
- package/dist/esm/src/lib/http-server-shutdown-handler.js.map +0 -1
- package/dist/esm/src/lib/json-rpc.d.ts +0 -54
- package/dist/esm/src/lib/json-rpc.d.ts.map +0 -1
- package/dist/esm/src/lib/json-rpc.js +0 -60
- package/dist/esm/src/lib/json-rpc.js.map +0 -1
- package/dist/esm/src/registration/proof-of-work-types.d.ts +0 -8
- package/dist/esm/src/registration/proof-of-work-types.d.ts.map +0 -1
- package/dist/esm/src/registration/proof-of-work-types.js +0 -2
- package/dist/esm/src/registration/proof-of-work-types.js.map +0 -1
- package/dist/esm/src/registration/registration-types.d.ts +0 -18
- package/dist/esm/src/registration/registration-types.d.ts.map +0 -1
- package/dist/esm/src/registration/registration-types.js +0 -2
- package/dist/esm/src/registration/registration-types.js.map +0 -1
- package/dist/esm/tests/common-scenario-validator.d.ts +0 -11
- package/dist/esm/tests/common-scenario-validator.d.ts.map +0 -1
- package/dist/esm/tests/common-scenario-validator.js +0 -114
- package/dist/esm/tests/common-scenario-validator.js.map +0 -1
- package/dist/esm/tests/connection/connection-manager.spec.d.ts +0 -2
- package/dist/esm/tests/connection/connection-manager.spec.d.ts.map +0 -1
- package/dist/esm/tests/connection/connection-manager.spec.js +0 -47
- package/dist/esm/tests/connection/connection-manager.spec.js.map +0 -1
- package/dist/esm/tests/connection/socket-connection.spec.d.ts +0 -2
- package/dist/esm/tests/connection/socket-connection.spec.d.ts.map +0 -1
- package/dist/esm/tests/connection/socket-connection.spec.js +0 -125
- package/dist/esm/tests/connection/socket-connection.spec.js.map +0 -1
- package/dist/esm/tests/cors/http-api.browser.d.ts +0 -2
- package/dist/esm/tests/cors/http-api.browser.d.ts.map +0 -1
- package/dist/esm/tests/cors/http-api.browser.js +0 -60
- package/dist/esm/tests/cors/http-api.browser.js.map +0 -1
- package/dist/esm/tests/cors/ping.browser.d.ts +0 -2
- package/dist/esm/tests/cors/ping.browser.d.ts.map +0 -1
- package/dist/esm/tests/cors/ping.browser.js +0 -7
- package/dist/esm/tests/cors/ping.browser.js.map +0 -1
- package/dist/esm/tests/dwn-process-message.spec.d.ts +0 -2
- package/dist/esm/tests/dwn-process-message.spec.d.ts.map +0 -1
- package/dist/esm/tests/dwn-process-message.spec.js +0 -172
- package/dist/esm/tests/dwn-process-message.spec.js.map +0 -1
- package/dist/esm/tests/dwn-server.spec.d.ts +0 -2
- package/dist/esm/tests/dwn-server.spec.d.ts.map +0 -1
- package/dist/esm/tests/dwn-server.spec.js +0 -49
- package/dist/esm/tests/dwn-server.spec.js.map +0 -1
- package/dist/esm/tests/http-api.spec.d.ts +0 -2
- package/dist/esm/tests/http-api.spec.d.ts.map +0 -1
- package/dist/esm/tests/http-api.spec.js +0 -775
- package/dist/esm/tests/http-api.spec.js.map +0 -1
- package/dist/esm/tests/json-rpc-socket.spec.d.ts +0 -2
- package/dist/esm/tests/json-rpc-socket.spec.d.ts.map +0 -1
- package/dist/esm/tests/json-rpc-socket.spec.js +0 -225
- package/dist/esm/tests/json-rpc-socket.spec.js.map +0 -1
- package/dist/esm/tests/plugins/data-store-sqlite.d.ts +0 -17
- package/dist/esm/tests/plugins/data-store-sqlite.d.ts.map +0 -1
- package/dist/esm/tests/plugins/data-store-sqlite.js +0 -23
- package/dist/esm/tests/plugins/data-store-sqlite.js.map +0 -1
- package/dist/esm/tests/plugins/event-log-sqlite.d.ts +0 -17
- package/dist/esm/tests/plugins/event-log-sqlite.d.ts.map +0 -1
- package/dist/esm/tests/plugins/event-log-sqlite.js +0 -23
- package/dist/esm/tests/plugins/event-log-sqlite.js.map +0 -1
- package/dist/esm/tests/plugins/event-stream-in-memory.d.ts +0 -17
- package/dist/esm/tests/plugins/event-stream-in-memory.d.ts.map +0 -1
- package/dist/esm/tests/plugins/event-stream-in-memory.js +0 -21
- package/dist/esm/tests/plugins/event-stream-in-memory.js.map +0 -1
- package/dist/esm/tests/plugins/message-store-sqlite.d.ts +0 -17
- package/dist/esm/tests/plugins/message-store-sqlite.d.ts.map +0 -1
- package/dist/esm/tests/plugins/message-store-sqlite.js +0 -23
- package/dist/esm/tests/plugins/message-store-sqlite.js.map +0 -1
- package/dist/esm/tests/plugins/resumable-task-store-sqlite.d.ts +0 -17
- package/dist/esm/tests/plugins/resumable-task-store-sqlite.d.ts.map +0 -1
- package/dist/esm/tests/plugins/resumable-task-store-sqlite.js +0 -23
- package/dist/esm/tests/plugins/resumable-task-store-sqlite.js.map +0 -1
- package/dist/esm/tests/process-handler.spec.d.ts +0 -2
- package/dist/esm/tests/process-handler.spec.d.ts.map +0 -1
- package/dist/esm/tests/process-handler.spec.js +0 -60
- package/dist/esm/tests/process-handler.spec.js.map +0 -1
- package/dist/esm/tests/registration/proof-of-work-manager.spec.d.ts +0 -2
- package/dist/esm/tests/registration/proof-of-work-manager.spec.d.ts.map +0 -1
- package/dist/esm/tests/registration/proof-of-work-manager.spec.js +0 -157
- package/dist/esm/tests/registration/proof-of-work-manager.spec.js.map +0 -1
- package/dist/esm/tests/rpc-subscribe-close.spec.d.ts +0 -2
- package/dist/esm/tests/rpc-subscribe-close.spec.d.ts.map +0 -1
- package/dist/esm/tests/rpc-subscribe-close.spec.js +0 -81
- package/dist/esm/tests/rpc-subscribe-close.spec.js.map +0 -1
- package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.d.ts +0 -2
- package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.d.ts.map +0 -1
- package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.js +0 -73
- package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.js.map +0 -1
- package/dist/esm/tests/scenarios/registration.spec.d.ts +0 -2
- package/dist/esm/tests/scenarios/registration.spec.d.ts.map +0 -1
- package/dist/esm/tests/scenarios/registration.spec.js +0 -507
- package/dist/esm/tests/scenarios/registration.spec.js.map +0 -1
- package/dist/esm/tests/scenarios/web5-connect.spec.d.ts +0 -2
- package/dist/esm/tests/scenarios/web5-connect.spec.d.ts.map +0 -1
- package/dist/esm/tests/scenarios/web5-connect.spec.js +0 -137
- package/dist/esm/tests/scenarios/web5-connect.spec.js.map +0 -1
- package/dist/esm/tests/test-dwn.d.ts +0 -7
- package/dist/esm/tests/test-dwn.d.ts.map +0 -1
- package/dist/esm/tests/test-dwn.js +0 -34
- package/dist/esm/tests/test-dwn.js.map +0 -1
- package/dist/esm/tests/utils.d.ts +0 -46
- package/dist/esm/tests/utils.d.ts.map +0 -1
- package/dist/esm/tests/utils.js +0 -116
- package/dist/esm/tests/utils.js.map +0 -1
- package/dist/esm/tests/ws-api.spec.d.ts +0 -2
- package/dist/esm/tests/ws-api.spec.d.ts.map +0 -1
- package/dist/esm/tests/ws-api.spec.js +0 -327
- package/dist/esm/tests/ws-api.spec.js.map +0 -1
- package/src/json-rpc-socket.ts +0 -155
- package/src/lib/http-server-shutdown-handler.ts +0 -79
- package/src/lib/json-rpc.ts +0 -126
- package/src/registration/proof-of-work-types.ts +0 -7
- package/src/registration/registration-types.ts +0 -18
package/src/http-api.ts
CHANGED
|
@@ -1,541 +1,885 @@
|
|
|
1
|
+
import type { JsonRpcRequest } from '@enbox/dwn-clients';
|
|
1
2
|
import type { RecordsReadReply } from '@enbox/dwn-sdk-js';
|
|
2
|
-
import
|
|
3
|
+
import type { ServerInfo } from '@enbox/dwn-clients';
|
|
4
|
+
import type { Server, ServerWebSocket } from 'bun';
|
|
5
|
+
|
|
6
|
+
import type { ActivityLog } from './admin/activity-log.js';
|
|
7
|
+
import type { AdminApi } from './admin/admin-api.js';
|
|
8
|
+
import type { AdminStore } from './admin/admin-store.js';
|
|
9
|
+
import type { DwnServerConfig } from './config.js';
|
|
10
|
+
import type { DwnServerError } from './dwn-error.js';
|
|
11
|
+
import type { MessageProcessedHook } from './message-processed-hook.js';
|
|
12
|
+
import type { OpenAuthHandler } from './registration/open-auth-handler.js';
|
|
13
|
+
import type { RateLimiter } from './rate-limiter.js';
|
|
14
|
+
import type { RegistrationManager } from './registration/registration-manager.js';
|
|
15
|
+
import type { RegistrationStore } from './registration/registration-store.js';
|
|
16
|
+
import type { RequestContext } from './lib/json-rpc-router.js';
|
|
17
|
+
import type { SocketConnection } from './connection/socket-connection.js';
|
|
3
18
|
|
|
4
|
-
import cors from 'cors';
|
|
5
|
-
import type { Express, Request, Response } from 'express';
|
|
6
|
-
import express from 'express';
|
|
7
|
-
import { readFileSync } from 'fs';
|
|
8
|
-
import http from 'http';
|
|
9
19
|
import log from 'loglevel';
|
|
20
|
+
|
|
21
|
+
import { Convert } from '@enbox/common';
|
|
10
22
|
import { register } from 'prom-client';
|
|
11
|
-
import responseTime from 'response-time';
|
|
12
23
|
import { v4 as uuidv4 } from 'uuid';
|
|
24
|
+
import { createJsonRpcErrorResponse, JsonRpcErrorCodes } from '@enbox/dwn-clients';
|
|
25
|
+
import { DataStream, DateSort, type Dwn, ProtocolsQuery, RecordsQuery, RecordsRead } from '@enbox/dwn-sdk-js';
|
|
26
|
+
import { existsSync, readFileSync } from 'fs';
|
|
27
|
+
import { join, resolve } from 'path';
|
|
13
28
|
|
|
14
|
-
import type { RequestContext } from './lib/json-rpc-router.js';
|
|
15
|
-
import type { JsonRpcRequest } from './lib/json-rpc.js';
|
|
16
|
-
|
|
17
|
-
import type { DwnServerConfig } from './config.js';
|
|
18
|
-
import type { DwnServerError } from './dwn-error.js';
|
|
19
|
-
import type { RegistrationManager } from './registration/registration-manager.js';
|
|
20
29
|
import { config } from './config.js';
|
|
21
30
|
import { jsonRpcRouter } from './json-rpc-api.js';
|
|
31
|
+
import { validateAdminAuth } from './admin/admin-auth.js';
|
|
22
32
|
import { Web5ConnectServer } from './web5-connect/web5-connect-server.js';
|
|
23
|
-
import { createJsonRpcErrorResponse, JsonRpcErrorCodes } from './lib/json-rpc.js';
|
|
24
33
|
import { requestCounter, responseHistogram } from './metrics.js';
|
|
25
|
-
import { Convert } from '@enbox/common';
|
|
26
34
|
|
|
35
|
+
/** Property names that must never be used as keys when building objects from user input. */
|
|
36
|
+
const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
37
|
+
|
|
38
|
+
// Resolve admin UI dist path at module load time. Gracefully handle the case
|
|
39
|
+
// where the admin UI package is not installed.
|
|
40
|
+
let resolvedAdminUiPath: string | undefined;
|
|
41
|
+
try {
|
|
42
|
+
const adminUiModule = require('@enbox/dwn-server-admin-ui');
|
|
43
|
+
resolvedAdminUiPath = adminUiModule.adminUiDistPath;
|
|
44
|
+
} catch {
|
|
45
|
+
// Admin UI package not installed — static serving will be disabled.
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Data attached to each Bun WebSocket via `ws.data`. */
|
|
49
|
+
export interface WsData {
|
|
50
|
+
connection: SocketConnection;
|
|
51
|
+
}
|
|
27
52
|
|
|
28
53
|
export class HttpApi {
|
|
29
54
|
#config: DwnServerConfig;
|
|
30
55
|
#packageInfo: { version?: string, sdkVersion?: string, server: string };
|
|
31
|
-
#
|
|
32
|
-
#
|
|
56
|
+
#server!: Server<WsData>;
|
|
57
|
+
#adminApi: AdminApi | undefined;
|
|
58
|
+
#activityLog: ActivityLog | undefined;
|
|
59
|
+
#adminStore: AdminStore | undefined;
|
|
60
|
+
#registrationStore: RegistrationStore | undefined;
|
|
61
|
+
#ipRateLimiter: RateLimiter | undefined;
|
|
62
|
+
#tenantRateLimiter: RateLimiter | undefined;
|
|
63
|
+
#messageProcessedHooks: MessageProcessedHook[];
|
|
64
|
+
#openAuthHandler: OpenAuthHandler | undefined;
|
|
65
|
+
#adminUiPath: string | undefined;
|
|
33
66
|
web5ConnectServer: Web5ConnectServer;
|
|
34
67
|
registrationManager: RegistrationManager;
|
|
35
68
|
dwn: Dwn;
|
|
36
69
|
|
|
70
|
+
/** Called by WsApi/ConnectionManager when a new WS connection is established. */
|
|
71
|
+
onWebSocketConnection?: (ws: ServerWebSocket<WsData>) => void;
|
|
72
|
+
|
|
37
73
|
private constructor() { }
|
|
38
74
|
|
|
39
|
-
public static async create(
|
|
75
|
+
public static async create(
|
|
76
|
+
config: DwnServerConfig, dwn: Dwn, registrationManager?: RegistrationManager,
|
|
77
|
+
adminApi?: AdminApi, activityLog?: ActivityLog,
|
|
78
|
+
options?: {
|
|
79
|
+
adminStore? : AdminStore;
|
|
80
|
+
registrationStore? : RegistrationStore;
|
|
81
|
+
ipRateLimiter? : RateLimiter;
|
|
82
|
+
tenantRateLimiter? : RateLimiter;
|
|
83
|
+
messageProcessedHooks? : MessageProcessedHook[];
|
|
84
|
+
openAuthHandler? : OpenAuthHandler;
|
|
85
|
+
},
|
|
86
|
+
): Promise<HttpApi> {
|
|
40
87
|
const httpApi = new HttpApi();
|
|
41
88
|
|
|
42
|
-
log.info(config);
|
|
89
|
+
log.info(HttpApi.#redactConfig(config));
|
|
43
90
|
|
|
44
91
|
httpApi.#packageInfo = {
|
|
45
92
|
server: config.serverName,
|
|
46
93
|
};
|
|
47
|
-
|
|
94
|
+
|
|
48
95
|
try {
|
|
49
|
-
// We populate the `version` and `sdkVersion` properties from the `package.json` file.
|
|
50
96
|
const packageJson = JSON.parse(readFileSync(config.packageJsonPath).toString());
|
|
51
97
|
httpApi.#packageInfo.version = packageJson.version;
|
|
52
|
-
httpApi.#packageInfo.sdkVersion = packageJson.dependencies
|
|
98
|
+
httpApi.#packageInfo.sdkVersion = packageJson.dependencies
|
|
99
|
+
? packageJson.dependencies['@enbox/dwn-sdk-js']
|
|
100
|
+
: undefined;
|
|
53
101
|
} catch (error: any) {
|
|
54
102
|
log.info('could not read `package.json` for version info', error);
|
|
55
103
|
}
|
|
56
104
|
|
|
57
105
|
httpApi.#config = config;
|
|
58
|
-
httpApi.#api = express();
|
|
59
|
-
httpApi.#server = http.createServer(httpApi.#api);
|
|
60
106
|
httpApi.dwn = dwn;
|
|
107
|
+
httpApi.#adminApi = adminApi;
|
|
108
|
+
httpApi.#activityLog = activityLog;
|
|
109
|
+
httpApi.#adminStore = options?.adminStore;
|
|
110
|
+
httpApi.#registrationStore = options?.registrationStore;
|
|
111
|
+
httpApi.#ipRateLimiter = options?.ipRateLimiter;
|
|
112
|
+
httpApi.#tenantRateLimiter = options?.tenantRateLimiter;
|
|
113
|
+
httpApi.#messageProcessedHooks = options?.messageProcessedHooks ?? [];
|
|
114
|
+
httpApi.#openAuthHandler = options?.openAuthHandler;
|
|
115
|
+
httpApi.#adminUiPath = resolvedAdminUiPath;
|
|
61
116
|
|
|
62
117
|
if (registrationManager !== undefined) {
|
|
63
118
|
httpApi.registrationManager = registrationManager;
|
|
64
119
|
}
|
|
65
120
|
|
|
66
|
-
// create the Web5 Connect Server
|
|
67
121
|
httpApi.web5ConnectServer = await Web5ConnectServer.create({
|
|
68
|
-
baseUrl: config.baseUrl,
|
|
69
|
-
sqlTtlCacheUrl: config.ttlCacheUrl,
|
|
122
|
+
baseUrl : config.baseUrl,
|
|
123
|
+
sqlTtlCacheUrl : config.ttlCacheUrl,
|
|
70
124
|
});
|
|
71
125
|
|
|
72
|
-
httpApi.#setupMiddleware();
|
|
73
|
-
httpApi.#setupRoutes();
|
|
74
|
-
|
|
75
126
|
return httpApi;
|
|
76
127
|
}
|
|
77
128
|
|
|
78
|
-
get server():
|
|
129
|
+
get server(): Server<WsData> {
|
|
79
130
|
return this.#server;
|
|
80
131
|
}
|
|
81
132
|
|
|
82
|
-
get
|
|
83
|
-
return this.#
|
|
133
|
+
get ipRateLimiter(): RateLimiter | undefined {
|
|
134
|
+
return this.#ipRateLimiter;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
get tenantRateLimiter(): RateLimiter | undefined {
|
|
138
|
+
return this.#tenantRateLimiter;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
get messageProcessedHooks(): MessageProcessedHook[] {
|
|
142
|
+
return this.#messageProcessedHooks;
|
|
84
143
|
}
|
|
85
144
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
this.#
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// HTTP request handler
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
async start(port: number): Promise<void> {
|
|
150
|
+
const self = this; // capture for closures
|
|
151
|
+
|
|
152
|
+
this.#server = Bun.serve<WsData>({
|
|
153
|
+
port,
|
|
154
|
+
|
|
155
|
+
async fetch(req: Request, server): Promise<Response | undefined> {
|
|
156
|
+
const startTime = performance.now();
|
|
157
|
+
const url = new URL(req.url);
|
|
158
|
+
const path = url.pathname;
|
|
159
|
+
const method = req.method;
|
|
160
|
+
|
|
161
|
+
// --- WebSocket upgrade ---
|
|
162
|
+
if (method === 'GET' && req.headers.get('upgrade') === 'websocket') {
|
|
163
|
+
const upgraded = server.upgrade(req, { data: { connection: null } });
|
|
164
|
+
if (upgraded) {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
return new Response('WebSocket upgrade failed', { status: 400 });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// --- Per-IP rate limiting ---
|
|
171
|
+
if (self.#ipRateLimiter) {
|
|
172
|
+
const ip = server.requestIP(req)?.address ?? 'unknown';
|
|
173
|
+
const result = self.#ipRateLimiter.consume(ip);
|
|
174
|
+
if (result.allowed === false) {
|
|
175
|
+
const retryAfterSec = Math.ceil(result.retryAfterMs / 1000);
|
|
176
|
+
return new Response(
|
|
177
|
+
JSON.stringify({ error: 'Rate limit exceeded' }),
|
|
178
|
+
{
|
|
179
|
+
status : 429,
|
|
180
|
+
headers : {
|
|
181
|
+
'content-type' : 'application/json',
|
|
182
|
+
'retry-after' : String(retryAfterSec),
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// --- Route matching ---
|
|
190
|
+
let response: Response;
|
|
191
|
+
try {
|
|
192
|
+
response = await self.#route(req, url, path, method);
|
|
193
|
+
} catch (error) {
|
|
194
|
+
log.error(`Unhandled error on ${method} ${path}:`, error);
|
|
195
|
+
response = new Response('Internal Server Error', { status: 500 });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// --- CORS headers ---
|
|
199
|
+
// Admin API and metrics endpoints do not receive wildcard CORS headers
|
|
200
|
+
// to limit cross-origin access when the admin token is configured.
|
|
201
|
+
const isAdminRoute = path.startsWith('/admin') || path === '/metrics';
|
|
202
|
+
if (!isAdminRoute) {
|
|
203
|
+
response.headers.set('access-control-allow-origin', '*');
|
|
204
|
+
response.headers.set('access-control-allow-methods', 'GET, POST, OPTIONS');
|
|
205
|
+
response.headers.set('access-control-allow-headers', '*');
|
|
206
|
+
response.headers.set('access-control-expose-headers', 'dwn-response');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// --- Response-time metrics ---
|
|
210
|
+
const elapsed = performance.now() - startTime;
|
|
211
|
+
const routeLabel = (method + (path === '/' ? '/jsonrpc' : path))
|
|
97
212
|
.toLowerCase()
|
|
98
213
|
.replace(/[:.]/g, '')
|
|
99
214
|
.replace(/\//g, '_');
|
|
215
|
+
responseHistogram.labels(routeLabel, String(response.status)).observe(elapsed);
|
|
216
|
+
log.info(method, decodeURI(path), response.status);
|
|
100
217
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
218
|
+
return response;
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
websocket: {
|
|
222
|
+
maxPayloadLength: self.#config.maxRecordDataSize,
|
|
223
|
+
open(ws: ServerWebSocket<WsData>): void {
|
|
224
|
+
if (self.onWebSocketConnection) {
|
|
225
|
+
self.onWebSocketConnection(ws);
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
message(ws: ServerWebSocket<WsData>, msg: string | Buffer): void {
|
|
229
|
+
const connection = ws.data?.connection;
|
|
230
|
+
if (connection) {
|
|
231
|
+
connection.message(typeof msg === 'string' ? Buffer.from(msg) : msg as Buffer);
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
close(ws: ServerWebSocket<WsData>): void {
|
|
235
|
+
const connection = ws.data?.connection;
|
|
236
|
+
if (connection) {
|
|
237
|
+
connection.close();
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
pong(ws: ServerWebSocket<WsData>): void {
|
|
241
|
+
const connection = ws.data?.connection;
|
|
242
|
+
if (connection) {
|
|
243
|
+
connection.pong();
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
});
|
|
106
248
|
}
|
|
107
249
|
|
|
250
|
+
async close(): Promise<void> {
|
|
251
|
+
if (this.#openAuthHandler) {
|
|
252
|
+
this.#openAuthHandler.destroy();
|
|
253
|
+
}
|
|
254
|
+
if (this.#server) {
|
|
255
|
+
this.#server.stop(true); // close all connections immediately
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
260
|
+
// Admin UI static file serving
|
|
261
|
+
// ---------------------------------------------------------------------------
|
|
262
|
+
|
|
108
263
|
/**
|
|
109
|
-
*
|
|
264
|
+
* Serves static files from the admin UI dist directory. Returns `null` when
|
|
265
|
+
* the admin UI package is not installed or the requested file does not exist.
|
|
266
|
+
* All non-file paths under `/admin` fall back to `index.html` (SPA routing).
|
|
110
267
|
*/
|
|
111
|
-
#
|
|
268
|
+
#serveAdminUi(path: string): Response | null {
|
|
269
|
+
if (!this.#adminUiPath) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
112
272
|
|
|
113
|
-
|
|
273
|
+
// Strip the `/admin` prefix to get the file path within the dist directory.
|
|
274
|
+
const relativePath = path.replace(/^\/admin\/?/, '');
|
|
114
275
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
276
|
+
// Map to a file on disk. Empty path or paths without an extension get
|
|
277
|
+
// the SPA index.html (client-side routing).
|
|
278
|
+
let filePath: string;
|
|
279
|
+
if (relativePath === '' || !relativePath.includes('.')) {
|
|
280
|
+
filePath = join(this.#adminUiPath, 'index.html');
|
|
281
|
+
} else {
|
|
282
|
+
filePath = join(this.#adminUiPath, relativePath);
|
|
283
|
+
}
|
|
119
284
|
|
|
120
|
-
|
|
121
|
-
|
|
285
|
+
// Prevent path traversal: resolved path must stay within the admin UI directory.
|
|
286
|
+
const resolvedBase = resolve(this.#adminUiPath);
|
|
287
|
+
if (!resolve(filePath).startsWith(resolvedBase)) {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
122
290
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
return res.sendStatus(400);
|
|
126
|
-
}
|
|
127
|
-
} else if (reply.status.code === 401) {
|
|
128
|
-
return res.sendStatus(404);
|
|
129
|
-
} else {
|
|
130
|
-
return res.status(reply.status.code).send(reply);
|
|
131
|
-
}
|
|
291
|
+
if (!existsSync(filePath)) {
|
|
292
|
+
return null;
|
|
132
293
|
}
|
|
133
294
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
295
|
+
const file = Bun.file(filePath);
|
|
296
|
+
return new Response(file);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// ---------------------------------------------------------------------------
|
|
300
|
+
// Router
|
|
301
|
+
// ---------------------------------------------------------------------------
|
|
138
302
|
|
|
139
|
-
|
|
303
|
+
async #route(req: Request, url: URL, path: string, method: string): Promise<Response> {
|
|
304
|
+
// --- CORS preflight ---
|
|
305
|
+
if (method === 'OPTIONS') {
|
|
306
|
+
return new Response(null, { status: 204 });
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// --- Static routes ---
|
|
310
|
+
if (method === 'GET' && path === '/health') {
|
|
311
|
+
return Response.json({ ok: true });
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (method === 'GET' && path === '/metrics') {
|
|
315
|
+
// Metrics require admin authentication when an admin token is configured.
|
|
316
|
+
if (this.#config.adminToken) {
|
|
317
|
+
const authError = validateAdminAuth(req, this.#config);
|
|
318
|
+
if (authError) {
|
|
319
|
+
return authError;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
140
322
|
try {
|
|
141
|
-
|
|
142
|
-
|
|
323
|
+
const metricsBody = await register.metrics();
|
|
324
|
+
return new Response(metricsBody, {
|
|
325
|
+
headers: { 'content-type': register.contentType },
|
|
326
|
+
});
|
|
143
327
|
} catch (e) {
|
|
144
|
-
|
|
328
|
+
return new Response(String(e), { status: 500 });
|
|
145
329
|
}
|
|
146
|
-
}
|
|
330
|
+
}
|
|
147
331
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
332
|
+
if (method === 'GET' && path === '/') {
|
|
333
|
+
return new Response(
|
|
334
|
+
'please use am enbox client, for example: https://github.com/enboxorg/enbox ',
|
|
335
|
+
{ headers: { 'content-type': 'text/plain' } },
|
|
336
|
+
);
|
|
337
|
+
}
|
|
153
338
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
for (const param in req.query) {
|
|
158
|
-
const keys = param.split('.');
|
|
159
|
-
const lastKey = keys.pop();
|
|
160
|
-
const lastLevelObject = keys.reduce((obj, key) => obj[key] = obj[key] || {}, queryOptions)
|
|
161
|
-
lastLevelObject[lastKey] = req.query[param];
|
|
162
|
-
}
|
|
339
|
+
if (method === 'GET' && path === '/info') {
|
|
340
|
+
return this.#handleInfo();
|
|
341
|
+
}
|
|
163
342
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
queryOptions.filter.protocolPath = req.params[0].replace(leadTailSlashRegex, '');
|
|
343
|
+
// --- JSON-RPC POST ---
|
|
344
|
+
if (method === 'POST' && path === '/') {
|
|
345
|
+
return this.#handleJsonRpcPost(req);
|
|
346
|
+
}
|
|
169
347
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
});
|
|
348
|
+
// --- Admin API routes ---
|
|
349
|
+
if (path.startsWith('/admin/api/') && this.#adminApi) {
|
|
350
|
+
return this.#adminApi.route(req, url, path, method);
|
|
351
|
+
}
|
|
175
352
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
filter: { recordId: entries[0].recordId },
|
|
182
|
-
});
|
|
183
|
-
const reply = await this.dwn.processMessage(req.params.did, record.toJSON());
|
|
184
|
-
return readReplyHandler(res, reply);
|
|
185
|
-
} else {
|
|
186
|
-
return res.sendStatus(404);
|
|
187
|
-
}
|
|
188
|
-
} else if (status.code === 401) {
|
|
189
|
-
return res.sendStatus(404);
|
|
190
|
-
} else {
|
|
191
|
-
return res.sendStatus(status.code);
|
|
192
|
-
}
|
|
193
|
-
} catch(error) {
|
|
194
|
-
log.error(`Error processing request: ${decodeURI(req.url)}`, error);
|
|
195
|
-
return res.sendStatus(400);
|
|
353
|
+
// --- Admin UI static files (only when admin API is enabled) ---
|
|
354
|
+
if (method === 'GET' && path.startsWith('/admin') && this.#adminApi) {
|
|
355
|
+
const uiResponse = this.#serveAdminUi(path);
|
|
356
|
+
if (uiResponse) {
|
|
357
|
+
return uiResponse;
|
|
196
358
|
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
this.#api.get('/:did/read/protocols/:protocol', async (req, res) => {
|
|
200
|
-
// wrap request in a try-catch block to handle any unexpected errors
|
|
201
|
-
try {
|
|
359
|
+
}
|
|
202
360
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
filter: { protocol }
|
|
208
|
-
});
|
|
209
|
-
const { entries, status } = await this.dwn.processMessage(req.params.did, query.message);
|
|
210
|
-
if (status.code === 200) {
|
|
211
|
-
if (entries.length) {
|
|
212
|
-
res.status(status.code);
|
|
213
|
-
res.json(entries[0]);
|
|
214
|
-
} else {
|
|
215
|
-
return res.sendStatus(404);
|
|
216
|
-
}
|
|
217
|
-
} else if (status.code === 401) {
|
|
218
|
-
return res.sendStatus(404);
|
|
219
|
-
} else {
|
|
220
|
-
return res.sendStatus(status.code);
|
|
221
|
-
}
|
|
222
|
-
} catch(error) {
|
|
223
|
-
log.error(`Error processing request: ${decodeURI(req.url)}`, error);
|
|
224
|
-
return res.sendStatus(400);
|
|
361
|
+
// --- Provider auth (open-auth) routes ---
|
|
362
|
+
if (this.#openAuthHandler && path.startsWith('/provider-auth/')) {
|
|
363
|
+
if (method === 'GET' && path === '/provider-auth/authorize') {
|
|
364
|
+
return this.#openAuthHandler.handleAuthorize(url);
|
|
225
365
|
}
|
|
226
|
-
|
|
366
|
+
if (method === 'POST' && path === '/provider-auth/token') {
|
|
367
|
+
return this.#openAuthHandler.handleToken(req);
|
|
368
|
+
}
|
|
369
|
+
if (method === 'POST' && path === '/provider-auth/refresh') {
|
|
370
|
+
return this.#openAuthHandler.handleRefresh(req);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
227
373
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const reply = await this.dwn.processMessage(req.params.did, record.message);
|
|
233
|
-
return readReplyHandler(res, reply);
|
|
374
|
+
// --- Registration routes ---
|
|
375
|
+
const registrationResponse = await this.#matchRegistrationRoutes(req, path, method);
|
|
376
|
+
if (registrationResponse) {
|
|
377
|
+
return registrationResponse;
|
|
234
378
|
}
|
|
235
379
|
|
|
236
|
-
|
|
237
|
-
this.#
|
|
380
|
+
// --- Web5 Connect routes ---
|
|
381
|
+
const connectResponse = await this.#matchWeb5ConnectRoutes(req, path, method);
|
|
382
|
+
if (connectResponse) {
|
|
383
|
+
return connectResponse;
|
|
384
|
+
}
|
|
238
385
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if (status.code === 200) {
|
|
243
|
-
res.status(status.code);
|
|
244
|
-
res.json(entries);
|
|
245
|
-
} else if (status.code === 401) {
|
|
246
|
-
return res.sendStatus(404);
|
|
247
|
-
} else {
|
|
248
|
-
return res.sendStatus(status.code);
|
|
249
|
-
}
|
|
250
|
-
});
|
|
386
|
+
// --- DID routes (parameterized) ---
|
|
387
|
+
return this.#matchDidRoutes(req, url, path);
|
|
388
|
+
}
|
|
251
389
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
// builds a nested object from flat keys with dot notation which may share the same parent path
|
|
256
|
-
// e.g. "did:dht:123/query?filter.protocol=foo&filter.protocolPath=bar" becomes
|
|
257
|
-
// {
|
|
258
|
-
// filter: {
|
|
259
|
-
// protocol: 'foo',
|
|
260
|
-
// protocolPath: 'bar'
|
|
261
|
-
// }
|
|
262
|
-
// }
|
|
263
|
-
const recordsQueryOptions = {} as any;
|
|
264
|
-
for (const param in req.query) {
|
|
265
|
-
const keys = param.split('.');
|
|
266
|
-
const lastKey = keys.pop();
|
|
267
|
-
const lastLevelObject = keys.reduce((obj, key) => obj[key] = obj[key] || {}, recordsQueryOptions)
|
|
268
|
-
lastLevelObject[lastKey] = req.query[param];
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
const recordsQuery = await RecordsQuery.create({
|
|
272
|
-
filter: recordsQueryOptions.filter,
|
|
273
|
-
pagination: recordsQueryOptions.pagination,
|
|
274
|
-
dateSort: recordsQueryOptions.dateSort,
|
|
275
|
-
});
|
|
390
|
+
// ---------------------------------------------------------------------------
|
|
391
|
+
// DID convenience routes
|
|
392
|
+
// ---------------------------------------------------------------------------
|
|
276
393
|
|
|
277
|
-
|
|
278
|
-
|
|
394
|
+
async #matchDidRoutes(req: Request, url: URL, path: string): Promise<Response> {
|
|
395
|
+
const leadTailSlashRegex = /^\/|\/$/g;
|
|
279
396
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
397
|
+
// /:did/read/protocols/:protocol/* (also matches trailing slash with empty path)
|
|
398
|
+
{
|
|
399
|
+
const match = path.match(/^\/([^/]+)\/read\/protocols\/([^/]+)\/(.*)$/);
|
|
400
|
+
if (match && req.method === 'GET') {
|
|
401
|
+
const [, did, protocolParam, protocolPathRaw] = match;
|
|
402
|
+
return this.#handleReadProtocolRecord(did, protocolParam, protocolPathRaw, url, leadTailSlashRegex);
|
|
285
403
|
}
|
|
286
|
-
}
|
|
404
|
+
}
|
|
287
405
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
406
|
+
// /:did/read/protocols/:protocol
|
|
407
|
+
{
|
|
408
|
+
const match = path.match(/^\/([^/]+)\/read\/protocols\/([^/]+)$/);
|
|
409
|
+
if (match && req.method === 'GET') {
|
|
410
|
+
const [, did, protocolParam] = match;
|
|
411
|
+
return this.#handleReadProtocol(did, protocolParam);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
293
414
|
|
|
294
|
-
|
|
295
|
-
|
|
415
|
+
// /:did/read/records/:id OR /:did/records/:id
|
|
416
|
+
{
|
|
417
|
+
const match = path.match(/^\/([^/]+)\/(?:read\/)?records\/([^/]+)$/);
|
|
418
|
+
if (match && req.method === 'GET') {
|
|
419
|
+
const [, did, recordId] = match;
|
|
420
|
+
return this.#handleReadRecord(did, recordId);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
296
423
|
|
|
297
|
-
|
|
298
|
-
|
|
424
|
+
// /:did/query/protocols
|
|
425
|
+
{
|
|
426
|
+
const match = path.match(/^\/([^/]+)\/query\/protocols$/);
|
|
427
|
+
if (match && req.method === 'GET') {
|
|
428
|
+
const [, did] = match;
|
|
429
|
+
return this.#handleQueryProtocols(did);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
299
432
|
|
|
300
|
-
|
|
433
|
+
// /:did/query
|
|
434
|
+
{
|
|
435
|
+
const match = path.match(/^\/([^/]+)\/query$/);
|
|
436
|
+
if (match && req.method === 'GET') {
|
|
437
|
+
const [, did] = match;
|
|
438
|
+
return this.#handleQueryRecords(did, url);
|
|
301
439
|
}
|
|
440
|
+
}
|
|
302
441
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
dwnRpcRequest = JSON.parse(dwnRpcRequestString);
|
|
306
|
-
} catch (e) {
|
|
307
|
-
const reply = createJsonRpcErrorResponse(uuidv4(), JsonRpcErrorCodes.BadRequest, e.message);
|
|
442
|
+
return new Response('Not Found', { status: 404 });
|
|
443
|
+
}
|
|
308
444
|
|
|
309
|
-
|
|
445
|
+
// ---------------------------------------------------------------------------
|
|
446
|
+
// Security helpers
|
|
447
|
+
// ---------------------------------------------------------------------------
|
|
448
|
+
|
|
449
|
+
/** Returns `true` if the given key is a prototype-pollution-dangerous property name. */
|
|
450
|
+
static #isDangerousKey(key: string | undefined): boolean {
|
|
451
|
+
return key !== undefined && DANGEROUS_KEYS.has(key);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/** Returns `true` if any element in `keys` is a dangerous property name. */
|
|
455
|
+
static #hasDangerousKey(keys: string[]): boolean {
|
|
456
|
+
return keys.some(k => DANGEROUS_KEYS.has(k));
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/** Returns a shallow copy of the config with sensitive values redacted for logging. */
|
|
460
|
+
static #redactConfig(cfg: DwnServerConfig): Record<string, unknown> {
|
|
461
|
+
const redacted: Record<string, unknown> = { ...cfg };
|
|
462
|
+
const sensitiveKeys = ['adminToken', 'providerAuthJwtSecret'];
|
|
463
|
+
for (const key of sensitiveKeys) {
|
|
464
|
+
if (redacted[key]) {
|
|
465
|
+
redacted[key] = '[REDACTED]';
|
|
310
466
|
}
|
|
467
|
+
}
|
|
468
|
+
// Redact passwords in connection-string-like values.
|
|
469
|
+
for (const [key, value] of Object.entries(redacted)) {
|
|
470
|
+
if (typeof value === 'string' && /^(?:postgres|mysql|sqlite):\/\//.test(value) && value.includes('@')) {
|
|
471
|
+
redacted[key] = value.replace(/:\/\/([^:]+):([^@]+)@/, '://$1:****@');
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return redacted;
|
|
475
|
+
}
|
|
311
476
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
const requestDataStream = parseInt(contentLength) > 0 || transferEncoding !== undefined ? req : undefined;
|
|
477
|
+
// ---------------------------------------------------------------------------
|
|
478
|
+
// Handlers
|
|
479
|
+
// ---------------------------------------------------------------------------
|
|
316
480
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
481
|
+
#handleInfo(): Response {
|
|
482
|
+
const registrationRequirements: string[] = [];
|
|
483
|
+
if (config.registrationProofOfWorkEnabled) {
|
|
484
|
+
registrationRequirements.push('proof-of-work-sha256-v0');
|
|
485
|
+
}
|
|
486
|
+
if (config.termsOfServiceFilePath !== undefined) {
|
|
487
|
+
registrationRequirements.push('terms-of-service');
|
|
488
|
+
}
|
|
489
|
+
if (config.providerAuthEnabled && !registrationRequirements.includes('provider-auth-v0')) {
|
|
490
|
+
registrationRequirements.push('provider-auth-v0');
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const serverInfo: ServerInfo = {
|
|
494
|
+
maxFileSize : config.maxRecordDataSize,
|
|
495
|
+
maxInFlight : config.maxInFlight,
|
|
496
|
+
registrationRequirements : registrationRequirements,
|
|
497
|
+
server : this.#packageInfo.server,
|
|
498
|
+
sdkVersion : this.#packageInfo.sdkVersion,
|
|
499
|
+
url : config.baseUrl,
|
|
500
|
+
version : this.#packageInfo.version,
|
|
501
|
+
webSocketSupport : config.webSocketSupport,
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
if (config.providerAuthEnabled) {
|
|
505
|
+
serverInfo.providerAuth = {
|
|
506
|
+
authorizeUrl : config.providerAuthAuthorizeUrl,
|
|
507
|
+
tokenUrl : config.providerAuthTokenUrl,
|
|
508
|
+
refreshUrl : config.providerAuthRefreshUrl,
|
|
509
|
+
managementUrl : config.providerAuthManagementUrl,
|
|
321
510
|
};
|
|
322
|
-
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return Response.json(serverInfo);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
async #handleJsonRpcPost(req: Request): Promise<Response> {
|
|
517
|
+
const dwnRpcRequestString = req.headers.get('dwn-request');
|
|
518
|
+
|
|
519
|
+
if (!dwnRpcRequestString) {
|
|
520
|
+
const reply = createJsonRpcErrorResponse(
|
|
521
|
+
uuidv4(), JsonRpcErrorCodes.BadRequest, 'request payload required.'
|
|
522
|
+
);
|
|
523
|
+
return Response.json(reply, { status: 400 });
|
|
524
|
+
}
|
|
323
525
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
526
|
+
let dwnRpcRequest: JsonRpcRequest;
|
|
527
|
+
try {
|
|
528
|
+
dwnRpcRequest = JSON.parse(dwnRpcRequestString);
|
|
529
|
+
} catch (e) {
|
|
530
|
+
const reply = createJsonRpcErrorResponse(
|
|
531
|
+
uuidv4(), JsonRpcErrorCodes.BadRequest, (e as Error).message
|
|
532
|
+
);
|
|
533
|
+
return Response.json(reply, { status: 400 });
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Materialise the request body before passing to DWN.
|
|
537
|
+
// Bun's Bun.serve() returns a ReadableStream for req.body that is
|
|
538
|
+
// incompatible with the ReadableStream consumer code in dwn-sdk-js,
|
|
539
|
+
// causing DataStream.toBytes() to crash with "undefined is not a
|
|
540
|
+
// function" at reader.releaseLock(). Buffering via arrayBuffer()
|
|
541
|
+
// converts it into a well-behaved stream that dwn-sdk-js can consume.
|
|
542
|
+
// TODO: https://github.com/enboxorg/enbox/issues/90 — remove once Bun ships fix
|
|
543
|
+
const contentLength = req.headers.get('content-length');
|
|
544
|
+
const transferEncoding = req.headers.get('transfer-encoding');
|
|
545
|
+
let requestDataStream: ReadableStream<Uint8Array> | undefined;
|
|
546
|
+
if (parseInt(contentLength ?? '0') > 0 || transferEncoding !== null) {
|
|
547
|
+
const bodyBytes = new Uint8Array(await req.arrayBuffer());
|
|
548
|
+
requestDataStream = DataStream.fromBytes(bodyBytes);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const requestContext: RequestContext = {
|
|
552
|
+
dwn : this.dwn,
|
|
553
|
+
transport : 'http',
|
|
554
|
+
dataStream : requestDataStream,
|
|
555
|
+
activityLog : this.#activityLog,
|
|
556
|
+
adminStore : this.#adminStore,
|
|
557
|
+
registrationStore : this.#registrationStore,
|
|
558
|
+
config : this.#config,
|
|
559
|
+
tenantRateLimiter : this.#tenantRateLimiter,
|
|
560
|
+
messageProcessedHooks : this.#messageProcessedHooks,
|
|
561
|
+
};
|
|
562
|
+
const { jsonRpcResponse, dataStream: responseDataStream } =
|
|
563
|
+
await jsonRpcRouter.handle(dwnRpcRequest, requestContext);
|
|
564
|
+
|
|
565
|
+
if (jsonRpcResponse.error) {
|
|
566
|
+
requestCounter.inc({ method: dwnRpcRequest.method, error: 1 });
|
|
567
|
+
|
|
568
|
+
// Return HTTP 429 with Retry-After header for rate-limit rejections.
|
|
569
|
+
if (jsonRpcResponse.error.code === JsonRpcErrorCodes.TooManyRequests) {
|
|
570
|
+
const retryAfterSec = jsonRpcResponse.error.data?.retryAfterSec ?? 1;
|
|
571
|
+
return Response.json(jsonRpcResponse, {
|
|
572
|
+
status : 429,
|
|
573
|
+
headers : { 'retry-after': String(retryAfterSec) },
|
|
574
|
+
});
|
|
329
575
|
}
|
|
330
576
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
577
|
+
return Response.json(jsonRpcResponse, { status: 500 });
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
requestCounter.inc({
|
|
581
|
+
method : dwnRpcRequest.method,
|
|
582
|
+
status : jsonRpcResponse?.result?.reply?.status?.code || 0,
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
if (responseDataStream) {
|
|
586
|
+
return new Response(responseDataStream, {
|
|
587
|
+
headers: {
|
|
588
|
+
'content-type' : 'application/octet-stream',
|
|
589
|
+
'dwn-response' : JSON.stringify(jsonRpcResponse),
|
|
590
|
+
},
|
|
334
591
|
});
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
592
|
+
} else {
|
|
593
|
+
return Response.json(jsonRpcResponse);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
338
596
|
|
|
339
|
-
|
|
597
|
+
#readReplyToResponse(reply: RecordsReadReply): Response {
|
|
598
|
+
if (reply.status.code === 200) {
|
|
599
|
+
if (reply?.entry?.data) {
|
|
600
|
+
return new Response(reply.entry.data, {
|
|
601
|
+
headers: {
|
|
602
|
+
'content-type' : reply.entry.recordsWrite.descriptor.dataFormat,
|
|
603
|
+
'dwn-response' : JSON.stringify(reply),
|
|
604
|
+
},
|
|
605
|
+
});
|
|
340
606
|
} else {
|
|
341
|
-
return
|
|
607
|
+
return new Response(null, { status: 400 });
|
|
342
608
|
}
|
|
609
|
+
} else if (reply.status.code === 401) {
|
|
610
|
+
return new Response(null, { status: 404 });
|
|
611
|
+
} else {
|
|
612
|
+
return Response.json(reply, { status: reply.status.code });
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
async #handleReadRecord(did: string, recordId: string): Promise<Response> {
|
|
617
|
+
const record = await RecordsRead.create({
|
|
618
|
+
filter: { recordId },
|
|
343
619
|
});
|
|
620
|
+
const reply = await this.dwn.processMessage(did, record.message);
|
|
621
|
+
return this.#readReplyToResponse(reply);
|
|
622
|
+
}
|
|
344
623
|
|
|
345
|
-
|
|
624
|
+
async #handleReadProtocolRecord(
|
|
625
|
+
did: string, protocolParam: string, protocolPathRaw: string,
|
|
626
|
+
url: URL, leadTailSlashRegex: RegExp
|
|
627
|
+
): Promise<Response> {
|
|
628
|
+
if (!protocolPathRaw || protocolPathRaw.replace(leadTailSlashRegex, '') === '') {
|
|
629
|
+
return new Response('protocol path is required', { status: 400 });
|
|
630
|
+
}
|
|
346
631
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
632
|
+
try {
|
|
633
|
+
const queryOptions: Record<string, any> = { filter: {} };
|
|
634
|
+
for (const [param, value] of url.searchParams) {
|
|
635
|
+
const keys = param.split('.');
|
|
636
|
+
const lastKey = keys.pop();
|
|
637
|
+
if (HttpApi.#hasDangerousKey(keys) || HttpApi.#isDangerousKey(lastKey)) {
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
const nestObj = (obj: Record<string, any>, key: string): Record<string, any> =>
|
|
641
|
+
obj[key] = obj[key] || {};
|
|
642
|
+
const lastLevelObject = keys.reduce(nestObj, queryOptions);
|
|
643
|
+
lastLevelObject[lastKey!] = value;
|
|
355
644
|
}
|
|
356
645
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
646
|
+
const protocol = Convert.base64Url(protocolParam).toString();
|
|
647
|
+
queryOptions.filter.protocol = protocol;
|
|
648
|
+
queryOptions.filter.protocolPath = protocolPathRaw.replace(leadTailSlashRegex, '');
|
|
649
|
+
|
|
650
|
+
const query = await RecordsQuery.create({
|
|
651
|
+
filter : queryOptions.filter,
|
|
652
|
+
pagination : { limit: 1 },
|
|
653
|
+
dateSort : DateSort.PublishedDescending,
|
|
365
654
|
});
|
|
366
|
-
});
|
|
367
655
|
|
|
368
|
-
|
|
656
|
+
const { entries, status } = await this.dwn.processMessage(did, query.message);
|
|
657
|
+
|
|
658
|
+
if (status.code === 200) {
|
|
659
|
+
if (entries[0]) {
|
|
660
|
+
const record = await RecordsRead.create({
|
|
661
|
+
filter: { recordId: entries[0].recordId },
|
|
662
|
+
});
|
|
663
|
+
const reply = await this.dwn.processMessage(did, record.toJSON());
|
|
664
|
+
return this.#readReplyToResponse(reply);
|
|
665
|
+
} else {
|
|
666
|
+
return new Response(null, { status: 404 });
|
|
667
|
+
}
|
|
668
|
+
} else if (status.code === 401) {
|
|
669
|
+
return new Response(null, { status: 404 });
|
|
670
|
+
} else {
|
|
671
|
+
return new Response(null, { status: status.code });
|
|
672
|
+
}
|
|
673
|
+
} catch (error) {
|
|
674
|
+
log.error(`Error processing request: ${decodeURI(url.pathname)}`, error);
|
|
675
|
+
return new Response('Bad Request', { status: 400 });
|
|
676
|
+
}
|
|
369
677
|
}
|
|
370
678
|
|
|
371
|
-
#
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
679
|
+
async #handleReadProtocol(did: string, protocolParam: string): Promise<Response> {
|
|
680
|
+
try {
|
|
681
|
+
const protocol = Convert.base64Url(protocolParam).toString();
|
|
682
|
+
const query = await ProtocolsQuery.create({
|
|
683
|
+
filter: { protocol },
|
|
376
684
|
});
|
|
685
|
+
const { entries, status } = await this.dwn.processMessage(did, query.message);
|
|
686
|
+
if (status.code === 200) {
|
|
687
|
+
if (entries.length) {
|
|
688
|
+
return Response.json(entries[0], { status: status.code });
|
|
689
|
+
} else {
|
|
690
|
+
return new Response(null, { status: 404 });
|
|
691
|
+
}
|
|
692
|
+
} else if (status.code === 401) {
|
|
693
|
+
return new Response(null, { status: 404 });
|
|
694
|
+
} else {
|
|
695
|
+
return new Response(null, { status: status.code });
|
|
696
|
+
}
|
|
697
|
+
} catch (error) {
|
|
698
|
+
log.error(`Error processing request`, error);
|
|
699
|
+
return new Response('Bad Request', { status: 400 });
|
|
377
700
|
}
|
|
701
|
+
}
|
|
378
702
|
|
|
379
|
-
|
|
380
|
-
|
|
703
|
+
async #handleQueryProtocols(did: string): Promise<Response> {
|
|
704
|
+
const query = await ProtocolsQuery.create({});
|
|
705
|
+
const { entries, status } = await this.dwn.processMessage(did, query.message);
|
|
706
|
+
if (status.code === 200) {
|
|
707
|
+
return Response.json(entries, { status: status.code });
|
|
708
|
+
} else if (status.code === 401) {
|
|
709
|
+
return new Response(null, { status: 404 });
|
|
710
|
+
} else {
|
|
711
|
+
return new Response(null, { status: status.code });
|
|
381
712
|
}
|
|
713
|
+
}
|
|
382
714
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
715
|
+
async #handleQueryRecords(did: string, url: URL): Promise<Response> {
|
|
716
|
+
try {
|
|
717
|
+
const recordsQueryOptions: Record<string, any> = {};
|
|
718
|
+
for (const [param, value] of url.searchParams) {
|
|
719
|
+
const keys = param.split('.');
|
|
720
|
+
const lastKey = keys.pop();
|
|
721
|
+
if (HttpApi.#hasDangerousKey(keys) || HttpApi.#isDangerousKey(lastKey)) {
|
|
722
|
+
continue;
|
|
723
|
+
}
|
|
724
|
+
const nestObj = (obj: Record<string, any>, key: string): Record<string, any> =>
|
|
725
|
+
obj[key] = obj[key] || {};
|
|
726
|
+
const lastLevelObject = keys.reduce(nestObj, recordsQueryOptions);
|
|
727
|
+
lastLevelObject[lastKey!] = value;
|
|
728
|
+
}
|
|
387
729
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
730
|
+
const recordsQuery = await RecordsQuery.create({
|
|
731
|
+
filter : recordsQueryOptions.filter,
|
|
732
|
+
pagination : recordsQueryOptions.pagination,
|
|
733
|
+
dateSort : recordsQueryOptions.dateSort,
|
|
734
|
+
});
|
|
393
735
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
log.info('Error handling registration request:', error);
|
|
398
|
-
res.status(500).json({ success: false });
|
|
399
|
-
}
|
|
400
|
-
}
|
|
736
|
+
const reply = await this.dwn.processMessage(did, recordsQuery.message);
|
|
737
|
+
return Response.json(reply, {
|
|
738
|
+
headers: { 'content-type': 'application/json' },
|
|
401
739
|
});
|
|
740
|
+
} catch (error) {
|
|
741
|
+
log.error('Error processing query records request', error);
|
|
742
|
+
return Response.json({ error: 'Bad Request' }, { status: 400 });
|
|
402
743
|
}
|
|
403
744
|
}
|
|
404
745
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
746
|
+
// ---------------------------------------------------------------------------
|
|
747
|
+
// Registration routes
|
|
748
|
+
// ---------------------------------------------------------------------------
|
|
749
|
+
|
|
750
|
+
async #matchRegistrationRoutes(
|
|
751
|
+
req: Request, path: string, method: string
|
|
752
|
+
): Promise<Response | null> {
|
|
753
|
+
if (method === 'GET' && path === '/registration/proof-of-work'
|
|
754
|
+
&& this.#config.registrationProofOfWorkEnabled) {
|
|
755
|
+
const proofOfWorkChallenge = this.registrationManager.getProofOfWorkChallenge();
|
|
756
|
+
return Response.json(proofOfWorkChallenge);
|
|
757
|
+
}
|
|
414
758
|
|
|
415
|
-
|
|
416
|
-
|
|
759
|
+
if (method === 'GET' && path === '/registration/terms-of-service'
|
|
760
|
+
&& this.#config.termsOfServiceFilePath !== undefined) {
|
|
761
|
+
return new Response(this.registrationManager.getTermsOfService());
|
|
762
|
+
}
|
|
417
763
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
code: 400,
|
|
423
|
-
message: "Bad Request: Missing 'request' parameter",
|
|
424
|
-
},
|
|
425
|
-
});
|
|
426
|
-
}
|
|
764
|
+
if (method === 'POST' && path === '/registration'
|
|
765
|
+
&& this.#config.registrationStoreUrl !== undefined) {
|
|
766
|
+
const requestBody = await req.json();
|
|
767
|
+
log.info('Registration request received');
|
|
427
768
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
return
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
769
|
+
try {
|
|
770
|
+
await this.registrationManager.handleRegistrationRequest(requestBody);
|
|
771
|
+
return Response.json({ success: true }, { status: 200 });
|
|
772
|
+
} catch (error) {
|
|
773
|
+
const dwnServerError = error as DwnServerError;
|
|
774
|
+
if (dwnServerError.code !== undefined) {
|
|
775
|
+
return Response.json(dwnServerError, { status: 400 });
|
|
776
|
+
} else {
|
|
777
|
+
log.info('Error handling registration request:', error);
|
|
778
|
+
return Response.json({ success: false }, { status: 500 });
|
|
779
|
+
}
|
|
437
780
|
}
|
|
781
|
+
}
|
|
438
782
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
});
|
|
783
|
+
return null;
|
|
784
|
+
}
|
|
442
785
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
this.#api.get('/connect/authorize/:requestId.jwt', async (req, res) => {
|
|
447
|
-
log.info(`Retrieving Web5 Connect Request object of ID: ${req.params.requestId}...`);
|
|
786
|
+
// ---------------------------------------------------------------------------
|
|
787
|
+
// Web5 Connect routes
|
|
788
|
+
// ---------------------------------------------------------------------------
|
|
448
789
|
|
|
449
|
-
|
|
450
|
-
|
|
790
|
+
async #matchWeb5ConnectRoutes(
|
|
791
|
+
req: Request, path: string, method: string
|
|
792
|
+
): Promise<Response | null> {
|
|
793
|
+
// POST /connect/par
|
|
794
|
+
if (method === 'POST' && path === '/connect/par') {
|
|
795
|
+
log.info('Storing Pushed Authorization Request (PAR) request...');
|
|
796
|
+
const body = await req.json();
|
|
451
797
|
|
|
452
|
-
if (!
|
|
453
|
-
|
|
798
|
+
if (!body.request) {
|
|
799
|
+
return Response.json({
|
|
454
800
|
ok : false,
|
|
455
|
-
status : { code:
|
|
456
|
-
});
|
|
457
|
-
} else {
|
|
458
|
-
res.set('Content-Type', 'application/jwt');
|
|
459
|
-
res.send(requestObjectJwt);
|
|
801
|
+
status : { code: 400, message: 'Bad Request: Missing \'request\' parameter' },
|
|
802
|
+
}, { status: 400 });
|
|
460
803
|
}
|
|
461
|
-
});
|
|
462
804
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
805
|
+
if (body?.request?.request_uri) {
|
|
806
|
+
return Response.json({
|
|
807
|
+
ok : false,
|
|
808
|
+
status : { code: 400, message: 'Bad Request: \'request_uri\' parameter is not allowed in PAR' },
|
|
809
|
+
}, { status: 400 });
|
|
810
|
+
}
|
|
468
811
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
812
|
+
const result = await this.web5ConnectServer.setWeb5ConnectRequest(body.request);
|
|
813
|
+
return Response.json(result, { status: 201 });
|
|
814
|
+
}
|
|
472
815
|
|
|
473
|
-
|
|
816
|
+
// GET /connect/authorize/:requestId.jwt
|
|
817
|
+
{
|
|
818
|
+
const match = path.match(/^\/connect\/authorize\/([^/]+)\.jwt$/);
|
|
819
|
+
if (match && method === 'GET') {
|
|
820
|
+
const requestId = match[1];
|
|
821
|
+
log.info(`Retrieving Web5 Connect Request object of ID: ${requestId}...`);
|
|
822
|
+
|
|
823
|
+
const requestObjectJwt = await this.web5ConnectServer.getWeb5ConnectRequest(requestId);
|
|
824
|
+
if (!requestObjectJwt) {
|
|
825
|
+
return Response.json({
|
|
826
|
+
ok : false,
|
|
827
|
+
status : { code: 404, message: 'Not Found' },
|
|
828
|
+
}, { status: 404 });
|
|
829
|
+
} else {
|
|
830
|
+
const body = typeof requestObjectJwt === 'string'
|
|
831
|
+
? requestObjectJwt
|
|
832
|
+
: JSON.stringify(requestObjectJwt);
|
|
833
|
+
return new Response(body, {
|
|
834
|
+
headers: { 'content-type': 'application/jwt' },
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
474
839
|
|
|
475
|
-
|
|
840
|
+
// POST /connect/callback
|
|
841
|
+
if (method === 'POST' && path === '/connect/callback') {
|
|
842
|
+
log.info('Storing Identity Provider (wallet) pushed response with ID token...');
|
|
843
|
+
const body = await req.json();
|
|
844
|
+
const idToken = body.id_token;
|
|
845
|
+
const state = body.state;
|
|
476
846
|
|
|
477
|
-
|
|
847
|
+
if (idToken !== undefined && state != undefined) {
|
|
848
|
+
await this.web5ConnectServer.setWeb5ConnectResponse(state, idToken);
|
|
849
|
+
return Response.json({
|
|
478
850
|
ok : true,
|
|
479
|
-
status : { code: 201, message: 'Created' }
|
|
480
|
-
});
|
|
481
|
-
|
|
851
|
+
status : { code: 201, message: 'Created' },
|
|
852
|
+
}, { status: 201 });
|
|
482
853
|
} else {
|
|
483
|
-
|
|
854
|
+
return Response.json({
|
|
484
855
|
ok : false,
|
|
485
|
-
status : { code: 400, message: 'Bad Request' }
|
|
486
|
-
});
|
|
856
|
+
status : { code: 400, message: 'Bad Request' },
|
|
857
|
+
}, { status: 400 });
|
|
487
858
|
}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
/**
|
|
491
|
-
* Endpoint for the connecting Client to retrieve the Authorization Response
|
|
492
|
-
*/
|
|
493
|
-
this.#api.get('/connect/token/:state.jwt', async (req, res) => {
|
|
494
|
-
log.info(`Retrieving ID token for state: ${req.params.state}...`);
|
|
495
|
-
|
|
496
|
-
// Look up the ID token.
|
|
497
|
-
const idToken = await this.web5ConnectServer.getWeb5ConnectResponse(req.params.state);
|
|
498
|
-
|
|
499
|
-
if (!idToken) {
|
|
500
|
-
res.status(404).json({
|
|
501
|
-
ok : false,
|
|
502
|
-
status : { code: 404, message: 'Not Found' }
|
|
503
|
-
});
|
|
504
|
-
} else {
|
|
505
|
-
res.set('Content-Type', 'application/jwt');
|
|
506
|
-
res.send(idToken);
|
|
507
|
-
}
|
|
508
|
-
});
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
/**
|
|
512
|
-
* Starts the HTTP API endpoint on the given port.
|
|
513
|
-
* @returns The HTTP server instance.
|
|
514
|
-
*/
|
|
515
|
-
async start(port: number): Promise<void> {
|
|
516
|
-
// promisify http.Server.listen() and await on it
|
|
517
|
-
await new Promise<void>((resolve) => {
|
|
518
|
-
this.#server.listen(port, () => {
|
|
519
|
-
resolve();
|
|
520
|
-
});
|
|
521
|
-
});
|
|
522
|
-
}
|
|
859
|
+
}
|
|
523
860
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
861
|
+
// GET /connect/token/:state.jwt
|
|
862
|
+
{
|
|
863
|
+
const match = path.match(/^\/connect\/token\/([^/]+)\.jwt$/);
|
|
864
|
+
if (match && method === 'GET') {
|
|
865
|
+
const state = match[1];
|
|
866
|
+
log.info(`Retrieving ID token for state: ${state}...`);
|
|
867
|
+
|
|
868
|
+
const idToken = await this.web5ConnectServer.getWeb5ConnectResponse(state);
|
|
869
|
+
if (!idToken) {
|
|
870
|
+
return Response.json({
|
|
871
|
+
ok : false,
|
|
872
|
+
status : { code: 404, message: 'Not Found' },
|
|
873
|
+
}, { status: 404 });
|
|
533
874
|
} else {
|
|
534
|
-
|
|
875
|
+
const body = typeof idToken === 'string' ? idToken : JSON.stringify(idToken);
|
|
876
|
+
return new Response(body, {
|
|
877
|
+
headers: { 'content-type': 'application/jwt' },
|
|
878
|
+
});
|
|
535
879
|
}
|
|
536
|
-
}
|
|
537
|
-
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
538
882
|
|
|
539
|
-
|
|
883
|
+
return null;
|
|
540
884
|
}
|
|
541
885
|
}
|