@undefineds.co/xpod 0.1.6 → 0.2.0-preview.2

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 (307) hide show
  1. package/README.md +141 -2
  2. package/config/cli.json +9 -71
  3. package/config/cloud.json +34 -7
  4. package/config/local.json +6 -2
  5. package/config/resolver.json +11 -49
  6. package/config/runtime-open.json +22 -0
  7. package/config/xpod.base.json +32 -0
  8. package/config/xpod.cluster.json +2 -44
  9. package/config/xpod.json +5 -2
  10. package/dist/api/ApiServer.js +1 -1
  11. package/dist/api/ApiServer.js.map +1 -1
  12. package/dist/api/auth/AuthContext.d.ts +12 -1
  13. package/dist/api/auth/AuthContext.js +18 -1
  14. package/dist/api/auth/AuthContext.js.map +1 -1
  15. package/dist/api/auth/ClientCredentialsAuthenticator.d.ts +0 -1
  16. package/dist/api/auth/ClientCredentialsAuthenticator.js.map +1 -1
  17. package/dist/api/auth/ServiceTokenAuthenticator.d.ts +18 -0
  18. package/dist/api/auth/ServiceTokenAuthenticator.js +50 -0
  19. package/dist/api/auth/ServiceTokenAuthenticator.js.map +1 -0
  20. package/dist/api/auth/index.d.ts +1 -0
  21. package/dist/api/auth/index.js +1 -0
  22. package/dist/api/auth/index.js.map +1 -1
  23. package/dist/api/chatkit/ai-provider.d.ts +0 -10
  24. package/dist/api/chatkit/ai-provider.js +11 -120
  25. package/dist/api/chatkit/ai-provider.js.map +1 -1
  26. package/dist/api/chatkit/default-agent.js +11 -8
  27. package/dist/api/chatkit/default-agent.js.map +1 -1
  28. package/dist/api/chatkit/pod-store.js +19 -3
  29. package/dist/api/chatkit/pod-store.js.map +1 -1
  30. package/dist/api/chatkit/schema.d.ts +9 -3
  31. package/dist/api/chatkit/schema.js +14 -6
  32. package/dist/api/chatkit/schema.js.map +1 -1
  33. package/dist/api/container/business-token.d.ts +9 -0
  34. package/dist/api/container/business-token.js +32 -0
  35. package/dist/api/container/business-token.js.map +1 -0
  36. package/dist/api/container/cloud.js +36 -12
  37. package/dist/api/container/cloud.js.map +1 -1
  38. package/dist/api/container/common.js +12 -5
  39. package/dist/api/container/common.js.map +1 -1
  40. package/dist/api/container/index.js +94 -14
  41. package/dist/api/container/index.js.map +1 -1
  42. package/dist/api/container/local.js +2 -1
  43. package/dist/api/container/local.js.map +1 -1
  44. package/dist/api/container/routes.js +81 -15
  45. package/dist/api/container/routes.js.map +1 -1
  46. package/dist/api/container/types.d.ts +8 -6
  47. package/dist/api/container/types.js.map +1 -1
  48. package/dist/api/handlers/AdminHandler.js +9 -9
  49. package/dist/api/handlers/AdminHandler.js.map +1 -1
  50. package/dist/api/handlers/ApiKeyHandler.js +0 -6
  51. package/dist/api/handlers/ApiKeyHandler.js.map +1 -1
  52. package/dist/api/handlers/EdgeNodeSignalHandler.d.ts +17 -0
  53. package/dist/api/handlers/EdgeNodeSignalHandler.js +171 -0
  54. package/dist/api/handlers/EdgeNodeSignalHandler.js.map +1 -0
  55. package/dist/api/handlers/PodManagementHandler.d.ts +5 -4
  56. package/dist/api/handlers/PodManagementHandler.js +11 -10
  57. package/dist/api/handlers/PodManagementHandler.js.map +1 -1
  58. package/dist/api/handlers/ProvisionHandler.d.ts +42 -0
  59. package/dist/api/handlers/ProvisionHandler.js +161 -0
  60. package/dist/api/handlers/ProvisionHandler.js.map +1 -0
  61. package/dist/api/handlers/QuotaHandler.d.ts +7 -7
  62. package/dist/api/handlers/QuotaHandler.js +143 -73
  63. package/dist/api/handlers/QuotaHandler.js.map +1 -1
  64. package/dist/api/handlers/SubdomainClientHandler.js +2 -2
  65. package/dist/api/handlers/SubdomainClientHandler.js.map +1 -1
  66. package/dist/api/handlers/SubdomainHandler.js +13 -8
  67. package/dist/api/handlers/SubdomainHandler.js.map +1 -1
  68. package/dist/api/handlers/UsageHandler.d.ts +14 -0
  69. package/dist/api/handlers/UsageHandler.js +123 -0
  70. package/dist/api/handlers/UsageHandler.js.map +1 -0
  71. package/dist/api/handlers/index.d.ts +3 -1
  72. package/dist/api/handlers/index.js +3 -1
  73. package/dist/api/handlers/index.js.map +1 -1
  74. package/dist/api/main.js +18 -0
  75. package/dist/api/main.js.map +1 -1
  76. package/dist/api/middleware/OpenAuthMiddleware.d.ts +12 -0
  77. package/dist/api/middleware/OpenAuthMiddleware.js +27 -0
  78. package/dist/api/middleware/OpenAuthMiddleware.js.map +1 -0
  79. package/dist/api/runtime.d.ts +15 -0
  80. package/dist/api/runtime.js +104 -0
  81. package/dist/api/runtime.js.map +1 -0
  82. package/dist/api/service/VercelChatService.d.ts +16 -7
  83. package/dist/api/service/VercelChatService.js +98 -178
  84. package/dist/api/service/VercelChatService.js.map +1 -1
  85. package/dist/api/store/DrizzleClientCredentialsStore.d.ts +6 -11
  86. package/dist/api/store/DrizzleClientCredentialsStore.js +9 -39
  87. package/dist/api/store/DrizzleClientCredentialsStore.js.map +1 -1
  88. package/dist/authorization/AuthModeSelector.d.ts +10 -0
  89. package/dist/authorization/AuthModeSelector.js +27 -0
  90. package/dist/authorization/AuthModeSelector.js.map +1 -0
  91. package/dist/authorization/AuthModeSelector.jsonld +81 -0
  92. package/dist/cli/commands/account.d.ts +6 -0
  93. package/dist/cli/commands/account.js +119 -0
  94. package/dist/cli/commands/account.js.map +1 -0
  95. package/dist/cli/commands/auth.js +20 -29
  96. package/dist/cli/commands/auth.js.map +1 -1
  97. package/dist/cli/commands/backup.d.ts +15 -0
  98. package/dist/cli/commands/backup.js +286 -0
  99. package/dist/cli/commands/backup.js.map +1 -0
  100. package/dist/cli/commands/config.d.ts +34 -3
  101. package/dist/cli/commands/config.js +195 -258
  102. package/dist/cli/commands/config.js.map +1 -1
  103. package/dist/cli/commands/doctor.d.ts +6 -0
  104. package/dist/cli/commands/doctor.js +94 -0
  105. package/dist/cli/commands/doctor.js.map +1 -0
  106. package/dist/cli/commands/pod.d.ts +6 -0
  107. package/dist/cli/commands/pod.js +124 -0
  108. package/dist/cli/commands/pod.js.map +1 -0
  109. package/dist/cli/commands/start.js +28 -5
  110. package/dist/cli/commands/start.js.map +1 -1
  111. package/dist/cli/index.js +9 -0
  112. package/dist/cli/index.js.map +1 -1
  113. package/dist/cli/lib/credentials-store.d.ts +17 -0
  114. package/dist/cli/lib/credentials-store.js +73 -0
  115. package/dist/cli/lib/credentials-store.js.map +1 -0
  116. package/dist/cli/lib/css-account.d.ts +17 -0
  117. package/dist/cli/lib/css-account.js +56 -0
  118. package/dist/cli/lib/css-account.js.map +1 -1
  119. package/dist/cli/lib/pod-thread-store.d.ts +57 -0
  120. package/dist/cli/lib/pod-thread-store.js +310 -0
  121. package/dist/cli/lib/pod-thread-store.js.map +1 -0
  122. package/dist/cli/lib/solid-auth.d.ts +20 -0
  123. package/dist/cli/lib/solid-auth.js +70 -0
  124. package/dist/cli/lib/solid-auth.js.map +1 -0
  125. package/dist/components/components.jsonld +5 -8
  126. package/dist/components/context.jsonld +114 -244
  127. package/dist/edge/EdgeNodeAgent.js +2 -2
  128. package/dist/edge/EdgeNodeAgent.js.map +1 -1
  129. package/dist/edge/EdgeNodeDnsCoordinator.d.ts +1 -7
  130. package/dist/edge/EdgeNodeDnsCoordinator.js +31 -41
  131. package/dist/edge/EdgeNodeDnsCoordinator.js.map +1 -1
  132. package/dist/edge/EdgeNodeDnsCoordinator.jsonld +1 -27
  133. package/dist/edge/EdgeNodeModeDetector.d.ts +1 -1
  134. package/dist/edge/EdgeNodeModeDetector.js +9 -11
  135. package/dist/edge/EdgeNodeModeDetector.js.map +1 -1
  136. package/dist/http/ClusterIngressRouter.js +3 -3
  137. package/dist/http/ClusterIngressRouter.js.map +1 -1
  138. package/dist/http/ClusterWebSocketConfigurator.js +2 -2
  139. package/dist/http/ClusterWebSocketConfigurator.js.map +1 -1
  140. package/dist/http/PodRoutingHttpHandler.js +2 -2
  141. package/dist/http/PodRoutingHttpHandler.js.map +1 -1
  142. package/dist/http/cluster/PodMigrationHttpHandler.d.ts +1 -1
  143. package/dist/http/cluster/PodMigrationHttpHandler.js +1 -1
  144. package/dist/http/cluster/PodMigrationHttpHandler.js.map +1 -1
  145. package/dist/identity/drizzle/EdgeNodeRepository.d.ts +37 -4
  146. package/dist/identity/drizzle/EdgeNodeRepository.js +120 -128
  147. package/dist/identity/drizzle/EdgeNodeRepository.js.map +1 -1
  148. package/dist/identity/drizzle/ServiceTokenRepository.d.ts +52 -0
  149. package/dist/identity/drizzle/ServiceTokenRepository.js +143 -0
  150. package/dist/identity/drizzle/ServiceTokenRepository.js.map +1 -0
  151. package/dist/identity/drizzle/db.d.ts +9 -0
  152. package/dist/identity/drizzle/db.js +208 -1
  153. package/dist/identity/drizzle/db.js.map +1 -1
  154. package/dist/identity/drizzle/schema.pg.d.ts +5 -0
  155. package/dist/identity/drizzle/schema.pg.js +49 -20
  156. package/dist/identity/drizzle/schema.pg.js.map +1 -1
  157. package/dist/identity/drizzle/schema.sqlite.d.ts +332 -57
  158. package/dist/identity/drizzle/schema.sqlite.js +48 -18
  159. package/dist/identity/drizzle/schema.sqlite.js.map +1 -1
  160. package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js +6 -4
  161. package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js.map +1 -1
  162. package/dist/index.d.ts +6 -9
  163. package/dist/index.js +12 -14
  164. package/dist/index.js.map +1 -1
  165. package/dist/main.js +25 -8
  166. package/dist/main.js.map +1 -1
  167. package/dist/provision/ProvisionCodeCodec.d.ts +39 -0
  168. package/dist/provision/ProvisionCodeCodec.js +65 -0
  169. package/dist/provision/ProvisionCodeCodec.js.map +1 -0
  170. package/dist/provision/ProvisionCodeCodec.jsonld +47 -0
  171. package/dist/provision/ProvisionPodCreator.d.ts +20 -0
  172. package/dist/provision/ProvisionPodCreator.js +84 -0
  173. package/dist/provision/ProvisionPodCreator.js.map +1 -0
  174. package/dist/provision/ProvisionPodCreator.jsonld +118 -0
  175. package/dist/quota/DrizzleQuotaService.d.ts +17 -3
  176. package/dist/quota/DrizzleQuotaService.js +108 -8
  177. package/dist/quota/DrizzleQuotaService.js.map +1 -1
  178. package/dist/quota/DrizzleQuotaService.jsonld +33 -22
  179. package/dist/quota/NoopQuotaService.d.ts +7 -1
  180. package/dist/quota/NoopQuotaService.js +12 -0
  181. package/dist/quota/NoopQuotaService.js.map +1 -1
  182. package/dist/quota/NoopQuotaService.jsonld +24 -0
  183. package/dist/quota/QuotaService.d.ts +17 -0
  184. package/dist/quota/QuotaService.js +5 -0
  185. package/dist/quota/QuotaService.js.map +1 -1
  186. package/dist/quota/QuotaService.jsonld +50 -0
  187. package/dist/runtime/Proxy.d.ts +22 -4
  188. package/dist/runtime/Proxy.js +154 -35
  189. package/dist/runtime/Proxy.js.map +1 -1
  190. package/dist/runtime/XpodRuntime.d.ts +49 -0
  191. package/dist/runtime/XpodRuntime.js +374 -0
  192. package/dist/runtime/XpodRuntime.js.map +1 -0
  193. package/dist/runtime/env-utils.d.ts +2 -0
  194. package/dist/runtime/env-utils.js +55 -0
  195. package/dist/runtime/env-utils.js.map +1 -0
  196. package/dist/runtime/index.d.ts +4 -0
  197. package/dist/runtime/index.js +8 -1
  198. package/dist/runtime/index.js.map +1 -1
  199. package/dist/runtime/socket-fetch.d.ts +1 -0
  200. package/dist/runtime/socket-fetch.js +72 -0
  201. package/dist/runtime/socket-fetch.js.map +1 -0
  202. package/dist/runtime/socket-http.d.ts +1 -0
  203. package/dist/runtime/socket-http.js +142 -0
  204. package/dist/runtime/socket-http.js.map +1 -0
  205. package/dist/runtime/socket-utils.d.ts +2 -0
  206. package/dist/runtime/socket-utils.js +34 -0
  207. package/dist/runtime/socket-utils.js.map +1 -0
  208. package/dist/service/{EdgeNodeHeartbeatService.d.ts → EdgeNodeSignalClient.d.ts} +3 -3
  209. package/dist/service/{EdgeNodeHeartbeatService.js → EdgeNodeSignalClient.js} +4 -4
  210. package/dist/service/EdgeNodeSignalClient.js.map +1 -0
  211. package/dist/service/PodMigrationService.d.ts +1 -2
  212. package/dist/service/PodMigrationService.js +1 -2
  213. package/dist/service/PodMigrationService.js.map +1 -1
  214. package/dist/storage/SparqlUpdateResourceStore.js +1 -1
  215. package/dist/storage/SparqlUpdateResourceStore.js.map +1 -1
  216. package/dist/storage/accessors/MinioDataAccessor.d.ts +6 -0
  217. package/dist/storage/accessors/MinioDataAccessor.js +10 -0
  218. package/dist/storage/accessors/MinioDataAccessor.js.map +1 -1
  219. package/dist/storage/accessors/MinioDataAccessor.jsonld +4 -0
  220. package/dist/storage/accessors/MixDataAccessor.d.ts +2 -1
  221. package/dist/storage/accessors/MixDataAccessor.js +12 -1
  222. package/dist/storage/accessors/MixDataAccessor.js.map +1 -1
  223. package/dist/storage/accessors/MixDataAccessor.jsonld +19 -0
  224. package/dist/storage/locking/UrlAwareRedisLocker.d.ts +18 -0
  225. package/dist/storage/locking/UrlAwareRedisLocker.js +60 -0
  226. package/dist/storage/locking/UrlAwareRedisLocker.js.map +1 -0
  227. package/dist/storage/locking/UrlAwareRedisLocker.jsonld +123 -0
  228. package/dist/storage/quota/UsageRepository.d.ts +41 -8
  229. package/dist/storage/quota/UsageRepository.js +252 -50
  230. package/dist/storage/quota/UsageRepository.js.map +1 -1
  231. package/dist/storage/sparql/ComunicaQuintEngine.d.ts +9 -0
  232. package/dist/storage/sparql/ComunicaQuintEngine.js +50 -9
  233. package/dist/storage/sparql/ComunicaQuintEngine.js.map +1 -1
  234. package/dist/storage/sparql/QueryOptimizer.js +13 -1
  235. package/dist/storage/sparql/QueryOptimizer.js.map +1 -1
  236. package/dist/storage/sparql/QuintQuerySource.d.ts +14 -0
  237. package/dist/storage/sparql/QuintQuerySource.js +152 -1
  238. package/dist/storage/sparql/QuintQuerySource.js.map +1 -1
  239. package/dist/storage/sparql/SubgraphQueryEngine.d.ts +1 -0
  240. package/dist/storage/sparql/SubgraphQueryEngine.js +6 -2
  241. package/dist/storage/sparql/SubgraphQueryEngine.js.map +1 -1
  242. package/dist/storage/sparql/SubgraphQueryEngine.jsonld +4 -0
  243. package/dist/subdomain/SubdomainClient.d.ts +3 -3
  244. package/dist/subdomain/SubdomainClient.js +1 -1
  245. package/dist/subdomain/SubdomainClient.js.map +1 -1
  246. package/dist/subdomain/SubdomainService.d.ts +15 -16
  247. package/dist/subdomain/SubdomainService.js +80 -54
  248. package/dist/subdomain/SubdomainService.js.map +1 -1
  249. package/dist/subdomain/SubdomainService.jsonld +22 -26
  250. package/dist/supervisor/Supervisor.d.ts +7 -2
  251. package/dist/supervisor/Supervisor.js +33 -1
  252. package/dist/supervisor/Supervisor.js.map +1 -1
  253. package/dist/test-utils/index.d.ts +4 -0
  254. package/dist/test-utils/index.js +8 -0
  255. package/dist/test-utils/index.js.map +1 -0
  256. package/dist/test-utils/no-auth-xpod.d.ts +11 -0
  257. package/dist/test-utils/no-auth-xpod.js +25 -0
  258. package/dist/test-utils/no-auth-xpod.js.map +1 -0
  259. package/dist/test-utils/seed-pod.d.ts +5 -0
  260. package/dist/test-utils/seed-pod.js +61 -0
  261. package/dist/test-utils/seed-pod.js.map +1 -0
  262. package/package.json +23 -5
  263. package/templates/identity/account/create-pod.html.ejs +110 -0
  264. package/templates/main.html.ejs +10 -0
  265. package/dist/api/handlers/DevHandler.d.ts +0 -18
  266. package/dist/api/handlers/DevHandler.js +0 -276
  267. package/dist/api/handlers/DevHandler.js.map +0 -1
  268. package/dist/api/handlers/SignalHandler.d.ts +0 -13
  269. package/dist/api/handlers/SignalHandler.js +0 -122
  270. package/dist/api/handlers/SignalHandler.js.map +0 -1
  271. package/dist/gateway/Proxy.d.ts +0 -24
  272. package/dist/gateway/Proxy.js +0 -209
  273. package/dist/gateway/Proxy.js.map +0 -1
  274. package/dist/gateway/Supervisor.d.ts +0 -2
  275. package/dist/gateway/Supervisor.js +0 -7
  276. package/dist/gateway/Supervisor.js.map +0 -1
  277. package/dist/gateway/port-finder.d.ts +0 -4
  278. package/dist/gateway/port-finder.js +0 -15
  279. package/dist/gateway/port-finder.js.map +0 -1
  280. package/dist/gateway/types.d.ts +0 -1
  281. package/dist/gateway/types.js +0 -3
  282. package/dist/gateway/types.js.map +0 -1
  283. package/dist/http/SignalInterceptHttpHandler.d.ts +0 -24
  284. package/dist/http/SignalInterceptHttpHandler.js +0 -47
  285. package/dist/http/SignalInterceptHttpHandler.js.map +0 -1
  286. package/dist/http/SignalInterceptHttpHandler.jsonld +0 -103
  287. package/dist/http/admin/EdgeNodeSignalHttpHandler.d.ts +0 -71
  288. package/dist/http/admin/EdgeNodeSignalHttpHandler.js +0 -674
  289. package/dist/http/admin/EdgeNodeSignalHttpHandler.js.map +0 -1
  290. package/dist/http/admin/EdgeNodeSignalHttpHandler.jsonld +0 -406
  291. package/dist/http/cluster/PodMigrationHttpHandler.jsonld +0 -169
  292. package/dist/quota/DefaultQuotaService.d.ts +0 -16
  293. package/dist/quota/DefaultQuotaService.js +0 -37
  294. package/dist/quota/DefaultQuotaService.js.map +0 -1
  295. package/dist/quota/DefaultQuotaService.jsonld +0 -85
  296. package/dist/service/EdgeNodeHeartbeatService.js.map +0 -1
  297. package/dist/service/PodMigrationService.jsonld +0 -76
  298. package/dist/storage/MigratableDataAccessor.d.ts +0 -63
  299. package/dist/storage/MigratableDataAccessor.js +0 -11
  300. package/dist/storage/MigratableDataAccessor.js.map +0 -1
  301. package/dist/storage/MigratableDataAccessor.jsonld +0 -60
  302. package/dist/storage/accessors/TieredMinioDataAccessor.d.ts +0 -150
  303. package/dist/storage/accessors/TieredMinioDataAccessor.js +0 -582
  304. package/dist/storage/accessors/TieredMinioDataAccessor.js.map +0 -1
  305. package/dist/storage/accessors/TieredMinioDataAccessor.jsonld +0 -333
  306. package/static/app/assets/index.css +0 -1
  307. package/static/app/assets/main.js +0 -11
@@ -70,21 +70,15 @@ function registerApiKeyRoutes(server, options) {
70
70
  const payload = body;
71
71
  // These come from CSS client credentials creation
72
72
  const clientId = payload.clientId;
73
- const clientSecret = payload.clientSecret;
74
73
  const displayName = typeof payload.displayName === 'string' ? payload.displayName : undefined;
75
74
  if (typeof clientId !== 'string' || !clientId.trim()) {
76
75
  sendJson(response, 400, { error: 'clientId is required' });
77
76
  return;
78
77
  }
79
- if (typeof clientSecret !== 'string' || !clientSecret.trim()) {
80
- sendJson(response, 400, { error: 'clientSecret is required' });
81
- return;
82
- }
83
78
  try {
84
79
  // Use webId as account identifier
85
80
  await store.store({
86
81
  clientId,
87
- clientSecret,
88
82
  webId,
89
83
  accountId: webId,
90
84
  displayName,
@@ -1 +1 @@
1
- {"version":3,"file":"ApiKeyHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/ApiKeyHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ServerResponse } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { AuthenticatedRequest } from '../middleware/AuthMiddleware';\nimport type { ApiServer } from '../ApiServer';\nimport type { DrizzleClientCredentialsStore } from '../store/DrizzleClientCredentialsStore';\nimport { getWebId, isSolidAuth } from '../auth/AuthContext';\n\nexport interface ApiKeyHandlerOptions {\n store: DrizzleClientCredentialsStore;\n}\n\n/**\n * Handler for API Key management\n * \n * GET /v1/keys - List user's API keys\n * POST /v1/keys - Store a new API key (after creating in CSS)\n * DELETE /v1/keys/:clientId - Delete an API key\n * \n * All endpoints require Solid Token (only frontend can manage keys)\n */\nexport function registerApiKeyRoutes(server: ApiServer, options: ApiKeyHandlerOptions): void {\n const logger = getLoggerFor('ApiKeyHandler');\n const store = options.store;\n\n const rejectApiKey = (request: AuthenticatedRequest, response: ServerResponse): boolean => {\n const auth = request.auth;\n if (auth && isSolidAuth(auth) && auth.viaApiKey) {\n sendJson(response, 403, { error: 'API key is not allowed for this endpoint' });\n return true;\n }\n return false;\n };\n\n // GET /v1/keys - List user's API keys\n server.get('/v1/keys', async (request, response, _params) => {\n if (rejectApiKey(request, response)) {\n return;\n }\n const auth = request.auth!;\n const webId = getWebId(auth);\n\n if (!webId) {\n sendJson(response, 400, { error: 'Cannot determine user' });\n return;\n }\n\n try {\n // Use webId as account identifier\n const keys = await store.listByAccount(webId);\n sendJson(response, 200, {\n keys: keys.map((k) => ({\n clientId: k.clientId,\n webId: k.webId,\n displayName: k.displayName,\n createdAt: k.createdAt.toISOString(),\n })),\n });\n } catch (error) {\n logger.error(`Failed to list API keys: ${error}`);\n sendJson(response, 500, { error: 'Failed to list keys' });\n }\n });\n\n // POST /v1/keys - Store API key (frontend calls this after creating credentials in CSS)\n server.post('/v1/keys', async (request, response, _params) => {\n if (rejectApiKey(request, response)) {\n return;\n }\n const auth = request.auth!;\n const webId = getWebId(auth);\n\n if (!webId) {\n sendJson(response, 400, { error: 'Cannot determine user' });\n return;\n }\n\n const body = await readJsonBody(request);\n if (!body || typeof body !== 'object') {\n sendJson(response, 400, { error: 'Request body must be a JSON object' });\n return;\n }\n\n const payload = body as Record<string, unknown>;\n\n // These come from CSS client credentials creation\n const clientId = payload.clientId;\n const clientSecret = payload.clientSecret;\n const displayName = typeof payload.displayName === 'string' ? payload.displayName : undefined;\n\n if (typeof clientId !== 'string' || !clientId.trim()) {\n sendJson(response, 400, { error: 'clientId is required' });\n return;\n }\n\n if (typeof clientSecret !== 'string' || !clientSecret.trim()) {\n sendJson(response, 400, { error: 'clientSecret is required' });\n return;\n }\n\n try {\n // Use webId as account identifier\n await store.store({\n clientId,\n clientSecret,\n webId,\n accountId: webId,\n displayName,\n });\n\n logger.info(`Stored API key ${clientId} for user ${webId}`);\n\n sendJson(response, 201, {\n clientId,\n displayName,\n message: 'API key stored successfully.',\n });\n } catch (error) {\n logger.error(`Failed to store API key: ${error}`);\n sendJson(response, 500, { error: 'Failed to store key' });\n }\n });\n\n // DELETE /v1/keys/:clientId - Delete an API key\n server.delete('/v1/keys/:clientId', async (request, response, params) => {\n if (rejectApiKey(request, response)) {\n return;\n }\n const auth = request.auth!;\n const webId = getWebId(auth);\n const clientId = decodeURIComponent(params.clientId);\n\n if (!webId) {\n sendJson(response, 400, { error: 'Cannot determine user' });\n return;\n }\n\n try {\n // Delete with webId check to ensure ownership\n const deleted = await store.delete(clientId, webId);\n if (!deleted) {\n sendJson(response, 404, { error: 'Key not found or access denied' });\n return;\n }\n\n logger.info(`Deleted API key ${clientId}`);\n sendJson(response, 200, { status: 'deleted', clientId });\n } catch (error) {\n logger.error(`Failed to delete API key: ${error}`);\n sendJson(response, 500, { error: 'Failed to delete key' });\n }\n });\n}\n\nasync function readJsonBody(request: AuthenticatedRequest): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let data = '';\n request.setEncoding('utf8');\n request.on('data', (chunk: string) => {\n data += chunk;\n });\n request.on('end', () => {\n if (!data) {\n resolve(undefined);\n return;\n }\n try {\n resolve(JSON.parse(data));\n } catch {\n resolve(undefined);\n }\n });\n request.on('error', reject);\n });\n}\n\nfunction sendJson(response: ServerResponse, status: number, data: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(data));\n}"]}
1
+ {"version":3,"file":"ApiKeyHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/ApiKeyHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ServerResponse } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { AuthenticatedRequest } from '../middleware/AuthMiddleware';\nimport type { ApiServer } from '../ApiServer';\nimport type { DrizzleClientCredentialsStore } from '../store/DrizzleClientCredentialsStore';\nimport { getWebId, isSolidAuth } from '../auth/AuthContext';\n\nexport interface ApiKeyHandlerOptions {\n store: DrizzleClientCredentialsStore;\n}\n\n/**\n * Handler for API Key management\n * \n * GET /v1/keys - List user's API keys\n * POST /v1/keys - Store a new API key (after creating in CSS)\n * DELETE /v1/keys/:clientId - Delete an API key\n * \n * All endpoints require Solid Token (only frontend can manage keys)\n */\nexport function registerApiKeyRoutes(server: ApiServer, options: ApiKeyHandlerOptions): void {\n const logger = getLoggerFor('ApiKeyHandler');\n const store = options.store;\n\n const rejectApiKey = (request: AuthenticatedRequest, response: ServerResponse): boolean => {\n const auth = request.auth;\n if (auth && isSolidAuth(auth) && auth.viaApiKey) {\n sendJson(response, 403, { error: 'API key is not allowed for this endpoint' });\n return true;\n }\n return false;\n };\n\n // GET /v1/keys - List user's API keys\n server.get('/v1/keys', async (request, response, _params) => {\n if (rejectApiKey(request, response)) {\n return;\n }\n const auth = request.auth!;\n const webId = getWebId(auth);\n\n if (!webId) {\n sendJson(response, 400, { error: 'Cannot determine user' });\n return;\n }\n\n try {\n // Use webId as account identifier\n const keys = await store.listByAccount(webId);\n sendJson(response, 200, {\n keys: keys.map((k) => ({\n clientId: k.clientId,\n webId: k.webId,\n displayName: k.displayName,\n createdAt: k.createdAt.toISOString(),\n })),\n });\n } catch (error) {\n logger.error(`Failed to list API keys: ${error}`);\n sendJson(response, 500, { error: 'Failed to list keys' });\n }\n });\n\n // POST /v1/keys - Store API key (frontend calls this after creating credentials in CSS)\n server.post('/v1/keys', async (request, response, _params) => {\n if (rejectApiKey(request, response)) {\n return;\n }\n const auth = request.auth!;\n const webId = getWebId(auth);\n\n if (!webId) {\n sendJson(response, 400, { error: 'Cannot determine user' });\n return;\n }\n\n const body = await readJsonBody(request);\n if (!body || typeof body !== 'object') {\n sendJson(response, 400, { error: 'Request body must be a JSON object' });\n return;\n }\n\n const payload = body as Record<string, unknown>;\n\n // These come from CSS client credentials creation\n const clientId = payload.clientId;\n const displayName = typeof payload.displayName === 'string' ? payload.displayName : undefined;\n\n if (typeof clientId !== 'string' || !clientId.trim()) {\n sendJson(response, 400, { error: 'clientId is required' });\n return;\n }\n\n try {\n // Use webId as account identifier\n await store.store({\n clientId,\n webId,\n accountId: webId,\n displayName,\n });\n\n logger.info(`Stored API key ${clientId} for user ${webId}`);\n\n sendJson(response, 201, {\n clientId,\n displayName,\n message: 'API key stored successfully.',\n });\n } catch (error) {\n logger.error(`Failed to store API key: ${error}`);\n sendJson(response, 500, { error: 'Failed to store key' });\n }\n });\n\n // DELETE /v1/keys/:clientId - Delete an API key\n server.delete('/v1/keys/:clientId', async (request, response, params) => {\n if (rejectApiKey(request, response)) {\n return;\n }\n const auth = request.auth!;\n const webId = getWebId(auth);\n const clientId = decodeURIComponent(params.clientId);\n\n if (!webId) {\n sendJson(response, 400, { error: 'Cannot determine user' });\n return;\n }\n\n try {\n // Delete with webId check to ensure ownership\n const deleted = await store.delete(clientId, webId);\n if (!deleted) {\n sendJson(response, 404, { error: 'Key not found or access denied' });\n return;\n }\n\n logger.info(`Deleted API key ${clientId}`);\n sendJson(response, 200, { status: 'deleted', clientId });\n } catch (error) {\n logger.error(`Failed to delete API key: ${error}`);\n sendJson(response, 500, { error: 'Failed to delete key' });\n }\n });\n}\n\nasync function readJsonBody(request: AuthenticatedRequest): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let data = '';\n request.setEncoding('utf8');\n request.on('data', (chunk: string) => {\n data += chunk;\n });\n request.on('end', () => {\n if (!data) {\n resolve(undefined);\n return;\n }\n try {\n resolve(JSON.parse(data));\n } catch {\n resolve(undefined);\n }\n });\n request.on('error', reject);\n });\n}\n\nfunction sendJson(response: ServerResponse, status: number, data: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(data));\n}"]}
@@ -0,0 +1,17 @@
1
+ import type { ApiServer } from '../ApiServer';
2
+ import type { EdgeNodeRepository } from '../../identity/drizzle/EdgeNodeRepository';
3
+ import type { EdgeNodeDnsCoordinator } from '../../edge/EdgeNodeDnsCoordinator';
4
+ import type { EdgeNodeHealthProbeService } from '../../edge/EdgeNodeHealthProbeService';
5
+ export interface EdgeNodeSignalHandlerOptions {
6
+ repository: EdgeNodeRepository;
7
+ dnsCoordinator?: EdgeNodeDnsCoordinator;
8
+ healthProbeService?: EdgeNodeHealthProbeService;
9
+ }
10
+ /**
11
+ * Handler for edge node signaling API
12
+ *
13
+ * POST /v1/signal - Edge node heartbeat/signal
14
+ *
15
+ * Requires API authentication and a nodeId in the request body.
16
+ */
17
+ export declare function registerEdgeNodeSignalRoutes(server: ApiServer, options: EdgeNodeSignalHandlerOptions): void;
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerEdgeNodeSignalRoutes = registerEdgeNodeSignalRoutes;
4
+ const global_logger_factory_1 = require("global-logger-factory");
5
+ const AuthContext_1 = require("../auth/AuthContext");
6
+ /**
7
+ * Handler for edge node signaling API
8
+ *
9
+ * POST /v1/signal - Edge node heartbeat/signal
10
+ *
11
+ * Requires API authentication and a nodeId in the request body.
12
+ */
13
+ function registerEdgeNodeSignalRoutes(server, options) {
14
+ const logger = (0, global_logger_factory_1.getLoggerFor)('EdgeNodeSignalHandler');
15
+ const repo = options.repository;
16
+ const { dnsCoordinator, healthProbeService } = options;
17
+ // POST /v1/signal - authenticated via nodeToken, API key, or Solid token
18
+ server.post('/v1/signal', async (request, response, _params) => {
19
+ const auth = request.auth;
20
+ if (!auth) {
21
+ sendJson(response, 401, { error: 'Authentication required' });
22
+ return;
23
+ }
24
+ const body = await readJsonBody(request);
25
+ if (!body || typeof body !== 'object') {
26
+ sendJson(response, 400, { error: 'Request body must be a JSON object' });
27
+ return;
28
+ }
29
+ const payload = body;
30
+ // Resolve nodeId based on auth type
31
+ let nodeId;
32
+ if ((0, AuthContext_1.isNodeAuth)(auth)) {
33
+ // nodeToken 认证:nodeId 来自认证结果,无需 owner 检查
34
+ nodeId = (0, AuthContext_1.getNodeId)(auth);
35
+ }
36
+ else {
37
+ // WebID 认证:从 body 读 nodeId,检查 owner
38
+ const webId = (0, AuthContext_1.getWebId)(auth);
39
+ if (!webId) {
40
+ sendJson(response, 400, { error: 'Cannot determine user' });
41
+ return;
42
+ }
43
+ nodeId = typeof payload.nodeId === 'string' ? payload.nodeId.trim() : '';
44
+ if (!nodeId) {
45
+ sendJson(response, 400, { error: 'nodeId is required' });
46
+ return;
47
+ }
48
+ try {
49
+ const owner = await repo.getNodeOwner(nodeId);
50
+ if (!owner) {
51
+ sendJson(response, 404, { error: 'Node not found' });
52
+ return;
53
+ }
54
+ if (owner !== webId) {
55
+ sendJson(response, 403, { error: 'Access denied' });
56
+ return;
57
+ }
58
+ }
59
+ catch (error) {
60
+ logger.error(`Failed to validate node access: ${error}`);
61
+ sendJson(response, 500, { error: 'Failed to validate node access' });
62
+ return;
63
+ }
64
+ }
65
+ const now = new Date();
66
+ try {
67
+ // Get current metadata to merge
68
+ const existing = await repo.getNodeMetadata(nodeId);
69
+ let metadata = mergeMetadata(existing?.metadata ?? {}, payload, now);
70
+ // 从 DB connectivity 列注入 subdomain/ipv4,供 dnsCoordinator 使用
71
+ const connectivityInfo = await repo.getNodeConnectivityInfo(nodeId);
72
+ if (connectivityInfo) {
73
+ if (connectivityInfo.subdomain && !metadata.subdomain) {
74
+ metadata.subdomain = connectivityInfo.subdomain;
75
+ }
76
+ if (connectivityInfo.ipv4 && !metadata.ipv4) {
77
+ metadata.ipv4 = connectivityInfo.ipv4;
78
+ }
79
+ if (connectivityInfo.connectivityStatus && !metadata.connectivityStatus) {
80
+ metadata.connectivityStatus = connectivityInfo.connectivityStatus;
81
+ }
82
+ }
83
+ // Update heartbeat
84
+ await repo.updateNodeHeartbeat(nodeId, metadata, now);
85
+ // Update pods if provided
86
+ if (Array.isArray(payload.pods)) {
87
+ await repo.replaceNodePods(nodeId, payload.pods);
88
+ }
89
+ // 健康检查 → DNS 同步
90
+ if (healthProbeService) {
91
+ await healthProbeService.probeNode(nodeId);
92
+ // 健康检查结果写入了 DB metadata,重新读取
93
+ const freshMeta = await repo.getNodeMetadata(nodeId);
94
+ if (freshMeta?.metadata) {
95
+ const reachability = freshMeta.metadata.reachability;
96
+ if (reachability) {
97
+ metadata.reachability = reachability;
98
+ const status = reachability.status;
99
+ if (typeof status === 'string') {
100
+ metadata.connectivityStatus = status === 'unreachable' ? 'unreachable' : 'reachable';
101
+ }
102
+ }
103
+ }
104
+ }
105
+ if (dnsCoordinator) {
106
+ await dnsCoordinator.synchronize(nodeId, metadata);
107
+ }
108
+ logger.debug(`Signal received from node ${nodeId}`);
109
+ sendJson(response, 200, {
110
+ status: 'ok',
111
+ nodeId,
112
+ lastSeen: now.toISOString(),
113
+ metadata,
114
+ });
115
+ }
116
+ catch (error) {
117
+ logger.error(`Signal handling error for node ${nodeId}:`, error);
118
+ if (error instanceof Error) {
119
+ logger.error(`Stack trace: ${error.stack}`);
120
+ }
121
+ sendJson(response, 500, { error: 'Failed to process signal' });
122
+ }
123
+ });
124
+ }
125
+ function mergeMetadata(previous, payload, now) {
126
+ const next = { ...previous };
127
+ next.lastHeartbeatAt = now.toISOString();
128
+ const copyIfPresent = (key) => {
129
+ if (payload[key] !== undefined) {
130
+ next[key] = payload[key];
131
+ }
132
+ };
133
+ copyIfPresent('baseUrl');
134
+ copyIfPresent('publicAddress');
135
+ copyIfPresent('hostname');
136
+ copyIfPresent('ipv4');
137
+ copyIfPresent('ipv6');
138
+ copyIfPresent('version');
139
+ copyIfPresent('status');
140
+ copyIfPresent('capabilities');
141
+ copyIfPresent('metrics');
142
+ return next;
143
+ }
144
+ async function readJsonBody(request) {
145
+ return new Promise((resolve, reject) => {
146
+ let data = '';
147
+ request.setEncoding('utf8');
148
+ request.on('data', (chunk) => {
149
+ data += chunk;
150
+ });
151
+ request.on('end', () => {
152
+ if (!data) {
153
+ resolve(undefined);
154
+ return;
155
+ }
156
+ try {
157
+ resolve(JSON.parse(data));
158
+ }
159
+ catch {
160
+ resolve(undefined);
161
+ }
162
+ });
163
+ request.on('error', reject);
164
+ });
165
+ }
166
+ function sendJson(response, status, data) {
167
+ response.statusCode = status;
168
+ response.setHeader('Content-Type', 'application/json');
169
+ response.end(JSON.stringify(data));
170
+ }
171
+ //# sourceMappingURL=EdgeNodeSignalHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EdgeNodeSignalHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/EdgeNodeSignalHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ServerResponse } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { AuthenticatedRequest } from '../middleware/AuthMiddleware';\nimport type { ApiServer } from '../ApiServer';\nimport type { EdgeNodeRepository } from '../../identity/drizzle/EdgeNodeRepository';\nimport type { EdgeNodeDnsCoordinator } from '../../edge/EdgeNodeDnsCoordinator';\nimport type { EdgeNodeHealthProbeService } from '../../edge/EdgeNodeHealthProbeService';\nimport { isNodeAuth, getNodeId, getWebId } from '../auth/AuthContext';\n\nexport interface EdgeNodeSignalHandlerOptions {\n repository: EdgeNodeRepository;\n dnsCoordinator?: EdgeNodeDnsCoordinator;\n healthProbeService?: EdgeNodeHealthProbeService;\n}\n\n/**\n * Handler for edge node signaling API\n *\n * POST /v1/signal - Edge node heartbeat/signal\n *\n * Requires API authentication and a nodeId in the request body.\n */\nexport function registerEdgeNodeSignalRoutes(server: ApiServer, options: EdgeNodeSignalHandlerOptions): void {\n const logger = getLoggerFor('EdgeNodeSignalHandler');\n const repo = options.repository;\n const { dnsCoordinator, healthProbeService } = options;\n\n // POST /v1/signal - authenticated via nodeToken, API key, or Solid token\n server.post('/v1/signal', async (request, response, _params) => {\n const auth = request.auth;\n if (!auth) {\n sendJson(response, 401, { error: 'Authentication required' });\n return;\n }\n\n const body = await readJsonBody(request);\n if (!body || typeof body !== 'object') {\n sendJson(response, 400, { error: 'Request body must be a JSON object' });\n return;\n }\n const payload = body as Record<string, unknown>;\n\n // Resolve nodeId based on auth type\n let nodeId: string;\n\n if (isNodeAuth(auth)) {\n // nodeToken 认证:nodeId 来自认证结果,无需 owner 检查\n nodeId = getNodeId(auth)!;\n } else {\n // WebID 认证:从 body 读 nodeId,检查 owner\n const webId = getWebId(auth);\n if (!webId) {\n sendJson(response, 400, { error: 'Cannot determine user' });\n return;\n }\n nodeId = typeof payload.nodeId === 'string' ? payload.nodeId.trim() : '';\n if (!nodeId) {\n sendJson(response, 400, { error: 'nodeId is required' });\n return;\n }\n try {\n const owner = await repo.getNodeOwner(nodeId);\n if (!owner) {\n sendJson(response, 404, { error: 'Node not found' });\n return;\n }\n if (owner !== webId) {\n sendJson(response, 403, { error: 'Access denied' });\n return;\n }\n } catch (error) {\n logger.error(`Failed to validate node access: ${error}`);\n sendJson(response, 500, { error: 'Failed to validate node access' });\n return;\n }\n }\n\n const now = new Date();\n\n try {\n // Get current metadata to merge\n const existing = await repo.getNodeMetadata(nodeId);\n let metadata = mergeMetadata(existing?.metadata ?? {}, payload, now);\n\n // 从 DB connectivity 列注入 subdomain/ipv4,供 dnsCoordinator 使用\n const connectivityInfo = await repo.getNodeConnectivityInfo(nodeId);\n if (connectivityInfo) {\n if (connectivityInfo.subdomain && !metadata.subdomain) {\n metadata.subdomain = connectivityInfo.subdomain;\n }\n if (connectivityInfo.ipv4 && !metadata.ipv4) {\n metadata.ipv4 = connectivityInfo.ipv4;\n }\n if (connectivityInfo.connectivityStatus && !metadata.connectivityStatus) {\n metadata.connectivityStatus = connectivityInfo.connectivityStatus;\n }\n }\n\n // Update heartbeat\n await repo.updateNodeHeartbeat(nodeId, metadata, now);\n\n // Update pods if provided\n if (Array.isArray(payload.pods)) {\n await repo.replaceNodePods(nodeId, payload.pods as string[]);\n }\n\n // 健康检查 → DNS 同步\n if (healthProbeService) {\n await healthProbeService.probeNode(nodeId);\n // 健康检查结果写入了 DB metadata,重新读取\n const freshMeta = await repo.getNodeMetadata(nodeId);\n if (freshMeta?.metadata) {\n const reachability = (freshMeta.metadata as Record<string, unknown>).reachability;\n if (reachability) {\n metadata.reachability = reachability;\n const status = (reachability as Record<string, unknown>).status;\n if (typeof status === 'string') {\n metadata.connectivityStatus = status === 'unreachable' ? 'unreachable' : 'reachable';\n }\n }\n }\n }\n\n if (dnsCoordinator) {\n await dnsCoordinator.synchronize(nodeId, metadata);\n }\n\n logger.debug(`Signal received from node ${nodeId}`);\n\n sendJson(response, 200, {\n status: 'ok',\n nodeId,\n lastSeen: now.toISOString(),\n metadata,\n });\n } catch (error) {\n logger.error(`Signal handling error for node ${nodeId}:`, error);\n if (error instanceof Error) {\n logger.error(`Stack trace: ${error.stack}`);\n }\n sendJson(response, 500, { error: 'Failed to process signal' });\n }\n });\n}\n\nfunction mergeMetadata(\n previous: Record<string, unknown>,\n payload: Record<string, unknown>,\n now: Date,\n): Record<string, unknown> {\n const next: Record<string, unknown> = { ...previous };\n next.lastHeartbeatAt = now.toISOString();\n\n const copyIfPresent = (key: string) => {\n if (payload[key] !== undefined) {\n next[key] = payload[key];\n }\n };\n\n copyIfPresent('baseUrl');\n copyIfPresent('publicAddress');\n copyIfPresent('hostname');\n copyIfPresent('ipv4');\n copyIfPresent('ipv6');\n copyIfPresent('version');\n copyIfPresent('status');\n copyIfPresent('capabilities');\n copyIfPresent('metrics');\n\n return next;\n}\n\nasync function readJsonBody(request: AuthenticatedRequest): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let data = '';\n request.setEncoding('utf8');\n request.on('data', (chunk: string) => {\n data += chunk;\n });\n request.on('end', () => {\n if (!data) {\n resolve(undefined);\n return;\n }\n try {\n resolve(JSON.parse(data));\n } catch {\n resolve(undefined);\n }\n });\n request.on('error', reject);\n });\n}\n\nfunction sendJson(response: ServerResponse, status: number, data: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(data));\n}\n"]}
@@ -26,11 +26,12 @@ export interface DeletePodResponse {
26
26
  * Pod Management Handler
27
27
  *
28
28
  * SP (Storage Provider) 端供 IdP 调用的 API。
29
- * 用于创建/删除 Pod 目录。
29
+ * 用于创建/删除/查询 Pod 目录。
30
30
  *
31
- * 端点:
32
- * - POST /api/v1/pods - 创建 Pod
33
- * - DELETE /api/v1/pods/:podName - 删除 Pod
31
+ * 端点 (Solid Storage Provision Protocol):
32
+ * - POST /provision/pods - 创建 Pod
33
+ * - GET /provision/pods/:podName - 查询 Pod
34
+ * - DELETE /provision/pods/:podName - 删除 Pod
34
35
  *
35
36
  * 认证:
36
37
  * - 使用 IdP service token (Bearer)
@@ -29,11 +29,12 @@ const global_logger_factory_1 = require("global-logger-factory");
29
29
  * Pod Management Handler
30
30
  *
31
31
  * SP (Storage Provider) 端供 IdP 调用的 API。
32
- * 用于创建/删除 Pod 目录。
32
+ * 用于创建/删除/查询 Pod 目录。
33
33
  *
34
- * 端点:
35
- * - POST /api/v1/pods - 创建 Pod
36
- * - DELETE /api/v1/pods/:podName - 删除 Pod
34
+ * 端点 (Solid Storage Provision Protocol):
35
+ * - POST /provision/pods - 创建 Pod
36
+ * - GET /provision/pods/:podName - 查询 Pod
37
+ * - DELETE /provision/pods/:podName - 删除 Pod
37
38
  *
38
39
  * 认证:
39
40
  * - 使用 IdP service token (Bearer)
@@ -63,7 +64,7 @@ function registerPodManagementRoutes(server, options) {
63
64
  return podNameRegex.test(podName);
64
65
  }
65
66
  /**
66
- * POST /api/v1/pods
67
+ * POST /provision/pods
67
68
  *
68
69
  * 创建 Pod 目录
69
70
  *
@@ -78,7 +79,7 @@ function registerPodManagementRoutes(server, options) {
78
79
  * 401: { error: "Unauthorized" }
79
80
  * 409: { error: "Pod already exists" }
80
81
  */
81
- server.post('/api/v1/pods', async (request, response) => {
82
+ server.post('/provision/pods', async (request, response) => {
82
83
  // 1. 认证
83
84
  if (!await authenticate(request)) {
84
85
  sendJson(response, 401, { error: 'Unauthorized', message: 'Invalid or missing service token' });
@@ -135,7 +136,7 @@ function registerPodManagementRoutes(server, options) {
135
136
  }
136
137
  }, { public: true }); // Service token auth handled internally
137
138
  /**
138
- * DELETE /api/v1/pods/:podName
139
+ * DELETE /provision/pods/:podName
139
140
  *
140
141
  * 删除 Pod 目录
141
142
  *
@@ -147,7 +148,7 @@ function registerPodManagementRoutes(server, options) {
147
148
  * 401: { error: "Unauthorized" }
148
149
  * 404: { error: "Pod not found" }
149
150
  */
150
- server.delete('/api/v1/pods/:podName', async (request, response, params) => {
151
+ server.delete('/provision/pods/:podName', async (request, response, params) => {
151
152
  // 1. 认证
152
153
  if (!await authenticate(request)) {
153
154
  sendJson(response, 401, { error: 'Unauthorized', message: 'Invalid or missing service token' });
@@ -188,11 +189,11 @@ function registerPodManagementRoutes(server, options) {
188
189
  }
189
190
  }, { public: true });
190
191
  /**
191
- * GET /api/v1/pods/:podName
192
+ * GET /provision/pods/:podName
192
193
  *
193
194
  * 获取 Pod 信息(存在性检查)
194
195
  */
195
- server.get('/api/v1/pods/:podName', async (request, response, params) => {
196
+ server.get('/provision/pods/:podName', async (request, response, params) => {
196
197
  // 1. 认证
197
198
  if (!await authenticate(request)) {
198
199
  sendJson(response, 401, { error: 'Unauthorized', message: 'Invalid or missing service token' });
@@ -1 +1 @@
1
- {"version":3,"file":"PodManagementHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/PodManagementHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ServerResponse, IncomingMessage } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { ApiServer } from '../ApiServer';\n\nexport interface PodManagementHandlerOptions {\n /** Pod 存储根目录 */\n rootDir: string;\n /** 验证 IdP service token */\n verifyServiceToken: (token: string) => Promise<boolean>;\n /** 可选:限制允许的 pod 名称正则 */\n podNameRegex?: RegExp;\n}\n\nexport interface CreatePodRequest {\n /** Pod 名称(通常是用户名) */\n podName: string;\n /** 可选:初始资源 */\n initialResources?: Record<string, string>;\n}\n\nexport interface CreatePodResponse {\n success: boolean;\n podUrl: string;\n message: string;\n}\n\nexport interface DeletePodResponse {\n success: boolean;\n message: string;\n}\n\n/**\n * Pod Management Handler\n *\n * SP (Storage Provider) 端供 IdP 调用的 API。\n * 用于创建/删除 Pod 目录。\n *\n * 端点:\n * - POST /api/v1/pods - 创建 Pod\n * - DELETE /api/v1/pods/:podName - 删除 Pod\n *\n * 认证:\n * - 使用 IdP service token (Bearer)\n * - 验证 token 是否来自信任的 IdP\n */\nexport function registerPodManagementRoutes(\n server: ApiServer,\n options: PodManagementHandlerOptions\n): void {\n const logger = getLoggerFor('PodManagementHandler');\n const { rootDir, verifyServiceToken, podNameRegex = /^[a-zA-Z0-9_-]+$/ } = options;\n\n /**\n * 验证 service token\n */\n async function authenticate(request: IncomingMessage): Promise<boolean> {\n const authHeader = request.headers.authorization;\n if (!authHeader || !authHeader.startsWith('Bearer ')) {\n return false;\n }\n const token = authHeader.slice(7);\n return verifyServiceToken(token);\n }\n\n /**\n * 验证 pod 名称\n */\n function validatePodName(podName: string): boolean {\n if (!podName || podName.length < 1 || podName.length > 64) {\n return false;\n }\n return podNameRegex.test(podName);\n }\n\n /**\n * POST /api/v1/pods\n *\n * 创建 Pod 目录\n *\n * Request:\n * Authorization: Bearer {service_token}\n * Content-Type: application/json\n * Body: { podName: \"alice\", initialResources?: {...} }\n *\n * Response:\n * 201: { success: true, podUrl: \"https://node1.pods.site/alice/\" }\n * 400: { error: \"Invalid pod name\" }\n * 401: { error: \"Unauthorized\" }\n * 409: { error: \"Pod already exists\" }\n */\n server.post('/api/v1/pods', async (request, response) => {\n // 1. 认证\n if (!await authenticate(request)) {\n sendJson(response, 401, { error: 'Unauthorized', message: 'Invalid or missing service token' });\n return;\n }\n\n // 2. 解析请求体\n let body: CreatePodRequest;\n try {\n body = await readJsonBody(request) as CreatePodRequest;\n } catch (error) {\n sendJson(response, 400, { error: 'Bad Request', message: 'Invalid JSON body' });\n return;\n }\n\n const { podName, initialResources } = body;\n\n // 3. 验证 pod 名称\n if (!validatePodName(podName)) {\n sendJson(response, 400, {\n error: 'Bad Request',\n message: `Invalid pod name: ${podName}. Must match ${podNameRegex.toString()}`\n });\n return;\n }\n\n // 4. 检查是否已存在\n const podPath = `${rootDir}/${podName}`;\n try {\n const exists = await fileExists(podPath);\n if (exists) {\n sendJson(response, 409, { error: 'Conflict', message: `Pod ${podName} already exists` });\n return;\n }\n } catch (error) {\n logger.error(`Error checking pod existence: ${(error as Error).message}`);\n sendJson(response, 500, { error: 'Internal Server Error', message: 'Failed to check pod existence' });\n return;\n }\n\n // 5. 创建 Pod 目录\n try {\n await createPodDirectory(podPath, initialResources);\n logger.info(`Created pod: ${podName} at ${podPath}`);\n\n // 构建 pod URL (基于请求的 host)\n const host = request.headers.host || 'localhost';\n const podUrl = `https://${host}/${podName}/`;\n\n sendJson(response, 201, {\n success: true,\n podUrl,\n message: `Pod ${podName} created successfully`\n });\n } catch (error) {\n logger.error(`Failed to create pod: ${(error as Error).message}`);\n sendJson(response, 500, { error: 'Internal Server Error', message: 'Failed to create pod' });\n }\n }, { public: true }); // Service token auth handled internally\n\n /**\n * DELETE /api/v1/pods/:podName\n *\n * 删除 Pod 目录\n *\n * Request:\n * Authorization: Bearer {service_token}\n *\n * Response:\n * 200: { success: true }\n * 401: { error: \"Unauthorized\" }\n * 404: { error: \"Pod not found\" }\n */\n server.delete('/api/v1/pods/:podName', async (request, response, params) => {\n // 1. 认证\n if (!await authenticate(request)) {\n sendJson(response, 401, { error: 'Unauthorized', message: 'Invalid or missing service token' });\n return;\n }\n\n const podName = decodeURIComponent(params.podName);\n\n // 2. 验证 pod 名称\n if (!validatePodName(podName)) {\n sendJson(response, 400, { error: 'Bad Request', message: `Invalid pod name: ${podName}` });\n return;\n }\n\n // 3. 检查是否存在\n const podPath = `${rootDir}/${podName}`;\n try {\n const exists = await fileExists(podPath);\n if (!exists) {\n sendJson(response, 404, { error: 'Not Found', message: `Pod ${podName} not found` });\n return;\n }\n } catch (error) {\n logger.error(`Error checking pod existence: ${(error as Error).message}`);\n sendJson(response, 500, { error: 'Internal Server Error', message: 'Failed to check pod existence' });\n return;\n }\n\n // 4. 删除 Pod 目录\n try {\n await deletePodDirectory(podPath);\n logger.info(`Deleted pod: ${podName}`);\n\n sendJson(response, 200, {\n success: true,\n message: `Pod ${podName} deleted successfully`\n });\n } catch (error) {\n logger.error(`Failed to delete pod: ${(error as Error).message}`);\n sendJson(response, 500, { error: 'Internal Server Error', message: 'Failed to delete pod' });\n }\n }, { public: true });\n\n /**\n * GET /api/v1/pods/:podName\n *\n * 获取 Pod 信息(存在性检查)\n */\n server.get('/api/v1/pods/:podName', async (request, response, params) => {\n // 1. 认证\n if (!await authenticate(request)) {\n sendJson(response, 401, { error: 'Unauthorized', message: 'Invalid or missing service token' });\n return;\n }\n\n const podName = decodeURIComponent(params.podName);\n const podPath = `${rootDir}/${podName}`;\n\n try {\n const exists = await fileExists(podPath);\n if (!exists) {\n sendJson(response, 404, { error: 'Not Found', message: `Pod ${podName} not found` });\n return;\n }\n\n const host = request.headers.host || 'localhost';\n const podUrl = `https://${host}/${podName}/`;\n\n sendJson(response, 200, {\n exists: true,\n podName,\n podUrl,\n storagePath: podPath\n });\n } catch (error) {\n logger.error(`Error getting pod info: ${(error as Error).message}`);\n sendJson(response, 500, { error: 'Internal Server Error', message: 'Failed to get pod info' });\n }\n }, { public: true });\n\n logger.info(`Pod management routes registered with rootDir: ${rootDir}`);\n}\n\n/**\n * 读取 JSON 请求体\n */\nasync function readJsonBody(request: IncomingMessage): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let data = '';\n request.setEncoding('utf8');\n request.on('data', (chunk: string) => {\n data += chunk;\n });\n request.on('end', () => {\n if (!data) {\n resolve({});\n return;\n }\n try {\n resolve(JSON.parse(data));\n } catch (error) {\n reject(error);\n }\n });\n request.on('error', reject);\n });\n}\n\n/**\n * 发送 JSON 响应\n */\nfunction sendJson(response: ServerResponse, status: number, data: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(data));\n}\n\n/**\n * 检查文件/目录是否存在\n */\nasync function fileExists(path: string): Promise<boolean> {\n const { stat } = await import('node:fs/promises');\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * 创建 Pod 目录\n */\nasync function createPodDirectory(\n podPath: string,\n initialResources?: Record<string, string>\n): Promise<void> {\n const { mkdir, writeFile } = await import('node:fs/promises');\n const { join } = await import('node:path');\n\n // 创建目录\n await mkdir(podPath, { recursive: true });\n\n // 创建初始资源\n if (initialResources) {\n for (const [filename, content] of Object.entries(initialResources)) {\n const filePath = join(podPath, filename);\n await writeFile(filePath, content, 'utf8');\n }\n }\n}\n\n/**\n * 删除 Pod 目录\n */\nasync function deletePodDirectory(podPath: string): Promise<void> {\n const { rm } = await import('node:fs/promises');\n await rm(podPath, { recursive: true, force: true });\n}\n"]}
1
+ {"version":3,"file":"PodManagementHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/PodManagementHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ServerResponse, IncomingMessage } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { ApiServer } from '../ApiServer';\n\nexport interface PodManagementHandlerOptions {\n /** Pod 存储根目录 */\n rootDir: string;\n /** 验证 IdP service token */\n verifyServiceToken: (token: string) => Promise<boolean>;\n /** 可选:限制允许的 pod 名称正则 */\n podNameRegex?: RegExp;\n}\n\nexport interface CreatePodRequest {\n /** Pod 名称(通常是用户名) */\n podName: string;\n /** 可选:初始资源 */\n initialResources?: Record<string, string>;\n}\n\nexport interface CreatePodResponse {\n success: boolean;\n podUrl: string;\n message: string;\n}\n\nexport interface DeletePodResponse {\n success: boolean;\n message: string;\n}\n\n/**\n * Pod Management Handler\n *\n * SP (Storage Provider) 端供 IdP 调用的 API。\n * 用于创建/删除/查询 Pod 目录。\n *\n * 端点 (Solid Storage Provision Protocol):\n * - POST /provision/pods - 创建 Pod\n * - GET /provision/pods/:podName - 查询 Pod\n * - DELETE /provision/pods/:podName - 删除 Pod\n *\n * 认证:\n * - 使用 IdP service token (Bearer)\n * - 验证 token 是否来自信任的 IdP\n */\nexport function registerPodManagementRoutes(\n server: ApiServer,\n options: PodManagementHandlerOptions\n): void {\n const logger = getLoggerFor('PodManagementHandler');\n const { rootDir, verifyServiceToken, podNameRegex = /^[a-zA-Z0-9_-]+$/ } = options;\n\n /**\n * 验证 service token\n */\n async function authenticate(request: IncomingMessage): Promise<boolean> {\n const authHeader = request.headers.authorization;\n if (!authHeader || !authHeader.startsWith('Bearer ')) {\n return false;\n }\n const token = authHeader.slice(7);\n return verifyServiceToken(token);\n }\n\n /**\n * 验证 pod 名称\n */\n function validatePodName(podName: string): boolean {\n if (!podName || podName.length < 1 || podName.length > 64) {\n return false;\n }\n return podNameRegex.test(podName);\n }\n\n /**\n * POST /provision/pods\n *\n * 创建 Pod 目录\n *\n * Request:\n * Authorization: Bearer {service_token}\n * Content-Type: application/json\n * Body: { podName: \"alice\", initialResources?: {...} }\n *\n * Response:\n * 201: { success: true, podUrl: \"https://node1.pods.site/alice/\" }\n * 400: { error: \"Invalid pod name\" }\n * 401: { error: \"Unauthorized\" }\n * 409: { error: \"Pod already exists\" }\n */\n server.post('/provision/pods', async (request, response) => {\n // 1. 认证\n if (!await authenticate(request)) {\n sendJson(response, 401, { error: 'Unauthorized', message: 'Invalid or missing service token' });\n return;\n }\n\n // 2. 解析请求体\n let body: CreatePodRequest;\n try {\n body = await readJsonBody(request) as CreatePodRequest;\n } catch (error) {\n sendJson(response, 400, { error: 'Bad Request', message: 'Invalid JSON body' });\n return;\n }\n\n const { podName, initialResources } = body;\n\n // 3. 验证 pod 名称\n if (!validatePodName(podName)) {\n sendJson(response, 400, {\n error: 'Bad Request',\n message: `Invalid pod name: ${podName}. Must match ${podNameRegex.toString()}`\n });\n return;\n }\n\n // 4. 检查是否已存在\n const podPath = `${rootDir}/${podName}`;\n try {\n const exists = await fileExists(podPath);\n if (exists) {\n sendJson(response, 409, { error: 'Conflict', message: `Pod ${podName} already exists` });\n return;\n }\n } catch (error) {\n logger.error(`Error checking pod existence: ${(error as Error).message}`);\n sendJson(response, 500, { error: 'Internal Server Error', message: 'Failed to check pod existence' });\n return;\n }\n\n // 5. 创建 Pod 目录\n try {\n await createPodDirectory(podPath, initialResources);\n logger.info(`Created pod: ${podName} at ${podPath}`);\n\n // 构建 pod URL (基于请求的 host)\n const host = request.headers.host || 'localhost';\n const podUrl = `https://${host}/${podName}/`;\n\n sendJson(response, 201, {\n success: true,\n podUrl,\n message: `Pod ${podName} created successfully`\n });\n } catch (error) {\n logger.error(`Failed to create pod: ${(error as Error).message}`);\n sendJson(response, 500, { error: 'Internal Server Error', message: 'Failed to create pod' });\n }\n }, { public: true }); // Service token auth handled internally\n\n /**\n * DELETE /provision/pods/:podName\n *\n * 删除 Pod 目录\n *\n * Request:\n * Authorization: Bearer {service_token}\n *\n * Response:\n * 200: { success: true }\n * 401: { error: \"Unauthorized\" }\n * 404: { error: \"Pod not found\" }\n */\n server.delete('/provision/pods/:podName', async (request, response, params) => {\n // 1. 认证\n if (!await authenticate(request)) {\n sendJson(response, 401, { error: 'Unauthorized', message: 'Invalid or missing service token' });\n return;\n }\n\n const podName = decodeURIComponent(params.podName);\n\n // 2. 验证 pod 名称\n if (!validatePodName(podName)) {\n sendJson(response, 400, { error: 'Bad Request', message: `Invalid pod name: ${podName}` });\n return;\n }\n\n // 3. 检查是否存在\n const podPath = `${rootDir}/${podName}`;\n try {\n const exists = await fileExists(podPath);\n if (!exists) {\n sendJson(response, 404, { error: 'Not Found', message: `Pod ${podName} not found` });\n return;\n }\n } catch (error) {\n logger.error(`Error checking pod existence: ${(error as Error).message}`);\n sendJson(response, 500, { error: 'Internal Server Error', message: 'Failed to check pod existence' });\n return;\n }\n\n // 4. 删除 Pod 目录\n try {\n await deletePodDirectory(podPath);\n logger.info(`Deleted pod: ${podName}`);\n\n sendJson(response, 200, {\n success: true,\n message: `Pod ${podName} deleted successfully`\n });\n } catch (error) {\n logger.error(`Failed to delete pod: ${(error as Error).message}`);\n sendJson(response, 500, { error: 'Internal Server Error', message: 'Failed to delete pod' });\n }\n }, { public: true });\n\n /**\n * GET /provision/pods/:podName\n *\n * 获取 Pod 信息(存在性检查)\n */\n server.get('/provision/pods/:podName', async (request, response, params) => {\n // 1. 认证\n if (!await authenticate(request)) {\n sendJson(response, 401, { error: 'Unauthorized', message: 'Invalid or missing service token' });\n return;\n }\n\n const podName = decodeURIComponent(params.podName);\n const podPath = `${rootDir}/${podName}`;\n\n try {\n const exists = await fileExists(podPath);\n if (!exists) {\n sendJson(response, 404, { error: 'Not Found', message: `Pod ${podName} not found` });\n return;\n }\n\n const host = request.headers.host || 'localhost';\n const podUrl = `https://${host}/${podName}/`;\n\n sendJson(response, 200, {\n exists: true,\n podName,\n podUrl,\n storagePath: podPath\n });\n } catch (error) {\n logger.error(`Error getting pod info: ${(error as Error).message}`);\n sendJson(response, 500, { error: 'Internal Server Error', message: 'Failed to get pod info' });\n }\n }, { public: true });\n\n logger.info(`Pod management routes registered with rootDir: ${rootDir}`);\n}\n\n/**\n * 读取 JSON 请求体\n */\nasync function readJsonBody(request: IncomingMessage): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let data = '';\n request.setEncoding('utf8');\n request.on('data', (chunk: string) => {\n data += chunk;\n });\n request.on('end', () => {\n if (!data) {\n resolve({});\n return;\n }\n try {\n resolve(JSON.parse(data));\n } catch (error) {\n reject(error);\n }\n });\n request.on('error', reject);\n });\n}\n\n/**\n * 发送 JSON 响应\n */\nfunction sendJson(response: ServerResponse, status: number, data: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(data));\n}\n\n/**\n * 检查文件/目录是否存在\n */\nasync function fileExists(path: string): Promise<boolean> {\n const { stat } = await import('node:fs/promises');\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * 创建 Pod 目录\n */\nasync function createPodDirectory(\n podPath: string,\n initialResources?: Record<string, string>\n): Promise<void> {\n const { mkdir, writeFile } = await import('node:fs/promises');\n const { join } = await import('node:path');\n\n // 创建目录\n await mkdir(podPath, { recursive: true });\n\n // 创建初始资源\n if (initialResources) {\n for (const [filename, content] of Object.entries(initialResources)) {\n const filePath = join(podPath, filename);\n await writeFile(filePath, content, 'utf8');\n }\n }\n}\n\n/**\n * 删除 Pod 目录\n */\nasync function deletePodDirectory(podPath: string): Promise<void> {\n const { rm } = await import('node:fs/promises');\n await rm(podPath, { recursive: true, force: true });\n}\n"]}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Provision Handler
3
+ *
4
+ * Cloud 端的 SP 注册 API
5
+ *
6
+ * POST /provision/nodes - SP 注册(公开,无需认证)
7
+ * 返回 nodeId、nodeToken、serviceToken、provisionCode(自包含 JWT)
8
+ *
9
+ * provisionCode 是自包含 token,编码了 SP 的 publicUrl 和 serviceToken。
10
+ * CSS 侧的 ProvisionPodCreator 解码后直接回调 SP,不需要查数据库。
11
+ *
12
+ * GET /provision/status - Local 端 SP 状态查询(公开)
13
+ * 返回 SP 配置状态,供 Linx 查询
14
+ */
15
+ import type { ApiServer } from '../ApiServer';
16
+ import type { EdgeNodeRepository } from '../../identity/drizzle/EdgeNodeRepository';
17
+ export interface ProvisionHandlerOptions {
18
+ repository: EdgeNodeRepository;
19
+ /** Cloud baseUrl,用于派生 provisionCode 签名密钥 */
20
+ baseUrl: string;
21
+ /** 节点域名根域名,如 "undefineds.site" */
22
+ baseStorageDomain?: string;
23
+ /** provisionCode 有效期(秒),默认 24 小时 */
24
+ provisionCodeTtl?: number;
25
+ }
26
+ export declare function registerProvisionRoutes(server: ApiServer, options: ProvisionHandlerOptions): void;
27
+ /**
28
+ * Local 端 SP 状态查询路由
29
+ */
30
+ export interface ProvisionStatusOptions {
31
+ /** Cloud API 端点 */
32
+ cloudUrl?: string;
33
+ /** 节点 ID */
34
+ nodeId?: string;
35
+ /** SP 子域名 */
36
+ spDomain?: string;
37
+ /** Cloud baseUrl,用于拼 provisionUrl */
38
+ cloudBaseUrl?: string;
39
+ /** provisionCode(可选,由环境变量传入) */
40
+ provisionCode?: string;
41
+ }
42
+ export declare function registerProvisionStatusRoute(server: ApiServer, options: ProvisionStatusOptions): void;