emdash 0.0.0-b → 0.0.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 (661) hide show
  1. package/README.md +87 -43
  2. package/dist/adapters-BLMa4JGD.d.mts +106 -0
  3. package/dist/adapters-BLMa4JGD.d.mts.map +1 -0
  4. package/dist/apply-Bjfq_b4-.mjs +1293 -0
  5. package/dist/apply-Bjfq_b4-.mjs.map +1 -0
  6. package/dist/astro/index.d.mts +51 -0
  7. package/dist/astro/index.d.mts.map +1 -0
  8. package/dist/astro/index.mjs +1336 -0
  9. package/dist/astro/index.mjs.map +1 -0
  10. package/dist/astro/middleware/auth.d.mts +31 -0
  11. package/dist/astro/middleware/auth.d.mts.map +1 -0
  12. package/dist/astro/middleware/auth.mjs +654 -0
  13. package/dist/astro/middleware/auth.mjs.map +1 -0
  14. package/dist/astro/middleware/redirect.d.mts +22 -0
  15. package/dist/astro/middleware/redirect.d.mts.map +1 -0
  16. package/dist/astro/middleware/redirect.mjs +63 -0
  17. package/dist/astro/middleware/redirect.mjs.map +1 -0
  18. package/dist/astro/middleware/request-context.d.mts +18 -0
  19. package/dist/astro/middleware/request-context.d.mts.map +1 -0
  20. package/dist/astro/middleware/request-context.mjs +1310 -0
  21. package/dist/astro/middleware/request-context.mjs.map +1 -0
  22. package/dist/astro/middleware/setup.d.mts +20 -0
  23. package/dist/astro/middleware/setup.d.mts.map +1 -0
  24. package/dist/astro/middleware/setup.mjs +47 -0
  25. package/dist/astro/middleware/setup.mjs.map +1 -0
  26. package/dist/astro/middleware.d.mts +13 -0
  27. package/dist/astro/middleware.d.mts.map +1 -0
  28. package/dist/astro/middleware.mjs +1613 -0
  29. package/dist/astro/middleware.mjs.map +1 -0
  30. package/dist/astro/types.d.mts +250 -0
  31. package/dist/astro/types.d.mts.map +1 -0
  32. package/dist/astro/types.mjs +1 -0
  33. package/dist/base64-MBPo9ozB.mjs +59 -0
  34. package/dist/base64-MBPo9ozB.mjs.map +1 -0
  35. package/dist/byline-CL847F26.mjs +213 -0
  36. package/dist/byline-CL847F26.mjs.map +1 -0
  37. package/dist/bylines-C2a-2TGt.mjs +136 -0
  38. package/dist/bylines-C2a-2TGt.mjs.map +1 -0
  39. package/dist/chunk-ClPoSABd.mjs +21 -0
  40. package/dist/cli/index.d.mts +1 -0
  41. package/dist/cli/index.mjs +3909 -0
  42. package/dist/cli/index.mjs.map +1 -0
  43. package/dist/client/cf-access.d.mts +60 -0
  44. package/dist/client/cf-access.d.mts.map +1 -0
  45. package/dist/client/cf-access.mjs +179 -0
  46. package/dist/client/cf-access.mjs.map +1 -0
  47. package/dist/client/index.d.mts +398 -0
  48. package/dist/client/index.d.mts.map +1 -0
  49. package/dist/client/index.mjs +346 -0
  50. package/dist/client/index.mjs.map +1 -0
  51. package/dist/config-CKE8p9xM.mjs +55 -0
  52. package/dist/config-CKE8p9xM.mjs.map +1 -0
  53. package/dist/connection-B4zVnQIa.mjs +40 -0
  54. package/dist/connection-B4zVnQIa.mjs.map +1 -0
  55. package/dist/content-D6C2WsZC.mjs +824 -0
  56. package/dist/content-D6C2WsZC.mjs.map +1 -0
  57. package/dist/db/index.d.mts +4 -0
  58. package/dist/db/index.mjs +62 -0
  59. package/dist/db/index.mjs.map +1 -0
  60. package/dist/db/libsql.d.mts +11 -0
  61. package/dist/db/libsql.d.mts.map +1 -0
  62. package/dist/db/libsql.mjs +17 -0
  63. package/dist/db/libsql.mjs.map +1 -0
  64. package/dist/db/postgres.d.mts +11 -0
  65. package/dist/db/postgres.d.mts.map +1 -0
  66. package/dist/db/postgres.mjs +30 -0
  67. package/dist/db/postgres.mjs.map +1 -0
  68. package/dist/db/sqlite.d.mts +11 -0
  69. package/dist/db/sqlite.d.mts.map +1 -0
  70. package/dist/db/sqlite.mjs +16 -0
  71. package/dist/db/sqlite.mjs.map +1 -0
  72. package/dist/default-Cyi4aAxu.mjs +81 -0
  73. package/dist/default-Cyi4aAxu.mjs.map +1 -0
  74. package/dist/dialect-helpers-B9uSp2GJ.mjs +90 -0
  75. package/dist/dialect-helpers-B9uSp2GJ.mjs.map +1 -0
  76. package/dist/error-Cxz0tQeO.mjs +27 -0
  77. package/dist/error-Cxz0tQeO.mjs.map +1 -0
  78. package/dist/index-C1xF3OGh.d.mts +4527 -0
  79. package/dist/index-C1xF3OGh.d.mts.map +1 -0
  80. package/dist/index.d.mts +16 -0
  81. package/dist/index.mjs +30 -0
  82. package/dist/load-yOOlckBj.mjs +28 -0
  83. package/dist/load-yOOlckBj.mjs.map +1 -0
  84. package/dist/loader-fz8Q_3EO.mjs +447 -0
  85. package/dist/loader-fz8Q_3EO.mjs.map +1 -0
  86. package/dist/manifest-schema-Dcl0R6nM.mjs +184 -0
  87. package/dist/manifest-schema-Dcl0R6nM.mjs.map +1 -0
  88. package/dist/media/index.d.mts +26 -0
  89. package/dist/media/index.d.mts.map +1 -0
  90. package/dist/media/index.mjs +55 -0
  91. package/dist/media/index.mjs.map +1 -0
  92. package/dist/media/local-runtime.d.mts +39 -0
  93. package/dist/media/local-runtime.d.mts.map +1 -0
  94. package/dist/media/local-runtime.mjs +133 -0
  95. package/dist/media/local-runtime.mjs.map +1 -0
  96. package/dist/media-DqHVh136.mjs +200 -0
  97. package/dist/media-DqHVh136.mjs.map +1 -0
  98. package/dist/mode-C2EzN1uE.mjs +23 -0
  99. package/dist/mode-C2EzN1uE.mjs.map +1 -0
  100. package/dist/page/index.d.mts +140 -0
  101. package/dist/page/index.d.mts.map +1 -0
  102. package/dist/page/index.mjs +416 -0
  103. package/dist/page/index.mjs.map +1 -0
  104. package/dist/placeholder-CmGAmqeO.d.mts +276 -0
  105. package/dist/placeholder-CmGAmqeO.d.mts.map +1 -0
  106. package/dist/placeholder-SmpOx-_v.mjs +243 -0
  107. package/dist/placeholder-SmpOx-_v.mjs.map +1 -0
  108. package/dist/plugin-utils.d.mts +58 -0
  109. package/dist/plugin-utils.d.mts.map +1 -0
  110. package/dist/plugin-utils.mjs +78 -0
  111. package/dist/plugin-utils.mjs.map +1 -0
  112. package/dist/plugins/adapt-sandbox-entry.d.mts +22 -0
  113. package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -0
  114. package/dist/plugins/adapt-sandbox-entry.mjs +113 -0
  115. package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -0
  116. package/dist/query-CS_iSj34.mjs +460 -0
  117. package/dist/query-CS_iSj34.mjs.map +1 -0
  118. package/dist/redirect-DIfIni3r.mjs +329 -0
  119. package/dist/redirect-DIfIni3r.mjs.map +1 -0
  120. package/dist/registry-D_w5HW4G.mjs +863 -0
  121. package/dist/registry-D_w5HW4G.mjs.map +1 -0
  122. package/dist/request-context.d.mts +49 -0
  123. package/dist/request-context.d.mts.map +1 -0
  124. package/dist/request-context.mjs +43 -0
  125. package/dist/request-context.mjs.map +1 -0
  126. package/dist/runner-C0hCbYnD.mjs +1412 -0
  127. package/dist/runner-C0hCbYnD.mjs.map +1 -0
  128. package/dist/runner-EAtf0ZIe.d.mts +27 -0
  129. package/dist/runner-EAtf0ZIe.d.mts.map +1 -0
  130. package/dist/runtime.d.mts +26 -0
  131. package/dist/runtime.d.mts.map +1 -0
  132. package/dist/runtime.mjs +42 -0
  133. package/dist/runtime.mjs.map +1 -0
  134. package/dist/search-DG603UrT.mjs +9211 -0
  135. package/dist/search-DG603UrT.mjs.map +1 -0
  136. package/dist/seed/index.d.mts +3 -0
  137. package/dist/seed/index.mjs +15 -0
  138. package/dist/seo/index.d.mts +70 -0
  139. package/dist/seo/index.d.mts.map +1 -0
  140. package/dist/seo/index.mjs +70 -0
  141. package/dist/seo/index.mjs.map +1 -0
  142. package/dist/storage/local.d.mts +39 -0
  143. package/dist/storage/local.d.mts.map +1 -0
  144. package/dist/storage/local.mjs +166 -0
  145. package/dist/storage/local.mjs.map +1 -0
  146. package/dist/storage/s3.d.mts +32 -0
  147. package/dist/storage/s3.d.mts.map +1 -0
  148. package/dist/storage/s3.mjs +175 -0
  149. package/dist/storage/s3.mjs.map +1 -0
  150. package/dist/tokens-DpgrkrXK.mjs +171 -0
  151. package/dist/tokens-DpgrkrXK.mjs.map +1 -0
  152. package/dist/transport-BFGblqwG.d.mts +42 -0
  153. package/dist/transport-BFGblqwG.d.mts.map +1 -0
  154. package/dist/transport-yxiQsi8I.mjs +418 -0
  155. package/dist/transport-yxiQsi8I.mjs.map +1 -0
  156. package/dist/types-BRuPJGdV.d.mts +102 -0
  157. package/dist/types-BRuPJGdV.d.mts.map +1 -0
  158. package/dist/types-C4-fAxN3.d.mts +182 -0
  159. package/dist/types-C4-fAxN3.d.mts.map +1 -0
  160. package/dist/types-CMMN0pNg.mjs +31 -0
  161. package/dist/types-CMMN0pNg.mjs.map +1 -0
  162. package/dist/types-CUBbjgmP.mjs +16 -0
  163. package/dist/types-CUBbjgmP.mjs.map +1 -0
  164. package/dist/types-DRjfYOEv.d.mts +426 -0
  165. package/dist/types-DRjfYOEv.d.mts.map +1 -0
  166. package/dist/types-DY5zk5HN.mjs +73 -0
  167. package/dist/types-DY5zk5HN.mjs.map +1 -0
  168. package/dist/types-DaNLHo_T.d.mts +184 -0
  169. package/dist/types-DaNLHo_T.d.mts.map +1 -0
  170. package/dist/types-DvhsUmSJ.d.mts +1111 -0
  171. package/dist/types-DvhsUmSJ.d.mts.map +1 -0
  172. package/dist/validate-CpBtVMsD.d.mts +378 -0
  173. package/dist/validate-CpBtVMsD.d.mts.map +1 -0
  174. package/dist/validate-CqRJb_xU.mjs +97 -0
  175. package/dist/validate-CqRJb_xU.mjs.map +1 -0
  176. package/dist/validate-O7PWmlnq.mjs +328 -0
  177. package/dist/validate-O7PWmlnq.mjs.map +1 -0
  178. package/locals.d.ts +46 -0
  179. package/package.json +233 -19
  180. package/src/api/authorize.ts +63 -0
  181. package/src/api/csrf.ts +48 -0
  182. package/src/api/error.ts +99 -0
  183. package/src/api/errors.ts +445 -0
  184. package/src/api/escape.ts +9 -0
  185. package/src/api/handlers/api-tokens.ts +240 -0
  186. package/src/api/handlers/comments.ts +314 -0
  187. package/src/api/handlers/content.ts +1315 -0
  188. package/src/api/handlers/dashboard.ts +205 -0
  189. package/src/api/handlers/device-flow.ts +684 -0
  190. package/src/api/handlers/index.ts +163 -0
  191. package/src/api/handlers/manifest.ts +158 -0
  192. package/src/api/handlers/marketplace.ts +930 -0
  193. package/src/api/handlers/media.ts +207 -0
  194. package/src/api/handlers/menus.ts +493 -0
  195. package/src/api/handlers/oauth-authorization.ts +429 -0
  196. package/src/api/handlers/oauth-clients.ts +349 -0
  197. package/src/api/handlers/oauth-user-lookup.ts +39 -0
  198. package/src/api/handlers/plugins.ts +254 -0
  199. package/src/api/handlers/redirects.ts +360 -0
  200. package/src/api/handlers/revision.ts +145 -0
  201. package/src/api/handlers/schema.ts +534 -0
  202. package/src/api/handlers/sections.ts +289 -0
  203. package/src/api/handlers/seo.ts +115 -0
  204. package/src/api/handlers/settings.ts +49 -0
  205. package/src/api/handlers/snapshot.ts +350 -0
  206. package/src/api/handlers/taxonomies.ts +523 -0
  207. package/src/api/index.ts +6 -0
  208. package/src/api/openapi/document.ts +2368 -0
  209. package/src/api/openapi/index.ts +1 -0
  210. package/src/api/parse.ts +139 -0
  211. package/src/api/redirect.ts +14 -0
  212. package/src/api/rev.ts +67 -0
  213. package/src/api/schemas/auth.ts +112 -0
  214. package/src/api/schemas/bylines.ts +85 -0
  215. package/src/api/schemas/comments.ts +117 -0
  216. package/src/api/schemas/common.ts +89 -0
  217. package/src/api/schemas/content.ts +191 -0
  218. package/src/api/schemas/import.ts +52 -0
  219. package/src/api/schemas/index.ts +17 -0
  220. package/src/api/schemas/media.ts +116 -0
  221. package/src/api/schemas/menus.ts +111 -0
  222. package/src/api/schemas/redirects.ts +155 -0
  223. package/src/api/schemas/schema.ts +203 -0
  224. package/src/api/schemas/search.ts +63 -0
  225. package/src/api/schemas/sections.ts +67 -0
  226. package/src/api/schemas/settings.ts +63 -0
  227. package/src/api/schemas/setup.ts +37 -0
  228. package/src/api/schemas/taxonomies.ts +113 -0
  229. package/src/api/schemas/users.ts +96 -0
  230. package/src/api/schemas/widgets.ts +80 -0
  231. package/src/api/site-url.ts +25 -0
  232. package/src/api/types.ts +82 -0
  233. package/src/astro/index.ts +27 -0
  234. package/src/astro/integration/index.ts +303 -0
  235. package/src/astro/integration/routes.ts +834 -0
  236. package/src/astro/integration/runtime.ts +338 -0
  237. package/src/astro/integration/virtual-modules.ts +469 -0
  238. package/src/astro/integration/vite-config.ts +335 -0
  239. package/src/astro/middleware/auth.ts +743 -0
  240. package/src/astro/middleware/redirect.ts +89 -0
  241. package/src/astro/middleware/request-context.ts +129 -0
  242. package/src/astro/middleware/setup.ts +89 -0
  243. package/src/astro/middleware.ts +398 -0
  244. package/src/astro/routes/PluginRegistry.tsx +15 -0
  245. package/src/astro/routes/admin.astro +81 -0
  246. package/src/astro/routes/api/admin/allowed-domains/[domain].ts +112 -0
  247. package/src/astro/routes/api/admin/allowed-domains/index.ts +108 -0
  248. package/src/astro/routes/api/admin/api-tokens/[id].ts +40 -0
  249. package/src/astro/routes/api/admin/api-tokens/index.ts +68 -0
  250. package/src/astro/routes/api/admin/bylines/[id]/index.ts +87 -0
  251. package/src/astro/routes/api/admin/bylines/index.ts +72 -0
  252. package/src/astro/routes/api/admin/comments/[id]/status.ts +116 -0
  253. package/src/astro/routes/api/admin/comments/[id].ts +64 -0
  254. package/src/astro/routes/api/admin/comments/bulk.ts +42 -0
  255. package/src/astro/routes/api/admin/comments/counts.ts +30 -0
  256. package/src/astro/routes/api/admin/comments/index.ts +46 -0
  257. package/src/astro/routes/api/admin/hooks/exclusive/[hookName].ts +91 -0
  258. package/src/astro/routes/api/admin/hooks/exclusive/index.ts +51 -0
  259. package/src/astro/routes/api/admin/oauth-clients/[id].ts +110 -0
  260. package/src/astro/routes/api/admin/oauth-clients/index.ts +71 -0
  261. package/src/astro/routes/api/admin/plugins/[id]/disable.ts +39 -0
  262. package/src/astro/routes/api/admin/plugins/[id]/enable.ts +39 -0
  263. package/src/astro/routes/api/admin/plugins/[id]/index.ts +38 -0
  264. package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +48 -0
  265. package/src/astro/routes/api/admin/plugins/[id]/update.ts +59 -0
  266. package/src/astro/routes/api/admin/plugins/index.ts +32 -0
  267. package/src/astro/routes/api/admin/plugins/marketplace/[id]/icon.ts +61 -0
  268. package/src/astro/routes/api/admin/plugins/marketplace/[id]/index.ts +33 -0
  269. package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +62 -0
  270. package/src/astro/routes/api/admin/plugins/marketplace/index.ts +38 -0
  271. package/src/astro/routes/api/admin/plugins/updates.ts +28 -0
  272. package/src/astro/routes/api/admin/themes/marketplace/[id]/index.ts +33 -0
  273. package/src/astro/routes/api/admin/themes/marketplace/[id]/thumbnail.ts +61 -0
  274. package/src/astro/routes/api/admin/themes/marketplace/index.ts +45 -0
  275. package/src/astro/routes/api/admin/users/[id]/disable.ts +69 -0
  276. package/src/astro/routes/api/admin/users/[id]/enable.ts +48 -0
  277. package/src/astro/routes/api/admin/users/[id]/index.ts +146 -0
  278. package/src/astro/routes/api/admin/users/[id]/send-recovery.ts +72 -0
  279. package/src/astro/routes/api/admin/users/index.ts +66 -0
  280. package/src/astro/routes/api/auth/dev-bypass.ts +139 -0
  281. package/src/astro/routes/api/auth/invite/accept.ts +52 -0
  282. package/src/astro/routes/api/auth/invite/complete.ts +84 -0
  283. package/src/astro/routes/api/auth/invite/index.ts +99 -0
  284. package/src/astro/routes/api/auth/logout.ts +40 -0
  285. package/src/astro/routes/api/auth/magic-link/send.ts +89 -0
  286. package/src/astro/routes/api/auth/magic-link/verify.ts +71 -0
  287. package/src/astro/routes/api/auth/me.ts +60 -0
  288. package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +219 -0
  289. package/src/astro/routes/api/auth/oauth/[provider].ts +119 -0
  290. package/src/astro/routes/api/auth/passkey/[id].ts +124 -0
  291. package/src/astro/routes/api/auth/passkey/index.ts +54 -0
  292. package/src/astro/routes/api/auth/passkey/options.ts +82 -0
  293. package/src/astro/routes/api/auth/passkey/register/options.ts +86 -0
  294. package/src/astro/routes/api/auth/passkey/register/verify.ts +115 -0
  295. package/src/astro/routes/api/auth/passkey/verify.ts +66 -0
  296. package/src/astro/routes/api/auth/signup/complete.ts +85 -0
  297. package/src/astro/routes/api/auth/signup/request.ts +77 -0
  298. package/src/astro/routes/api/auth/signup/verify.ts +53 -0
  299. package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +312 -0
  300. package/src/astro/routes/api/content/[collection]/[id]/compare.ts +28 -0
  301. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +54 -0
  302. package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +61 -0
  303. package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +33 -0
  304. package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +107 -0
  305. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +56 -0
  306. package/src/astro/routes/api/content/[collection]/[id]/restore.ts +54 -0
  307. package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +31 -0
  308. package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +101 -0
  309. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +140 -0
  310. package/src/astro/routes/api/content/[collection]/[id]/translations.ts +30 -0
  311. package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +56 -0
  312. package/src/astro/routes/api/content/[collection]/[id].ts +137 -0
  313. package/src/astro/routes/api/content/[collection]/index.ts +59 -0
  314. package/src/astro/routes/api/content/[collection]/trash.ts +33 -0
  315. package/src/astro/routes/api/dashboard.ts +32 -0
  316. package/src/astro/routes/api/dev/emails.ts +36 -0
  317. package/src/astro/routes/api/import/probe.ts +47 -0
  318. package/src/astro/routes/api/import/wordpress/analyze.ts +510 -0
  319. package/src/astro/routes/api/import/wordpress/execute.ts +283 -0
  320. package/src/astro/routes/api/import/wordpress/media.ts +338 -0
  321. package/src/astro/routes/api/import/wordpress/prepare.ts +181 -0
  322. package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +393 -0
  323. package/src/astro/routes/api/import/wordpress-plugin/analyze.ts +111 -0
  324. package/src/astro/routes/api/import/wordpress-plugin/callback.ts +58 -0
  325. package/src/astro/routes/api/import/wordpress-plugin/execute.ts +347 -0
  326. package/src/astro/routes/api/manifest.ts +62 -0
  327. package/src/astro/routes/api/mcp.ts +124 -0
  328. package/src/astro/routes/api/media/[id]/confirm.ts +93 -0
  329. package/src/astro/routes/api/media/[id].ts +145 -0
  330. package/src/astro/routes/api/media/file/[key].ts +79 -0
  331. package/src/astro/routes/api/media/providers/[providerId]/[itemId].ts +86 -0
  332. package/src/astro/routes/api/media/providers/[providerId]/index.ts +111 -0
  333. package/src/astro/routes/api/media/providers/index.ts +30 -0
  334. package/src/astro/routes/api/media/upload-url.ts +137 -0
  335. package/src/astro/routes/api/media.ts +190 -0
  336. package/src/astro/routes/api/menus/[name]/items.ts +87 -0
  337. package/src/astro/routes/api/menus/[name]/reorder.ts +33 -0
  338. package/src/astro/routes/api/menus/[name].ts +65 -0
  339. package/src/astro/routes/api/menus/index.ts +47 -0
  340. package/src/astro/routes/api/oauth/authorize.ts +412 -0
  341. package/src/astro/routes/api/oauth/device/authorize.ts +45 -0
  342. package/src/astro/routes/api/oauth/device/code.ts +51 -0
  343. package/src/astro/routes/api/oauth/device/token.ts +69 -0
  344. package/src/astro/routes/api/oauth/token/refresh.ts +38 -0
  345. package/src/astro/routes/api/oauth/token/revoke.ts +38 -0
  346. package/src/astro/routes/api/oauth/token.ts +184 -0
  347. package/src/astro/routes/api/openapi.json.ts +32 -0
  348. package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +92 -0
  349. package/src/astro/routes/api/redirects/404s/index.ts +72 -0
  350. package/src/astro/routes/api/redirects/404s/summary.ts +33 -0
  351. package/src/astro/routes/api/redirects/[id].ts +84 -0
  352. package/src/astro/routes/api/redirects/index.ts +52 -0
  353. package/src/astro/routes/api/revisions/[revisionId]/index.ts +29 -0
  354. package/src/astro/routes/api/revisions/[revisionId]/restore.ts +58 -0
  355. package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +76 -0
  356. package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +52 -0
  357. package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +32 -0
  358. package/src/astro/routes/api/schema/collections/[slug]/index.ts +80 -0
  359. package/src/astro/routes/api/schema/collections/index.ts +47 -0
  360. package/src/astro/routes/api/schema/index.ts +109 -0
  361. package/src/astro/routes/api/schema/orphans/[slug].ts +36 -0
  362. package/src/astro/routes/api/schema/orphans/index.ts +26 -0
  363. package/src/astro/routes/api/search/enable.ts +64 -0
  364. package/src/astro/routes/api/search/index.ts +55 -0
  365. package/src/astro/routes/api/search/rebuild.ts +72 -0
  366. package/src/astro/routes/api/search/stats.ts +35 -0
  367. package/src/astro/routes/api/search/suggest.ts +53 -0
  368. package/src/astro/routes/api/sections/[slug].ts +84 -0
  369. package/src/astro/routes/api/sections/index.ts +52 -0
  370. package/src/astro/routes/api/settings/email.ts +150 -0
  371. package/src/astro/routes/api/settings.ts +67 -0
  372. package/src/astro/routes/api/setup/admin-verify.ts +100 -0
  373. package/src/astro/routes/api/setup/admin.ts +94 -0
  374. package/src/astro/routes/api/setup/dev-bypass.ts +199 -0
  375. package/src/astro/routes/api/setup/dev-reset.ts +40 -0
  376. package/src/astro/routes/api/setup/index.ts +126 -0
  377. package/src/astro/routes/api/setup/status.ts +122 -0
  378. package/src/astro/routes/api/snapshot.ts +75 -0
  379. package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +95 -0
  380. package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +69 -0
  381. package/src/astro/routes/api/taxonomies/index.ts +59 -0
  382. package/src/astro/routes/api/themes/preview.ts +77 -0
  383. package/src/astro/routes/api/typegen.ts +114 -0
  384. package/src/astro/routes/api/well-known/auth.ts +68 -0
  385. package/src/astro/routes/api/well-known/oauth-authorization-server.ts +44 -0
  386. package/src/astro/routes/api/well-known/oauth-protected-resource.ts +37 -0
  387. package/src/astro/routes/api/widget-areas/[name]/reorder.ts +68 -0
  388. package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +127 -0
  389. package/src/astro/routes/api/widget-areas/[name]/widgets.ts +80 -0
  390. package/src/astro/routes/api/widget-areas/[name].ts +87 -0
  391. package/src/astro/routes/api/widget-areas/index.ts +99 -0
  392. package/src/astro/routes/api/widget-components.ts +22 -0
  393. package/src/astro/routes/robots.txt.ts +77 -0
  394. package/src/astro/routes/sitemap.xml.ts +97 -0
  395. package/src/astro/storage/adapters.ts +74 -0
  396. package/src/astro/storage/index.ts +19 -0
  397. package/src/astro/storage/types.ts +60 -0
  398. package/src/astro/types.ts +346 -0
  399. package/src/auth/api-tokens.ts +25 -0
  400. package/src/auth/challenge-store.ts +80 -0
  401. package/src/auth/mode.ts +96 -0
  402. package/src/auth/oauth-state-store.ts +96 -0
  403. package/src/auth/passkey-config.ts +27 -0
  404. package/src/auth/rate-limit.ts +158 -0
  405. package/src/auth/scopes.ts +33 -0
  406. package/src/auth/types.ts +104 -0
  407. package/src/aws-sdk.d.ts +100 -0
  408. package/src/bylines/index.ts +237 -0
  409. package/src/cleanup.ts +153 -0
  410. package/src/cli/client-factory.ts +100 -0
  411. package/src/cli/commands/auth.ts +46 -0
  412. package/src/cli/commands/bundle-utils.ts +247 -0
  413. package/src/cli/commands/bundle.ts +609 -0
  414. package/src/cli/commands/content.ts +442 -0
  415. package/src/cli/commands/dev.ts +191 -0
  416. package/src/cli/commands/doctor.ts +211 -0
  417. package/src/cli/commands/export-seed.ts +630 -0
  418. package/src/cli/commands/import/wordpress.ts +1056 -0
  419. package/src/cli/commands/init.ts +192 -0
  420. package/src/cli/commands/login.ts +547 -0
  421. package/src/cli/commands/media.ts +165 -0
  422. package/src/cli/commands/menu.ts +67 -0
  423. package/src/cli/commands/plugin-init.ts +291 -0
  424. package/src/cli/commands/plugin-validate.ts +31 -0
  425. package/src/cli/commands/plugin.ts +33 -0
  426. package/src/cli/commands/publish.ts +697 -0
  427. package/src/cli/commands/schema.ts +233 -0
  428. package/src/cli/commands/search-cmd.ts +54 -0
  429. package/src/cli/commands/seed.ts +286 -0
  430. package/src/cli/commands/taxonomy.ts +128 -0
  431. package/src/cli/commands/types.ts +68 -0
  432. package/src/cli/credentials.ts +236 -0
  433. package/src/cli/index.ts +70 -0
  434. package/src/cli/output.ts +75 -0
  435. package/src/cli/wxr/parser.ts +969 -0
  436. package/src/client/cf-access.ts +193 -0
  437. package/src/client/index.ts +854 -0
  438. package/src/client/portable-text.ts +413 -0
  439. package/src/client/transport.ts +200 -0
  440. package/src/comments/moderator.ts +46 -0
  441. package/src/comments/notifications.ts +144 -0
  442. package/src/comments/query.ts +105 -0
  443. package/src/comments/service.ts +213 -0
  444. package/src/components/Break.astro +45 -0
  445. package/src/components/Button.astro +71 -0
  446. package/src/components/Buttons.astro +49 -0
  447. package/src/components/Code.astro +59 -0
  448. package/src/components/Columns.astro +59 -0
  449. package/src/components/CommentForm.astro +315 -0
  450. package/src/components/Comments.astro +232 -0
  451. package/src/components/Cover.astro +128 -0
  452. package/src/components/EmDashBodyEnd.astro +32 -0
  453. package/src/components/EmDashBodyStart.astro +32 -0
  454. package/src/components/EmDashHead.astro +53 -0
  455. package/src/components/EmDashImage.astro +178 -0
  456. package/src/components/EmDashMedia.astro +167 -0
  457. package/src/components/Embed.astro +128 -0
  458. package/src/components/File.astro +122 -0
  459. package/src/components/Gallery.astro +93 -0
  460. package/src/components/HtmlBlock.astro +33 -0
  461. package/src/components/Image.astro +178 -0
  462. package/src/components/InlineEditor.astro +27 -0
  463. package/src/components/InlinePortableTextEditor.tsx +1905 -0
  464. package/src/components/LiveSearch.astro +614 -0
  465. package/src/components/PortableText.astro +51 -0
  466. package/src/components/Pullquote.astro +51 -0
  467. package/src/components/Table.astro +108 -0
  468. package/src/components/WidgetArea.astro +22 -0
  469. package/src/components/WidgetRenderer.astro +72 -0
  470. package/src/components/index.ts +116 -0
  471. package/src/components/marks/Link.astro +31 -0
  472. package/src/components/marks/StrikeThrough.astro +7 -0
  473. package/src/components/marks/Subscript.astro +7 -0
  474. package/src/components/marks/Superscript.astro +7 -0
  475. package/src/components/marks/Underline.astro +7 -0
  476. package/src/components/widgets/Archives.astro +65 -0
  477. package/src/components/widgets/Categories.astro +35 -0
  478. package/src/components/widgets/RecentPosts.astro +51 -0
  479. package/src/components/widgets/Search.astro +18 -0
  480. package/src/components/widgets/Tags.astro +38 -0
  481. package/src/content/converters/index.ts +9 -0
  482. package/src/content/converters/portable-text-to-prosemirror.ts +385 -0
  483. package/src/content/converters/prosemirror-to-portable-text.ts +413 -0
  484. package/src/content/converters/types.ts +120 -0
  485. package/src/content/index.ts +5 -0
  486. package/src/database/connection.ts +67 -0
  487. package/src/database/dialect-helpers.ts +138 -0
  488. package/src/database/index.ts +5 -0
  489. package/src/database/migrations/001_initial.ts +170 -0
  490. package/src/database/migrations/002_media_status.ts +26 -0
  491. package/src/database/migrations/003_schema_registry.ts +79 -0
  492. package/src/database/migrations/004_plugins.ts +62 -0
  493. package/src/database/migrations/005_menus.ts +67 -0
  494. package/src/database/migrations/006_taxonomy_defs.ts +51 -0
  495. package/src/database/migrations/007_widgets.ts +42 -0
  496. package/src/database/migrations/008_auth.ts +194 -0
  497. package/src/database/migrations/009_user_disabled.ts +27 -0
  498. package/src/database/migrations/011_sections.ts +65 -0
  499. package/src/database/migrations/012_search.ts +25 -0
  500. package/src/database/migrations/013_scheduled_publishing.ts +51 -0
  501. package/src/database/migrations/014_draft_revisions.ts +72 -0
  502. package/src/database/migrations/015_indexes.ts +82 -0
  503. package/src/database/migrations/016_api_tokens.ts +89 -0
  504. package/src/database/migrations/017_authorization_codes.ts +45 -0
  505. package/src/database/migrations/018_seo.ts +56 -0
  506. package/src/database/migrations/019_i18n.ts +618 -0
  507. package/src/database/migrations/020_collection_url_pattern.ts +23 -0
  508. package/src/database/migrations/021_remove_section_categories.ts +43 -0
  509. package/src/database/migrations/022_marketplace_plugin_state.ts +46 -0
  510. package/src/database/migrations/023_plugin_metadata.ts +33 -0
  511. package/src/database/migrations/024_media_placeholders.ts +32 -0
  512. package/src/database/migrations/025_oauth_clients.ts +28 -0
  513. package/src/database/migrations/026_cron_tasks.ts +49 -0
  514. package/src/database/migrations/027_comments.ts +87 -0
  515. package/src/database/migrations/028_drop_author_url.ts +9 -0
  516. package/src/database/migrations/029_redirects.ts +67 -0
  517. package/src/database/migrations/030_widen_scheduled_index.ts +48 -0
  518. package/src/database/migrations/031_bylines.ts +90 -0
  519. package/src/database/migrations/032_rate_limits.ts +39 -0
  520. package/src/database/migrations/runner.ts +170 -0
  521. package/src/database/repositories/audit.ts +294 -0
  522. package/src/database/repositories/byline.ts +387 -0
  523. package/src/database/repositories/comment.ts +458 -0
  524. package/src/database/repositories/content.ts +1144 -0
  525. package/src/database/repositories/index.ts +30 -0
  526. package/src/database/repositories/media.ts +347 -0
  527. package/src/database/repositories/options.ts +150 -0
  528. package/src/database/repositories/plugin-storage.ts +373 -0
  529. package/src/database/repositories/redirect.ts +480 -0
  530. package/src/database/repositories/revision.ts +200 -0
  531. package/src/database/repositories/seo.ts +176 -0
  532. package/src/database/repositories/taxonomy.ts +294 -0
  533. package/src/database/repositories/types.ts +132 -0
  534. package/src/database/repositories/user.ts +258 -0
  535. package/src/database/transaction.ts +54 -0
  536. package/src/database/types.ts +501 -0
  537. package/src/database/validate.ts +138 -0
  538. package/src/db/adapters.ts +125 -0
  539. package/src/db/index.ts +37 -0
  540. package/src/db/libsql.ts +23 -0
  541. package/src/db/postgres.ts +30 -0
  542. package/src/db/sqlite.ts +27 -0
  543. package/src/emdash-runtime.ts +2096 -0
  544. package/src/fields/boolean.ts +34 -0
  545. package/src/fields/datetime.ts +44 -0
  546. package/src/fields/file.ts +41 -0
  547. package/src/fields/image.ts +34 -0
  548. package/src/fields/index.ts +42 -0
  549. package/src/fields/integer.ts +50 -0
  550. package/src/fields/json.ts +37 -0
  551. package/src/fields/multiselect.ts +48 -0
  552. package/src/fields/number.ts +52 -0
  553. package/src/fields/portable-text.ts +33 -0
  554. package/src/fields/reference.ts +29 -0
  555. package/src/fields/richtext.ts +31 -0
  556. package/src/fields/select.ts +46 -0
  557. package/src/fields/slug.ts +38 -0
  558. package/src/fields/text.ts +55 -0
  559. package/src/fields/textarea.ts +52 -0
  560. package/src/fields/types.ts +64 -0
  561. package/src/i18n/config.ts +68 -0
  562. package/src/import/index.ts +90 -0
  563. package/src/import/menus.ts +436 -0
  564. package/src/import/registry.ts +111 -0
  565. package/src/import/sections.ts +103 -0
  566. package/src/import/settings.ts +281 -0
  567. package/src/import/sources/wordpress-plugin.ts +641 -0
  568. package/src/import/sources/wordpress-rest.ts +191 -0
  569. package/src/import/sources/wxr.ts +330 -0
  570. package/src/import/ssrf.ts +260 -0
  571. package/src/import/types.ts +418 -0
  572. package/src/import/utils.ts +412 -0
  573. package/src/index.ts +481 -0
  574. package/src/loader.ts +770 -0
  575. package/src/mcp/server.ts +1463 -0
  576. package/src/media/index.ts +32 -0
  577. package/src/media/local-runtime.ts +213 -0
  578. package/src/media/local.ts +46 -0
  579. package/src/media/normalize.ts +190 -0
  580. package/src/media/placeholder.ts +150 -0
  581. package/src/media/provider-loader.ts +78 -0
  582. package/src/media/types.ts +279 -0
  583. package/src/menus/index.ts +324 -0
  584. package/src/menus/types.ts +112 -0
  585. package/src/page/context.ts +93 -0
  586. package/src/page/fragments.ts +89 -0
  587. package/src/page/index.ts +58 -0
  588. package/src/page/jsonld.ts +94 -0
  589. package/src/page/metadata.ts +185 -0
  590. package/src/page/seo-contributions.ts +136 -0
  591. package/src/plugin-utils.ts +80 -0
  592. package/src/plugins/adapt-sandbox-entry.ts +207 -0
  593. package/src/plugins/context.ts +833 -0
  594. package/src/plugins/cron.ts +361 -0
  595. package/src/plugins/define-plugin.ts +259 -0
  596. package/src/plugins/email-console.ts +73 -0
  597. package/src/plugins/email.ts +209 -0
  598. package/src/plugins/hooks.ts +1273 -0
  599. package/src/plugins/index.ts +193 -0
  600. package/src/plugins/manager.ts +595 -0
  601. package/src/plugins/manifest-schema.ts +230 -0
  602. package/src/plugins/marketplace.ts +460 -0
  603. package/src/plugins/request-meta.ts +139 -0
  604. package/src/plugins/routes.ts +302 -0
  605. package/src/plugins/sandbox/index.ts +18 -0
  606. package/src/plugins/sandbox/noop.ts +76 -0
  607. package/src/plugins/sandbox/types.ts +173 -0
  608. package/src/plugins/scheduler/node.ts +122 -0
  609. package/src/plugins/scheduler/piggyback.ts +71 -0
  610. package/src/plugins/scheduler/types.ts +27 -0
  611. package/src/plugins/state.ts +208 -0
  612. package/src/plugins/storage-indexes.ts +326 -0
  613. package/src/plugins/storage-query.ts +240 -0
  614. package/src/plugins/types.ts +1284 -0
  615. package/src/preview/helpers.ts +27 -0
  616. package/src/preview/index.ts +40 -0
  617. package/src/preview/tokens.ts +279 -0
  618. package/src/preview/urls.ts +118 -0
  619. package/src/query.ts +674 -0
  620. package/src/redirects/patterns.ts +224 -0
  621. package/src/request-context.ts +67 -0
  622. package/src/runtime.ts +21 -0
  623. package/src/schema/index.ts +29 -0
  624. package/src/schema/query.ts +44 -0
  625. package/src/schema/registry.ts +965 -0
  626. package/src/schema/types.ts +276 -0
  627. package/src/schema/zod-generator.ts +413 -0
  628. package/src/search/fts-manager.ts +452 -0
  629. package/src/search/index.ts +26 -0
  630. package/src/search/query.ts +396 -0
  631. package/src/search/text-extraction.ts +162 -0
  632. package/src/search/types.ts +114 -0
  633. package/src/sections/index.ts +226 -0
  634. package/src/sections/types.ts +86 -0
  635. package/src/seed/apply.ts +1141 -0
  636. package/src/seed/default.ts +86 -0
  637. package/src/seed/index.ts +28 -0
  638. package/src/seed/load.ts +35 -0
  639. package/src/seed/types.ts +341 -0
  640. package/src/seed/validate.ts +642 -0
  641. package/src/seo/index.ts +179 -0
  642. package/src/settings/index.ts +203 -0
  643. package/src/settings/types.ts +58 -0
  644. package/src/storage/index.ts +28 -0
  645. package/src/storage/local.ts +249 -0
  646. package/src/storage/s3.ts +263 -0
  647. package/src/storage/types.ts +204 -0
  648. package/src/taxonomies/index.ts +309 -0
  649. package/src/taxonomies/types.ts +61 -0
  650. package/src/ui.ts +75 -0
  651. package/src/utils/base64.ts +73 -0
  652. package/src/utils/hash.ts +36 -0
  653. package/src/utils/sanitize.ts +20 -0
  654. package/src/utils/slugify.ts +29 -0
  655. package/src/utils/url.ts +48 -0
  656. package/src/virtual-modules.d.ts +111 -0
  657. package/src/visual-editing/editable.ts +108 -0
  658. package/src/visual-editing/toolbar.ts +1229 -0
  659. package/src/widgets/components.ts +105 -0
  660. package/src/widgets/index.ts +131 -0
  661. package/src/widgets/types.ts +81 -0
@@ -0,0 +1,684 @@
1
+ /**
2
+ * OAuth Device Flow handlers (RFC 8628).
3
+ *
4
+ * EmDash acts as an OAuth 2.0 authorization server. The CLI requests
5
+ * a device code, displays a URL + user code, and polls for a token.
6
+ * The user opens a browser, logs in, enters the code, and the CLI gets
7
+ * an access + refresh token pair.
8
+ *
9
+ * Uses arctic for code generation and @emdash-cms/auth for token utilities.
10
+ */
11
+
12
+ import { clampScopes } from "@emdash-cms/auth";
13
+ import type { RoleLevel } from "@emdash-cms/auth";
14
+ import { generateCodeVerifier } from "arctic";
15
+ import type { Kysely } from "kysely";
16
+
17
+ import {
18
+ generatePrefixedToken,
19
+ hashApiToken,
20
+ TOKEN_PREFIXES,
21
+ VALID_SCOPES,
22
+ } from "../../auth/api-tokens.js";
23
+ import type { Database } from "../../database/types.js";
24
+ import type { ApiResult } from "../types.js";
25
+ import { lookupOAuthClient } from "./oauth-clients.js";
26
+ import { lookupUserRoleAndStatus } from "./oauth-user-lookup.js";
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Constants
30
+ // ---------------------------------------------------------------------------
31
+
32
+ /** Device codes expire after 15 minutes */
33
+ const DEVICE_CODE_TTL_SECONDS = 15 * 60;
34
+
35
+ /** Default polling interval in seconds */
36
+ const DEFAULT_INTERVAL = 5;
37
+
38
+ /** RFC 8628 §3.5: interval increase on slow_down */
39
+ const SLOW_DOWN_INCREMENT = 5;
40
+
41
+ /** Maximum slow_down interval cap (seconds) */
42
+ const MAX_SLOW_DOWN_INTERVAL = 60;
43
+
44
+ /** Access token TTL: 1 hour */
45
+ const ACCESS_TOKEN_TTL_SECONDS = 60 * 60;
46
+
47
+ /** Refresh token TTL: 90 days */
48
+ const REFRESH_TOKEN_TTL_SECONDS = 90 * 24 * 60 * 60;
49
+
50
+ /** Default scopes for CLI login */
51
+ const DEFAULT_SCOPES = [
52
+ "content:read",
53
+ "content:write",
54
+ "media:read",
55
+ "media:write",
56
+ "schema:read",
57
+ ] as const;
58
+
59
+ /** Pattern to normalize user codes (strip hyphens) */
60
+ const HYPHEN_PATTERN = /-/g;
61
+
62
+ /** Characters for user codes (uppercase, no ambiguous chars like 0/O, 1/I) */
63
+ const USER_CODE_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // Types
67
+ // ---------------------------------------------------------------------------
68
+
69
+ export interface DeviceCodeResponse {
70
+ device_code: string;
71
+ user_code: string;
72
+ verification_uri: string;
73
+ expires_in: number;
74
+ interval: number;
75
+ }
76
+
77
+ export interface TokenResponse {
78
+ access_token: string;
79
+ refresh_token: string;
80
+ token_type: "Bearer";
81
+ expires_in: number;
82
+ scope: string;
83
+ }
84
+
85
+ // RFC 8628 error codes
86
+ export type DeviceFlowError =
87
+ | "authorization_pending"
88
+ | "slow_down"
89
+ | "expired_token"
90
+ | "access_denied";
91
+
92
+ // ---------------------------------------------------------------------------
93
+ // Helpers
94
+ // ---------------------------------------------------------------------------
95
+
96
+ /** Generate a short human-readable user code (XXXX-XXXX) */
97
+ function generateUserCode(): string {
98
+ const bytes = new Uint8Array(8);
99
+ crypto.getRandomValues(bytes);
100
+ const chars = Array.from(bytes, (b) => USER_CODE_CHARS[b % USER_CODE_CHARS.length]).join("");
101
+ return `${chars.slice(0, 4)}-${chars.slice(4, 8)}`;
102
+ }
103
+
104
+ /** Get an ISO datetime string offset from now */
105
+ function expiresAt(seconds: number): string {
106
+ return new Date(Date.now() + seconds * 1000).toISOString();
107
+ }
108
+
109
+ /** Validate and normalize scopes. Returns validated scope list. */
110
+ function normalizeScopes(requested?: string[]): string[] {
111
+ if (!requested || requested.length === 0) {
112
+ return [...DEFAULT_SCOPES];
113
+ }
114
+ const validSet = new Set<string>(VALID_SCOPES);
115
+ return requested.filter((s) => validSet.has(s));
116
+ }
117
+
118
+ // ---------------------------------------------------------------------------
119
+ // Handlers
120
+ // ---------------------------------------------------------------------------
121
+
122
+ /**
123
+ * POST /oauth/device/code
124
+ *
125
+ * Issue a device code + user code. The CLI displays the user code
126
+ * and tells the user to open the verification URI.
127
+ */
128
+ export async function handleDeviceCodeRequest(
129
+ db: Kysely<Database>,
130
+ input: {
131
+ client_id?: string;
132
+ scope?: string;
133
+ },
134
+ verificationUri: string,
135
+ ): Promise<ApiResult<DeviceCodeResponse>> {
136
+ try {
137
+ // Parse and validate scopes
138
+ const requestedScopes = input.scope ? input.scope.split(" ").filter(Boolean) : [];
139
+ const scopes = normalizeScopes(requestedScopes);
140
+
141
+ if (scopes.length === 0) {
142
+ return {
143
+ success: false,
144
+ error: { code: "INVALID_SCOPE", message: "No valid scopes requested" },
145
+ };
146
+ }
147
+
148
+ const deviceCode = generateCodeVerifier();
149
+ const userCode = generateUserCode();
150
+ const expires = expiresAt(DEVICE_CODE_TTL_SECONDS);
151
+
152
+ await db
153
+ .insertInto("_emdash_device_codes")
154
+ .values({
155
+ device_code: deviceCode,
156
+ user_code: userCode,
157
+ scopes: JSON.stringify(scopes),
158
+ status: "pending",
159
+ expires_at: expires,
160
+ interval: DEFAULT_INTERVAL,
161
+ })
162
+ .execute();
163
+
164
+ return {
165
+ success: true,
166
+ data: {
167
+ device_code: deviceCode,
168
+ user_code: userCode,
169
+ verification_uri: verificationUri,
170
+ expires_in: DEVICE_CODE_TTL_SECONDS,
171
+ interval: DEFAULT_INTERVAL,
172
+ },
173
+ };
174
+ } catch {
175
+ return {
176
+ success: false,
177
+ error: {
178
+ code: "DEVICE_CODE_ERROR",
179
+ message: "Failed to create device code",
180
+ },
181
+ };
182
+ }
183
+ }
184
+
185
+ /**
186
+ * POST /oauth/device/token
187
+ *
188
+ * CLI polls this endpoint with the device_code. Returns:
189
+ * - 200 with tokens if authorized
190
+ * - 400 with error "authorization_pending" while waiting
191
+ * - 400 with error "slow_down" if polling too fast
192
+ * - 400 with error "expired_token" if the code expired
193
+ * - 400 with error "access_denied" if the user denied
194
+ */
195
+ export async function handleDeviceTokenExchange(
196
+ db: Kysely<Database>,
197
+ input: {
198
+ device_code: string;
199
+ grant_type: string;
200
+ },
201
+ ): Promise<
202
+ ApiResult<TokenResponse> & { deviceFlowError?: DeviceFlowError; deviceFlowInterval?: number }
203
+ > {
204
+ try {
205
+ // Validate grant_type
206
+ if (input.grant_type !== "urn:ietf:params:oauth:grant-type:device_code") {
207
+ return {
208
+ success: false,
209
+ error: { code: "UNSUPPORTED_GRANT_TYPE", message: "Invalid grant_type" },
210
+ };
211
+ }
212
+
213
+ // Look up the device code
214
+ const row = await db
215
+ .selectFrom("_emdash_device_codes")
216
+ .selectAll()
217
+ .where("device_code", "=", input.device_code)
218
+ .executeTakeFirst();
219
+
220
+ if (!row) {
221
+ return {
222
+ success: false,
223
+ error: { code: "INVALID_GRANT", message: "Invalid device code" },
224
+ };
225
+ }
226
+
227
+ const now = new Date();
228
+
229
+ // Check expiry
230
+ if (new Date(row.expires_at) < now) {
231
+ // Clean up expired code
232
+ await db
233
+ .deleteFrom("_emdash_device_codes")
234
+ .where("device_code", "=", input.device_code)
235
+ .execute();
236
+
237
+ return {
238
+ success: false,
239
+ deviceFlowError: "expired_token",
240
+ error: { code: "expired_token", message: "The device code has expired" },
241
+ };
242
+ }
243
+
244
+ // Check status
245
+ if (row.status === "denied") {
246
+ // Clean up denied code
247
+ await db
248
+ .deleteFrom("_emdash_device_codes")
249
+ .where("device_code", "=", input.device_code)
250
+ .execute();
251
+
252
+ return {
253
+ success: false,
254
+ deviceFlowError: "access_denied",
255
+ error: { code: "access_denied", message: "The user denied the request" },
256
+ };
257
+ }
258
+
259
+ if (row.status === "pending") {
260
+ // RFC 8628 §3.5: slow_down enforcement during polling phase.
261
+ // Only applies while waiting for authorization — once authorized,
262
+ // the final exchange proceeds without throttling.
263
+ if (row.last_polled_at) {
264
+ const lastPolled = new Date(row.last_polled_at);
265
+ const elapsedSeconds = (now.getTime() - lastPolled.getTime()) / 1000;
266
+
267
+ if (elapsedSeconds < row.interval) {
268
+ // Too fast — increase interval by 5s per RFC 8628 §3.5, capped at 60s
269
+ const newInterval = Math.min(row.interval + SLOW_DOWN_INCREMENT, MAX_SLOW_DOWN_INTERVAL);
270
+ await db
271
+ .updateTable("_emdash_device_codes")
272
+ .set({
273
+ interval: newInterval,
274
+ last_polled_at: now.toISOString(),
275
+ })
276
+ .where("device_code", "=", input.device_code)
277
+ .execute();
278
+
279
+ return {
280
+ success: false,
281
+ deviceFlowError: "slow_down",
282
+ deviceFlowInterval: newInterval,
283
+ error: { code: "slow_down", message: "Polling too fast" },
284
+ };
285
+ }
286
+ }
287
+
288
+ // Update last_polled_at for future slow_down checks
289
+ await db
290
+ .updateTable("_emdash_device_codes")
291
+ .set({ last_polled_at: now.toISOString() })
292
+ .where("device_code", "=", input.device_code)
293
+ .execute();
294
+
295
+ return {
296
+ success: false,
297
+ deviceFlowError: "authorization_pending",
298
+ error: { code: "authorization_pending", message: "Authorization pending" },
299
+ };
300
+ }
301
+
302
+ if (row.status !== "authorized" || !row.user_id) {
303
+ return {
304
+ success: false,
305
+ error: { code: "INVALID_GRANT", message: "Invalid device code state" },
306
+ };
307
+ }
308
+
309
+ // Authorized! Generate tokens.
310
+ const scopes = JSON.parse(row.scopes) as string[];
311
+
312
+ // Generate access token
313
+ const accessToken = generatePrefixedToken(TOKEN_PREFIXES.OAUTH_ACCESS);
314
+ const accessExpires = expiresAt(ACCESS_TOKEN_TTL_SECONDS);
315
+
316
+ // Generate refresh token
317
+ const refreshToken = generatePrefixedToken(TOKEN_PREFIXES.OAUTH_REFRESH);
318
+ const refreshExpires = expiresAt(REFRESH_TOKEN_TTL_SECONDS);
319
+
320
+ // Store both tokens
321
+ await db
322
+ .insertInto("_emdash_oauth_tokens")
323
+ .values({
324
+ token_hash: accessToken.hash,
325
+ token_type: "access",
326
+ user_id: row.user_id,
327
+ scopes: JSON.stringify(scopes),
328
+ client_type: "cli",
329
+ expires_at: accessExpires,
330
+ refresh_token_hash: refreshToken.hash,
331
+ })
332
+ .execute();
333
+
334
+ await db
335
+ .insertInto("_emdash_oauth_tokens")
336
+ .values({
337
+ token_hash: refreshToken.hash,
338
+ token_type: "refresh",
339
+ user_id: row.user_id,
340
+ scopes: JSON.stringify(scopes),
341
+ client_type: "cli",
342
+ expires_at: refreshExpires,
343
+ refresh_token_hash: null,
344
+ })
345
+ .execute();
346
+
347
+ // Consume the device code (delete it)
348
+ await db
349
+ .deleteFrom("_emdash_device_codes")
350
+ .where("device_code", "=", input.device_code)
351
+ .execute();
352
+
353
+ return {
354
+ success: true,
355
+ data: {
356
+ access_token: accessToken.raw,
357
+ refresh_token: refreshToken.raw,
358
+ token_type: "Bearer",
359
+ expires_in: ACCESS_TOKEN_TTL_SECONDS,
360
+ scope: scopes.join(" "),
361
+ },
362
+ };
363
+ } catch {
364
+ return {
365
+ success: false,
366
+ error: {
367
+ code: "TOKEN_EXCHANGE_ERROR",
368
+ message: "Failed to exchange device code",
369
+ },
370
+ };
371
+ }
372
+ }
373
+
374
+ /**
375
+ * POST /oauth/device/authorize
376
+ *
377
+ * The user submits the user_code after logging in via the browser.
378
+ * This authorizes the device code, allowing the CLI to exchange it for tokens.
379
+ *
380
+ * Scopes are clamped to the user's role at this point. The stored scopes
381
+ * are replaced with the intersection of requested scopes and the scopes
382
+ * the user's role permits. This prevents scope escalation.
383
+ */
384
+ export async function handleDeviceAuthorize(
385
+ db: Kysely<Database>,
386
+ userId: string,
387
+ userRole: RoleLevel,
388
+ input: {
389
+ user_code: string;
390
+ action?: "approve" | "deny";
391
+ },
392
+ ): Promise<ApiResult<{ authorized: boolean }>> {
393
+ try {
394
+ // Normalize user code (strip hyphens, uppercase)
395
+ const normalizedCode = input.user_code.replace(HYPHEN_PATTERN, "").toUpperCase();
396
+
397
+ // Look up the device code by user_code
398
+ const row = await db
399
+ .selectFrom("_emdash_device_codes")
400
+ .selectAll()
401
+ .where("status", "=", "pending")
402
+ .execute();
403
+
404
+ // Find the matching code (strip hyphens for comparison)
405
+ const match = row.find(
406
+ (r) => r.user_code.replace(HYPHEN_PATTERN, "").toUpperCase() === normalizedCode,
407
+ );
408
+
409
+ if (!match) {
410
+ return {
411
+ success: false,
412
+ error: { code: "INVALID_CODE", message: "Invalid or expired code" },
413
+ };
414
+ }
415
+
416
+ // Check expiry
417
+ if (new Date(match.expires_at) < new Date()) {
418
+ await db
419
+ .deleteFrom("_emdash_device_codes")
420
+ .where("device_code", "=", match.device_code)
421
+ .execute();
422
+
423
+ return {
424
+ success: false,
425
+ error: { code: "EXPIRED_CODE", message: "This code has expired" },
426
+ };
427
+ }
428
+
429
+ const action = input.action ?? "approve";
430
+
431
+ if (action === "deny") {
432
+ await db
433
+ .updateTable("_emdash_device_codes")
434
+ .set({ status: "denied" })
435
+ .where("device_code", "=", match.device_code)
436
+ .execute();
437
+
438
+ return { success: true, data: { authorized: false } };
439
+ }
440
+
441
+ // Clamp requested scopes to those the user's role permits.
442
+ // effective_scopes = requested_scopes ∩ scopesForRole(user.role)
443
+ const requestedScopes = JSON.parse(match.scopes) as string[];
444
+ const effectiveScopes = clampScopes(requestedScopes, userRole);
445
+
446
+ if (effectiveScopes.length === 0) {
447
+ return {
448
+ success: false,
449
+ error: {
450
+ code: "INSUFFICIENT_ROLE",
451
+ message: "Your role does not permit any of the requested scopes",
452
+ },
453
+ };
454
+ }
455
+
456
+ // Approve: set user_id, status, and clamped scopes
457
+ await db
458
+ .updateTable("_emdash_device_codes")
459
+ .set({
460
+ status: "authorized",
461
+ user_id: userId,
462
+ scopes: JSON.stringify(effectiveScopes),
463
+ })
464
+ .where("device_code", "=", match.device_code)
465
+ .execute();
466
+
467
+ return { success: true, data: { authorized: true } };
468
+ } catch {
469
+ return {
470
+ success: false,
471
+ error: {
472
+ code: "AUTHORIZE_ERROR",
473
+ message: "Failed to authorize device",
474
+ },
475
+ };
476
+ }
477
+ }
478
+
479
+ /**
480
+ * POST /oauth/token/refresh
481
+ *
482
+ * Exchange a refresh token for a new access token.
483
+ * The refresh token itself is not rotated (per spec: optional rotation).
484
+ */
485
+ export async function handleTokenRefresh(
486
+ db: Kysely<Database>,
487
+ input: {
488
+ refresh_token: string;
489
+ grant_type: string;
490
+ },
491
+ ): Promise<ApiResult<TokenResponse>> {
492
+ try {
493
+ if (input.grant_type !== "refresh_token") {
494
+ return {
495
+ success: false,
496
+ error: { code: "UNSUPPORTED_GRANT_TYPE", message: "Invalid grant_type" },
497
+ };
498
+ }
499
+
500
+ if (!input.refresh_token.startsWith(TOKEN_PREFIXES.OAUTH_REFRESH)) {
501
+ return {
502
+ success: false,
503
+ error: { code: "INVALID_GRANT", message: "Invalid refresh token format" },
504
+ };
505
+ }
506
+
507
+ const refreshHash = hashApiToken(input.refresh_token);
508
+
509
+ const row = await db
510
+ .selectFrom("_emdash_oauth_tokens")
511
+ .selectAll()
512
+ .where("token_hash", "=", refreshHash)
513
+ .where("token_type", "=", "refresh")
514
+ .executeTakeFirst();
515
+
516
+ if (!row) {
517
+ return {
518
+ success: false,
519
+ error: { code: "INVALID_GRANT", message: "Invalid refresh token" },
520
+ };
521
+ }
522
+
523
+ // Check expiry
524
+ if (new Date(row.expires_at) < new Date()) {
525
+ // Clean up expired refresh token and its access tokens
526
+ await db.deleteFrom("_emdash_oauth_tokens").where("token_hash", "=", refreshHash).execute();
527
+ await db
528
+ .deleteFrom("_emdash_oauth_tokens")
529
+ .where("refresh_token_hash", "=", refreshHash)
530
+ .execute();
531
+
532
+ return {
533
+ success: false,
534
+ error: { code: "INVALID_GRANT", message: "Refresh token expired" },
535
+ };
536
+ }
537
+
538
+ // SEC-42: Revalidate user role before issuing new access token.
539
+ // SEC-43: Reject refresh if user is disabled or deleted.
540
+ const userInfo = await lookupUserRoleAndStatus(db, row.user_id);
541
+ if (!userInfo) {
542
+ // User no longer exists — revoke all their tokens
543
+ await db.deleteFrom("_emdash_oauth_tokens").where("user_id", "=", row.user_id).execute();
544
+ return {
545
+ success: false,
546
+ error: { code: "INVALID_GRANT", message: "User not found" },
547
+ };
548
+ }
549
+
550
+ if (userInfo.disabled) {
551
+ // User is disabled — revoke all their tokens
552
+ await db.deleteFrom("_emdash_oauth_tokens").where("user_id", "=", row.user_id).execute();
553
+ return {
554
+ success: false,
555
+ error: { code: "INVALID_GRANT", message: "User account is disabled" },
556
+ };
557
+ }
558
+
559
+ // Revalidate stored scopes against the user's current role.
560
+ // A demoted user's refresh token may carry stale elevated scopes.
561
+ const storedScopes = JSON.parse(row.scopes) as string[];
562
+ let scopes = clampScopes(storedScopes, userInfo.role);
563
+
564
+ // SEC-41: Intersect with the client's registered scopes (if any).
565
+ // Same check as the approval path — a client registered with limited
566
+ // scopes should never receive elevated scopes on refresh, even if the
567
+ // user's role would allow them.
568
+ if (row.client_id) {
569
+ const client = await lookupOAuthClient(db, row.client_id);
570
+ if (client?.scopes?.length) {
571
+ scopes = scopes.filter((s: string) => client.scopes!.includes(s));
572
+ }
573
+ }
574
+
575
+ if (scopes.length === 0) {
576
+ // User's role no longer supports any of the token's scopes — revoke
577
+ await db.deleteFrom("_emdash_oauth_tokens").where("token_hash", "=", refreshHash).execute();
578
+ await db
579
+ .deleteFrom("_emdash_oauth_tokens")
580
+ .where("refresh_token_hash", "=", refreshHash)
581
+ .execute();
582
+ return {
583
+ success: false,
584
+ error: {
585
+ code: "INVALID_GRANT",
586
+ message: "User role no longer supports any of the token's scopes",
587
+ },
588
+ };
589
+ }
590
+
591
+ // Delete old access tokens for this refresh token
592
+ await db
593
+ .deleteFrom("_emdash_oauth_tokens")
594
+ .where("refresh_token_hash", "=", refreshHash)
595
+ .where("token_type", "=", "access")
596
+ .execute();
597
+
598
+ // Generate new access token
599
+ const accessToken = generatePrefixedToken(TOKEN_PREFIXES.OAUTH_ACCESS);
600
+ const accessExpires = expiresAt(ACCESS_TOKEN_TTL_SECONDS);
601
+
602
+ await db
603
+ .insertInto("_emdash_oauth_tokens")
604
+ .values({
605
+ token_hash: accessToken.hash,
606
+ token_type: "access",
607
+ user_id: row.user_id,
608
+ scopes: JSON.stringify(scopes),
609
+ client_type: row.client_type,
610
+ expires_at: accessExpires,
611
+ refresh_token_hash: refreshHash,
612
+ })
613
+ .execute();
614
+
615
+ return {
616
+ success: true,
617
+ data: {
618
+ access_token: accessToken.raw,
619
+ refresh_token: input.refresh_token, // Return same refresh token
620
+ token_type: "Bearer",
621
+ expires_in: ACCESS_TOKEN_TTL_SECONDS,
622
+ scope: scopes.join(" "),
623
+ },
624
+ };
625
+ } catch {
626
+ return {
627
+ success: false,
628
+ error: {
629
+ code: "TOKEN_REFRESH_ERROR",
630
+ message: "Failed to refresh token",
631
+ },
632
+ };
633
+ }
634
+ }
635
+
636
+ /**
637
+ * POST /oauth/token/revoke
638
+ *
639
+ * Revoke an access or refresh token. If a refresh token is revoked,
640
+ * also revoke all associated access tokens.
641
+ *
642
+ * Per RFC 7009, this endpoint always returns 200 (even for invalid tokens).
643
+ */
644
+ export async function handleTokenRevoke(
645
+ db: Kysely<Database>,
646
+ input: {
647
+ token: string;
648
+ },
649
+ ): Promise<ApiResult<{ revoked: boolean }>> {
650
+ try {
651
+ const hash = hashApiToken(input.token);
652
+
653
+ // Look up the token
654
+ const row = await db
655
+ .selectFrom("_emdash_oauth_tokens")
656
+ .select(["token_hash", "token_type", "refresh_token_hash"])
657
+ .where("token_hash", "=", hash)
658
+ .executeTakeFirst();
659
+
660
+ if (!row) {
661
+ // Per RFC 7009: always 200, even for invalid tokens
662
+ return { success: true, data: { revoked: true } };
663
+ }
664
+
665
+ if (row.token_type === "refresh") {
666
+ // Revoke refresh token and all its access tokens
667
+ await db.deleteFrom("_emdash_oauth_tokens").where("refresh_token_hash", "=", hash).execute();
668
+ await db.deleteFrom("_emdash_oauth_tokens").where("token_hash", "=", hash).execute();
669
+ } else {
670
+ // Revoke just the access token
671
+ await db.deleteFrom("_emdash_oauth_tokens").where("token_hash", "=", hash).execute();
672
+ }
673
+
674
+ return { success: true, data: { revoked: true } };
675
+ } catch {
676
+ return {
677
+ success: false,
678
+ error: {
679
+ code: "TOKEN_REVOKE_ERROR",
680
+ message: "Failed to revoke token",
681
+ },
682
+ };
683
+ }
684
+ }