@edge-base/server 0.1.1

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 (309) hide show
  1. package/admin-build/.gitkeep +0 -0
  2. package/admin-build/_app/env.js +1 -0
  3. package/admin-build/_app/immutable/assets/0.Bm6cF078.css +1 -0
  4. package/admin-build/_app/immutable/assets/1.BfW3pUNa.css +1 -0
  5. package/admin-build/_app/immutable/assets/11.CVmQOewb.css +1 -0
  6. package/admin-build/_app/immutable/assets/12.B1EhbRZT.css +1 -0
  7. package/admin-build/_app/immutable/assets/13.BvwYeuwE.css +1 -0
  8. package/admin-build/_app/immutable/assets/14.CdVfcO0R.css +1 -0
  9. package/admin-build/_app/immutable/assets/15.2yeZ66b-.css +1 -0
  10. package/admin-build/_app/immutable/assets/17.BVg0JEVu.css +1 -0
  11. package/admin-build/_app/immutable/assets/18.Rwnl3x_i.css +1 -0
  12. package/admin-build/_app/immutable/assets/20.DsPWA9AV.css +1 -0
  13. package/admin-build/_app/immutable/assets/21.Dz2RJ56c.css +1 -0
  14. package/admin-build/_app/immutable/assets/22.DwNLk5Ai.css +1 -0
  15. package/admin-build/_app/immutable/assets/23.CFpu0gOO.css +1 -0
  16. package/admin-build/_app/immutable/assets/24.Cy5LBeoJ.css +1 -0
  17. package/admin-build/_app/immutable/assets/25.pUyLVf-h.css +1 -0
  18. package/admin-build/_app/immutable/assets/26.DBcGrlXa.css +1 -0
  19. package/admin-build/_app/immutable/assets/27.BswYyAJD.css +1 -0
  20. package/admin-build/_app/immutable/assets/28.B4ueB1Kf.css +1 -0
  21. package/admin-build/_app/immutable/assets/29.B-qU6PdF.css +1 -0
  22. package/admin-build/_app/immutable/assets/3.Dg81Pgmd.css +1 -0
  23. package/admin-build/_app/immutable/assets/30.CsdWum94.css +1 -0
  24. package/admin-build/_app/immutable/assets/31.U6OwIp50.css +1 -0
  25. package/admin-build/_app/immutable/assets/4.CyawCCux.css +1 -0
  26. package/admin-build/_app/immutable/assets/5.C0YO2HTk.css +1 -0
  27. package/admin-build/_app/immutable/assets/8.Br5jd6kD.css +1 -0
  28. package/admin-build/_app/immutable/assets/Badge.EMYLHBxE.css +1 -0
  29. package/admin-build/_app/immutable/assets/Button.DpzMRTjK.css +1 -0
  30. package/admin-build/_app/immutable/assets/ConfirmDialog.DAnaWRRk.css +1 -0
  31. package/admin-build/_app/immutable/assets/EmptyState.CwKsu57Y.css +1 -0
  32. package/admin-build/_app/immutable/assets/Input.BDUSenmU.css +1 -0
  33. package/admin-build/_app/immutable/assets/Modal.Dm5B0Xie.css +1 -0
  34. package/admin-build/_app/immutable/assets/PageShell.CmU-Xh-b.css +1 -0
  35. package/admin-build/_app/immutable/assets/SchemaFieldEditor.g4NsCdno.css +1 -0
  36. package/admin-build/_app/immutable/assets/Select.BW4Keufm.css +1 -0
  37. package/admin-build/_app/immutable/assets/Skeleton.KWUulTKJ.css +1 -0
  38. package/admin-build/_app/immutable/assets/Tabs.CniGYb67.css +1 -0
  39. package/admin-build/_app/immutable/assets/TimeChart.BTCDAvmT.css +1 -0
  40. package/admin-build/_app/immutable/assets/Toggle.Cy_K12OM.css +1 -0
  41. package/admin-build/_app/immutable/assets/TopList.ClFzmPlA.css +1 -0
  42. package/admin-build/_app/immutable/chunks/7B47DvSx.js +1 -0
  43. package/admin-build/_app/immutable/chunks/7f08Id8e.js +1 -0
  44. package/admin-build/_app/immutable/chunks/8wJeQ7LN.js +1 -0
  45. package/admin-build/_app/immutable/chunks/B-h2afW5.js +1 -0
  46. package/admin-build/_app/immutable/chunks/B8vJP3wz.js +1 -0
  47. package/admin-build/_app/immutable/chunks/BR_fL5Yv.js +1 -0
  48. package/admin-build/_app/immutable/chunks/BY92tFS2.js +1 -0
  49. package/admin-build/_app/immutable/chunks/BcR-Rdj9.js +1 -0
  50. package/admin-build/_app/immutable/chunks/BdrwyZv8.js +1 -0
  51. package/admin-build/_app/immutable/chunks/Bh56EfQ_.js +1 -0
  52. package/admin-build/_app/immutable/chunks/BkrCkgYp.js +1 -0
  53. package/admin-build/_app/immutable/chunks/BmRjiP5k.js +1 -0
  54. package/admin-build/_app/immutable/chunks/BsokvhWC.js +1 -0
  55. package/admin-build/_app/immutable/chunks/C4D51vTW.js +1 -0
  56. package/admin-build/_app/immutable/chunks/C6puvcoR.js +2 -0
  57. package/admin-build/_app/immutable/chunks/CCKNu7m7.js +1 -0
  58. package/admin-build/_app/immutable/chunks/CWj6FrbW.js +1 -0
  59. package/admin-build/_app/immutable/chunks/Ce-ngf4p.js +5 -0
  60. package/admin-build/_app/immutable/chunks/Cs0GwzJA.js +1 -0
  61. package/admin-build/_app/immutable/chunks/CwROoZK0.js +1 -0
  62. package/admin-build/_app/immutable/chunks/CxCPv_Ut.js +1 -0
  63. package/admin-build/_app/immutable/chunks/CxbRue-5.js +1 -0
  64. package/admin-build/_app/immutable/chunks/CyqB6g-D.js +1 -0
  65. package/admin-build/_app/immutable/chunks/D5h5A1cc.js +2 -0
  66. package/admin-build/_app/immutable/chunks/DnyL7Zq-.js +1 -0
  67. package/admin-build/_app/immutable/chunks/DoPXzH7F.js +1 -0
  68. package/admin-build/_app/immutable/chunks/DrQSgw-f.js +1 -0
  69. package/admin-build/_app/immutable/chunks/DttM2zNO.js +1 -0
  70. package/admin-build/_app/immutable/chunks/DuXuUBWN.js +1 -0
  71. package/admin-build/_app/immutable/chunks/MdeqaOQx.js +10 -0
  72. package/admin-build/_app/immutable/chunks/NuUjtcO2.js +1 -0
  73. package/admin-build/_app/immutable/chunks/Q2nPFxS6.js +1 -0
  74. package/admin-build/_app/immutable/chunks/R6arueIl.js +1 -0
  75. package/admin-build/_app/immutable/chunks/UUazaC_N.js +1 -0
  76. package/admin-build/_app/immutable/chunks/cOYbrQxx.js +1 -0
  77. package/admin-build/_app/immutable/chunks/eFQHTGwA.js +1 -0
  78. package/admin-build/_app/immutable/chunks/ehbppgYb.js +1 -0
  79. package/admin-build/_app/immutable/chunks/glwixJlP.js +1 -0
  80. package/admin-build/_app/immutable/chunks/vApWTCBs.js +1 -0
  81. package/admin-build/_app/immutable/chunks/w89G9Xpi.js +1 -0
  82. package/admin-build/_app/immutable/chunks/wJsUhbfZ.js +1 -0
  83. package/admin-build/_app/immutable/chunks/zfauFM8P.js +1 -0
  84. package/admin-build/_app/immutable/entry/app.CcO-Uos3.js +2 -0
  85. package/admin-build/_app/immutable/entry/start.COebYq3I.js +1 -0
  86. package/admin-build/_app/immutable/nodes/0.CjtHKU-6.js +1 -0
  87. package/admin-build/_app/immutable/nodes/1.DEisjlM0.js +1 -0
  88. package/admin-build/_app/immutable/nodes/10.CvhdyWVB.js +1 -0
  89. package/admin-build/_app/immutable/nodes/11.DjHqcOvy.js +1 -0
  90. package/admin-build/_app/immutable/nodes/12.mQLz4Mj_.js +1 -0
  91. package/admin-build/_app/immutable/nodes/13.CBonZZyP.js +110 -0
  92. package/admin-build/_app/immutable/nodes/14.d-oiZL0j.js +3 -0
  93. package/admin-build/_app/immutable/nodes/15.CKPQsUYF.js +1 -0
  94. package/admin-build/_app/immutable/nodes/16.wPzAPQGx.js +1 -0
  95. package/admin-build/_app/immutable/nodes/17.DayhKyEZ.js +1 -0
  96. package/admin-build/_app/immutable/nodes/18.DKwS0Ir0.js +1 -0
  97. package/admin-build/_app/immutable/nodes/19.wPzAPQGx.js +1 -0
  98. package/admin-build/_app/immutable/nodes/2.BKoKrw1i.js +1 -0
  99. package/admin-build/_app/immutable/nodes/20.BvIkkkrW.js +1 -0
  100. package/admin-build/_app/immutable/nodes/21.DMaFhdHk.js +128 -0
  101. package/admin-build/_app/immutable/nodes/22.3xdgwuK1.js +1 -0
  102. package/admin-build/_app/immutable/nodes/23.8Bvgjbsl.js +112 -0
  103. package/admin-build/_app/immutable/nodes/24.DzSSzRhG.js +2 -0
  104. package/admin-build/_app/immutable/nodes/25.9KKYBnAE.js +2 -0
  105. package/admin-build/_app/immutable/nodes/26.Bhn9dfhY.js +1 -0
  106. package/admin-build/_app/immutable/nodes/27.kRLiC24G.js +1 -0
  107. package/admin-build/_app/immutable/nodes/28.BVIN1-7N.js +1 -0
  108. package/admin-build/_app/immutable/nodes/29.3yabZWj4.js +1 -0
  109. package/admin-build/_app/immutable/nodes/3.BFtSOkX7.js +2 -0
  110. package/admin-build/_app/immutable/nodes/30.CyCQlwaP.js +1 -0
  111. package/admin-build/_app/immutable/nodes/31.C4LDXjES.js +1 -0
  112. package/admin-build/_app/immutable/nodes/4.CvbiMlCa.js +1 -0
  113. package/admin-build/_app/immutable/nodes/5.C6BLv2eM.js +1 -0
  114. package/admin-build/_app/immutable/nodes/6.BcXvfl2P.js +1 -0
  115. package/admin-build/_app/immutable/nodes/7.CIuqhPiK.js +1 -0
  116. package/admin-build/_app/immutable/nodes/8.BQOR_JfO.js +1 -0
  117. package/admin-build/_app/immutable/nodes/9.NZqXQxPy.js +1 -0
  118. package/admin-build/_app/version.json +1 -0
  119. package/admin-build/favicon.svg +26 -0
  120. package/admin-build/index.html +45 -0
  121. package/openapi.json +19543 -0
  122. package/package.json +66 -0
  123. package/src/__tests__/admin-assets.test.ts +55 -0
  124. package/src/__tests__/admin-data-routes.test.ts +488 -0
  125. package/src/__tests__/admin-db-target.test.ts +103 -0
  126. package/src/__tests__/admin-routing.test.ts +31 -0
  127. package/src/__tests__/admin-user-management.test.ts +311 -0
  128. package/src/__tests__/analytics-query.test.ts +75 -0
  129. package/src/__tests__/auth-d1.test.ts +749 -0
  130. package/src/__tests__/auth-db-adapter.test.ts +73 -0
  131. package/src/__tests__/auth-jwt.test.ts +440 -0
  132. package/src/__tests__/auth-oauth.test.ts +389 -0
  133. package/src/__tests__/auth-password.test.ts +367 -0
  134. package/src/__tests__/auth-redirect.test.ts +87 -0
  135. package/src/__tests__/backup-restore.test.ts +711 -0
  136. package/src/__tests__/broadcast.test.ts +128 -0
  137. package/src/__tests__/cli.test.ts +178 -0
  138. package/src/__tests__/cloudflare-realtime.test.ts +113 -0
  139. package/src/__tests__/config.test.ts +469 -0
  140. package/src/__tests__/cors.test.ts +154 -0
  141. package/src/__tests__/cron.test.ts +302 -0
  142. package/src/__tests__/d1-handler.test.ts +402 -0
  143. package/src/__tests__/d1-sql.test.ts +120 -0
  144. package/src/__tests__/database-live-config.test.ts +42 -0
  145. package/src/__tests__/database-live-emitter.test.ts +56 -0
  146. package/src/__tests__/database-live-filters.test.ts +63 -0
  147. package/src/__tests__/database-live-route.test.ts +113 -0
  148. package/src/__tests__/db-sql.test.ts +163 -0
  149. package/src/__tests__/do-lifecycle.test.ts +263 -0
  150. package/src/__tests__/do-router.test.ts +729 -0
  151. package/src/__tests__/email-provider.test.ts +128 -0
  152. package/src/__tests__/email-templates.test.ts +528 -0
  153. package/src/__tests__/error-format.test.ts +250 -0
  154. package/src/__tests__/field-ops.test.ts +242 -0
  155. package/src/__tests__/functions-context.test.ts +334 -0
  156. package/src/__tests__/functions-d1-proxy.test.ts +229 -0
  157. package/src/__tests__/functions-registry-runtime-config.test.ts +17 -0
  158. package/src/__tests__/functions-route.test.ts +139 -0
  159. package/src/__tests__/internal-request.test.ts +77 -0
  160. package/src/__tests__/log-writer.test.ts +44 -0
  161. package/src/__tests__/logger.test.ts +58 -0
  162. package/src/__tests__/meta-admin-proxy.test.ts +48 -0
  163. package/src/__tests__/meta-export-coverage.test.ts +191 -0
  164. package/src/__tests__/meta-route-registration.test.ts +47 -0
  165. package/src/__tests__/namespace-dump.test.ts +28 -0
  166. package/src/__tests__/oauth-providers.test.ts +337 -0
  167. package/src/__tests__/openapi-coverage.test.ts +144 -0
  168. package/src/__tests__/pagination.test.ts +59 -0
  169. package/src/__tests__/password-policy.test.ts +191 -0
  170. package/src/__tests__/plugin-migrations.test.ts +379 -0
  171. package/src/__tests__/postgres-batch-compat.test.ts +133 -0
  172. package/src/__tests__/postgres-dialect.test.ts +328 -0
  173. package/src/__tests__/postgres-executor.test.ts +79 -0
  174. package/src/__tests__/postgres-field-ops-compat.test.ts +222 -0
  175. package/src/__tests__/postgres-schema-init.test.ts +105 -0
  176. package/src/__tests__/postgres-table-utils.test.ts +107 -0
  177. package/src/__tests__/presence.test.ts +199 -0
  178. package/src/__tests__/provider.test.ts +550 -0
  179. package/src/__tests__/public-user-profile.test.ts +339 -0
  180. package/src/__tests__/push-handlers.test.ts +179 -0
  181. package/src/__tests__/push-provider.test.ts +80 -0
  182. package/src/__tests__/push-token.test.ts +418 -0
  183. package/src/__tests__/query.test.ts +771 -0
  184. package/src/__tests__/rate-limit.test.ts +260 -0
  185. package/src/__tests__/room-access-policy.test.ts +101 -0
  186. package/src/__tests__/room-handler-context.test.ts +130 -0
  187. package/src/__tests__/room-monitoring.test.ts +138 -0
  188. package/src/__tests__/room-runtime-routing.test.ts +222 -0
  189. package/src/__tests__/room.test.ts +254 -0
  190. package/src/__tests__/route-parser.test.ts +490 -0
  191. package/src/__tests__/rules.test.ts +234 -0
  192. package/src/__tests__/runtime-surface-accounting.test.ts +120 -0
  193. package/src/__tests__/scheduled.test.ts +80 -0
  194. package/src/__tests__/schema.test.ts +1273 -0
  195. package/src/__tests__/security-hardening.test.ts +312 -0
  196. package/src/__tests__/server.unit.test.ts +333 -0
  197. package/src/__tests__/service-key-db-proxy.test.ts +650 -0
  198. package/src/__tests__/service-key-provider-bypass.test.ts +138 -0
  199. package/src/__tests__/service-key.test.ts +757 -0
  200. package/src/__tests__/smoke-skip-report.test.ts +72 -0
  201. package/src/__tests__/sms-provider.test.ts +39 -0
  202. package/src/__tests__/sql-route.test.ts +218 -0
  203. package/src/__tests__/storage-hook-context.test.ts +115 -0
  204. package/src/__tests__/totp.test.ts +200 -0
  205. package/src/__tests__/uuid.test.ts +144 -0
  206. package/src/__tests__/validation.test.ts +773 -0
  207. package/src/__tests__/websocket-pending.test.ts +163 -0
  208. package/src/_functions-registry.ts +51 -0
  209. package/src/bench-entry.ts +9 -0
  210. package/src/cloudflare-test.d.ts +1 -0
  211. package/src/durable-objects/auth-do.ts +49 -0
  212. package/src/durable-objects/database-do.ts +2240 -0
  213. package/src/durable-objects/database-live-do.ts +949 -0
  214. package/src/durable-objects/logs-do.ts +1200 -0
  215. package/src/durable-objects/room-runtime-base.ts +1604 -0
  216. package/src/durable-objects/rooms-do.ts +2191 -0
  217. package/src/generated-config.ts +6 -0
  218. package/src/index.ts +382 -0
  219. package/src/lib/admin-assets.ts +54 -0
  220. package/src/lib/admin-db-target.ts +301 -0
  221. package/src/lib/admin-routing.ts +35 -0
  222. package/src/lib/admin-user-management.ts +464 -0
  223. package/src/lib/analytics-adapter.ts +103 -0
  224. package/src/lib/analytics-query.ts +579 -0
  225. package/src/lib/auth-d1-service.ts +1193 -0
  226. package/src/lib/auth-d1.ts +1056 -0
  227. package/src/lib/auth-db-adapter.ts +289 -0
  228. package/src/lib/auth-redirect.ts +116 -0
  229. package/src/lib/cidr.ts +115 -0
  230. package/src/lib/client-ip.ts +51 -0
  231. package/src/lib/cloudflare-realtime.ts +251 -0
  232. package/src/lib/control-db.ts +36 -0
  233. package/src/lib/cron.ts +163 -0
  234. package/src/lib/d1-handler.ts +1425 -0
  235. package/src/lib/d1-schema-init.ts +255 -0
  236. package/src/lib/d1-sql.ts +33 -0
  237. package/src/lib/database-live-config.ts +24 -0
  238. package/src/lib/database-live-emitter.ts +111 -0
  239. package/src/lib/db-sql.ts +66 -0
  240. package/src/lib/do-retry.ts +36 -0
  241. package/src/lib/do-router.ts +270 -0
  242. package/src/lib/do-sql.ts +73 -0
  243. package/src/lib/email-provider.ts +379 -0
  244. package/src/lib/email-templates.ts +285 -0
  245. package/src/lib/email-translations.ts +422 -0
  246. package/src/lib/errors.ts +151 -0
  247. package/src/lib/functions.ts +2091 -0
  248. package/src/lib/hono.ts +56 -0
  249. package/src/lib/internal-request.ts +56 -0
  250. package/src/lib/jwt.ts +354 -0
  251. package/src/lib/log-writer.ts +272 -0
  252. package/src/lib/namespace-dump.ts +125 -0
  253. package/src/lib/oauth-providers.ts +1225 -0
  254. package/src/lib/op-parser.ts +99 -0
  255. package/src/lib/openapi.ts +146 -0
  256. package/src/lib/pagination.ts +19 -0
  257. package/src/lib/password-policy.ts +102 -0
  258. package/src/lib/password.ts +145 -0
  259. package/src/lib/plugin-migrations.ts +612 -0
  260. package/src/lib/postgres-executor.ts +203 -0
  261. package/src/lib/postgres-handler.ts +1102 -0
  262. package/src/lib/postgres-schema-init.ts +341 -0
  263. package/src/lib/postgres-table-utils.ts +87 -0
  264. package/src/lib/public-user-profile.ts +187 -0
  265. package/src/lib/push-provider.ts +409 -0
  266. package/src/lib/push-token.ts +294 -0
  267. package/src/lib/query-engine.ts +768 -0
  268. package/src/lib/room-monitoring.ts +97 -0
  269. package/src/lib/room-runtime.ts +14 -0
  270. package/src/lib/route-parser.ts +434 -0
  271. package/src/lib/schema.ts +538 -0
  272. package/src/lib/schemas.ts +152 -0
  273. package/src/lib/service-key.ts +419 -0
  274. package/src/lib/sms-provider.ts +230 -0
  275. package/src/lib/startup-config.ts +99 -0
  276. package/src/lib/totp.ts +242 -0
  277. package/src/lib/uuid.ts +87 -0
  278. package/src/lib/validation.ts +205 -0
  279. package/src/lib/version.ts +2 -0
  280. package/src/lib/websocket-pending.ts +40 -0
  281. package/src/middleware/auth.ts +169 -0
  282. package/src/middleware/captcha-verify.ts +217 -0
  283. package/src/middleware/cors.ts +159 -0
  284. package/src/middleware/error-handler.ts +54 -0
  285. package/src/middleware/internal-guard.ts +26 -0
  286. package/src/middleware/logger.ts +126 -0
  287. package/src/middleware/rate-limit.ts +283 -0
  288. package/src/middleware/rules.ts +475 -0
  289. package/src/routes/admin-auth.ts +447 -0
  290. package/src/routes/admin.ts +3501 -0
  291. package/src/routes/analytics-api.ts +290 -0
  292. package/src/routes/auth.ts +4222 -0
  293. package/src/routes/backup.ts +1466 -0
  294. package/src/routes/config.ts +53 -0
  295. package/src/routes/d1.ts +109 -0
  296. package/src/routes/database-live.ts +281 -0
  297. package/src/routes/functions.ts +155 -0
  298. package/src/routes/health.ts +32 -0
  299. package/src/routes/kv.ts +167 -0
  300. package/src/routes/oauth.ts +1055 -0
  301. package/src/routes/push.ts +1465 -0
  302. package/src/routes/room.ts +639 -0
  303. package/src/routes/schema-endpoint.ts +76 -0
  304. package/src/routes/sql.ts +176 -0
  305. package/src/routes/storage.ts +1674 -0
  306. package/src/routes/tables.ts +699 -0
  307. package/src/routes/users.ts +21 -0
  308. package/src/routes/vectorize.ts +372 -0
  309. package/src/types.ts +99 -0
@@ -0,0 +1,447 @@
1
+ import { OpenAPIHono, createRoute, z, type HonoEnv } from '../lib/hono.js';
2
+ import { EdgeBaseError } from '@edge-base/shared';
3
+ import { zodDefaultHook, jsonResponseSchema, errorResponseSchema } from '../lib/schemas.js';
4
+ import { validateKey, buildConstraintCtx, extractBearerToken, resolveServiceKeyCandidate } from '../lib/service-key.js';
5
+ import { parsePagination } from '../lib/pagination.js';
6
+ import { generateId } from '../lib/uuid.js';
7
+ import {
8
+ ensureAuthSchema,
9
+ } from '../lib/auth-d1.js';
10
+ import { parseConfig } from '../lib/do-router.js';
11
+ import { getWorkerUrl } from '../lib/functions.js';
12
+ import { executeAuthHook } from './auth.js';
13
+ import * as authService from '../lib/auth-d1-service.js';
14
+ import { hashPassword } from '../lib/password.js';
15
+ import { resolveAuthDb, type AuthDb } from '../lib/auth-db-adapter.js';
16
+ import {
17
+ createManagedAdminUser,
18
+ deleteManagedAdminUser,
19
+ normalizeAdminUserUpdates,
20
+ prepareImportedPasswordHash,
21
+ updateManagedAdminUser,
22
+ } from '../lib/admin-user-management.js';
23
+
24
+ /** Resolve AuthDb from Hono context. Defaults to D1 (AUTH_DB binding). */
25
+ function getAuthDb(c: { env: unknown }): AuthDb {
26
+ return resolveAuthDb(c.env as Record<string, unknown>);
27
+ }
28
+
29
+ export const adminAuthRoute = new OpenAPIHono<HonoEnv>({ defaultHook: zodDefaultHook });
30
+
31
+ function normalizeOptionalRole(role: unknown): string | undefined {
32
+ if (role === undefined) {
33
+ return undefined;
34
+ }
35
+ if (typeof role !== 'string') {
36
+ throw new EdgeBaseError(400, 'Role must be a non-empty string.');
37
+ }
38
+ const normalized = role.trim();
39
+ if (!normalized) {
40
+ throw new EdgeBaseError(400, 'Role must be a non-empty string.');
41
+ }
42
+ if (normalized.length > 100) {
43
+ throw new EdgeBaseError(400, 'Role must not exceed 100 characters.');
44
+ }
45
+ return normalized;
46
+ }
47
+
48
+ // Error handler for admin sub-app — ensures EdgeBaseError returns correct status
49
+ adminAuthRoute.onError((err, c) => {
50
+ if (err instanceof EdgeBaseError) {
51
+ return c.json(err.toJSON(), err.code as 400);
52
+ }
53
+ console.error('Admin Auth unhandled error:', err);
54
+ return c.json({ code: 500, message: 'Internal server error.' }, 500);
55
+ });
56
+
57
+ // Service Key middleware — scoped validation
58
+ adminAuthRoute.use('*', async (c, next) => {
59
+ const config = parseConfig(c.env);
60
+ const explicitServiceKey =
61
+ c.req.header('X-EdgeBase-Service-Key') ??
62
+ c.req.header('x-edgebase-service-key') ??
63
+ c.req.raw.headers.get('X-EdgeBase-Service-Key') ??
64
+ c.req.raw.headers.get('x-edgebase-service-key');
65
+ const provided = explicitServiceKey ?? resolveServiceKeyCandidate(c.req, extractBearerToken(c.req));
66
+ const { result } = validateKey(provided, 'auth:admin:*:*', config, c.env, undefined, buildConstraintCtx(c.env, c.req));
67
+ if (result === 'missing') {
68
+ throw new EdgeBaseError(403, 'Service Key required for admin auth operations.');
69
+ }
70
+ if (result === 'invalid') {
71
+ throw new EdgeBaseError(401, 'Unauthorized. Service Key required.');
72
+ }
73
+ await ensureAuthSchema(getAuthDb(c));
74
+ await next();
75
+ });
76
+
77
+ const getAdminUser = createRoute({
78
+ operationId: 'adminAuthGetUser',
79
+ method: 'get',
80
+ path: '/users/{id}',
81
+ tags: ['admin'],
82
+ summary: 'Get user by ID',
83
+ request: {
84
+ params: z.object({ id: z.string() }),
85
+ },
86
+ responses: {
87
+ 200: { description: 'User details', content: { 'application/json': { schema: jsonResponseSchema } } },
88
+ 400: { description: 'Bad request', content: { 'application/json': { schema: errorResponseSchema } } },
89
+ },
90
+ });
91
+
92
+ adminAuthRoute.openapi(getAdminUser, async (c) => {
93
+ const userId = c.req.param('id')!;
94
+ const user = await authService.getUserById(getAuthDb(c), userId);
95
+ if (!user) throw new EdgeBaseError(404, 'User not found.');
96
+ return c.json({ user: authService.sanitizeUser(user, { includeAppMetadata: true }) });
97
+ });
98
+
99
+ const listAdminUsers = createRoute({
100
+ operationId: 'adminAuthListUsers',
101
+ method: 'get',
102
+ path: '/users',
103
+ tags: ['admin'],
104
+ summary: 'List users',
105
+ request: {
106
+ query: z.object({
107
+ limit: z.string().optional(),
108
+ cursor: z.string().optional(),
109
+ }),
110
+ },
111
+ responses: {
112
+ 200: { description: 'User list', content: { 'application/json': { schema: jsonResponseSchema } } },
113
+ 400: { description: 'Bad request', content: { 'application/json': { schema: errorResponseSchema } } },
114
+ },
115
+ });
116
+
117
+ adminAuthRoute.openapi(listAdminUsers, async (c) => {
118
+ const { limit, offset } = parsePagination(c.req.query('limit'), c.req.query('cursor'));
119
+ const { users, total } = await authService.listUsers(getAuthDb(c), limit, offset);
120
+ const sanitized = users.map((u) => authService.sanitizeUser(u, { includeAppMetadata: true }));
121
+ const hasMore = total > offset + limit;
122
+ return c.json({ users: sanitized, cursor: hasMore ? String(offset + limit) : null });
123
+ });
124
+
125
+ const createAdminUser = createRoute({
126
+ operationId: 'adminAuthCreateUser',
127
+ method: 'post',
128
+ path: '/users',
129
+ tags: ['admin'],
130
+ summary: 'Create a new user',
131
+ request: {
132
+ body: { content: { 'application/json': { schema: z.object({
133
+ email: z.string(),
134
+ password: z.string(),
135
+ displayName: z.string().optional(),
136
+ role: z.string().optional(),
137
+ }).passthrough() } }, required: true },
138
+ },
139
+ responses: {
140
+ 201: { description: 'User created', content: { 'application/json': { schema: jsonResponseSchema } } },
141
+ 400: { description: 'Bad request', content: { 'application/json': { schema: errorResponseSchema } } },
142
+ 409: { description: 'Conflict', content: { 'application/json': { schema: errorResponseSchema } } },
143
+ },
144
+ });
145
+
146
+ adminAuthRoute.openapi(createAdminUser, async (c) => {
147
+ const body = await c.req.json<{ email: string; password: string; displayName?: string; role?: string }>();
148
+ if (!body.email || !body.password) throw new EdgeBaseError(400, 'Email and password are required.');
149
+ body.email = body.email.trim().toLowerCase();
150
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(body.email)) {
151
+ throw new EdgeBaseError(400, 'Invalid email format. Please provide a valid email address.');
152
+ }
153
+ if (body.password.length < 8) throw new EdgeBaseError(400, 'Password must be at least 8 characters.');
154
+ if (body.password.length > 256) throw new EdgeBaseError(400, 'Password must not exceed 256 characters.');
155
+ body.role = normalizeOptionalRole(body.role);
156
+ if (body.displayName && body.displayName.length > 200) throw new EdgeBaseError(400, 'Display name must not exceed 200 characters.');
157
+
158
+ await ensureAuthSchema(getAuthDb(c));
159
+
160
+ const user = await createManagedAdminUser(
161
+ getAuthDb(c),
162
+ {
163
+ userId: generateId(),
164
+ email: body.email,
165
+ passwordHash: await hashPassword(body.password),
166
+ displayName: body.displayName,
167
+ role: body.role || 'user',
168
+ verified: true,
169
+ },
170
+ {
171
+ executionCtx: c.executionCtx,
172
+ kv: c.env.KV,
173
+ },
174
+ );
175
+
176
+ return c.json({ user: authService.sanitizeUser(user, { includeAppMetadata: true }) }, 201);
177
+ });
178
+
179
+ const updateAdminUser = createRoute({
180
+ operationId: 'adminAuthUpdateUser',
181
+ method: 'patch',
182
+ path: '/users/{id}',
183
+ tags: ['admin'],
184
+ summary: 'Update user by ID',
185
+ request: {
186
+ params: z.object({ id: z.string() }),
187
+ body: { content: { 'application/json': { schema: z.object({}).passthrough() } }, required: true },
188
+ },
189
+ responses: {
190
+ 200: { description: 'User updated', content: { 'application/json': { schema: jsonResponseSchema } } },
191
+ 400: { description: 'Bad request', content: { 'application/json': { schema: errorResponseSchema } } },
192
+ },
193
+ });
194
+
195
+ adminAuthRoute.openapi(updateAdminUser, async (c) => {
196
+ const userId = c.req.param('id')!;
197
+ const body = await c.req.json<Record<string, unknown>>();
198
+ const normalized = await normalizeAdminUserUpdates(body);
199
+ const user = await updateManagedAdminUser(getAuthDb(c), userId, normalized as Record<string, unknown>, {
200
+ executionCtx: c.executionCtx,
201
+ kv: c.env.KV,
202
+ });
203
+ if (!user) throw new EdgeBaseError(404, 'User not found.');
204
+ return c.json({ user: authService.sanitizeUser(user, { includeAppMetadata: true }) });
205
+ });
206
+
207
+ const deleteAdminUserMfa = createRoute({
208
+ operationId: 'adminAuthDeleteUserMfa',
209
+ method: 'delete',
210
+ path: '/users/{id}/mfa',
211
+ tags: ['admin'],
212
+ summary: 'Delete user MFA',
213
+ request: {
214
+ params: z.object({ id: z.string() }),
215
+ },
216
+ responses: {
217
+ 200: { description: 'MFA disabled', content: { 'application/json': { schema: jsonResponseSchema } } },
218
+ 400: { description: 'Bad request', content: { 'application/json': { schema: errorResponseSchema } } },
219
+ },
220
+ });
221
+
222
+ adminAuthRoute.openapi(deleteAdminUserMfa, async (c) => {
223
+ const userId = c.req.param('id')!;
224
+ await authService.disableMfa(getAuthDb(c), userId);
225
+ return c.json({ ok: true, message: 'MFA disabled.' });
226
+ });
227
+
228
+ const deleteAdminUser = createRoute({
229
+ operationId: 'adminAuthDeleteUser',
230
+ method: 'delete',
231
+ path: '/users/{id}',
232
+ tags: ['admin'],
233
+ summary: 'Delete user by ID',
234
+ request: {
235
+ params: z.object({ id: z.string() }),
236
+ },
237
+ responses: {
238
+ 200: { description: 'User deleted', content: { 'application/json': { schema: jsonResponseSchema } } },
239
+ 400: { description: 'Bad request', content: { 'application/json': { schema: errorResponseSchema } } },
240
+ 404: { description: 'Not found', content: { 'application/json': { schema: errorResponseSchema } } },
241
+ },
242
+ });
243
+
244
+ adminAuthRoute.openapi(deleteAdminUser, async (c) => {
245
+ const userId = c.req.param('id')!;
246
+ const deleted = await deleteManagedAdminUser(getAuthDb(c), userId, {
247
+ executionCtx: c.executionCtx,
248
+ kv: c.env.KV,
249
+ });
250
+ if (!deleted) {
251
+ return c.json({ code: 404, message: 'User not found.' }, 404);
252
+ }
253
+ c.executionCtx.waitUntil(
254
+ executeAuthHook(c.env, c.executionCtx, 'onDeleteAccount', { userId }, { workerUrl: getWorkerUrl(c.req.url, c.env) }).catch(() => {}),
255
+ );
256
+ return c.json({ ok: true });
257
+ });
258
+
259
+ const setAdminClaims = createRoute({
260
+ operationId: 'adminAuthSetClaims',
261
+ method: 'put',
262
+ path: '/users/{id}/claims',
263
+ tags: ['admin'],
264
+ summary: 'Set custom claims for user',
265
+ request: {
266
+ params: z.object({ id: z.string() }),
267
+ body: { content: { 'application/json': { schema: z.object({}).passthrough() } }, required: true },
268
+ },
269
+ responses: {
270
+ 200: { description: 'Claims updated', content: { 'application/json': { schema: jsonResponseSchema } } },
271
+ 400: { description: 'Bad request', content: { 'application/json': { schema: errorResponseSchema } } },
272
+ },
273
+ });
274
+
275
+ adminAuthRoute.openapi(setAdminClaims, async (c) => {
276
+ const userId = c.req.param('id')!;
277
+ const claims = await c.req.json();
278
+
279
+ // Limit custom claims size to prevent JWT bloat
280
+ const claimsJson = JSON.stringify(claims);
281
+ if (claimsJson.length > 4096) {
282
+ throw new EdgeBaseError(400, 'Custom claims must not exceed 4KB when serialized.');
283
+ }
284
+
285
+ const user = await authService.updateUser(getAuthDb(c), userId, {
286
+ customClaims: claimsJson,
287
+ });
288
+ if (!user) throw new EdgeBaseError(404, 'User not found.');
289
+ return c.json({ user: authService.sanitizeUser(user, { includeAppMetadata: true }) });
290
+ });
291
+
292
+ const revokeAdminUserSessions = createRoute({
293
+ operationId: 'adminAuthRevokeUserSessions',
294
+ method: 'post',
295
+ path: '/users/{id}/revoke',
296
+ tags: ['admin'],
297
+ summary: 'Revoke all sessions for user',
298
+ request: {
299
+ params: z.object({ id: z.string() }),
300
+ },
301
+ responses: {
302
+ 200: { description: 'Sessions revoked', content: { 'application/json': { schema: jsonResponseSchema } } },
303
+ 400: { description: 'Bad request', content: { 'application/json': { schema: errorResponseSchema } } },
304
+ },
305
+ });
306
+
307
+ adminAuthRoute.openapi(revokeAdminUserSessions, async (c) => {
308
+ const userId = c.req.param('id')!;
309
+ await authService.deleteAllUserSessions(getAuthDb(c), userId);
310
+ return c.json({ ok: true, message: 'All sessions revoked.' });
311
+ });
312
+
313
+ const importAdminUsers = createRoute({
314
+ operationId: 'adminAuthImportUsers',
315
+ method: 'post',
316
+ path: '/users/import',
317
+ tags: ['admin'],
318
+ summary: 'Batch import users',
319
+ request: {
320
+ body: { content: { 'application/json': { schema: z.object({
321
+ users: z.array(z.object({
322
+ id: z.string().optional(),
323
+ email: z.string(),
324
+ passwordHash: z.string().optional(),
325
+ password: z.string().optional(),
326
+ displayName: z.string().optional(),
327
+ avatarUrl: z.string().optional(),
328
+ role: z.string().optional(),
329
+ verified: z.boolean().optional(),
330
+ metadata: z.record(z.string(), z.unknown()).optional(),
331
+ appMetadata: z.record(z.string(), z.unknown()).optional(),
332
+ }).passthrough()),
333
+ }).passthrough() } }, required: true },
334
+ },
335
+ responses: {
336
+ 200: { description: 'Import results', content: { 'application/json': { schema: jsonResponseSchema } } },
337
+ 400: { description: 'Bad request', content: { 'application/json': { schema: errorResponseSchema } } },
338
+ },
339
+ });
340
+
341
+ // POST /users/import — batch import users with optional password hash preservation
342
+ adminAuthRoute.openapi(importAdminUsers, async (c) => {
343
+ interface ImportUser {
344
+ id?: string;
345
+ email: string;
346
+ passwordHash?: string;
347
+ password?: string;
348
+ displayName?: string;
349
+ avatarUrl?: string;
350
+ role?: string;
351
+ verified?: boolean;
352
+ metadata?: Record<string, unknown>;
353
+ appMetadata?: Record<string, unknown>;
354
+ }
355
+
356
+ const body = await c.req.json<{ users: ImportUser[] }>();
357
+ if (!body.users || !Array.isArray(body.users) || body.users.length === 0) {
358
+ throw new EdgeBaseError(400, 'users array is required.');
359
+ }
360
+ if (body.users.length > 1000) {
361
+ throw new EdgeBaseError(400, 'Maximum 1000 users per import batch.');
362
+ }
363
+
364
+ await ensureAuthSchema(getAuthDb(c));
365
+
366
+ const results: Array<{ id: string; email: string; status: 'created' | 'skipped' | 'error'; error?: string }> = [];
367
+
368
+ // Validate & normalize
369
+ const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
370
+ const usersWithIds = body.users.map((u) => {
371
+ const id = u.id || generateId();
372
+ const email = u.email.trim().toLowerCase();
373
+ return { ...u, id, email };
374
+ });
375
+
376
+ // Pre-validate all users
377
+ for (const u of usersWithIds) {
378
+ if (!EMAIL_RE.test(u.email)) {
379
+ results.push({ id: u.id, email: u.email, status: 'error', error: 'Invalid email format.' });
380
+ } else if (u.password && u.password.length < 8) {
381
+ results.push({ id: u.id, email: u.email, status: 'error', error: 'Password must be at least 8 characters.' });
382
+ } else if (u.password && u.password.length > 256) {
383
+ results.push({ id: u.id, email: u.email, status: 'error', error: 'Password must not exceed 256 characters.' });
384
+ } else if (u.role !== undefined) {
385
+ try {
386
+ u.role = normalizeOptionalRole(u.role);
387
+ } catch (err) {
388
+ results.push({ id: u.id, email: u.email, status: 'error', error: (err as Error).message });
389
+ }
390
+ }
391
+ }
392
+
393
+ // Check for duplicate emails in the batch itself
394
+ const emailSet = new Set<string>();
395
+ for (const u of usersWithIds) {
396
+ if (emailSet.has(u.email)) {
397
+ results.push({ id: u.id, email: u.email, status: 'error', error: 'Duplicate email in batch.' });
398
+ continue;
399
+ }
400
+ emailSet.add(u.email);
401
+ }
402
+
403
+ for (const u of usersWithIds) {
404
+ // Skip already-errored users (e.g. duplicate emails)
405
+ if (results.find((r) => r.id === u.id && r.status === 'error')) continue;
406
+
407
+ try {
408
+ await createManagedAdminUser(
409
+ getAuthDb(c),
410
+ {
411
+ userId: u.id,
412
+ email: u.email,
413
+ passwordHash: await prepareImportedPasswordHash(u),
414
+ displayName: u.displayName,
415
+ avatarUrl: u.avatarUrl,
416
+ role: u.role || 'user',
417
+ verified: u.verified ?? true,
418
+ metadata: u.metadata,
419
+ appMetadata: u.appMetadata,
420
+ },
421
+ {
422
+ executionCtx: c.executionCtx,
423
+ kv: c.env.KV,
424
+ },
425
+ );
426
+ results.push({ id: u.id, email: u.email, status: 'created' });
427
+ } catch (err) {
428
+ const message = err instanceof Error ? err.message : 'User import failed.';
429
+ if (err instanceof EdgeBaseError && err.code === 409) {
430
+ results.push({ id: u.id, email: u.email, status: 'skipped', error: message });
431
+ } else {
432
+ results.push({ id: u.id, email: u.email, status: 'error', error: message });
433
+ }
434
+ }
435
+ }
436
+
437
+ const created = results.filter((r) => r.status === 'created').length;
438
+ const skipped = results.filter((r) => r.status === 'skipped').length;
439
+ const errors = results.filter((r) => r.status === 'error').length;
440
+
441
+ return c.json({
442
+ imported: created,
443
+ skipped,
444
+ errors,
445
+ results,
446
+ });
447
+ });