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