@enbox/dwn-server 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (295) hide show
  1. package/LICENSE +3 -2
  2. package/README.md +112 -212
  3. package/dist/esm/src/admin/activity-log.d.ts +44 -0
  4. package/dist/esm/src/admin/activity-log.d.ts.map +1 -0
  5. package/dist/esm/src/admin/activity-log.js +85 -0
  6. package/dist/esm/src/admin/activity-log.js.map +1 -0
  7. package/dist/esm/src/admin/admin-api.d.ts +61 -0
  8. package/dist/esm/src/admin/admin-api.d.ts.map +1 -0
  9. package/dist/esm/src/admin/admin-api.js +1047 -0
  10. package/dist/esm/src/admin/admin-api.js.map +1 -0
  11. package/dist/esm/src/admin/admin-auth.d.ts +9 -0
  12. package/dist/esm/src/admin/admin-auth.d.ts.map +1 -0
  13. package/dist/esm/src/admin/admin-auth.js +45 -0
  14. package/dist/esm/src/admin/admin-auth.js.map +1 -0
  15. package/dist/esm/src/admin/admin-store.d.ts +111 -0
  16. package/dist/esm/src/admin/admin-store.d.ts.map +1 -0
  17. package/dist/esm/src/admin/admin-store.js +376 -0
  18. package/dist/esm/src/admin/admin-store.js.map +1 -0
  19. package/dist/esm/src/admin/audit-log.d.ts +94 -0
  20. package/dist/esm/src/admin/audit-log.d.ts.map +1 -0
  21. package/dist/esm/src/admin/audit-log.js +220 -0
  22. package/dist/esm/src/admin/audit-log.js.map +1 -0
  23. package/dist/esm/src/admin/index.d.ts +10 -0
  24. package/dist/esm/src/admin/index.d.ts.map +1 -0
  25. package/dist/esm/src/admin/index.js +7 -0
  26. package/dist/esm/src/admin/index.js.map +1 -0
  27. package/dist/esm/src/admin/types.d.ts +306 -0
  28. package/dist/esm/src/admin/types.d.ts.map +1 -0
  29. package/dist/esm/src/admin/types.js +2 -0
  30. package/dist/esm/src/admin/types.js.map +1 -0
  31. package/dist/esm/src/admin/webhook-manager.d.ts +55 -0
  32. package/dist/esm/src/admin/webhook-manager.d.ts.map +1 -0
  33. package/dist/esm/src/admin/webhook-manager.js +184 -0
  34. package/dist/esm/src/admin/webhook-manager.js.map +1 -0
  35. package/dist/esm/src/config.d.ts +122 -3
  36. package/dist/esm/src/config.d.ts.map +1 -1
  37. package/dist/esm/src/config.js +151 -5
  38. package/dist/esm/src/config.js.map +1 -1
  39. package/dist/esm/src/connection/connection-manager.d.ts +24 -1
  40. package/dist/esm/src/connection/connection-manager.d.ts.map +1 -1
  41. package/dist/esm/src/connection/connection-manager.js +33 -2
  42. package/dist/esm/src/connection/connection-manager.js.map +1 -1
  43. package/dist/esm/src/connection/flow-controller.d.ts +53 -0
  44. package/dist/esm/src/connection/flow-controller.d.ts.map +1 -0
  45. package/dist/esm/src/connection/flow-controller.js +101 -0
  46. package/dist/esm/src/connection/flow-controller.js.map +1 -0
  47. package/dist/esm/src/connection/socket-connection.d.ts +39 -4
  48. package/dist/esm/src/connection/socket-connection.d.ts.map +1 -1
  49. package/dist/esm/src/connection/socket-connection.js +80 -9
  50. package/dist/esm/src/connection/socket-connection.js.map +1 -1
  51. package/dist/esm/src/delivery-service.d.ts +43 -0
  52. package/dist/esm/src/delivery-service.d.ts.map +1 -0
  53. package/dist/esm/src/delivery-service.js +574 -0
  54. package/dist/esm/src/delivery-service.js.map +1 -0
  55. package/dist/esm/src/dwn-error.d.ts +10 -1
  56. package/dist/esm/src/dwn-error.d.ts.map +1 -1
  57. package/dist/esm/src/dwn-error.js +9 -0
  58. package/dist/esm/src/dwn-error.js.map +1 -1
  59. package/dist/esm/src/dwn-server.d.ts +8 -0
  60. package/dist/esm/src/dwn-server.d.ts.map +1 -1
  61. package/dist/esm/src/dwn-server.js +198 -12
  62. package/dist/esm/src/dwn-server.js.map +1 -1
  63. package/dist/esm/src/http-api.d.ts +19 -2
  64. package/dist/esm/src/http-api.d.ts.map +1 -1
  65. package/dist/esm/src/http-api.js +219 -19
  66. package/dist/esm/src/http-api.js.map +1 -1
  67. package/dist/esm/src/index.d.ts +6 -2
  68. package/dist/esm/src/index.d.ts.map +1 -1
  69. package/dist/esm/src/index.js +4 -1
  70. package/dist/esm/src/index.js.map +1 -1
  71. package/dist/esm/src/json-rpc-api.js +2 -1
  72. package/dist/esm/src/json-rpc-api.js.map +1 -1
  73. package/dist/esm/src/json-rpc-handlers/dwn/process-message.d.ts.map +1 -1
  74. package/dist/esm/src/json-rpc-handlers/dwn/process-message.js +106 -4
  75. package/dist/esm/src/json-rpc-handlers/dwn/process-message.js.map +1 -1
  76. package/dist/esm/src/json-rpc-handlers/subscription/ack.d.ts +20 -0
  77. package/dist/esm/src/json-rpc-handlers/subscription/ack.d.ts.map +1 -0
  78. package/dist/esm/src/json-rpc-handlers/subscription/ack.js +41 -0
  79. package/dist/esm/src/json-rpc-handlers/subscription/ack.js.map +1 -0
  80. package/dist/esm/src/json-rpc-handlers/subscription/close.d.ts.map +1 -1
  81. package/dist/esm/src/json-rpc-handlers/subscription/close.js +1 -1
  82. package/dist/esm/src/json-rpc-handlers/subscription/close.js.map +1 -1
  83. package/dist/esm/src/json-rpc-handlers/subscription/index.d.ts +1 -0
  84. package/dist/esm/src/json-rpc-handlers/subscription/index.d.ts.map +1 -1
  85. package/dist/esm/src/json-rpc-handlers/subscription/index.js +1 -0
  86. package/dist/esm/src/json-rpc-handlers/subscription/index.js.map +1 -1
  87. package/dist/esm/src/lib/json-rpc-router.d.ts +22 -4
  88. package/dist/esm/src/lib/json-rpc-router.d.ts.map +1 -1
  89. package/dist/esm/src/lib/json-rpc-router.js.map +1 -1
  90. package/dist/esm/src/lib/sql-utils.d.ts +6 -0
  91. package/dist/esm/src/lib/sql-utils.d.ts.map +1 -0
  92. package/dist/esm/src/lib/sql-utils.js +8 -0
  93. package/dist/esm/src/lib/sql-utils.js.map +1 -0
  94. package/dist/esm/src/main.js +0 -6
  95. package/dist/esm/src/main.js.map +1 -1
  96. package/dist/esm/src/message-processed-hook.d.ts +35 -0
  97. package/dist/esm/src/message-processed-hook.d.ts.map +1 -0
  98. package/dist/esm/src/message-processed-hook.js +2 -0
  99. package/dist/esm/src/message-processed-hook.js.map +1 -0
  100. package/dist/esm/src/metrics.d.ts +13 -1
  101. package/dist/esm/src/metrics.d.ts.map +1 -1
  102. package/dist/esm/src/metrics.js +41 -1
  103. package/dist/esm/src/metrics.js.map +1 -1
  104. package/dist/esm/src/plugins/event-log-nats.d.ts +25 -0
  105. package/dist/esm/src/plugins/event-log-nats.d.ts.map +1 -0
  106. package/dist/esm/src/plugins/event-log-nats.js +379 -0
  107. package/dist/esm/src/plugins/event-log-nats.js.map +1 -0
  108. package/dist/esm/src/rate-limiter.d.ts +60 -0
  109. package/dist/esm/src/rate-limiter.d.ts.map +1 -0
  110. package/dist/esm/src/rate-limiter.js +116 -0
  111. package/dist/esm/src/rate-limiter.js.map +1 -0
  112. package/dist/esm/src/registration/jwt-provider-auth-plugin.d.ts +53 -0
  113. package/dist/esm/src/registration/jwt-provider-auth-plugin.d.ts.map +1 -0
  114. package/dist/esm/src/registration/jwt-provider-auth-plugin.js +90 -0
  115. package/dist/esm/src/registration/jwt-provider-auth-plugin.js.map +1 -0
  116. package/dist/esm/src/registration/open-auth-handler.d.ts +37 -0
  117. package/dist/esm/src/registration/open-auth-handler.d.ts.map +1 -0
  118. package/dist/esm/src/registration/open-auth-handler.js +214 -0
  119. package/dist/esm/src/registration/open-auth-handler.js.map +1 -0
  120. package/dist/esm/src/registration/proof-of-work-manager.d.ts +1 -1
  121. package/dist/esm/src/registration/proof-of-work-manager.d.ts.map +1 -1
  122. package/dist/esm/src/registration/provider-auth-plugin.d.ts +46 -0
  123. package/dist/esm/src/registration/provider-auth-plugin.d.ts.map +1 -0
  124. package/dist/esm/src/registration/provider-auth-plugin.js +29 -0
  125. package/dist/esm/src/registration/provider-auth-plugin.js.map +1 -0
  126. package/dist/esm/src/registration/registration-manager.d.ts +27 -4
  127. package/dist/esm/src/registration/registration-manager.d.ts.map +1 -1
  128. package/dist/esm/src/registration/registration-manager.js +77 -6
  129. package/dist/esm/src/registration/registration-manager.js.map +1 -1
  130. package/dist/esm/src/registration/registration-store.d.ts +83 -3
  131. package/dist/esm/src/registration/registration-store.d.ts.map +1 -1
  132. package/dist/esm/src/registration/registration-store.js +248 -11
  133. package/dist/esm/src/registration/registration-store.js.map +1 -1
  134. package/dist/esm/src/storage.d.ts +4 -4
  135. package/dist/esm/src/storage.d.ts.map +1 -1
  136. package/dist/esm/src/storage.js +100 -20
  137. package/dist/esm/src/storage.js.map +1 -1
  138. package/dist/esm/src/web5-connect/sql-ttl-cache.d.ts.map +1 -1
  139. package/dist/esm/src/web5-connect/sql-ttl-cache.js +8 -1
  140. package/dist/esm/src/web5-connect/sql-ttl-cache.js.map +1 -1
  141. package/dist/esm/src/ws-api.d.ts +17 -1
  142. package/dist/esm/src/ws-api.d.ts.map +1 -1
  143. package/dist/esm/src/ws-api.js +9 -2
  144. package/dist/esm/src/ws-api.js.map +1 -1
  145. package/package.json +18 -16
  146. package/src/admin/activity-log.ts +100 -0
  147. package/src/admin/admin-api.ts +1308 -0
  148. package/src/admin/admin-auth.ts +56 -0
  149. package/src/admin/admin-store.ts +515 -0
  150. package/src/admin/audit-log.ts +327 -0
  151. package/src/admin/index.ts +34 -0
  152. package/src/admin/types.ts +352 -0
  153. package/src/admin/webhook-manager.ts +245 -0
  154. package/src/config.ts +177 -5
  155. package/src/connection/connection-manager.ts +50 -6
  156. package/src/connection/flow-controller.ts +117 -0
  157. package/src/connection/socket-connection.ts +103 -21
  158. package/src/delivery-service.ts +740 -0
  159. package/src/dwn-error.ts +9 -0
  160. package/src/dwn-server.ts +242 -14
  161. package/src/http-api.ts +271 -30
  162. package/src/index.ts +13 -2
  163. package/src/json-rpc-api.ts +2 -1
  164. package/src/json-rpc-handlers/dwn/process-message.ts +140 -5
  165. package/src/json-rpc-handlers/subscription/ack.ts +63 -0
  166. package/src/json-rpc-handlers/subscription/close.ts +2 -6
  167. package/src/json-rpc-handlers/subscription/index.ts +1 -0
  168. package/src/lib/json-rpc-router.ts +22 -6
  169. package/src/lib/sql-utils.ts +7 -0
  170. package/src/main.ts +0 -8
  171. package/src/message-processed-hook.ts +33 -0
  172. package/src/metrics.ts +50 -1
  173. package/src/plugins/event-log-nats.ts +466 -0
  174. package/src/rate-limiter.ts +143 -0
  175. package/src/registration/jwt-provider-auth-plugin.ts +119 -0
  176. package/src/registration/open-auth-handler.ts +263 -0
  177. package/src/registration/proof-of-work-manager.ts +1 -1
  178. package/src/registration/provider-auth-plugin.ts +84 -0
  179. package/src/registration/registration-manager.ts +108 -12
  180. package/src/registration/registration-store.ts +326 -17
  181. package/src/storage.ts +121 -27
  182. package/src/web5-connect/sql-ttl-cache.ts +7 -1
  183. package/src/ws-api.ts +30 -2
  184. package/dist/esm/src/json-rpc-socket.d.ts +0 -39
  185. package/dist/esm/src/json-rpc-socket.d.ts.map +0 -1
  186. package/dist/esm/src/json-rpc-socket.js +0 -125
  187. package/dist/esm/src/json-rpc-socket.js.map +0 -1
  188. package/dist/esm/src/lib/json-rpc.d.ts +0 -54
  189. package/dist/esm/src/lib/json-rpc.d.ts.map +0 -1
  190. package/dist/esm/src/lib/json-rpc.js +0 -60
  191. package/dist/esm/src/lib/json-rpc.js.map +0 -1
  192. package/dist/esm/src/registration/proof-of-work-types.d.ts +0 -8
  193. package/dist/esm/src/registration/proof-of-work-types.d.ts.map +0 -1
  194. package/dist/esm/src/registration/proof-of-work-types.js +0 -2
  195. package/dist/esm/src/registration/proof-of-work-types.js.map +0 -1
  196. package/dist/esm/src/registration/registration-types.d.ts +0 -18
  197. package/dist/esm/src/registration/registration-types.d.ts.map +0 -1
  198. package/dist/esm/src/registration/registration-types.js +0 -2
  199. package/dist/esm/src/registration/registration-types.js.map +0 -1
  200. package/dist/esm/tests/common-scenario-validator.d.ts +0 -11
  201. package/dist/esm/tests/common-scenario-validator.d.ts.map +0 -1
  202. package/dist/esm/tests/common-scenario-validator.js +0 -113
  203. package/dist/esm/tests/common-scenario-validator.js.map +0 -1
  204. package/dist/esm/tests/connection/connection-manager.spec.d.ts +0 -2
  205. package/dist/esm/tests/connection/connection-manager.spec.d.ts.map +0 -1
  206. package/dist/esm/tests/connection/connection-manager.spec.js +0 -49
  207. package/dist/esm/tests/connection/connection-manager.spec.js.map +0 -1
  208. package/dist/esm/tests/connection/socket-connection.spec.d.ts +0 -2
  209. package/dist/esm/tests/connection/socket-connection.spec.d.ts.map +0 -1
  210. package/dist/esm/tests/connection/socket-connection.spec.js +0 -147
  211. package/dist/esm/tests/connection/socket-connection.spec.js.map +0 -1
  212. package/dist/esm/tests/cors/http-api.browser.d.ts +0 -2
  213. package/dist/esm/tests/cors/http-api.browser.d.ts.map +0 -1
  214. package/dist/esm/tests/cors/http-api.browser.js +0 -60
  215. package/dist/esm/tests/cors/http-api.browser.js.map +0 -1
  216. package/dist/esm/tests/cors/ping.browser.d.ts +0 -2
  217. package/dist/esm/tests/cors/ping.browser.d.ts.map +0 -1
  218. package/dist/esm/tests/cors/ping.browser.js +0 -7
  219. package/dist/esm/tests/cors/ping.browser.js.map +0 -1
  220. package/dist/esm/tests/dwn-process-message.spec.d.ts +0 -2
  221. package/dist/esm/tests/dwn-process-message.spec.d.ts.map +0 -1
  222. package/dist/esm/tests/dwn-process-message.spec.js +0 -172
  223. package/dist/esm/tests/dwn-process-message.spec.js.map +0 -1
  224. package/dist/esm/tests/dwn-server.spec.d.ts +0 -2
  225. package/dist/esm/tests/dwn-server.spec.d.ts.map +0 -1
  226. package/dist/esm/tests/dwn-server.spec.js +0 -48
  227. package/dist/esm/tests/dwn-server.spec.js.map +0 -1
  228. package/dist/esm/tests/http-api.spec.d.ts +0 -2
  229. package/dist/esm/tests/http-api.spec.d.ts.map +0 -1
  230. package/dist/esm/tests/http-api.spec.js +0 -782
  231. package/dist/esm/tests/http-api.spec.js.map +0 -1
  232. package/dist/esm/tests/json-rpc-socket.spec.d.ts +0 -2
  233. package/dist/esm/tests/json-rpc-socket.spec.d.ts.map +0 -1
  234. package/dist/esm/tests/json-rpc-socket.spec.js +0 -227
  235. package/dist/esm/tests/json-rpc-socket.spec.js.map +0 -1
  236. package/dist/esm/tests/plugins/data-store-sqlite.d.ts +0 -17
  237. package/dist/esm/tests/plugins/data-store-sqlite.d.ts.map +0 -1
  238. package/dist/esm/tests/plugins/data-store-sqlite.js +0 -23
  239. package/dist/esm/tests/plugins/data-store-sqlite.js.map +0 -1
  240. package/dist/esm/tests/plugins/event-log-sqlite.d.ts +0 -17
  241. package/dist/esm/tests/plugins/event-log-sqlite.d.ts.map +0 -1
  242. package/dist/esm/tests/plugins/event-log-sqlite.js +0 -23
  243. package/dist/esm/tests/plugins/event-log-sqlite.js.map +0 -1
  244. package/dist/esm/tests/plugins/event-stream-in-memory.d.ts +0 -17
  245. package/dist/esm/tests/plugins/event-stream-in-memory.d.ts.map +0 -1
  246. package/dist/esm/tests/plugins/event-stream-in-memory.js +0 -21
  247. package/dist/esm/tests/plugins/event-stream-in-memory.js.map +0 -1
  248. package/dist/esm/tests/plugins/message-store-sqlite.d.ts +0 -17
  249. package/dist/esm/tests/plugins/message-store-sqlite.d.ts.map +0 -1
  250. package/dist/esm/tests/plugins/message-store-sqlite.js +0 -23
  251. package/dist/esm/tests/plugins/message-store-sqlite.js.map +0 -1
  252. package/dist/esm/tests/plugins/resumable-task-store-sqlite.d.ts +0 -17
  253. package/dist/esm/tests/plugins/resumable-task-store-sqlite.d.ts.map +0 -1
  254. package/dist/esm/tests/plugins/resumable-task-store-sqlite.js +0 -23
  255. package/dist/esm/tests/plugins/resumable-task-store-sqlite.js.map +0 -1
  256. package/dist/esm/tests/process-handler.spec.d.ts +0 -2
  257. package/dist/esm/tests/process-handler.spec.d.ts.map +0 -1
  258. package/dist/esm/tests/process-handler.spec.js +0 -60
  259. package/dist/esm/tests/process-handler.spec.js.map +0 -1
  260. package/dist/esm/tests/registration/proof-of-work-manager.spec.d.ts +0 -2
  261. package/dist/esm/tests/registration/proof-of-work-manager.spec.d.ts.map +0 -1
  262. package/dist/esm/tests/registration/proof-of-work-manager.spec.js +0 -156
  263. package/dist/esm/tests/registration/proof-of-work-manager.spec.js.map +0 -1
  264. package/dist/esm/tests/rpc-subscribe-close.spec.d.ts +0 -2
  265. package/dist/esm/tests/rpc-subscribe-close.spec.d.ts.map +0 -1
  266. package/dist/esm/tests/rpc-subscribe-close.spec.js +0 -81
  267. package/dist/esm/tests/rpc-subscribe-close.spec.js.map +0 -1
  268. package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.d.ts +0 -2
  269. package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.d.ts.map +0 -1
  270. package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.js +0 -74
  271. package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.js.map +0 -1
  272. package/dist/esm/tests/scenarios/registration.spec.d.ts +0 -2
  273. package/dist/esm/tests/scenarios/registration.spec.d.ts.map +0 -1
  274. package/dist/esm/tests/scenarios/registration.spec.js +0 -511
  275. package/dist/esm/tests/scenarios/registration.spec.js.map +0 -1
  276. package/dist/esm/tests/scenarios/web5-connect.spec.d.ts +0 -2
  277. package/dist/esm/tests/scenarios/web5-connect.spec.d.ts.map +0 -1
  278. package/dist/esm/tests/scenarios/web5-connect.spec.js +0 -141
  279. package/dist/esm/tests/scenarios/web5-connect.spec.js.map +0 -1
  280. package/dist/esm/tests/test-dwn.d.ts +0 -7
  281. package/dist/esm/tests/test-dwn.d.ts.map +0 -1
  282. package/dist/esm/tests/test-dwn.js +0 -28
  283. package/dist/esm/tests/test-dwn.js.map +0 -1
  284. package/dist/esm/tests/utils.d.ts +0 -43
  285. package/dist/esm/tests/utils.d.ts.map +0 -1
  286. package/dist/esm/tests/utils.js +0 -107
  287. package/dist/esm/tests/utils.js.map +0 -1
  288. package/dist/esm/tests/ws-api.spec.d.ts +0 -2
  289. package/dist/esm/tests/ws-api.spec.d.ts.map +0 -1
  290. package/dist/esm/tests/ws-api.spec.js +0 -332
  291. package/dist/esm/tests/ws-api.spec.js.map +0 -1
  292. package/src/json-rpc-socket.ts +0 -156
  293. package/src/lib/json-rpc.ts +0 -126
  294. package/src/registration/proof-of-work-types.ts +0 -7
  295. package/src/registration/registration-types.ts +0 -18
@@ -0,0 +1,119 @@
1
+ import type { ProviderAuthPlugin, ProviderAuthValidationResult } from './provider-auth-plugin.js';
2
+
3
+ import * as jose from 'jose';
4
+ import log from 'loglevel';
5
+
6
+ /**
7
+ * Options for creating a {@link JwtProviderAuthPlugin}.
8
+ * Exactly one of `secret` or `jwksUrl` must be provided.
9
+ */
10
+ export type JwtProviderAuthPluginOptions = {
11
+ /** HMAC shared secret for HS256/HS384/HS512 tokens. */
12
+ secret? : string;
13
+ /** JWKS endpoint URL for RS256/ES256/EdDSA tokens. */
14
+ jwksUrl? : string;
15
+ /** Expected JWT `iss` claim. If set, tokens without a matching issuer are rejected. */
16
+ issuer? : string;
17
+ /** Expected JWT `aud` claim. If set, tokens without a matching audience are rejected. */
18
+ audience? : string;
19
+ };
20
+
21
+ /**
22
+ * Built-in {@link ProviderAuthPlugin} that validates JWT registration tokens.
23
+ *
24
+ * Supports two modes:
25
+ * - **HMAC secret** (`DWN_PROVIDER_AUTH_JWT_SECRET`): symmetric HS256 verification.
26
+ * - **JWKS URL** (`DWN_PROVIDER_AUTH_JWT_JWKS_URL`): asymmetric verification via
27
+ * a remote JWKS endpoint (RS256, ES256, EdDSA, etc.). Keys are cached and
28
+ * rotated automatically by `jose`.
29
+ *
30
+ * The JWT payload may include:
31
+ * - `sub` — mapped to `accountId` in the validation result.
32
+ * - `metadata` — arbitrary provider-defined object stored with the tenant.
33
+ *
34
+ * This plugin ships with `@enbox/dwn-server` and requires no external code.
35
+ * Providers who need custom validation logic can implement their own
36
+ * {@link ProviderAuthPlugin} instead.
37
+ */
38
+ export class JwtProviderAuthPlugin implements ProviderAuthPlugin {
39
+ #getKey: Uint8Array | jose.JWTVerifyGetKey;
40
+ #issuer: string | undefined;
41
+ #audience: string | undefined;
42
+
43
+ private constructor(
44
+ getKey: Uint8Array | jose.JWTVerifyGetKey,
45
+ issuer?: string,
46
+ audience?: string,
47
+ ) {
48
+ this.#getKey = getKey;
49
+ this.#issuer = issuer;
50
+ this.#audience = audience;
51
+ }
52
+
53
+ /**
54
+ * Create a {@link JwtProviderAuthPlugin} from options.
55
+ *
56
+ * @param options - Must include exactly one of `secret` or `jwksUrl`.
57
+ * @throws If neither `secret` nor `jwksUrl` is provided.
58
+ */
59
+ public static async create(options: JwtProviderAuthPluginOptions): Promise<JwtProviderAuthPlugin> {
60
+ if (!options.secret && !options.jwksUrl) {
61
+ throw new Error(
62
+ 'JwtProviderAuthPlugin: exactly one of `secret` or `jwksUrl` must be provided.',
63
+ );
64
+ }
65
+
66
+ let getKey: any;
67
+ if (options.secret) {
68
+ getKey = new TextEncoder().encode(options.secret);
69
+ } else {
70
+ getKey = jose.createRemoteJWKSet(new URL(options.jwksUrl!));
71
+ }
72
+
73
+ return new JwtProviderAuthPlugin(getKey, options.issuer, options.audience);
74
+ }
75
+
76
+ /**
77
+ * Create a {@link JwtProviderAuthPlugin} from environment variables.
78
+ *
79
+ * Reads `DWN_PROVIDER_AUTH_JWT_SECRET`, `DWN_PROVIDER_AUTH_JWT_JWKS_URL`,
80
+ * and optionally `DWN_PROVIDER_AUTH_JWT_ISSUER` / `DWN_PROVIDER_AUTH_JWT_AUDIENCE`.
81
+ */
82
+ public static async fromEnv(): Promise<JwtProviderAuthPlugin> {
83
+ return JwtProviderAuthPlugin.create({
84
+ secret : process.env.DWN_PROVIDER_AUTH_JWT_SECRET,
85
+ jwksUrl : process.env.DWN_PROVIDER_AUTH_JWT_JWKS_URL,
86
+ issuer : process.env.DWN_PROVIDER_AUTH_JWT_ISSUER,
87
+ audience : process.env.DWN_PROVIDER_AUTH_JWT_AUDIENCE,
88
+ });
89
+ }
90
+
91
+ /** @inheritdoc */
92
+ public async validateRegistrationToken(token: string): Promise<ProviderAuthValidationResult> {
93
+ try {
94
+ const verifyOptions: jose.JWTVerifyOptions = {};
95
+ if (this.#issuer !== undefined) {
96
+ verifyOptions.issuer = this.#issuer;
97
+ }
98
+ if (this.#audience !== undefined) {
99
+ verifyOptions.audience = this.#audience;
100
+ }
101
+
102
+ const { payload } = await jose.jwtVerify(token, this.#getKey as any, verifyOptions);
103
+
104
+ return {
105
+ isValid : true,
106
+ accountId : typeof payload.sub === 'string' ? payload.sub : undefined,
107
+ metadata : typeof payload.metadata === 'object' && payload.metadata !== null
108
+ ? payload.metadata as Record<string, unknown>
109
+ : undefined,
110
+ };
111
+ } catch (error) {
112
+ log.debug('JWT validation failed:', (error as Error).message);
113
+ return {
114
+ isValid : false,
115
+ detail : (error as Error).message,
116
+ };
117
+ }
118
+ }
119
+ }
@@ -0,0 +1,263 @@
1
+ import * as jose from 'jose';
2
+ import log from 'loglevel';
3
+
4
+ /**
5
+ * Built-in "open auth" handler for DWN servers that want registration
6
+ * without any user authentication (open-to-all).
7
+ *
8
+ * Implements the provider-auth-v0 authorize / token / refresh endpoints
9
+ * locally on the DWN server. The authorize endpoint immediately returns
10
+ * an authorization code (no user interaction), the token endpoint exchanges
11
+ * it for a JWT registration token, and the refresh endpoint issues a new token.
12
+ *
13
+ * This is the simplest possible auth backend — suitable for free/public DWN
14
+ * providers. Providers who want real authentication (passkey, OIDC, etc.)
15
+ * host their own auth service and point `providerAuth.authorizeUrl` /
16
+ * `tokenUrl` to it instead.
17
+ *
18
+ * The JWTs issued here are verified by {@link JwtProviderAuthPlugin} using
19
+ * the same shared secret (`DWN_PROVIDER_AUTH_JWT_SECRET`).
20
+ */
21
+ /**
22
+ * Maximum number of pending authorization codes held in memory.
23
+ * When the limit is reached, new authorize requests are rejected with 503.
24
+ */
25
+ const MAX_PENDING_CODES = 10_000;
26
+
27
+ /** Interval (ms) at which expired authorization codes are purged. */
28
+ const CLEANUP_INTERVAL_MS = 60_000;
29
+
30
+ export class OpenAuthHandler {
31
+ #secret: Uint8Array;
32
+ #issuer: string;
33
+ /** Pending authorization codes. Maps code → { redirectUri, expiresAt }. */
34
+ #pendingCodes: Map<string, { redirectUri: string; expiresAt: number }>;
35
+ /** Registration token TTL in seconds. Default: 1 year. */
36
+ #tokenTtlSeconds: number;
37
+ /** Periodic cleanup timer for expired codes. */
38
+ #cleanupTimer: ReturnType<typeof setInterval>;
39
+
40
+ private constructor(secret: Uint8Array, issuer: string, tokenTtlSeconds: number) {
41
+ this.#secret = secret;
42
+ this.#issuer = issuer;
43
+ this.#pendingCodes = new Map();
44
+ this.#tokenTtlSeconds = tokenTtlSeconds;
45
+
46
+ // Periodically purge expired codes so memory does not grow unbounded
47
+ // even when no new authorize requests arrive.
48
+ this.#cleanupTimer = setInterval(() => this.#cleanExpiredCodes(), CLEANUP_INTERVAL_MS);
49
+ // Allow the process to exit even if the timer is still running.
50
+ if (this.#cleanupTimer.unref) {
51
+ this.#cleanupTimer.unref();
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Create an {@link OpenAuthHandler}.
57
+ *
58
+ * @param secret - HMAC shared secret (same as `DWN_PROVIDER_AUTH_JWT_SECRET`).
59
+ * @param issuer - JWT issuer claim (typically the DWN server's base URL).
60
+ * @param tokenTtlSeconds - Registration token lifetime in seconds. Default: 1 year.
61
+ */
62
+ public static create(secret: string, issuer: string, tokenTtlSeconds = 365 * 24 * 60 * 60): OpenAuthHandler {
63
+ return new OpenAuthHandler(
64
+ new TextEncoder().encode(secret),
65
+ issuer,
66
+ tokenTtlSeconds,
67
+ );
68
+ }
69
+
70
+ /**
71
+ * Handle `GET /provider-auth/authorize`.
72
+ *
73
+ * In open-auth mode this immediately generates a code and redirects back
74
+ * to the `redirect_uri` with `code` and `state` query parameters.
75
+ * No user interaction is required.
76
+ */
77
+ public handleAuthorize(url: URL): Response {
78
+ const redirectUri = url.searchParams.get('redirect_uri');
79
+ const state = url.searchParams.get('state');
80
+
81
+ if (!redirectUri) {
82
+ return Response.json(
83
+ { error: 'missing redirect_uri parameter' },
84
+ { status: 400 },
85
+ );
86
+ }
87
+
88
+ // Reject new codes when the map is at capacity to prevent memory exhaustion.
89
+ if (this.#pendingCodes.size >= MAX_PENDING_CODES) {
90
+ log.warn(`OpenAuthHandler: pending codes map is full (${MAX_PENDING_CODES}), rejecting authorize request`);
91
+ return Response.json(
92
+ { error: 'server is busy, try again later' },
93
+ { status: 503 },
94
+ );
95
+ }
96
+
97
+ // Generate a random authorization code.
98
+ const code = crypto.randomUUID();
99
+
100
+ // Store the code with a 10-minute expiry.
101
+ this.#pendingCodes.set(code, {
102
+ redirectUri,
103
+ expiresAt: Date.now() + 10 * 60 * 1000,
104
+ });
105
+
106
+ // Periodically clean up expired codes (simple inline sweep).
107
+ this.#cleanExpiredCodes();
108
+
109
+ // Build redirect URL.
110
+ const redirect = new URL(redirectUri);
111
+ redirect.searchParams.set('code', code);
112
+ if (state) {
113
+ redirect.searchParams.set('state', state);
114
+ }
115
+
116
+ log.debug(`OpenAuthHandler: issued code ${code.slice(0, 8)}… for redirect to ${redirectUri}`);
117
+
118
+ return Response.json({
119
+ code,
120
+ state : state ?? undefined,
121
+ redirectUri : redirect.toString(),
122
+ });
123
+ }
124
+
125
+ /**
126
+ * Handle `POST /provider-auth/token`.
127
+ *
128
+ * Exchanges an authorization code for a JWT registration token.
129
+ * Request body: `{ code: string, redirectUri: string }`.
130
+ */
131
+ public async handleToken(req: Request): Promise<Response> {
132
+ let body: { code?: string; redirectUri?: string };
133
+ try {
134
+ body = await req.json() as { code?: string; redirectUri?: string };
135
+ } catch {
136
+ return Response.json({ error: 'invalid JSON body' }, { status: 400 });
137
+ }
138
+
139
+ const { code, redirectUri } = body;
140
+ if (!code || !redirectUri) {
141
+ return Response.json(
142
+ { error: 'missing code or redirectUri' },
143
+ { status: 400 },
144
+ );
145
+ }
146
+
147
+ // Validate the authorization code.
148
+ const pending = this.#pendingCodes.get(code);
149
+ if (!pending) {
150
+ return Response.json({ error: 'invalid or expired code' }, { status: 400 });
151
+ }
152
+
153
+ if (pending.redirectUri !== redirectUri) {
154
+ return Response.json({ error: 'redirect_uri mismatch' }, { status: 400 });
155
+ }
156
+
157
+ if (Date.now() > pending.expiresAt) {
158
+ this.#pendingCodes.delete(code);
159
+ return Response.json({ error: 'code expired' }, { status: 400 });
160
+ }
161
+
162
+ // Consume the code (one-time use).
163
+ this.#pendingCodes.delete(code);
164
+
165
+ // Generate an account ID for this session (open auth = anonymous accounts).
166
+ const accountId = crypto.randomUUID();
167
+
168
+ // Issue JWT registration token.
169
+ const registrationToken = await this.#signToken(accountId, this.#tokenTtlSeconds);
170
+
171
+ // Issue refresh token with longer TTL (2x).
172
+ const refreshToken = await this.#signToken(accountId, this.#tokenTtlSeconds * 2, 'refresh');
173
+
174
+ log.debug(`OpenAuthHandler: exchanged code for account ${accountId.slice(0, 8)}…`);
175
+
176
+ return Response.json({
177
+ registrationToken,
178
+ refreshToken,
179
+ expiresIn : this.#tokenTtlSeconds,
180
+ tokenType : 'bearer',
181
+ });
182
+ }
183
+
184
+ /**
185
+ * Handle `POST /provider-auth/refresh`.
186
+ *
187
+ * Exchanges a refresh token for a new registration token.
188
+ * Request body: `{ refreshToken: string }`.
189
+ */
190
+ public async handleRefresh(req: Request): Promise<Response> {
191
+ let body: { refreshToken?: string };
192
+ try {
193
+ body = await req.json() as { refreshToken?: string };
194
+ } catch {
195
+ return Response.json({ error: 'invalid JSON body' }, { status: 400 });
196
+ }
197
+
198
+ if (!body.refreshToken) {
199
+ return Response.json({ error: 'missing refreshToken' }, { status: 400 });
200
+ }
201
+
202
+ // Verify the refresh token.
203
+ let payload: jose.JWTPayload;
204
+ try {
205
+ const result = await jose.jwtVerify(body.refreshToken, this.#secret, {
206
+ issuer: this.#issuer,
207
+ });
208
+ payload = result.payload;
209
+ } catch (error) {
210
+ return Response.json(
211
+ { error: `invalid refresh token: ${(error as Error).message}` },
212
+ { status: 400 },
213
+ );
214
+ }
215
+
216
+ if (payload.purpose !== 'refresh') {
217
+ return Response.json({ error: 'token is not a refresh token' }, { status: 400 });
218
+ }
219
+
220
+ const accountId = payload.sub ?? crypto.randomUUID();
221
+
222
+ // Issue new tokens.
223
+ const registrationToken = await this.#signToken(accountId, this.#tokenTtlSeconds);
224
+ const refreshToken = await this.#signToken(accountId, this.#tokenTtlSeconds * 2, 'refresh');
225
+
226
+ log.debug(`OpenAuthHandler: refreshed token for account ${accountId.slice(0, 8)}…`);
227
+
228
+ return Response.json({
229
+ registrationToken,
230
+ refreshToken,
231
+ expiresIn : this.#tokenTtlSeconds,
232
+ tokenType : 'bearer',
233
+ });
234
+ }
235
+
236
+ /** Sign a JWT with the shared secret. */
237
+ async #signToken(accountId: string, ttlSeconds: number, purpose = 'registration'): Promise<string> {
238
+ return new jose.SignJWT({ purpose })
239
+ .setProtectedHeader({ alg: 'HS256' })
240
+ .setIssuer(this.#issuer)
241
+ .setAudience(this.#issuer)
242
+ .setSubject(accountId)
243
+ .setIssuedAt()
244
+ .setExpirationTime(`${ttlSeconds}s`)
245
+ .sign(this.#secret);
246
+ }
247
+
248
+ /** Stops the periodic cleanup timer and clears all pending codes. */
249
+ public destroy(): void {
250
+ clearInterval(this.#cleanupTimer);
251
+ this.#pendingCodes.clear();
252
+ }
253
+
254
+ /** Remove expired authorization codes from the pending map. */
255
+ #cleanExpiredCodes(): void {
256
+ const now = Date.now();
257
+ for (const [code, data] of this.#pendingCodes) {
258
+ if (now > data.expiresAt) {
259
+ this.#pendingCodes.delete(code);
260
+ }
261
+ }
262
+ }
263
+ }
@@ -1,4 +1,4 @@
1
- import type { ProofOfWorkChallengeModel } from './proof-of-work-types.js';
1
+ import type { ProofOfWorkChallengeModel } from '@enbox/dwn-clients';
2
2
 
3
3
  import { ProofOfWork } from './proof-of-work.js';
4
4
  import { DwnServerError, DwnServerErrorCode } from '../dwn-error.js';
@@ -0,0 +1,84 @@
1
+ import { DwnServerError, DwnServerErrorCode } from '../dwn-error.js';
2
+
3
+ /**
4
+ * Interface for validating provider auth registration tokens.
5
+ * DWN server operators implement this to integrate with their auth service.
6
+ *
7
+ * The token is intentionally opaque — it could be a JWT, blind RSA token,
8
+ * ecash token, or any other format. The plugin is responsible for all
9
+ * validation logic.
10
+ */
11
+ export interface ProviderAuthPlugin {
12
+ /**
13
+ * Validate a registration token presented during DID registration.
14
+ *
15
+ * @param token - The opaque registration token from the client
16
+ * @returns Validation result with optional account metadata
17
+ */
18
+ validateRegistrationToken(token: string): Promise<ProviderAuthValidationResult>;
19
+ }
20
+
21
+ /**
22
+ * Result of validating a provider auth registration token.
23
+ */
24
+ export type ProviderAuthValidationResult = {
25
+ /** Whether the token is valid and the registration should proceed. */
26
+ isValid : boolean;
27
+
28
+ /**
29
+ * Optional account identifier that this DID registration is associated with.
30
+ * Omit for privacy-preserving providers where DID-to-account correlation
31
+ * should not be stored.
32
+ */
33
+ accountId? : string;
34
+
35
+ /**
36
+ * Optional provider-defined metadata to store with the tenant registration.
37
+ * Can include plan details, quotas, or any other provider-specific data.
38
+ * Stored as JSON in the registeredTenants table.
39
+ */
40
+ metadata? : Record<string, unknown>;
41
+
42
+ /** Error detail message when isValid is false. */
43
+ detail? : string;
44
+ };
45
+
46
+ /**
47
+ * Load a ProviderAuthPlugin from a file path.
48
+ * The module must export a default class or object that implements ProviderAuthPlugin.
49
+ *
50
+ * Follows the same pattern as the existing PluginLoader.
51
+ */
52
+ export async function loadProviderAuthPlugin(pluginPath: string): Promise<ProviderAuthPlugin> {
53
+ let module: any;
54
+ try {
55
+ module = await import(pluginPath);
56
+ } catch (error) {
57
+ throw new DwnServerError(
58
+ DwnServerErrorCode.ProviderAuthPluginLoadFailed,
59
+ `Failed to load provider auth plugin at ${pluginPath}: ${(error as Error).message}`,
60
+ );
61
+ }
62
+
63
+ const plugin = module.default;
64
+ if (plugin === undefined) {
65
+ throw new DwnServerError(
66
+ DwnServerErrorCode.ProviderAuthPluginLoadFailed,
67
+ `Provider auth plugin at ${pluginPath} does not have a default export.`,
68
+ );
69
+ }
70
+
71
+ // Support both class (instantiate) and plain object exports.
72
+ const instance: ProviderAuthPlugin = typeof plugin === 'function'
73
+ ? new plugin() as ProviderAuthPlugin
74
+ : plugin as ProviderAuthPlugin;
75
+
76
+ if (typeof instance.validateRegistrationToken !== 'function') {
77
+ throw new DwnServerError(
78
+ DwnServerErrorCode.ProviderAuthPluginLoadFailed,
79
+ `Provider auth plugin at ${pluginPath} does not implement validateRegistrationToken().`,
80
+ );
81
+ }
82
+
83
+ return instance;
84
+ }
@@ -1,6 +1,6 @@
1
- import type { ProofOfWorkChallengeModel } from './proof-of-work-types.js';
1
+ import type { ProviderAuthPlugin } from './provider-auth-plugin.js';
2
2
  import type { ActiveTenantCheckResult, TenantGate } from '@enbox/dwn-sdk-js';
3
- import type { RegistrationData, RegistrationRequest } from './registration-types.js';
3
+ import type { ProofOfWorkChallengeModel, RegistrationRequest } from '@enbox/dwn-clients';
4
4
 
5
5
  import { readFileSync } from 'fs';
6
6
 
@@ -16,6 +16,7 @@ import { DwnServerError, DwnServerErrorCode } from '../dwn-error.js';
16
16
  */
17
17
  export class RegistrationManager implements TenantGate {
18
18
  private proofOfWorkManager: ProofOfWorkManager;
19
+ private providerAuthPlugin?: ProviderAuthPlugin;
19
20
  private registrationStore: RegistrationStore;
20
21
 
21
22
  private termsOfServiceHash?: string;
@@ -51,9 +52,10 @@ export class RegistrationManager implements TenantGate {
51
52
  */
52
53
  public static async create(input: {
53
54
  registrationStoreUrl?: string,
54
- termsOfServiceFilePath?: string
55
+ termsOfServiceFilePath?: string,
55
56
  proofOfWorkChallengeNonceSeed?: string,
56
57
  proofOfWorkInitialMaximumAllowedHash?: string,
58
+ providerAuthPlugin?: ProviderAuthPlugin,
57
59
  }): Promise<RegistrationManager> {
58
60
  const { termsOfServiceFilePath, registrationStoreUrl } = input;
59
61
 
@@ -83,6 +85,11 @@ export class RegistrationManager implements TenantGate {
83
85
  const registrationStore = await RegistrationStore.create(sqlDialect);
84
86
  registrationManager.registrationStore = registrationStore;
85
87
 
88
+ // Store optional provider auth plugin.
89
+ if (input.providerAuthPlugin !== undefined) {
90
+ registrationManager.providerAuthPlugin = input.providerAuthPlugin;
91
+ }
92
+
86
93
  return registrationManager;
87
94
  }
88
95
 
@@ -95,17 +102,79 @@ export class RegistrationManager implements TenantGate {
95
102
  }
96
103
 
97
104
  /**
98
- * Handles a registration request.
105
+ * Handles a registration request by dispatching to the appropriate handler
106
+ * based on the credentials provided.
99
107
  */
100
108
  public async handleRegistrationRequest(registrationRequest: RegistrationRequest): Promise<void> {
101
- // Ensure the supplied terms of service hash matches the one we require.
102
- if (registrationRequest.registrationData.termsOfServiceHash !== this.termsOfServiceHash) {
103
- throw new DwnServerError(DwnServerErrorCode.RegistrationManagerInvalidOrOutdatedTermsOfServiceHash,
104
- `Expecting terms-of-service hash ${this.termsOfServiceHash}, but got ${registrationRequest.registrationData.termsOfServiceHash}.`
109
+ if (registrationRequest.providerAuth?.registrationToken !== undefined) {
110
+ await this.handleProviderAuthRegistration(registrationRequest);
111
+ } else if (registrationRequest.proofOfWork !== undefined) {
112
+ await this.handleProofOfWorkRegistration(registrationRequest);
113
+ } else {
114
+ throw new DwnServerError(
115
+ DwnServerErrorCode.RegistrationRequestMissingCredentials,
116
+ 'Registration request must include either providerAuth or proofOfWork credentials.',
117
+ );
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Handles a provider-auth registration request.
123
+ */
124
+ private async handleProviderAuthRegistration(registrationRequest: RegistrationRequest): Promise<void> {
125
+ if (this.providerAuthPlugin === undefined) {
126
+ throw new DwnServerError(
127
+ DwnServerErrorCode.ProviderAuthNotEnabled,
128
+ 'Provider auth registration is not enabled on this server.',
105
129
  );
106
130
  }
107
131
 
108
- const { challengeNonce, responseNonce } = registrationRequest.proofOfWork;
132
+ const token = registrationRequest.providerAuth!.registrationToken;
133
+ const validationResult = await this.providerAuthPlugin.validateRegistrationToken(token);
134
+
135
+ if (!validationResult.isValid) {
136
+ throw new DwnServerError(
137
+ DwnServerErrorCode.ProviderAuthTokenInvalid,
138
+ validationResult.detail ?? 'Provider auth registration token is invalid.',
139
+ );
140
+ }
141
+
142
+ // Validate ToS only when the server has ToS configured AND the request includes a hash.
143
+ if (this.termsOfServiceHash !== undefined && registrationRequest.registrationData.termsOfServiceHash !== undefined) {
144
+ if (registrationRequest.registrationData.termsOfServiceHash !== this.termsOfServiceHash) {
145
+ throw new DwnServerError(DwnServerErrorCode.RegistrationManagerInvalidOrOutdatedTermsOfServiceHash,
146
+ `Expecting terms-of-service hash ${this.termsOfServiceHash}, ` +
147
+ `but got ${registrationRequest.registrationData.termsOfServiceHash}.`,
148
+ );
149
+ }
150
+ }
151
+
152
+ await this.registrationStore.insertOrUpdateTenantRegistration({
153
+ did : registrationRequest.registrationData.did,
154
+ termsOfServiceHash : registrationRequest.registrationData.termsOfServiceHash,
155
+ accountId : validationResult.accountId,
156
+ registrationType : 'provider-auth',
157
+ metadata : validationResult.metadata !== undefined
158
+ ? JSON.stringify(validationResult.metadata)
159
+ : undefined,
160
+ });
161
+ }
162
+
163
+ /**
164
+ * Handles a proof-of-work registration request.
165
+ */
166
+ private async handleProofOfWorkRegistration(registrationRequest: RegistrationRequest): Promise<void> {
167
+ // Validate ToS only when the server has ToS configured.
168
+ if (this.termsOfServiceHash !== undefined) {
169
+ if (registrationRequest.registrationData.termsOfServiceHash !== this.termsOfServiceHash) {
170
+ throw new DwnServerError(DwnServerErrorCode.RegistrationManagerInvalidOrOutdatedTermsOfServiceHash,
171
+ `Expecting terms-of-service hash ${this.termsOfServiceHash}, ` +
172
+ `but got ${registrationRequest.registrationData.termsOfServiceHash}.`,
173
+ );
174
+ }
175
+ }
176
+
177
+ const { challengeNonce, responseNonce } = registrationRequest.proofOfWork!;
109
178
 
110
179
  await this.proofOfWorkManager.verifyProofOfWork({
111
180
  challengeNonce,
@@ -114,14 +183,24 @@ export class RegistrationManager implements TenantGate {
114
183
  });
115
184
 
116
185
  // Store tenant registration data in database.
117
- await this.recordTenantRegistration(registrationRequest.registrationData);
186
+ await this.recordTenantRegistration({
187
+ did : registrationRequest.registrationData.did,
188
+ termsOfServiceHash : registrationRequest.registrationData.termsOfServiceHash,
189
+ registrationType : 'proof-of-work',
190
+ });
118
191
  }
119
192
 
120
193
  /**
121
194
  * Records the given registration data in the database.
122
195
  * Exposed as a public method for testing purposes.
123
196
  */
124
- public async recordTenantRegistration(registrationData: RegistrationData): Promise<void> {
197
+ public async recordTenantRegistration(registrationData: {
198
+ did: string;
199
+ termsOfServiceHash?: string;
200
+ accountId?: string;
201
+ registrationType?: string;
202
+ metadata?: string;
203
+ }): Promise<void> {
125
204
  await this.registrationStore.insertOrUpdateTenantRegistration(registrationData);
126
205
  }
127
206
 
@@ -143,7 +222,16 @@ export class RegistrationManager implements TenantGate {
143
222
  };
144
223
  }
145
224
 
146
- if (tenantRegistration.termsOfServiceHash !== this.termsOfServiceHash) {
225
+ if (tenantRegistration.suspended) {
226
+ return {
227
+ isActiveTenant : false,
228
+ detail : 'Tenant is suspended.'
229
+ };
230
+ }
231
+
232
+ // Only enforce ToS hash check when the server has a ToS configured.
233
+ if (this.termsOfServiceHash !== undefined &&
234
+ tenantRegistration.termsOfServiceHash !== this.termsOfServiceHash) {
147
235
  return {
148
236
  isActiveTenant : false,
149
237
  detail : 'Agreed terms-of-service is outdated.'
@@ -152,4 +240,12 @@ export class RegistrationManager implements TenantGate {
152
240
 
153
241
  return { isActiveTenant: true };
154
242
  }
243
+
244
+ /**
245
+ * Returns the underlying RegistrationStore, if initialized.
246
+ * Used by the admin API to access tenant data.
247
+ */
248
+ public getRegistrationStore(): RegistrationStore | undefined {
249
+ return this.registrationStore;
250
+ }
155
251
  }