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,219 @@
1
+ /**
2
+ * GET /_emdash/api/auth/oauth/[provider]/callback
3
+ *
4
+ * Handle OAuth callback from provider
5
+ */
6
+
7
+ import type { APIRoute } from "astro";
8
+
9
+ export const prerender = false;
10
+
11
+ import {
12
+ handleOAuthCallback,
13
+ OAuthError,
14
+ Role,
15
+ type OAuthConsumerConfig,
16
+ type RoleLevel,
17
+ } from "@emdash-cms/auth";
18
+ import { createKyselyAdapter } from "@emdash-cms/auth/adapters/kysely";
19
+
20
+ import { createOAuthStateStore } from "#auth/oauth-state-store.js";
21
+
22
+ type ProviderName = "github" | "google";
23
+
24
+ const VALID_PROVIDERS = new Set<string>(["github", "google"]);
25
+
26
+ function isValidProvider(provider: string): provider is ProviderName {
27
+ return VALID_PROVIDERS.has(provider);
28
+ }
29
+
30
+ /** Safely extract a string value from an env-like record */
31
+ function envString(env: Record<string, unknown>, ...keys: string[]): string | undefined {
32
+ for (const key of keys) {
33
+ const val = env[key];
34
+ if (typeof val === "string" && val) return val;
35
+ }
36
+ return undefined;
37
+ }
38
+
39
+ /**
40
+ * Get OAuth config from environment variables
41
+ */
42
+ function getOAuthConfig(env: Record<string, unknown>): OAuthConsumerConfig["providers"] {
43
+ const providers: OAuthConsumerConfig["providers"] = {};
44
+
45
+ // GitHub
46
+ const githubClientId = envString(env, "EMDASH_OAUTH_GITHUB_CLIENT_ID", "GITHUB_CLIENT_ID");
47
+ const githubClientSecret = envString(
48
+ env,
49
+ "EMDASH_OAUTH_GITHUB_CLIENT_SECRET",
50
+ "GITHUB_CLIENT_SECRET",
51
+ );
52
+ if (githubClientId && githubClientSecret) {
53
+ providers.github = {
54
+ clientId: githubClientId,
55
+ clientSecret: githubClientSecret,
56
+ };
57
+ }
58
+
59
+ // Google
60
+ const googleClientId = envString(env, "EMDASH_OAUTH_GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_ID");
61
+ const googleClientSecret = envString(
62
+ env,
63
+ "EMDASH_OAUTH_GOOGLE_CLIENT_SECRET",
64
+ "GOOGLE_CLIENT_SECRET",
65
+ );
66
+ if (googleClientId && googleClientSecret) {
67
+ providers.google = {
68
+ clientId: googleClientId,
69
+ clientSecret: googleClientSecret,
70
+ };
71
+ }
72
+
73
+ return providers;
74
+ }
75
+
76
+ export const GET: APIRoute = async ({ params, request, locals, session, redirect }) => {
77
+ const { emdash } = locals;
78
+ const provider = params.provider;
79
+
80
+ // Validate provider
81
+ if (!provider || !isValidProvider(provider)) {
82
+ return redirect(
83
+ `/_emdash/admin/login?error=invalid_provider&message=${encodeURIComponent("Invalid OAuth provider")}`,
84
+ );
85
+ }
86
+
87
+ if (!emdash?.db) {
88
+ return redirect(
89
+ `/_emdash/admin/login?error=server_error&message=${encodeURIComponent("Database not configured")}`,
90
+ );
91
+ }
92
+
93
+ const url = new URL(request.url);
94
+ const code = url.searchParams.get("code");
95
+ const state = url.searchParams.get("state");
96
+ const error = url.searchParams.get("error");
97
+ const errorDescription = url.searchParams.get("error_description");
98
+
99
+ // Handle OAuth errors from provider
100
+ if (error) {
101
+ const message = errorDescription || error;
102
+ return redirect(
103
+ `/_emdash/admin/login?error=oauth_denied&message=${encodeURIComponent(message)}`,
104
+ );
105
+ }
106
+
107
+ // Validate required params
108
+ if (!code || !state) {
109
+ return redirect(
110
+ `/_emdash/admin/login?error=invalid_callback&message=${encodeURIComponent("Missing code or state parameter")}`,
111
+ );
112
+ }
113
+
114
+ try {
115
+ // Get OAuth providers from environment
116
+ // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- locals.runtime is injected by the Cloudflare adapter at runtime; not declared on App.Locals since the adapter is optional
117
+ const runtimeLocals = locals as unknown as { runtime?: { env?: Record<string, unknown> } };
118
+ // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- import.meta.env is typed as ImportMetaEnv but we need Record<string, unknown> for getOAuthConfig
119
+ const env = runtimeLocals.runtime?.env ?? (import.meta.env as Record<string, unknown>);
120
+ const providers = getOAuthConfig(env);
121
+
122
+ if (!providers[provider]) {
123
+ return redirect(
124
+ `/_emdash/admin/login?error=provider_not_configured&message=${encodeURIComponent(`OAuth provider ${provider} is not configured`)}`,
125
+ );
126
+ }
127
+
128
+ const config: OAuthConsumerConfig = {
129
+ baseUrl: `${url.origin}/_emdash`,
130
+ providers,
131
+ canSelfSignup: async (email: string) => {
132
+ // Extract domain from email
133
+ const domain = email.split("@")[1]?.toLowerCase();
134
+ if (!domain) {
135
+ return null;
136
+ }
137
+
138
+ // Check allowed_domains table for a matching, enabled entry
139
+ const entry = await emdash.db
140
+ .selectFrom("allowed_domains")
141
+ .selectAll()
142
+ .where("domain", "=", domain)
143
+ .where("enabled", "=", 1)
144
+ .executeTakeFirst();
145
+
146
+ if (!entry) {
147
+ return null;
148
+ }
149
+
150
+ // Map the stored role level to the Role enum
151
+ const roleLevel = entry.default_role;
152
+ const roleMap: Record<number, RoleLevel> = {
153
+ 50: Role.ADMIN,
154
+ 40: Role.EDITOR,
155
+ 30: Role.AUTHOR,
156
+ 20: Role.CONTRIBUTOR,
157
+ 10: Role.SUBSCRIBER,
158
+ };
159
+ const role = roleMap[roleLevel] ?? Role.CONTRIBUTOR;
160
+ if (!roleMap[roleLevel]) {
161
+ console.warn(
162
+ `[oauth] Unknown role level ${roleLevel} for domain ${domain}, defaulting to CONTRIBUTOR`,
163
+ );
164
+ }
165
+
166
+ return { allowed: true, role };
167
+ },
168
+ };
169
+
170
+ const adapter = createKyselyAdapter(emdash.db);
171
+ const stateStore = createOAuthStateStore(emdash.db);
172
+
173
+ const user = await handleOAuthCallback(config, adapter, provider, code, state, stateStore);
174
+
175
+ // Create session
176
+ if (session) {
177
+ session.set("user", { id: user.id });
178
+ }
179
+
180
+ // Redirect to admin dashboard
181
+ return redirect("/_emdash/admin");
182
+ } catch (callbackError) {
183
+ console.error("OAuth callback error:", callbackError);
184
+
185
+ let message = "Authentication failed";
186
+ let errorCode = "oauth_error";
187
+
188
+ if (callbackError instanceof OAuthError) {
189
+ errorCode = callbackError.code;
190
+
191
+ // Map all error codes to user-friendly messages (never expose raw error.message)
192
+ switch (callbackError.code) {
193
+ case "invalid_state":
194
+ message = "OAuth session expired or invalid. Please try again.";
195
+ break;
196
+ case "signup_not_allowed":
197
+ message = "Self-signup is not allowed for your email. Please contact an administrator.";
198
+ break;
199
+ case "user_not_found":
200
+ message = "Your account was not found. It may have been deleted.";
201
+ break;
202
+ case "token_exchange_failed":
203
+ message = "Failed to complete authentication. Please try again.";
204
+ break;
205
+ case "profile_fetch_failed":
206
+ message = "Failed to retrieve your profile. Please try again.";
207
+ break;
208
+ default:
209
+ message = "Authentication failed. Please try again.";
210
+ break;
211
+ }
212
+ }
213
+ // For generic errors, keep the default "Authentication failed" message
214
+
215
+ return redirect(
216
+ `/_emdash/admin/login?error=${errorCode}&message=${encodeURIComponent(message)}`,
217
+ );
218
+ }
219
+ };
@@ -0,0 +1,119 @@
1
+ /**
2
+ * GET /_emdash/api/auth/oauth/[provider]
3
+ *
4
+ * Start OAuth flow - redirects to provider authorization URL
5
+ */
6
+
7
+ import type { APIRoute } from "astro";
8
+
9
+ export const prerender = false;
10
+
11
+ import { createAuthorizationUrl, type OAuthConsumerConfig } from "@emdash-cms/auth";
12
+
13
+ import { createOAuthStateStore } from "#auth/oauth-state-store.js";
14
+
15
+ type ProviderName = "github" | "google";
16
+
17
+ const VALID_PROVIDERS = new Set<string>(["github", "google"]);
18
+
19
+ function isValidProvider(provider: string): provider is ProviderName {
20
+ return VALID_PROVIDERS.has(provider);
21
+ }
22
+
23
+ /** Safely extract a string value from an env-like record */
24
+ function envString(env: Record<string, unknown>, ...keys: string[]): string | undefined {
25
+ for (const key of keys) {
26
+ const val = env[key];
27
+ if (typeof val === "string" && val) return val;
28
+ }
29
+ return undefined;
30
+ }
31
+
32
+ /**
33
+ * Get OAuth config from environment variables
34
+ */
35
+ function getOAuthConfig(env: Record<string, unknown>): OAuthConsumerConfig["providers"] {
36
+ const providers: OAuthConsumerConfig["providers"] = {};
37
+
38
+ // GitHub
39
+ const githubClientId = envString(env, "EMDASH_OAUTH_GITHUB_CLIENT_ID", "GITHUB_CLIENT_ID");
40
+ const githubClientSecret = envString(
41
+ env,
42
+ "EMDASH_OAUTH_GITHUB_CLIENT_SECRET",
43
+ "GITHUB_CLIENT_SECRET",
44
+ );
45
+ if (githubClientId && githubClientSecret) {
46
+ providers.github = {
47
+ clientId: githubClientId,
48
+ clientSecret: githubClientSecret,
49
+ };
50
+ }
51
+
52
+ // Google
53
+ const googleClientId = envString(env, "EMDASH_OAUTH_GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_ID");
54
+ const googleClientSecret = envString(
55
+ env,
56
+ "EMDASH_OAUTH_GOOGLE_CLIENT_SECRET",
57
+ "GOOGLE_CLIENT_SECRET",
58
+ );
59
+ if (googleClientId && googleClientSecret) {
60
+ providers.google = {
61
+ clientId: googleClientId,
62
+ clientSecret: googleClientSecret,
63
+ };
64
+ }
65
+
66
+ return providers;
67
+ }
68
+
69
+ export const GET: APIRoute = async ({ params, request, locals, redirect }) => {
70
+ const { emdash } = locals;
71
+ const provider = params.provider;
72
+
73
+ // Validate provider
74
+ if (!provider || !isValidProvider(provider)) {
75
+ return redirect(
76
+ `/_emdash/admin/login?error=invalid_provider&message=${encodeURIComponent("Invalid OAuth provider")}`,
77
+ );
78
+ }
79
+
80
+ if (!emdash?.db) {
81
+ return redirect(
82
+ `/_emdash/admin/login?error=server_error&message=${encodeURIComponent("Database not configured")}`,
83
+ );
84
+ }
85
+
86
+ try {
87
+ const url = new URL(request.url);
88
+
89
+ // Get OAuth providers from environment
90
+ // Access via locals.runtime for Cloudflare, or import.meta.env for Node
91
+ // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- locals.runtime is injected by the Cloudflare adapter at runtime; not declared on App.Locals since the adapter is optional
92
+ const runtimeLocals = locals as unknown as { runtime?: { env?: Record<string, unknown> } };
93
+ // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- import.meta.env is typed as ImportMetaEnv but we need Record<string, unknown> for getOAuthConfig
94
+ const env = runtimeLocals.runtime?.env ?? (import.meta.env as Record<string, unknown>);
95
+ const providers = getOAuthConfig(env);
96
+
97
+ if (!providers[provider]) {
98
+ return redirect(
99
+ `/_emdash/admin/login?error=provider_not_configured&message=${encodeURIComponent(`OAuth provider ${provider} is not configured`)}`,
100
+ );
101
+ }
102
+
103
+ const config: OAuthConsumerConfig = {
104
+ baseUrl: `${url.origin}/_emdash`,
105
+ providers,
106
+ };
107
+
108
+ const stateStore = createOAuthStateStore(emdash.db);
109
+
110
+ const { url: authUrl } = await createAuthorizationUrl(config, provider, stateStore);
111
+
112
+ return redirect(authUrl);
113
+ } catch (error) {
114
+ console.error("OAuth initiation error:", error);
115
+ return redirect(
116
+ `/_emdash/admin/login?error=oauth_error&message=${encodeURIComponent("Failed to start OAuth flow. Please try again.")}`,
117
+ );
118
+ }
119
+ };
@@ -0,0 +1,124 @@
1
+ /**
2
+ * PATCH/DELETE /_emdash/api/auth/passkey/[id]
3
+ *
4
+ * Rename or delete a passkey
5
+ */
6
+
7
+ import type { APIRoute } from "astro";
8
+
9
+ export const prerender = false;
10
+
11
+ import { createKyselyAdapter } from "@emdash-cms/auth/adapters/kysely";
12
+
13
+ import { apiError, apiSuccess, handleError } from "#api/error.js";
14
+ import { isParseError, parseBody } from "#api/parse.js";
15
+ import { passkeyRenameBody } from "#api/schemas.js";
16
+
17
+ interface PasskeyResponse {
18
+ id: string;
19
+ name: string | null;
20
+ deviceType: "singleDevice" | "multiDevice";
21
+ backedUp: boolean;
22
+ createdAt: string;
23
+ lastUsedAt: string;
24
+ }
25
+
26
+ /**
27
+ * PATCH - Rename a passkey
28
+ */
29
+ export const PATCH: APIRoute = async ({ params, request, locals }) => {
30
+ const { emdash, user } = locals;
31
+ const { id } = params;
32
+
33
+ if (!emdash?.db) {
34
+ return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
35
+ }
36
+
37
+ // Require authentication
38
+ if (!user) {
39
+ return apiError("NOT_AUTHENTICATED", "Not authenticated", 401);
40
+ }
41
+
42
+ if (!id) {
43
+ return apiError("MISSING_PARAM", "Passkey ID is required", 400);
44
+ }
45
+
46
+ try {
47
+ const adapter = createKyselyAdapter(emdash.db);
48
+
49
+ // Get the credential and verify ownership
50
+ const credential = await adapter.getCredentialById(id);
51
+
52
+ if (!credential || credential.userId !== user.id) {
53
+ return apiError("NOT_FOUND", "Passkey not found", 404);
54
+ }
55
+
56
+ // Parse request body
57
+ const body = await parseBody(request, passkeyRenameBody);
58
+ if (isParseError(body)) return body;
59
+
60
+ // Update the name
61
+ const trimmedName = body.name.trim() || null;
62
+ await adapter.updateCredentialName(id, trimmedName);
63
+
64
+ // Return updated passkey info
65
+ const passkey: PasskeyResponse = {
66
+ id: credential.id,
67
+ name: trimmedName,
68
+ deviceType: credential.deviceType,
69
+ backedUp: credential.backedUp,
70
+ createdAt: credential.createdAt.toISOString(),
71
+ lastUsedAt: credential.lastUsedAt.toISOString(),
72
+ };
73
+
74
+ return apiSuccess({ passkey });
75
+ } catch (error) {
76
+ return handleError(error, "Failed to rename passkey", "PASSKEY_RENAME_ERROR");
77
+ }
78
+ };
79
+
80
+ /**
81
+ * DELETE - Remove a passkey
82
+ */
83
+ export const DELETE: APIRoute = async ({ params, locals }) => {
84
+ const { emdash, user } = locals;
85
+ const { id } = params;
86
+
87
+ if (!emdash?.db) {
88
+ return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
89
+ }
90
+
91
+ // Require authentication
92
+ if (!user) {
93
+ return apiError("NOT_AUTHENTICATED", "Not authenticated", 401);
94
+ }
95
+
96
+ if (!id) {
97
+ return apiError("MISSING_PARAM", "Passkey ID is required", 400);
98
+ }
99
+
100
+ try {
101
+ const adapter = createKyselyAdapter(emdash.db);
102
+
103
+ // Get the credential and verify ownership
104
+ const credential = await adapter.getCredentialById(id);
105
+
106
+ if (!credential || credential.userId !== user.id) {
107
+ return apiError("NOT_FOUND", "Passkey not found", 404);
108
+ }
109
+
110
+ // Check that this isn't the last passkey
111
+ const count = await adapter.countCredentialsByUserId(user.id);
112
+
113
+ if (count <= 1) {
114
+ return apiError("LAST_PASSKEY", "Cannot remove your last passkey", 400);
115
+ }
116
+
117
+ // Delete the passkey
118
+ await adapter.deleteCredential(id);
119
+
120
+ return apiSuccess({ success: true });
121
+ } catch (error) {
122
+ return handleError(error, "Failed to delete passkey", "PASSKEY_DELETE_ERROR");
123
+ }
124
+ };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * GET /_emdash/api/auth/passkey
3
+ *
4
+ * List all passkeys for the authenticated user
5
+ */
6
+
7
+ import type { APIRoute } from "astro";
8
+
9
+ export const prerender = false;
10
+
11
+ import { createKyselyAdapter } from "@emdash-cms/auth/adapters/kysely";
12
+
13
+ import { apiError, apiSuccess, handleError } from "#api/error.js";
14
+
15
+ interface PasskeyResponse {
16
+ id: string;
17
+ name: string | null;
18
+ deviceType: "singleDevice" | "multiDevice";
19
+ backedUp: boolean;
20
+ createdAt: string;
21
+ lastUsedAt: string;
22
+ }
23
+
24
+ export const GET: APIRoute = async ({ locals }) => {
25
+ const { emdash, user } = locals;
26
+
27
+ if (!emdash?.db) {
28
+ return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
29
+ }
30
+
31
+ // Require authentication
32
+ if (!user) {
33
+ return apiError("NOT_AUTHENTICATED", "Not authenticated", 401);
34
+ }
35
+
36
+ try {
37
+ const adapter = createKyselyAdapter(emdash.db);
38
+ const credentials = await adapter.getCredentialsByUserId(user.id);
39
+
40
+ // Map to public response format (exclude sensitive fields)
41
+ const passkeys: PasskeyResponse[] = credentials.map((cred) => ({
42
+ id: cred.id,
43
+ name: cred.name,
44
+ deviceType: cred.deviceType,
45
+ backedUp: cred.backedUp,
46
+ createdAt: cred.createdAt.toISOString(),
47
+ lastUsedAt: cred.lastUsedAt.toISOString(),
48
+ }));
49
+
50
+ return apiSuccess({ items: passkeys });
51
+ } catch (error) {
52
+ return handleError(error, "Failed to list passkeys", "PASSKEY_LIST_ERROR");
53
+ }
54
+ };
@@ -0,0 +1,82 @@
1
+ /**
2
+ * POST /_emdash/api/auth/passkey/options
3
+ *
4
+ * Get authentication options for passkey login.
5
+ *
6
+ * Rate limited: 10 requests per minute per IP.
7
+ */
8
+
9
+ import type { APIRoute } from "astro";
10
+
11
+ export const prerender = false;
12
+
13
+ import { createKyselyAdapter } from "@emdash-cms/auth/adapters/kysely";
14
+ import { generateAuthenticationOptions } from "@emdash-cms/auth/passkey";
15
+
16
+ import { apiError, apiSuccess, handleError } from "#api/error.js";
17
+ import { isParseError, parseOptionalBody } from "#api/parse.js";
18
+ import { passkeyOptionsBody } from "#api/schemas.js";
19
+ import { createChallengeStore, cleanupExpiredChallenges } from "#auth/challenge-store.js";
20
+ import { getPasskeyConfig } from "#auth/passkey-config.js";
21
+ import { checkRateLimit, getClientIp, rateLimitResponse } from "#auth/rate-limit.js";
22
+ import { OptionsRepository } from "#db/repositories/options.js";
23
+
24
+ export const POST: APIRoute = async ({ request, locals }) => {
25
+ const { emdash } = locals;
26
+
27
+ if (!emdash?.db) {
28
+ return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
29
+ }
30
+
31
+ try {
32
+ // Fire-and-forget cleanup of expired challenges -- prevents accumulation
33
+ void cleanupExpiredChallenges(emdash.db).catch(() => {});
34
+
35
+ // Parse body before rate limiting so malformed requests don't consume slots
36
+ const body = await parseOptionalBody(request, passkeyOptionsBody, {});
37
+ if (isParseError(body)) return body;
38
+
39
+ // Rate limit: 10 requests per 60 seconds per IP
40
+ const ip = getClientIp(request);
41
+ const rateLimit = await checkRateLimit(emdash.db, ip, "passkey/options", 10, 60);
42
+ if (!rateLimit.allowed) {
43
+ return rateLimitResponse(60);
44
+ }
45
+
46
+ const adapter = createKyselyAdapter(emdash.db);
47
+
48
+ // Get credentials to allow
49
+ let credentials: Awaited<ReturnType<typeof adapter.getCredentialsByUserId>> = [];
50
+
51
+ if (body.email) {
52
+ // Get credentials for specific user
53
+ const user = await adapter.getUserByEmail(body.email);
54
+ if (user) {
55
+ credentials = await adapter.getCredentialsByUserId(user.id);
56
+ }
57
+ // Don't reveal if user exists - just return empty allowCredentials
58
+ }
59
+ // If no email provided, allowCredentials will be undefined (allow any discoverable credential)
60
+
61
+ // Get passkey config
62
+ const url = new URL(request.url);
63
+ const options = new OptionsRepository(emdash.db);
64
+ const siteName = (await options.get<string>("emdash:site_title")) ?? undefined;
65
+ const passkeyConfig = getPasskeyConfig(url, siteName);
66
+
67
+ // Generate authentication options
68
+ const challengeStore = createChallengeStore(emdash.db);
69
+ const authOptions = await generateAuthenticationOptions(
70
+ passkeyConfig,
71
+ credentials,
72
+ challengeStore,
73
+ );
74
+
75
+ return apiSuccess({
76
+ success: true,
77
+ options: authOptions,
78
+ });
79
+ } catch (error) {
80
+ return handleError(error, "Failed to generate passkey options", "PASSKEY_OPTIONS_ERROR");
81
+ }
82
+ };
@@ -0,0 +1,86 @@
1
+ /**
2
+ * POST /_emdash/api/auth/passkey/register/options
3
+ *
4
+ * Get WebAuthn registration options for adding a new passkey (authenticated user)
5
+ */
6
+
7
+ import type { APIRoute } from "astro";
8
+
9
+ export const prerender = false;
10
+
11
+ import { createKyselyAdapter } from "@emdash-cms/auth/adapters/kysely";
12
+ import { generateRegistrationOptions } from "@emdash-cms/auth/passkey";
13
+
14
+ import { apiError, apiSuccess, handleError } from "#api/error.js";
15
+ import { isParseError, parseOptionalBody } from "#api/parse.js";
16
+ import { passkeyRegisterOptionsBody } from "#api/schemas.js";
17
+ import { createChallengeStore } from "#auth/challenge-store.js";
18
+ import { getPasskeyConfig } from "#auth/passkey-config.js";
19
+ import { OptionsRepository } from "#db/repositories/options.js";
20
+
21
+ const MAX_PASSKEYS = 10;
22
+
23
+ export const POST: APIRoute = async ({ request, locals }) => {
24
+ const { emdash, user } = locals;
25
+
26
+ if (!emdash?.db) {
27
+ return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
28
+ }
29
+
30
+ // Require authentication
31
+ if (!user) {
32
+ return apiError("NOT_AUTHENTICATED", "Not authenticated", 401);
33
+ }
34
+
35
+ try {
36
+ const adapter = createKyselyAdapter(emdash.db);
37
+
38
+ // Check passkey limit
39
+ const count = await adapter.countCredentialsByUserId(user.id);
40
+ if (count >= MAX_PASSKEYS) {
41
+ return apiError("PASSKEY_LIMIT", `Maximum of ${MAX_PASSKEYS} passkeys allowed`, 400);
42
+ }
43
+
44
+ // Parse optional name from request
45
+ const body = await parseOptionalBody(request, passkeyRegisterOptionsBody, {});
46
+ if (isParseError(body)) return body;
47
+
48
+ // Get existing credentials for excludeCredentials
49
+ const existingCredentials = await adapter.getCredentialsByUserId(user.id);
50
+
51
+ // Get passkey config
52
+ const url = new URL(request.url);
53
+ const optionsRepo = new OptionsRepository(emdash.db);
54
+ const siteName = (await optionsRepo.get<string>("emdash:site_title")) ?? undefined;
55
+ const passkeyConfig = getPasskeyConfig(url, siteName);
56
+
57
+ // Generate registration options
58
+ const challengeStore = createChallengeStore(emdash.db);
59
+ const registrationOptions = await generateRegistrationOptions(
60
+ passkeyConfig,
61
+ { id: user.id, email: user.email, name: user.name },
62
+ existingCredentials,
63
+ challengeStore,
64
+ );
65
+
66
+ // Store the passkey name in the challenge metadata if provided
67
+ // We'll retrieve it during verification
68
+ if (body.name) {
69
+ // Store name with challenge for later retrieval
70
+ // The challenge store will need this when verifying
71
+ await optionsRepo.set(`emdash:passkey_pending:${user.id}`, {
72
+ name: body.name,
73
+ });
74
+ }
75
+
76
+ return apiSuccess({
77
+ options: registrationOptions,
78
+ });
79
+ } catch (error) {
80
+ return handleError(
81
+ error,
82
+ "Failed to generate registration options",
83
+ "PASSKEY_REGISTER_OPTIONS_ERROR",
84
+ );
85
+ }
86
+ };