emdash 0.25.1 → 0.27.0

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 (418) hide show
  1. package/dist/{adapters-DAAjOID4.d.mts → adapters-u037EnTR.d.mts} +1 -1
  2. package/dist/{adapters-DAAjOID4.d.mts.map → adapters-u037EnTR.d.mts.map} +1 -1
  3. package/dist/api/route-utils.d.mts +3 -3
  4. package/dist/api/route-utils.mjs +11 -11
  5. package/dist/api/schemas/index.d.mts +1 -1
  6. package/dist/{api-D2_sbavn.mjs → api-80kycR9o.mjs} +86 -30
  7. package/dist/api-80kycR9o.mjs.map +1 -0
  8. package/dist/{apply-Bk2mAuLn.mjs → apply-DSypk6Iu.mjs} +20 -18
  9. package/dist/apply-DSypk6Iu.mjs.map +1 -0
  10. package/dist/astro/index.d.mts +11 -10
  11. package/dist/astro/index.d.mts.map +1 -1
  12. package/dist/astro/index.mjs +12 -3
  13. package/dist/astro/index.mjs.map +1 -1
  14. package/dist/astro/middleware/auth.d.mts +10 -9
  15. package/dist/astro/middleware/auth.d.mts.map +1 -1
  16. package/dist/astro/middleware/auth.mjs +4 -3
  17. package/dist/astro/middleware/auth.mjs.map +1 -1
  18. package/dist/astro/middleware/redirect.mjs +4 -4
  19. package/dist/astro/middleware.d.mts +2 -9
  20. package/dist/astro/middleware.d.mts.map +1 -1
  21. package/dist/astro/middleware.mjs +86 -85
  22. package/dist/astro/middleware.mjs.map +1 -1
  23. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +3 -3
  24. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +3 -3
  25. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +2 -2
  26. package/dist/astro/routes/api/admin/api-tokens/index.mjs +3 -3
  27. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +3 -3
  28. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +4 -4
  29. package/dist/astro/routes/api/admin/byline-fields/index.mjs +4 -4
  30. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +4 -4
  31. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +10 -10
  32. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +10 -10
  33. package/dist/astro/routes/api/admin/bylines/index.mjs +10 -10
  34. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +8 -8
  35. package/dist/astro/routes/api/admin/comments/_id_.mjs +6 -6
  36. package/dist/astro/routes/api/admin/comments/bulk.mjs +7 -7
  37. package/dist/astro/routes/api/admin/comments/counts.mjs +6 -6
  38. package/dist/astro/routes/api/admin/comments/index.mjs +7 -7
  39. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +4 -4
  40. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
  41. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +3 -3
  42. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +3 -3
  43. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +29 -29
  44. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
  45. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +29 -29
  46. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
  47. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +29 -29
  48. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
  49. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +28 -28
  50. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +28 -28
  51. package/dist/astro/routes/api/admin/plugins/index.mjs +29 -29
  52. package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
  53. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
  54. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +28 -28
  55. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +28 -28
  56. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +28 -28
  57. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +28 -28
  58. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +29 -29
  59. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +28 -28
  60. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +29 -29
  61. package/dist/astro/routes/api/admin/plugins/updates.mjs +28 -28
  62. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +28 -28
  63. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
  64. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +28 -28
  65. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +2 -2
  66. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
  67. package/dist/astro/routes/api/admin/users/_id_/index.mjs +3 -3
  68. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +2 -2
  69. package/dist/astro/routes/api/admin/users/index.mjs +3 -3
  70. package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -3
  71. package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
  72. package/dist/astro/routes/api/auth/invite/complete.mjs +3 -3
  73. package/dist/astro/routes/api/auth/invite/index.mjs +3 -3
  74. package/dist/astro/routes/api/auth/invite/register-options.mjs +3 -3
  75. package/dist/astro/routes/api/auth/logout.mjs +2 -2
  76. package/dist/astro/routes/api/auth/magic-link/send.mjs +4 -4
  77. package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
  78. package/dist/astro/routes/api/auth/me.mjs +4 -4
  79. package/dist/astro/routes/api/auth/passkey/_id_.mjs +3 -3
  80. package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
  81. package/dist/astro/routes/api/auth/passkey/options.mjs +4 -4
  82. package/dist/astro/routes/api/auth/passkey/register/options.mjs +3 -3
  83. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +3 -3
  84. package/dist/astro/routes/api/auth/passkey/verify.mjs +3 -3
  85. package/dist/astro/routes/api/auth/signup/complete.mjs +3 -3
  86. package/dist/astro/routes/api/auth/signup/request.mjs +4 -4
  87. package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
  88. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +7 -7
  89. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +7 -7
  90. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
  91. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +3 -3
  92. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  93. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
  94. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +4 -4
  95. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +4 -4
  96. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  97. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
  98. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +4 -4
  99. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +9 -9
  100. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
  101. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +3 -3
  102. package/dist/astro/routes/api/content/_collection_/_id_.mjs +4 -4
  103. package/dist/astro/routes/api/content/_collection_/authors.mjs +3 -3
  104. package/dist/astro/routes/api/content/_collection_/index.mjs +4 -4
  105. package/dist/astro/routes/api/content/_collection_/trash.mjs +4 -4
  106. package/dist/astro/routes/api/dashboard.mjs +8 -8
  107. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  108. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  109. package/dist/astro/routes/api/import/probe.mjs +4 -4
  110. package/dist/astro/routes/api/import/wordpress/analyze.mjs +3 -3
  111. package/dist/astro/routes/api/import/wordpress/execute.d.mts +10 -9
  112. package/dist/astro/routes/api/import/wordpress/execute.d.mts.map +1 -1
  113. package/dist/astro/routes/api/import/wordpress/execute.mjs +9 -9
  114. package/dist/astro/routes/api/import/wordpress/media.d.mts +10 -9
  115. package/dist/astro/routes/api/import/wordpress/media.d.mts.map +1 -1
  116. package/dist/astro/routes/api/import/wordpress/media.mjs +4 -4
  117. package/dist/astro/routes/api/import/wordpress/prepare.mjs +6 -6
  118. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +5 -5
  119. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  120. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +4 -4
  121. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +1 -1
  122. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +7 -7
  123. package/dist/astro/routes/api/manifest.d.mts.map +1 -1
  124. package/dist/astro/routes/api/manifest.mjs +13 -4
  125. package/dist/astro/routes/api/manifest.mjs.map +1 -1
  126. package/dist/astro/routes/api/mcp.mjs +34 -34
  127. package/dist/astro/routes/api/media/_id_/confirm.mjs +4 -4
  128. package/dist/astro/routes/api/media/_id_.mjs +4 -4
  129. package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
  130. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
  131. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
  132. package/dist/astro/routes/api/media/providers/index.mjs +3 -3
  133. package/dist/astro/routes/api/media/upload-url.mjs +4 -4
  134. package/dist/astro/routes/api/media.mjs +5 -5
  135. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +6 -6
  136. package/dist/astro/routes/api/menus/_name_/items.mjs +6 -6
  137. package/dist/astro/routes/api/menus/_name_/reorder.mjs +6 -6
  138. package/dist/astro/routes/api/menus/_name_/translations.mjs +6 -6
  139. package/dist/astro/routes/api/menus/_name_.mjs +6 -6
  140. package/dist/astro/routes/api/menus/index.mjs +6 -6
  141. package/dist/astro/routes/api/oauth/device/authorize.mjs +3 -3
  142. package/dist/astro/routes/api/oauth/device/code.mjs +4 -4
  143. package/dist/astro/routes/api/oauth/device/token.mjs +4 -4
  144. package/dist/astro/routes/api/oauth/register.mjs +2 -2
  145. package/dist/astro/routes/api/oauth/token/refresh.mjs +3 -3
  146. package/dist/astro/routes/api/oauth/token/revoke.mjs +3 -3
  147. package/dist/astro/routes/api/oauth/token.mjs +2 -2
  148. package/dist/astro/routes/api/openapi.json.mjs +2 -2
  149. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +3 -3
  150. package/dist/astro/routes/api/redirects/404s/index.mjs +6 -6
  151. package/dist/astro/routes/api/redirects/404s/summary.mjs +6 -6
  152. package/dist/astro/routes/api/redirects/_id_.mjs +7 -7
  153. package/dist/astro/routes/api/redirects/index.mjs +7 -7
  154. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
  155. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  156. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +28 -28
  157. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +28 -28
  158. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +28 -28
  159. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +28 -28
  160. package/dist/astro/routes/api/schema/collections/index.mjs +28 -28
  161. package/dist/astro/routes/api/schema/index.mjs +6 -6
  162. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +28 -28
  163. package/dist/astro/routes/api/schema/orphans/index.mjs +28 -28
  164. package/dist/astro/routes/api/search/enable.mjs +7 -7
  165. package/dist/astro/routes/api/search/index.mjs +6 -6
  166. package/dist/astro/routes/api/search/rebuild.mjs +7 -7
  167. package/dist/astro/routes/api/search/stats.mjs +6 -6
  168. package/dist/astro/routes/api/search/suggest.mjs +6 -6
  169. package/dist/astro/routes/api/sections/_slug_.mjs +6 -6
  170. package/dist/astro/routes/api/sections/index.mjs +6 -6
  171. package/dist/astro/routes/api/settings/email.mjs +4 -4
  172. package/dist/astro/routes/api/settings.mjs +9 -9
  173. package/dist/astro/routes/api/setup/admin-verify.mjs +3 -3
  174. package/dist/astro/routes/api/setup/admin.mjs +3 -3
  175. package/dist/astro/routes/api/setup/dev-bypass.d.mts.map +1 -1
  176. package/dist/astro/routes/api/setup/dev-bypass.mjs +20 -18
  177. package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
  178. package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
  179. package/dist/astro/routes/api/setup/index.mjs +18 -18
  180. package/dist/astro/routes/api/setup/status.mjs +3 -3
  181. package/dist/astro/routes/api/snapshot.mjs +3 -3
  182. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +9 -9
  183. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +9 -9
  184. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +9 -9
  185. package/dist/astro/routes/api/taxonomies/index.mjs +9 -9
  186. package/dist/astro/routes/api/themes/preview.mjs +3 -3
  187. package/dist/astro/routes/api/typegen.mjs +5 -5
  188. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +4 -4
  189. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +6 -6
  190. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +6 -6
  191. package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -5
  192. package/dist/astro/routes/api/widget-areas/index.mjs +6 -6
  193. package/dist/astro/routes/api/widget-components.mjs +2 -2
  194. package/dist/astro/routes/robots.txt.mjs +5 -5
  195. package/dist/astro/routes/sitemap-_collection_.xml.mjs +10 -9
  196. package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
  197. package/dist/astro/routes/sitemap.xml.mjs +5 -5
  198. package/dist/astro/types.d.mts +12 -10
  199. package/dist/astro/types.d.mts.map +1 -1
  200. package/dist/auth/providers/github.d.mts +1 -1
  201. package/dist/auth/providers/google.d.mts +1 -1
  202. package/dist/{authorize-CuxZ0i2t.mjs → authorize-C3a_zGsM.mjs} +2 -2
  203. package/dist/{authorize-CuxZ0i2t.mjs.map → authorize-C3a_zGsM.mjs.map} +1 -1
  204. package/dist/{byline-C-YajfV7.mjs → byline-DEKSVPTy.mjs} +6 -6
  205. package/dist/{byline-C-YajfV7.mjs.map → byline-DEKSVPTy.mjs.map} +1 -1
  206. package/dist/{byline-fields-KD3JFRuH.d.mts → byline-fields-CtQ6blXd.d.mts} +32 -32
  207. package/dist/{byline-fields-KD3JFRuH.d.mts.map → byline-fields-CtQ6blXd.d.mts.map} +1 -1
  208. package/dist/{bylines-CPc_q2Vd.mjs → bylines-CiqgTHWF.mjs} +5 -5
  209. package/dist/{bylines-CPc_q2Vd.mjs.map → bylines-CiqgTHWF.mjs.map} +1 -1
  210. package/dist/{bylines-D4KADDAi.mjs → bylines-Ds4-otXl.mjs} +4 -4
  211. package/dist/{bylines-D4KADDAi.mjs.map → bylines-Ds4-otXl.mjs.map} +1 -1
  212. package/dist/{cache-C35VWD5D.mjs → cache-Ciu3e3AF.mjs} +2 -2
  213. package/dist/{cache-C35VWD5D.mjs.map → cache-Ciu3e3AF.mjs.map} +1 -1
  214. package/dist/{chunks-BHgcHh2C.mjs → chunks-CZFiauig.mjs} +2 -2
  215. package/dist/{chunks-BHgcHh2C.mjs.map → chunks-CZFiauig.mjs.map} +1 -1
  216. package/dist/cli/index.mjs +17 -17
  217. package/dist/cli/index.mjs.map +1 -1
  218. package/dist/client/cf-access.d.mts +1 -1
  219. package/dist/client/index.d.mts +1 -1
  220. package/dist/{comment-BDx50EX5.mjs → comment-CH1IrFFs.mjs} +3 -3
  221. package/dist/{comment-BDx50EX5.mjs.map → comment-CH1IrFFs.mjs.map} +1 -1
  222. package/dist/{comment-reaction-Gv_pgfI3.mjs → comment-reaction-kruNY-kP.mjs} +2 -2
  223. package/dist/{comment-reaction-Gv_pgfI3.mjs.map → comment-reaction-kruNY-kP.mjs.map} +1 -1
  224. package/dist/{comments-CXHuQ6jI.mjs → comments-BUJyXbgq.mjs} +3 -3
  225. package/dist/{comments-CXHuQ6jI.mjs.map → comments-BUJyXbgq.mjs.map} +1 -1
  226. package/dist/{content-B43-YrIq.mjs → content-RmfHoWqK.mjs} +4 -4
  227. package/dist/{content-B43-YrIq.mjs.map → content-RmfHoWqK.mjs.map} +1 -1
  228. package/dist/{context-BW3N7wyt.mjs → context-CzUU_WzL.mjs} +7 -7
  229. package/dist/{context-BW3N7wyt.mjs.map → context-CzUU_WzL.mjs.map} +1 -1
  230. package/dist/{dashboard-FIe0S512.mjs → dashboard-CVyGhift.mjs} +4 -4
  231. package/dist/{dashboard-FIe0S512.mjs.map → dashboard-CVyGhift.mjs.map} +1 -1
  232. package/dist/db/index.d.mts +3 -3
  233. package/dist/db/index.mjs +1 -1
  234. package/dist/db/libsql.d.mts +1 -1
  235. package/dist/db/postgres.d.mts +1 -1
  236. package/dist/db/sqlite.d.mts +1 -1
  237. package/dist/{error-DXnK2wF4.mjs → error-BHkLZk-w.mjs} +2 -2
  238. package/dist/{error-DXnK2wF4.mjs.map → error-BHkLZk-w.mjs.map} +1 -1
  239. package/dist/{field-defs-cache-CYYx8xlG.mjs → field-defs-cache-BA-8ZPiT.mjs} +2 -2
  240. package/dist/{field-defs-cache-CYYx8xlG.mjs.map → field-defs-cache-BA-8ZPiT.mjs.map} +1 -1
  241. package/dist/{fts-manager-DW_NR-Fs.mjs → fts-manager-Db9jGavf.mjs} +2 -2
  242. package/dist/{fts-manager-DW_NR-Fs.mjs.map → fts-manager-Db9jGavf.mjs.map} +1 -1
  243. package/dist/{index-Cz6_JBL3.d.mts → index-CDZZjrLT.d.mts} +2430 -2363
  244. package/dist/index-CDZZjrLT.d.mts.map +1 -0
  245. package/dist/{index-qbL02uhs.d.mts → index-DGIjmUXQ.d.mts} +3 -3
  246. package/dist/{index-qbL02uhs.d.mts.map → index-DGIjmUXQ.d.mts.map} +1 -1
  247. package/dist/index.d.mts +16 -15
  248. package/dist/index.mjs +38 -38
  249. package/dist/{load-DMFi4n1p.mjs → load-DpHICVPh.mjs} +2 -2
  250. package/dist/{load-DMFi4n1p.mjs.map → load-DpHICVPh.mjs.map} +1 -1
  251. package/dist/{loader-DrpofRon.mjs → loader-4K37qA-y.mjs} +81 -18
  252. package/dist/loader-4K37qA-y.mjs.map +1 -0
  253. package/dist/{manifest-schema-BOfPXse0.mjs → manifest-schema-kPGX7VS-.mjs} +3 -1
  254. package/dist/manifest-schema-kPGX7VS-.mjs.map +1 -0
  255. package/dist/media/local-runtime.d.mts +11 -10
  256. package/dist/media/local-runtime.d.mts.map +1 -1
  257. package/dist/media/local-runtime.mjs +5 -5
  258. package/dist/{media-BjKxjzk8.mjs → media-C0nRRfs1.mjs} +2 -2
  259. package/dist/{media-BjKxjzk8.mjs.map → media-C0nRRfs1.mjs.map} +1 -1
  260. package/dist/{menus-CxqJ-z-N.mjs → menus-DzRyC1ID.mjs} +50 -68
  261. package/dist/menus-DzRyC1ID.mjs.map +1 -0
  262. package/dist/{menus-8O6xNcYW.mjs → menus-SL2K0xq2.mjs} +3 -3
  263. package/dist/{menus-8O6xNcYW.mjs.map → menus-SL2K0xq2.mjs.map} +1 -1
  264. package/dist/object-cache/memory.d.mts +1 -1
  265. package/dist/{object-cache-SEb2IM4Z.mjs → object-cache-CHbHv83-.mjs} +8 -4
  266. package/dist/object-cache-CHbHv83-.mjs.map +1 -0
  267. package/dist/{options-BvClpP5Q.d.mts → options-41nCWqi9.d.mts} +2 -2
  268. package/dist/{options-BvClpP5Q.d.mts.map → options-41nCWqi9.d.mts.map} +1 -1
  269. package/dist/page/index.d.mts +6 -3
  270. package/dist/page/index.d.mts.map +1 -1
  271. package/dist/page/index.mjs +3 -2
  272. package/dist/page/index.mjs.map +1 -1
  273. package/dist/{parse-DqMWHQ8x.mjs → parse-CqyZj3Hp.mjs} +2 -2
  274. package/dist/{parse-DqMWHQ8x.mjs.map → parse-CqyZj3Hp.mjs.map} +1 -1
  275. package/dist/plugin-types.d.mts +4 -2
  276. package/dist/plugin-types.d.mts.map +1 -1
  277. package/dist/plugin-utils.d.mts +10 -9
  278. package/dist/plugin-utils.d.mts.map +1 -1
  279. package/dist/plugins/adapt-sandbox-entry.d.mts +10 -9
  280. package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -1
  281. package/dist/plugins/adapt-sandbox-entry.mjs +1 -1
  282. package/dist/{query-34pfm02Q.mjs → query-B0M-9LYR.mjs} +14 -14
  283. package/dist/{query-34pfm02Q.mjs.map → query-B0M-9LYR.mjs.map} +1 -1
  284. package/dist/{rate-limit-CBQK4ygJ.mjs → rate-limit-BwoUdIpC.mjs} +2 -2
  285. package/dist/{rate-limit-CBQK4ygJ.mjs.map → rate-limit-BwoUdIpC.mjs.map} +1 -1
  286. package/dist/{redirect-C1KVGYWe.mjs → redirect-lMOzG04S.mjs} +2 -2
  287. package/dist/{redirect-C1KVGYWe.mjs.map → redirect-lMOzG04S.mjs.map} +1 -1
  288. package/dist/{redirects-DgnypVir.mjs → redirects-9218MmYl.mjs} +3 -3
  289. package/dist/{redirects-DgnypVir.mjs.map → redirects-9218MmYl.mjs.map} +1 -1
  290. package/dist/{registry-3aOAS4My.mjs → registry-DP-iEtdR.mjs} +4 -4
  291. package/dist/{registry-3aOAS4My.mjs.map → registry-DP-iEtdR.mjs.map} +1 -1
  292. package/dist/{runner-CEtq-6Sm.d.mts → runner-BbR3DfrL.d.mts} +2 -2
  293. package/dist/{runner-CEtq-6Sm.d.mts.map → runner-BbR3DfrL.d.mts.map} +1 -1
  294. package/dist/{runner-BsquExlK.mjs → runner-CFngB4WZ.mjs} +296 -191
  295. package/dist/runner-CFngB4WZ.mjs.map +1 -0
  296. package/dist/runtime.d.mts +10 -9
  297. package/dist/runtime.d.mts.map +1 -1
  298. package/dist/runtime.mjs +2 -2
  299. package/dist/scheduled-publish-BMljtY5G.d.mts +11 -0
  300. package/dist/scheduled-publish-BMljtY5G.d.mts.map +1 -0
  301. package/dist/{schema-BGwx8AQY.mjs → schema-BfJ7bIXH.mjs} +5 -5
  302. package/dist/{schema-BGwx8AQY.mjs.map → schema-BfJ7bIXH.mjs.map} +1 -1
  303. package/dist/{search-Gl747zMP.mjs → search-C7t3PH1J.mjs} +6 -4
  304. package/dist/search-C7t3PH1J.mjs.map +1 -0
  305. package/dist/{sections-C-umuuLj.mjs → sections-Dm0CVX23.mjs} +3 -3
  306. package/dist/{sections-C-umuuLj.mjs.map → sections-Dm0CVX23.mjs.map} +1 -1
  307. package/dist/seed/index.d.mts +2 -2
  308. package/dist/seed/index.mjs +15 -15
  309. package/dist/{seo-BNzkiK_W.mjs → seo-Dd3tbY_k.mjs} +3 -3
  310. package/dist/{seo-BNzkiK_W.mjs.map → seo-Dd3tbY_k.mjs.map} +1 -1
  311. package/dist/{service-CQHK_10l.mjs → service-CTrqp_Jy.mjs} +2 -2
  312. package/dist/{service-CQHK_10l.mjs.map → service-CTrqp_Jy.mjs.map} +1 -1
  313. package/dist/{settings-C4tXRYiG.mjs → settings-BX8cWBus.mjs} +4 -4
  314. package/dist/{settings-C4tXRYiG.mjs.map → settings-BX8cWBus.mjs.map} +1 -1
  315. package/dist/{settings-fPSR6dJa.mjs → settings-CZ7ZP9cW.mjs} +3 -3
  316. package/dist/{settings-fPSR6dJa.mjs.map → settings-CZ7ZP9cW.mjs.map} +1 -1
  317. package/dist/storage/local.d.mts +1 -1
  318. package/dist/storage/s3.d.mts +1 -1
  319. package/dist/{taxonomies-DbAtL6m4.mjs → taxonomies-DChKcVxf.mjs} +5 -4
  320. package/dist/taxonomies-DChKcVxf.mjs.map +1 -0
  321. package/dist/{taxonomies-n8w72lLQ.mjs → taxonomies-ckPjz-6d.mjs} +75 -20
  322. package/dist/taxonomies-ckPjz-6d.mjs.map +1 -0
  323. package/dist/{taxonomy-BBUlGug5.mjs → taxonomy-Cru-he6s.mjs} +4 -4
  324. package/dist/{taxonomy-BBUlGug5.mjs.map → taxonomy-Cru-he6s.mjs.map} +1 -1
  325. package/dist/{transport-BZ-03aFT.d.mts → transport-Blrl2k_o.d.mts} +1 -1
  326. package/dist/{transport-BZ-03aFT.d.mts.map → transport-Blrl2k_o.d.mts.map} +1 -1
  327. package/dist/{types-h2Cwbzi2.d.mts → types-BYnDBVwe.d.mts} +24 -5
  328. package/dist/{types-h2Cwbzi2.d.mts.map → types-BYnDBVwe.d.mts.map} +1 -1
  329. package/dist/{types-BG8_XTLj.d.mts → types-BkZ8DUEI.d.mts} +1 -1
  330. package/dist/{types-BG8_XTLj.d.mts.map → types-BkZ8DUEI.d.mts.map} +1 -1
  331. package/dist/{types-DrYMm9s5.d.mts → types-ByChcBgE.d.mts} +1 -1
  332. package/dist/{types-DrYMm9s5.d.mts.map → types-ByChcBgE.d.mts.map} +1 -1
  333. package/dist/{types-BWqGVhFn.d.mts → types-CvuKO5Pn.d.mts} +1 -1
  334. package/dist/{types-BWqGVhFn.d.mts.map → types-CvuKO5Pn.d.mts.map} +1 -1
  335. package/dist/{types-CmI1OyQg.mjs → types-DU4TSFS5.mjs} +2 -2
  336. package/dist/{types-CmI1OyQg.mjs.map → types-DU4TSFS5.mjs.map} +1 -1
  337. package/dist/{types-CUsXGRVj.d.mts → types-Dbqff978.d.mts} +1 -1
  338. package/dist/{types-CUsXGRVj.d.mts.map → types-Dbqff978.d.mts.map} +1 -1
  339. package/dist/{types-fS7-VTdR.d.mts → types-Y09-wtyU.d.mts} +1 -1
  340. package/dist/{types-fS7-VTdR.d.mts.map → types-Y09-wtyU.d.mts.map} +1 -1
  341. package/dist/{types-B-gIxFCE.d.mts → types-xIfVRNLp.d.mts} +38 -1
  342. package/dist/types-xIfVRNLp.d.mts.map +1 -0
  343. package/dist/{user-JAuc8VUf.mjs → user-DgGbKL9s.mjs} +3 -3
  344. package/dist/{user-JAuc8VUf.mjs.map → user-DgGbKL9s.mjs.map} +1 -1
  345. package/dist/{validate-Va_An3qb.d.mts → validate-9ECmtEpJ.d.mts} +10 -5
  346. package/dist/{validate-Va_An3qb.d.mts.map → validate-9ECmtEpJ.d.mts.map} +1 -1
  347. package/dist/{validate-BzXHWnkV.mjs → validate-CRkcYZAR.mjs} +2 -2
  348. package/dist/{validate-BzXHWnkV.mjs.map → validate-CRkcYZAR.mjs.map} +1 -1
  349. package/dist/{validation-B6DKPTtU.mjs → validation-sR26SOwD.mjs} +5 -5
  350. package/dist/{validation-B6DKPTtU.mjs.map → validation-sR26SOwD.mjs.map} +1 -1
  351. package/dist/version-CrqhKGWn.mjs +7 -0
  352. package/dist/{version-DHdCA24u.mjs.map → version-CrqhKGWn.mjs.map} +1 -1
  353. package/dist/{widgets-C2RbFN0G.mjs → widgets-DRgtn7Cl.mjs} +2 -2
  354. package/dist/{widgets-C2RbFN0G.mjs.map → widgets-DRgtn7Cl.mjs.map} +1 -1
  355. package/dist/{zod-generator-xEHODMJf.mjs → zod-generator-C5JaV--i.mjs} +6 -3
  356. package/dist/zod-generator-C5JaV--i.mjs.map +1 -0
  357. package/package.json +8 -8
  358. package/src/api/handlers/content.ts +59 -16
  359. package/src/api/handlers/plugins.ts +77 -10
  360. package/src/api/handlers/taxonomies.ts +5 -1
  361. package/src/astro/integration/virtual-modules.ts +15 -0
  362. package/src/astro/integration/vite-config.ts +19 -1
  363. package/src/astro/middleware/auth.ts +1 -0
  364. package/src/astro/middleware.ts +1 -0
  365. package/src/astro/routes/api/admin/plugins/[id]/disable.ts +6 -1
  366. package/src/astro/routes/api/admin/plugins/[id]/enable.ts +6 -1
  367. package/src/astro/routes/api/admin/plugins/[id]/index.ts +1 -0
  368. package/src/astro/routes/api/admin/plugins/index.ts +1 -0
  369. package/src/astro/routes/api/manifest.ts +20 -1
  370. package/src/astro/routes/api/setup/dev-bypass.ts +7 -2
  371. package/src/astro/routes/sitemap-[collection].xml.ts +5 -4
  372. package/src/astro/types.ts +4 -0
  373. package/src/cli/commands/seed.ts +1 -1
  374. package/src/components/EmDashHead.astro +23 -3
  375. package/src/components/JsonLdScript.astro +9 -0
  376. package/src/components/LiveSearch.astro +40 -4
  377. package/src/components/live-search-routing.ts +30 -0
  378. package/src/database/migrations/036_i18n_menus_and_taxonomies.ts +39 -18
  379. package/src/database/migrations/046_media_usage_index.ts +115 -0
  380. package/src/database/migrations/047_restore_taxonomy_parent_index.ts +29 -0
  381. package/src/database/migrations/048_restore_content_taxonomies_term_index.ts +32 -0
  382. package/src/database/migrations/runner.ts +6 -0
  383. package/src/database/repositories/media-usage.ts +444 -0
  384. package/src/database/types.ts +39 -0
  385. package/src/emdash-runtime.ts +61 -52
  386. package/src/index.ts +3 -0
  387. package/src/loader.ts +110 -31
  388. package/src/media/usage/extractor.ts +302 -0
  389. package/src/media/usage/types.ts +46 -0
  390. package/src/object-cache/index.ts +8 -2
  391. package/src/page/metadata.ts +23 -3
  392. package/src/plugin-types.ts +10 -0
  393. package/src/plugins/define-plugin.ts +14 -5
  394. package/src/plugins/hooks.ts +53 -65
  395. package/src/plugins/index.ts +5 -0
  396. package/src/plugins/manager.ts +22 -0
  397. package/src/plugins/manifest-schema.ts +2 -0
  398. package/src/plugins/types.ts +35 -3
  399. package/src/schema/zod-generator.ts +9 -1
  400. package/src/search/query.ts +3 -0
  401. package/src/search/types.ts +2 -0
  402. package/src/seed/apply.ts +8 -2
  403. package/src/seed/types.ts +6 -1
  404. package/src/taxonomies/index.ts +121 -18
  405. package/dist/api-D2_sbavn.mjs.map +0 -1
  406. package/dist/apply-Bk2mAuLn.mjs.map +0 -1
  407. package/dist/index-Cz6_JBL3.d.mts.map +0 -1
  408. package/dist/loader-DrpofRon.mjs.map +0 -1
  409. package/dist/manifest-schema-BOfPXse0.mjs.map +0 -1
  410. package/dist/menus-CxqJ-z-N.mjs.map +0 -1
  411. package/dist/object-cache-SEb2IM4Z.mjs.map +0 -1
  412. package/dist/runner-BsquExlK.mjs.map +0 -1
  413. package/dist/search-Gl747zMP.mjs.map +0 -1
  414. package/dist/taxonomies-DbAtL6m4.mjs.map +0 -1
  415. package/dist/taxonomies-n8w72lLQ.mjs.map +0 -1
  416. package/dist/types-B-gIxFCE.d.mts.map +0 -1
  417. package/dist/version-DHdCA24u.mjs +0 -7
  418. package/dist/zod-generator-xEHODMJf.mjs.map +0 -1
@@ -1,12 +1,13 @@
1
- import "../../options-BvClpP5Q.mjs";
2
- import "../../types-B-gIxFCE.mjs";
3
- import "../../types-h2Cwbzi2.mjs";
4
- import "../../byline-fields-KD3JFRuH.mjs";
5
- import "../../index-Cz6_JBL3.mjs";
6
- import "../../runner-CEtq-6Sm.mjs";
7
- import "../../index-qbL02uhs.mjs";
8
- import "../../types-CUsXGRVj.mjs";
9
- import "../../validate-Va_An3qb.mjs";
1
+ import "../../options-41nCWqi9.mjs";
2
+ import "../../types-xIfVRNLp.mjs";
3
+ import "../../types-BYnDBVwe.mjs";
4
+ import "../../byline-fields-CtQ6blXd.mjs";
5
+ import "../../index-CDZZjrLT.mjs";
6
+ import "../../runner-BbR3DfrL.mjs";
7
+ import "../../index-DGIjmUXQ.mjs";
8
+ import "../../scheduled-publish-BMljtY5G.mjs";
9
+ import "../../types-Dbqff978.mjs";
10
+ import "../../validate-9ECmtEpJ.mjs";
10
11
  import { EmDashHandlers } from "../types.mjs";
11
12
  import { User } from "@emdash-cms/auth";
12
13
  import * as astro from "astro";
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.mts","names":[],"sources":["../../../src/astro/middleware/auth.ts"],"mappings":";;;;;;;;;;;;;;QAsCQ,MAAA;EAAA,UACG,GAAA;IAAA,UACC,MAAA;MACT,IAAA,GAAO,IAAA;MAGgB;MADvB,WAAA;MACA,MAAA,GAAS,cAAA;IAAA;IAAA,UAEA,WAAA;MACT,IAAA;QAAQ,EAAA;MAAA;MACR,cAAA;IAAA;EAAA;AAAA;AAAA,cAiIU,SAAA,EAmJX,KAAA,CAnJoB,iBAAA"}
1
+ {"version":3,"file":"auth.d.mts","names":[],"sources":["../../../src/astro/middleware/auth.ts"],"mappings":";;;;;;;;;;;;;;;QAsCQ,MAAA;EAAA,UACG,GAAA;IAAA,UACC,MAAA;MACT,IAAA,GAAO,IAAA;MAAA;MAEP,WAAA;MACA,MAAA,GAAS,cAAA;IAAA;IAAA,UAEA,WAAA;MACT,IAAA;QAAQ,EAAA;MAAA;MACR,cAAA;IAAA;EAAA;AAAA;AAAA,cAkIU,SAAA,EAmJX,KAAA,CAnJoB,iBAAA"}
@@ -1,7 +1,7 @@
1
1
  import "../../after-B1IIdH3Y.mjs";
2
2
  import "../../base64-CmWvODNW.mjs";
3
- import "../../types-CmI1OyQg.mjs";
4
- import { t as apiError } from "../../error-DXnK2wF4.mjs";
3
+ import "../../types-DU4TSFS5.mjs";
4
+ import { t as apiError } from "../../error-BHkLZk-w.mjs";
5
5
  import { n as getPublicOrigin } from "../../public-url-CTVqgMmg.mjs";
6
6
  import { t as getAuthMode } from "../../mode-BB0F8xTC.mjs";
7
7
  import { t as resolveSessionUser } from "../../session-user-B8aLtKAH.mjs";
@@ -148,7 +148,8 @@ const PUBLIC_API_EXACT = new Set([
148
148
  "/_emdash/api/auth/mode",
149
149
  "/_emdash/api/oauth/token",
150
150
  "/_emdash/api/snapshot",
151
- "/_emdash/api/search"
151
+ "/_emdash/api/search",
152
+ "/_emdash/api/search/suggest"
152
153
  ]);
153
154
  const { exact: _providerExactRoutes, prefixes: _providerPrefixRoutes } = (() => {
154
155
  const exact = /* @__PURE__ */ new Set();
@@ -1 +1 @@
1
- {"version":3,"file":"auth.mjs","names":["virtualAuthenticate"],"sources":["../../../src/api/csrf.ts","../../../src/astro/middleware/csp.ts","../../../src/astro/middleware/auth.ts"],"sourcesContent":["/**\n * CSRF protection utilities.\n *\n * Two mechanisms:\n * 1. Custom header check (X-EmDash-Request: 1) — used for authenticated API routes.\n * Browsers block cross-origin custom headers, so presence proves same-origin.\n * 2. Origin check — used for public API routes that skip auth. Compares the Origin\n * header against the request origin. Same approach as Astro's `checkOrigin`.\n */\n\nimport { apiError } from \"./error.js\";\n\n/**\n * Origin-based CSRF check for public API routes that skip auth.\n *\n * State-changing requests (POST/PUT/DELETE) to public endpoints must either:\n * 1. Include the X-EmDash-Request: 1 header (custom header blocked cross-origin), OR\n * 2. Have an Origin header matching the request origin (or the configured public origin)\n *\n * This prevents cross-origin form submissions (which can't set custom headers)\n * and cross-origin fetch (blocked by CORS unless allowed). Same-origin requests\n * always include a matching Origin header.\n *\n * Returns a 403 Response if the check fails, or null if allowed.\n *\n * @param request The incoming request\n * @param url The request URL (internal origin)\n * @param publicOrigin The public-facing origin from config.siteUrl. Must be\n * `undefined` when absent — never `null` or `\"\"` (security invariant H-1a).\n */\nexport function checkPublicCsrf(\n\trequest: Request,\n\turl: URL,\n\tpublicOrigin?: string,\n): Response | null {\n\t// Custom header present — browser blocks cross-origin custom headers\n\tconst csrfHeader = request.headers.get(\"X-EmDash-Request\");\n\tif (csrfHeader === \"1\") return null;\n\n\t// Check Origin header — present on all POST/PUT/DELETE from browsers\n\tconst origin = request.headers.get(\"Origin\");\n\tif (origin) {\n\t\ttry {\n\t\t\tconst originUrl = new URL(origin);\n\t\t\t// Accept if Origin matches either the internal or public origin\n\t\t\tif (originUrl.origin === url.origin) return null;\n\t\t\tif (publicOrigin && originUrl.origin === publicOrigin) return null;\n\t\t} catch {\n\t\t\t// Malformed Origin — fall through to reject\n\t\t}\n\n\t\treturn apiError(\"CSRF_REJECTED\", \"Cross-origin request blocked\", 403);\n\t}\n\n\t// No Origin header — non-browser client (curl, server-to-server).\n\t// Allow these through since CSRF is a browser-specific attack vector.\n\t// Server-to-server requests don't carry ambient credentials (cookies).\n\treturn null;\n}\n","/**\n * Strict Content-Security-Policy for /_emdash routes (admin + API).\n *\n * Applied via middleware header rather than Astro's built-in CSP because\n * Astro's auto-hashing defeats 'unsafe-inline' (CSP3 ignores 'unsafe-inline'\n * when hashes are present), which would break user-facing pages.\n *\n * img-src allows any HTTPS origin because the admin renders user content that\n * may reference external images (migrations, external hosting, embeds).\n * Plugin security does not rely on img-src -- plugins run in V8 isolates with\n * no DOM access. connect-src stays at 'self' unless the experimental registry\n * is configured, in which case the configured aggregator origin is allowed.\n */\nimport type { RegistryConfigInput } from \"../../registry/types.js\";\n\nfunction getRegistryAggregatorOrigin(\n\tregistry: RegistryConfigInput | undefined,\n): string | undefined {\n\tconst aggregatorUrl = typeof registry === \"string\" ? registry : registry?.aggregatorUrl;\n\tif (!aggregatorUrl) return undefined;\n\n\ttry {\n\t\tconst url = new URL(aggregatorUrl);\n\t\tif (url.protocol !== \"http:\" && url.protocol !== \"https:\") return undefined;\n\t\treturn url.origin;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport function buildEmDashCsp(registry?: RegistryConfigInput): string {\n\tconst connectSrc = [\"connect-src 'self'\"];\n\tconst registryAggregatorOrigin = getRegistryAggregatorOrigin(registry);\n\tif (registryAggregatorOrigin) connectSrc.push(registryAggregatorOrigin);\n\n\treturn [\n\t\t\"default-src 'self'\",\n\t\t\"script-src 'self' 'unsafe-inline'\",\n\t\t\"style-src 'self' 'unsafe-inline'\",\n\t\tconnectSrc.join(\" \"),\n\t\t\"form-action 'self'\",\n\t\t\"frame-ancestors 'none'\",\n\t\t\"img-src 'self' https: data: blob:\",\n\t\t\"object-src 'none'\",\n\t\t\"base-uri 'self'\",\n\t].join(\"; \");\n}\n","/**\n * Auth middleware for admin routes\n *\n * Checks if the user is authenticated and has appropriate permissions.\n * Supports two auth modes:\n * - Passkey (default): Session-based auth with passkey login\n * - External providers: JWT-based auth (Cloudflare Access, etc.)\n *\n * This middleware runs AFTER the setup middleware - so if we get here,\n * we know setup is complete and users exist.\n */\n\nimport type { User, RoleLevel } from \"@emdash-cms/auth\";\nimport { createKyselyAdapter } from \"@emdash-cms/auth/adapters/kysely\";\nimport { defineMiddleware } from \"astro:middleware\";\nimport { ulid } from \"ulidx\";\n// Import auth provider via virtual module (statically bundled)\n// This avoids dynamic import issues in Cloudflare Workers\nimport { authenticate as virtualAuthenticate } from \"virtual:emdash/auth\";\n// @ts-ignore - virtual module\nimport virtualConfig from \"virtual:emdash/config\";\n\nimport { checkPublicCsrf } from \"../../api/csrf.js\";\nimport { apiError } from \"../../api/error.js\";\nimport { getPublicOrigin } from \"../../api/public-url.js\";\n\n/** Cache headers for middleware error responses (matches API_CACHE_HEADERS in api/error.ts) */\nconst MW_CACHE_HEADERS = {\n\t\"Cache-Control\": \"private, no-store\",\n} as const;\nimport { resolveApiToken, resolveOAuthToken } from \"../../api/handlers/api-tokens.js\";\nimport { hasScope } from \"../../auth/api-tokens.js\";\nimport { getAuthMode, type ExternalAuthMode } from \"../../auth/mode.js\";\nimport type { ExternalAuthConfig } from \"../../auth/types.js\";\nimport { resolveSessionUser } from \"../session-user.js\";\nimport type { EmDashHandlers } from \"../types.js\";\nimport { buildEmDashCsp } from \"./csp.js\";\n\ndeclare global {\n\tnamespace App {\n\t\tinterface Locals {\n\t\t\tuser?: User;\n\t\t\t/** Token scopes when authenticated via API token or OAuth token. Undefined for session auth. */\n\t\t\ttokenScopes?: string[];\n\t\t\temdash?: EmDashHandlers;\n\t\t}\n\t\tinterface SessionData {\n\t\t\tuser: { id: string };\n\t\t\thasSeenWelcome: boolean;\n\t\t}\n\t}\n}\n\n// Role level constants (matching @emdash-cms/auth)\nconst ROLE_ADMIN = 50;\nconst MCP_ENDPOINT_PATH = \"/_emdash/api/mcp\";\n\nfunction isUnsafeMethod(method: string): boolean {\n\treturn method !== \"GET\" && method !== \"HEAD\" && method !== \"OPTIONS\";\n}\n\nfunction csrfRejectedResponse(): Response {\n\treturn new Response(\n\t\tJSON.stringify({ error: { code: \"CSRF_REJECTED\", message: \"Missing required header\" } }),\n\t\t{\n\t\t\tstatus: 403,\n\t\t\theaders: { \"Content-Type\": \"application/json\", ...MW_CACHE_HEADERS },\n\t\t},\n\t);\n}\n\nfunction mcpUnauthorizedResponse(\n\turl: URL,\n\tconfig?: Parameters<typeof getPublicOrigin>[1],\n): Response {\n\tconst origin = getPublicOrigin(url, config);\n\treturn Response.json(\n\t\t{ error: { code: \"NOT_AUTHENTICATED\", message: \"Not authenticated\" } },\n\t\t{\n\t\t\tstatus: 401,\n\t\t\theaders: {\n\t\t\t\t\"WWW-Authenticate\": `Bearer resource_metadata=\"${origin}/.well-known/oauth-protected-resource\"`,\n\t\t\t\t...MW_CACHE_HEADERS,\n\t\t\t},\n\t\t},\n\t);\n}\n\n/**\n * API routes that skip auth — each handles its own access control.\n *\n * Prefix entries match any path starting with that prefix.\n * Exact entries (no trailing slash or wildcard) match that path only.\n */\nconst PUBLIC_API_PREFIXES = [\n\t\"/_emdash/api/setup\",\n\t\"/_emdash/api/auth/login\",\n\t\"/_emdash/api/auth/register\",\n\t\"/_emdash/api/auth/dev-bypass\",\n\t\"/_emdash/api/auth/signup/\",\n\t\"/_emdash/api/auth/magic-link/\",\n\t\"/_emdash/api/auth/invite/\",\n\t\"/_emdash/api/auth/oauth/\",\n\t\"/_emdash/api/oauth/device/token\",\n\t\"/_emdash/api/oauth/device/code\",\n\t\"/_emdash/api/oauth/token\",\n\t\"/_emdash/api/oauth/register\",\n\t\"/_emdash/api/comments/\",\n\t\"/_emdash/api/media/file/\",\n\t\"/_emdash/.well-known/\",\n];\n\nconst PUBLIC_API_EXACT = new Set([\n\t\"/_emdash/api/auth/passkey/options\",\n\t\"/_emdash/api/auth/passkey/verify\",\n\t\"/_emdash/api/auth/mode\",\n\t\"/_emdash/api/oauth/token\",\n\t\"/_emdash/api/snapshot\",\n\t// Public site search — read-only. The query layer hardcodes status='published'\n\t// so unauthenticated callers only see published content. Admin endpoints\n\t// (/enable, /rebuild, /stats) remain private because they're not in this set.\n\t\"/_emdash/api/search\",\n]);\n\n// Build merged public routes at module load from auth provider descriptors.\n// Routes ending with \"/\" are treated as prefixes; all others are exact matches.\nconst { exact: _providerExactRoutes, prefixes: _providerPrefixRoutes } = (() => {\n\tconst exact = new Set<string>();\n\tconst prefixes: string[] = [];\n\tif (!virtualConfig?.authProviders) return { exact, prefixes };\n\tfor (const route of virtualConfig.authProviders.flatMap((p) => p.publicRoutes ?? [])) {\n\t\tif (route.endsWith(\"/\")) {\n\t\t\tprefixes.push(route);\n\t\t} else {\n\t\t\texact.add(route);\n\t\t}\n\t}\n\treturn { exact, prefixes };\n})();\n\n/**\n * OAuth protocol endpoints that are CSRF-exempt by design.\n *\n * These are RFC-defined endpoints (RFC 6749 §3.2, RFC 7591 §3, RFC 8628 §3.1/§3.4)\n * specified to be called cross-origin by external clients (MCP clients, CLIs,\n * native apps). They authenticate each request on its own merits:\n *\n * - /oauth/token: requires PKCE code_verifier, device_code, or refresh_token\n * - /oauth/register: RFC 7591 dynamic client registration — anonymous by design\n * - /oauth/device/code: RFC 8628 device flow initiation — anonymous by design\n * - /oauth/device/token: requires device_code the client already holds\n *\n * None of these rely on ambient cookie credentials, so browser-based CSRF\n * attacks have nothing to exploit. The endpoints themselves advertise\n * `Access-Control-Allow-Origin: *`. Note: /oauth/device/authorize (the user\n * consent step) is NOT in this list — it is session-authenticated.\n */\nconst CSRF_EXEMPT_PUBLIC_ROUTES = new Set([\n\t\"/_emdash/api/oauth/token\",\n\t\"/_emdash/api/oauth/register\",\n\t\"/_emdash/api/oauth/device/code\",\n\t\"/_emdash/api/oauth/device/token\",\n]);\n\nfunction isPublicEmDashRoute(pathname: string): boolean {\n\tif (PUBLIC_API_EXACT.has(pathname)) return true;\n\tif (PUBLIC_API_PREFIXES.some((p) => pathname.startsWith(p))) return true;\n\tif (_providerExactRoutes.has(pathname)) return true;\n\tif (_providerPrefixRoutes.some((p) => pathname.startsWith(p))) return true;\n\tif (import.meta.env.DEV && pathname === \"/_emdash/api/typegen\") return true;\n\treturn false;\n}\n\nfunction isCsrfExemptPublicRoute(pathname: string): boolean {\n\treturn CSRF_EXEMPT_PUBLIC_ROUTES.has(pathname);\n}\n\nexport const onRequest = defineMiddleware(async (context, next) => {\n\tconst { url } = context;\n\n\t// Only check auth on admin routes and API routes\n\tconst isAdminRoute = url.pathname.startsWith(\"/_emdash/admin\");\n\tconst isSetupRoute = url.pathname.startsWith(\"/_emdash/admin/setup\");\n\tconst isApiRoute = url.pathname.startsWith(\"/_emdash/api\");\n\tconst isPublicApiRoute = isPublicEmDashRoute(url.pathname);\n\n\tconst isPublicRoute = !isAdminRoute && !isApiRoute;\n\n\t// Public API routes skip auth but still need CSRF protection on state-changing methods.\n\t// We check Origin header against the request host (same approach as Astro's checkOrigin).\n\t// This prevents cross-origin form submissions and fetch requests from malicious sites.\n\tif (isPublicApiRoute) {\n\t\tconst method = context.request.method.toUpperCase();\n\t\tif (\n\t\t\tisUnsafeMethod(method) &&\n\t\t\t!isCsrfExemptPublicRoute(url.pathname) // OAuth protocol endpoints — cross-origin by design\n\t\t) {\n\t\t\tconst publicOrigin = getPublicOrigin(url, context.locals.emdash?.config);\n\t\t\tconst csrfError = checkPublicCsrf(context.request, url, publicOrigin);\n\t\t\tif (csrfError) return csrfError;\n\t\t}\n\t\treturn next();\n\t}\n\n\t// Plugin routes: soft auth (resolve user if credentials present, but never block).\n\t// The catch-all handler decides per-route whether auth is required (public vs private).\n\t// Public plugin routes that accept POST are vulnerable to cross-origin form submissions,\n\t// so we apply the same Origin-based CSRF check as other public routes.\n\tconst isPluginRoute = url.pathname.startsWith(\"/_emdash/api/plugins/\");\n\tif (isPluginRoute) {\n\t\tconst method = context.request.method.toUpperCase();\n\t\tif (method !== \"GET\" && method !== \"HEAD\" && method !== \"OPTIONS\") {\n\t\t\tconst publicOrigin = getPublicOrigin(url, context.locals.emdash?.config);\n\t\t\tconst csrfError = checkPublicCsrf(context.request, url, publicOrigin);\n\t\t\tif (csrfError) return csrfError;\n\t\t}\n\t\treturn handlePluginRouteAuth(context, next);\n\t}\n\n\t// Setup routes: skip auth but still enforce CSRF on state-changing methods\n\tif (isSetupRoute) {\n\t\tconst method = context.request.method.toUpperCase();\n\t\tif (method !== \"GET\" && method !== \"HEAD\" && method !== \"OPTIONS\") {\n\t\t\tconst csrfHeader = context.request.headers.get(\"X-EmDash-Request\");\n\t\t\tif (csrfHeader !== \"1\") {\n\t\t\t\treturn new Response(\n\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\terror: { code: \"CSRF_REJECTED\", message: \"Missing required header\" },\n\t\t\t\t\t}),\n\t\t\t\t\t{\n\t\t\t\t\t\tstatus: 403,\n\t\t\t\t\t\theaders: { \"Content-Type\": \"application/json\", ...MW_CACHE_HEADERS },\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\treturn next();\n\t}\n\n\t// For public routes: soft auth check (set locals.user if session exists, but never block)\n\tif (isPublicRoute) {\n\t\treturn handlePublicRouteAuth(context, next);\n\t}\n\n\t// --- Everything below is /_emdash (admin + API) ---\n\n\t// Try Bearer token auth first (API tokens and OAuth tokens).\n\t// If successful, skip CSRF (tokens aren't ambient credentials like cookies).\n\tconst bearerResult = await handleBearerAuth(context);\n\n\tif (bearerResult === \"invalid\") {\n\t\tconst headers: Record<string, string> = {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t...MW_CACHE_HEADERS,\n\t\t};\n\t\t// Add WWW-Authenticate header on MCP endpoint 401s to trigger OAuth discovery\n\t\tif (url.pathname === \"/_emdash/api/mcp\") {\n\t\t\tconst origin = getPublicOrigin(url, context.locals.emdash?.config);\n\t\t\theaders[\"WWW-Authenticate\"] =\n\t\t\t\t`Bearer resource_metadata=\"${origin}/.well-known/oauth-protected-resource\"`;\n\t\t}\n\t\treturn new Response(\n\t\t\tJSON.stringify({ error: { code: \"INVALID_TOKEN\", message: \"Invalid or expired token\" } }),\n\t\t\t{ status: 401, headers },\n\t\t);\n\t}\n\n\tconst isTokenAuth = bearerResult === \"authenticated\";\n\n\t// MCP discovery/tooling is bearer-only. Session/external auth should never\n\t// be consulted for this endpoint, and unauthenticated requests must return\n\t// the OAuth discovery-style 401 response.\n\tconst method = context.request.method.toUpperCase();\n\tconst isMcpEndpoint = url.pathname === MCP_ENDPOINT_PATH;\n\tif (isMcpEndpoint && !isTokenAuth) {\n\t\treturn mcpUnauthorizedResponse(url, context.locals.emdash?.config);\n\t}\n\n\t// CSRF protection: require X-EmDash-Request header on state-changing requests.\n\t// Skip for token-authenticated requests (tokens aren't ambient credentials).\n\t// Browsers block cross-origin custom headers, so this prevents CSRF without tokens.\n\t// OAuth authorize consent is exempt: it's a standard HTML form POST that can't\n\t// include custom headers. The consent flow is protected by session + single-use codes.\n\tconst isOAuthConsent = url.pathname.startsWith(\"/_emdash/oauth/authorize\");\n\tif (\n\t\tisApiRoute &&\n\t\t!isTokenAuth &&\n\t\t!isOAuthConsent &&\n\t\tisUnsafeMethod(method) &&\n\t\t!isPublicApiRoute\n\t) {\n\t\tconst csrfHeader = context.request.headers.get(\"X-EmDash-Request\");\n\t\tif (csrfHeader !== \"1\") {\n\t\t\treturn csrfRejectedResponse();\n\t\t}\n\t}\n\n\t// If already authenticated via Bearer token, enforce scope then skip session/external auth\n\tif (isTokenAuth) {\n\t\t// Enforce API token scopes based on URL pattern + HTTP method\n\t\tconst scopeError = enforceTokenScope(url.pathname, method, context.locals.tokenScopes);\n\t\tif (scopeError) return scopeError;\n\n\t\tconst response = await next();\n\t\tif (!import.meta.env.DEV) {\n\t\t\tresponse.headers.set(\n\t\t\t\t\"Content-Security-Policy\",\n\t\t\t\tbuildEmDashCsp(context.locals.emdash?.config.experimental?.registry),\n\t\t\t);\n\t\t}\n\t\treturn response;\n\t}\n\n\tconst response = await handleEmDashAuth(context, next);\n\n\t// Set strict CSP on all /_emdash responses (prod only)\n\tif (!import.meta.env.DEV) {\n\t\tresponse.headers.set(\n\t\t\t\"Content-Security-Policy\",\n\t\t\tbuildEmDashCsp(context.locals.emdash?.config.experimental?.registry),\n\t\t);\n\t}\n\n\treturn response;\n});\n\n/**\n * Auth handling for /_emdash routes. Returns a Response from either\n * an auth error/redirect or the downstream route handler.\n */\nasync function handleEmDashAuth(\n\tcontext: Parameters<Parameters<typeof defineMiddleware>[0]>[0],\n\tnext: Parameters<Parameters<typeof defineMiddleware>[0]>[1],\n): Promise<Response> {\n\tconst { url, locals } = context;\n\tconst { emdash } = locals;\n\n\tconst isPublicAdminRoute =\n\t\turl.pathname.startsWith(\"/_emdash/admin/login\") ||\n\t\turl.pathname.startsWith(\"/_emdash/admin/invite/accept\");\n\tconst isApiRoute = url.pathname.startsWith(\"/_emdash/api\");\n\n\tif (!emdash?.db) {\n\t\t// No database - let the admin handle this error\n\t\treturn next();\n\t}\n\n\t// Determine auth mode from config\n\tconst authMode = getAuthMode(emdash.config);\n\n\tif (authMode.type === \"external\") {\n\t\t// In dev mode, fall back to passkey auth since external JWT won't be present\n\t\tif (import.meta.env.DEV) {\n\t\t\tif (isPublicAdminRoute) {\n\t\t\t\treturn next();\n\t\t\t}\n\n\t\t\treturn handlePasskeyAuth(context, next, isApiRoute);\n\t\t}\n\n\t\t// External auth provider (Cloudflare Access, etc.)\n\t\treturn handleExternalAuth(context, next, authMode, isApiRoute);\n\t}\n\n\t// Passkey authentication (default)\n\tif (isPublicAdminRoute) {\n\t\treturn next();\n\t}\n\n\treturn handlePasskeyAuth(context, next, isApiRoute);\n}\n\n/**\n * Soft auth for plugin routes: resolve user from Bearer token or session if present,\n * but never block unauthenticated requests. The catch-all handler checks route\n * metadata to decide whether auth is required (public vs private routes).\n */\nasync function handlePluginRouteAuth(\n\tcontext: Parameters<Parameters<typeof defineMiddleware>[0]>[0],\n\tnext: Parameters<Parameters<typeof defineMiddleware>[0]>[1],\n): Promise<Response> {\n\tconst { locals } = context;\n\tconst { emdash } = locals;\n\n\ttry {\n\t\t// Try Bearer token auth first (API tokens and OAuth tokens)\n\t\tconst bearerResult = await handleBearerAuth(context);\n\t\tif (bearerResult === \"authenticated\") {\n\t\t\t// User and tokenScopes are set on locals by handleBearerAuth\n\t\t\treturn next();\n\t\t}\n\t\tif (bearerResult === \"invalid\") {\n\t\t\t// A token was presented but is invalid/expired — return 401 so the\n\t\t\t// caller knows their token is bad (don't silently downgrade to no-auth).\n\t\t\treturn new Response(\n\t\t\t\tJSON.stringify({ error: { code: \"INVALID_TOKEN\", message: \"Invalid or expired token\" } }),\n\t\t\t\t{\n\t\t\t\t\tstatus: 401,\n\t\t\t\t\theaders: { \"Content-Type\": \"application/json\", ...MW_CACHE_HEADERS },\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\t// \"none\" — no token presented, try session auth below.\n\t} catch (error) {\n\t\tconsole.error(\"Plugin route bearer auth error:\", error);\n\t}\n\n\ttry {\n\t\t// Try session auth (sets locals.user if session exists)\n\t\tconst { session } = context;\n\t\tconst sessionUser = await resolveSessionUser(session);\n\t\tif (sessionUser?.id && emdash?.db) {\n\t\t\tconst adapter = createKyselyAdapter(emdash.db);\n\t\t\tconst user = await adapter.getUserById(sessionUser.id);\n\t\t\tif (user && !user.disabled) {\n\t\t\t\tlocals.user = user;\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\t// Log but don't block — public routes should still work without session\n\t\tconsole.error(\"Plugin route session auth error:\", error);\n\t}\n\n\treturn next();\n}\n\n/**\n * Soft auth check for public routes with edit mode cookie.\n * Checks the session and sets locals.user if valid, but never blocks the request.\n */\nasync function handlePublicRouteAuth(\n\tcontext: Parameters<Parameters<typeof defineMiddleware>[0]>[0],\n\tnext: Parameters<Parameters<typeof defineMiddleware>[0]>[1],\n): Promise<Response> {\n\tconst { locals, session } = context;\n\tconst { emdash } = locals;\n\n\ttry {\n\t\tconst sessionUser = await resolveSessionUser(session);\n\t\tif (sessionUser?.id && emdash?.db) {\n\t\t\tconst adapter = createKyselyAdapter(emdash.db);\n\t\t\tconst user = await adapter.getUserById(sessionUser.id);\n\t\t\tif (user && !user.disabled) {\n\t\t\t\tlocals.user = user;\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Silently continue — public page should render normally\n\t}\n\n\treturn next();\n}\n\n/**\n * Handle external auth provider authentication (Cloudflare Access, etc.)\n */\nasync function handleExternalAuth(\n\tcontext: Parameters<Parameters<typeof defineMiddleware>[0]>[0],\n\tnext: Parameters<Parameters<typeof defineMiddleware>[0]>[1],\n\tauthMode: ExternalAuthMode,\n\t_isApiRoute: boolean,\n): Promise<Response> {\n\tconst { locals, request } = context;\n\tconst { emdash } = locals;\n\n\ttry {\n\t\t// Use the authenticate function from the virtual module\n\t\t// (statically imported at build time to work with Cloudflare Workers)\n\t\tif (typeof virtualAuthenticate !== \"function\") {\n\t\t\tthrow new Error(\n\t\t\t\t`Auth provider ${authMode.entrypoint} does not export an authenticate function`,\n\t\t\t);\n\t\t}\n\n\t\t// Authenticate via the provider\n\t\tconst authResult = await virtualAuthenticate(request, authMode.config);\n\n\t\t// Get external auth config for auto-provision settings\n\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- narrowing AuthModeConfig to ExternalAuthConfig after provider check\n\t\tconst externalConfig = authMode.config as ExternalAuthConfig;\n\n\t\t// Find or create user\n\t\tconst adapter = createKyselyAdapter(emdash.db);\n\t\tlet user = await adapter.getUserByEmail(authResult.email);\n\n\t\tif (!user) {\n\t\t\t// User doesn't exist\n\t\t\tif (externalConfig.autoProvision === false) {\n\t\t\t\treturn new Response(\"User not authorized\", {\n\t\t\t\t\tstatus: 403,\n\t\t\t\t\theaders: { \"Content-Type\": \"text/plain\", ...MW_CACHE_HEADERS },\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Check if this is the first user (they become admin)\n\t\t\tconst userCount = await emdash.db\n\t\t\t\t.selectFrom(\"users\")\n\t\t\t\t.select(emdash.db.fn.count(\"id\").as(\"count\"))\n\t\t\t\t.executeTakeFirst();\n\n\t\t\tconst isFirstUser = Number(userCount?.count ?? 0) === 0;\n\t\t\tconst role = isFirstUser ? ROLE_ADMIN : authResult.role;\n\n\t\t\t// Create user\n\t\t\tconst now = new Date().toISOString();\n\t\t\tconst newUser = {\n\t\t\t\tid: ulid(),\n\t\t\t\temail: authResult.email,\n\t\t\t\tname: authResult.name,\n\t\t\t\trole,\n\t\t\t\temail_verified: 1,\n\t\t\t\tcreated_at: now,\n\t\t\t\tupdated_at: now,\n\t\t\t};\n\n\t\t\tawait emdash.db.insertInto(\"users\").values(newUser).execute();\n\n\t\t\tuser = await adapter.getUserByEmail(authResult.email);\n\n\t\t\tconsole.log(\n\t\t\t\t`[external-auth] Provisioned user: ${authResult.email} (role: ${role}, first: ${isFirstUser})`,\n\t\t\t);\n\t\t} else {\n\t\t\t// User exists - check if we need to sync anything\n\t\t\tconst updates: Record<string, unknown> = {};\n\t\t\tlet newName: string | undefined;\n\t\t\tlet newRole: RoleLevel | undefined;\n\n\t\t\t// Sync name from provider if provider provides one and local differs\n\t\t\tif (authResult.name && user.name !== authResult.name) {\n\t\t\t\tnewName = authResult.name;\n\t\t\t\tupdates.name = newName;\n\t\t\t}\n\n\t\t\t// Sync role if enabled\n\t\t\tif (externalConfig.syncRoles && user.role !== authResult.role) {\n\t\t\t\tnewRole = authResult.role;\n\t\t\t\tupdates.role = newRole;\n\t\t\t}\n\n\t\t\tif (Object.keys(updates).length > 0) {\n\t\t\t\tupdates.updated_at = new Date().toISOString();\n\t\t\t\tawait emdash.db.updateTable(\"users\").set(updates).where(\"id\", \"=\", user.id).execute();\n\n\t\t\t\tuser = {\n\t\t\t\t\t...user,\n\t\t\t\t\t...(newName ? { name: newName } : {}),\n\t\t\t\t\t...(newRole ? { role: newRole } : {}),\n\t\t\t\t};\n\n\t\t\t\tconsole.log(\n\t\t\t\t\t`[external-auth] Updated user ${authResult.email}:`,\n\t\t\t\t\tObject.keys(updates).filter((k) => k !== \"updated_at\"),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!user) {\n\t\t\t// This shouldn't happen, but handle it gracefully\n\t\t\treturn new Response(\"Failed to provision user\", {\n\t\t\t\tstatus: 500,\n\t\t\t\theaders: { \"Content-Type\": \"text/plain\", ...MW_CACHE_HEADERS },\n\t\t\t});\n\t\t}\n\n\t\t// Check if user is disabled locally\n\t\tif (user.disabled) {\n\t\t\treturn new Response(\"Account disabled\", {\n\t\t\t\tstatus: 403,\n\t\t\t\theaders: { \"Content-Type\": \"text/plain\", ...MW_CACHE_HEADERS },\n\t\t\t});\n\t\t}\n\n\t\t// Set user in locals\n\t\tlocals.user = user;\n\n\t\t// Persist to session so public pages can identify the user\n\t\t// (external auth headers are only verified on /_emdash routes)\n\t\tconst { session } = context;\n\t\tsession?.set(\"user\", { id: user.id });\n\n\t\treturn next();\n\t} catch (error) {\n\t\tconsole.error(\"[external-auth] Auth error:\", error);\n\n\t\treturn new Response(\"Authentication failed\", {\n\t\t\tstatus: 401,\n\t\t\theaders: { \"Content-Type\": \"text/plain\", ...MW_CACHE_HEADERS },\n\t\t});\n\t}\n}\n\n/**\n * Try to authenticate via Bearer token (API token or OAuth token).\n *\n * Returns:\n * - \"authenticated\" if token is valid and user is resolved\n * - \"invalid\" if a token was provided but is invalid/expired\n * - \"none\" if no Bearer token was provided\n */\nasync function handleBearerAuth(\n\tcontext: Parameters<Parameters<typeof defineMiddleware>[0]>[0],\n): Promise<\"authenticated\" | \"invalid\" | \"none\"> {\n\tconst authHeader = context.request.headers.get(\"Authorization\");\n\tif (!authHeader?.startsWith(\"Bearer \")) return \"none\";\n\n\tconst token = authHeader.slice(7);\n\tif (!token) return \"none\";\n\n\tconst { locals } = context;\n\tconst { emdash } = locals;\n\tif (!emdash?.db) return \"none\";\n\n\t// Resolve token based on prefix\n\tlet resolved: { userId: string; scopes: string[] } | null = null;\n\n\tif (token.startsWith(\"ec_pat_\")) {\n\t\tresolved = await resolveApiToken(emdash.db, token);\n\t} else if (token.startsWith(\"ec_oat_\")) {\n\t\tresolved = await resolveOAuthToken(emdash.db, token);\n\t} else {\n\t\t// Unknown token format\n\t\treturn \"invalid\";\n\t}\n\n\tif (!resolved) return \"invalid\";\n\n\t// Look up the user\n\tconst adapter = createKyselyAdapter(emdash.db);\n\tconst user = await adapter.getUserById(resolved.userId);\n\n\tif (!user || user.disabled) return \"invalid\";\n\n\t// Set user and scopes on locals\n\tlocals.user = user;\n\tlocals.tokenScopes = resolved.scopes;\n\n\treturn \"authenticated\";\n}\n\n/**\n * Handle passkey (session-based) authentication\n */\nasync function handlePasskeyAuth(\n\tcontext: Parameters<Parameters<typeof defineMiddleware>[0]>[0],\n\tnext: Parameters<Parameters<typeof defineMiddleware>[0]>[1],\n\tisApiRoute: boolean,\n): Promise<Response> {\n\tconst { url, locals, session } = context;\n\tconst { emdash } = locals;\n\n\ttry {\n\t\t// Check session for user (session.get returns a Promise)\n\t\tconst sessionUser = await resolveSessionUser(session);\n\n\t\tif (!sessionUser?.id) {\n\t\t\tif (isApiRoute) {\n\t\t\t\treturn Response.json(\n\t\t\t\t\t{ error: { code: \"NOT_AUTHENTICATED\", message: \"Not authenticated\" } },\n\t\t\t\t\t{ status: 401, headers: MW_CACHE_HEADERS },\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst loginUrl = new URL(\"/_emdash/admin/login\", getPublicOrigin(url, emdash?.config));\n\t\t\tloginUrl.searchParams.set(\"redirect\", url.pathname);\n\t\t\treturn context.redirect(loginUrl.toString());\n\t\t}\n\n\t\t// Get full user from database\n\t\tconst adapter = createKyselyAdapter(emdash.db);\n\t\tconst user = await adapter.getUserById(sessionUser.id);\n\n\t\tif (!user) {\n\t\t\t// User no longer exists - clear session\n\t\t\tsession?.destroy();\n\t\t\tif (isApiRoute) {\n\t\t\t\treturn Response.json(\n\t\t\t\t\t{ error: { code: \"NOT_FOUND\", message: \"User not found\" } },\n\t\t\t\t\t{ status: 401, headers: MW_CACHE_HEADERS },\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst loginUrl = new URL(\"/_emdash/admin/login\", getPublicOrigin(url, emdash?.config));\n\t\t\treturn context.redirect(loginUrl.toString());\n\t\t}\n\n\t\t// Check if user is disabled\n\t\tif (user.disabled) {\n\t\t\tsession?.destroy();\n\t\t\tif (isApiRoute) {\n\t\t\t\treturn apiError(\"ACCOUNT_DISABLED\", \"Account disabled\", 403);\n\t\t\t}\n\t\t\tconst loginUrl = new URL(\"/_emdash/admin/login\", getPublicOrigin(url, emdash?.config));\n\t\t\tloginUrl.searchParams.set(\"error\", \"account_disabled\");\n\t\t\treturn context.redirect(loginUrl.toString());\n\t\t}\n\n\t\t// Set user in locals for use by routes\n\t\tlocals.user = user;\n\t} catch (error) {\n\t\tconsole.error(\"Auth middleware error:\", error);\n\t\t// On error, redirect to login\n\t\treturn context.redirect(\"/_emdash/admin/login\");\n\t}\n\n\treturn next();\n}\n\n// =============================================================================\n// Token scope enforcement\n// =============================================================================\n\n/**\n * Scope rules: ordered list of (pathPrefix, method, requiredScope) tuples.\n * First matching rule wins. Methods: \"*\" = any, \"WRITE\" = POST/PUT/PATCH/DELETE.\n *\n * Routes not matched by any rule default to \"admin\" scope (fail-closed).\n */\nconst SCOPE_RULES: Array<[prefix: string, method: string, scope: string]> = [\n\t// Content routes\n\t[\"/_emdash/api/content\", \"GET\", \"content:read\"],\n\t[\"/_emdash/api/content\", \"WRITE\", \"content:write\"],\n\n\t// Media routes (excluding /file/ which is public)\n\t[\"/_emdash/api/media/file\", \"*\", \"media:read\"], // public anyway, but scope if token-authed\n\t[\"/_emdash/api/media\", \"GET\", \"media:read\"],\n\t[\"/_emdash/api/media\", \"WRITE\", \"media:write\"],\n\n\t// Schema routes\n\t[\"/_emdash/api/schema\", \"GET\", \"schema:read\"],\n\t[\"/_emdash/api/schema\", \"WRITE\", \"schema:write\"],\n\n\t// Taxonomy, menu, section, widget, revision — all content domain\n\t// GET uses content:read (implicit from taxonomies:read / menus:read via role).\n\t// WRITE uses the granular scope so tokens with only taxonomies:manage or\n\t// menus:manage are not rejected. content:write implicitly grants these via\n\t// IMPLICIT_SCOPE_GRANTS in @emdash-cms/auth.\n\t[\"/_emdash/api/taxonomies\", \"GET\", \"content:read\"],\n\t[\"/_emdash/api/taxonomies\", \"WRITE\", \"taxonomies:manage\"],\n\t[\"/_emdash/api/menus\", \"GET\", \"content:read\"],\n\t[\"/_emdash/api/menus\", \"WRITE\", \"menus:manage\"],\n\t[\"/_emdash/api/sections\", \"GET\", \"content:read\"],\n\t[\"/_emdash/api/sections\", \"WRITE\", \"content:write\"],\n\t[\"/_emdash/api/widget-areas\", \"GET\", \"content:read\"],\n\t[\"/_emdash/api/widget-areas\", \"WRITE\", \"content:write\"],\n\t[\"/_emdash/api/revisions\", \"GET\", \"content:read\"],\n\t[\"/_emdash/api/revisions\", \"WRITE\", \"content:write\"],\n\n\t// Search\n\t[\"/_emdash/api/search\", \"GET\", \"content:read\"],\n\t[\"/_emdash/api/search\", \"WRITE\", \"admin\"],\n\n\t// Import, admin, plugins — all require admin scope\n\t[\"/_emdash/api/import\", \"*\", \"admin\"],\n\t[\"/_emdash/api/admin\", \"*\", \"admin\"],\n\t[\"/_emdash/api/plugins\", \"*\", \"admin\"],\n\n\t// Settings — use granular scopes so tokens with settings:read or\n\t// settings:manage are not rejected at the middleware level.\n\t[\"/_emdash/api/settings\", \"GET\", \"settings:read\"],\n\t[\"/_emdash/api/settings\", \"WRITE\", \"settings:manage\"],\n\n\t// MCP endpoint — scopes enforced per-tool inside mcp/server.ts\n\t[\"/_emdash/api/mcp\", \"*\", \"content:read\"],\n];\n\nconst WRITE_METHODS = new Set([\"POST\", \"PUT\", \"PATCH\", \"DELETE\"]);\n\n/**\n * Enforce API token scopes based on the request URL and HTTP method.\n * Returns a 403 Response if the scope is insufficient, or null if allowed.\n *\n * Session-authenticated requests (tokenScopes === undefined) are never checked.\n */\nfunction enforceTokenScope(\n\tpathname: string,\n\tmethod: string,\n\ttokenScopes: string[] | undefined,\n): Response | null {\n\t// Session auth — implicit full access, no scope restrictions\n\tif (!tokenScopes) return null;\n\n\tconst isWrite = WRITE_METHODS.has(method);\n\n\tfor (const [prefix, ruleMethod, scope] of SCOPE_RULES) {\n\t\t// Match exact prefix or prefix followed by /\n\t\tif (pathname !== prefix && !pathname.startsWith(prefix + \"/\")) continue;\n\n\t\t// Check method match\n\t\tif (ruleMethod === \"*\" || (ruleMethod === \"WRITE\" && isWrite) || ruleMethod === method) {\n\t\t\tif (hasScope(tokenScopes, scope)) return null;\n\n\t\t\treturn new Response(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcode: \"INSUFFICIENT_SCOPE\",\n\t\t\t\t\t\tmessage: `Token lacks required scope: ${scope}`,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\t{ status: 403, headers: { \"Content-Type\": \"application/json\", ...MW_CACHE_HEADERS } },\n\t\t\t);\n\t\t}\n\t}\n\n\t// No rule matched — default to admin scope (fail-closed)\n\tif (hasScope(tokenScopes, \"admin\")) return null;\n\n\treturn new Response(\n\t\tJSON.stringify({\n\t\t\terror: {\n\t\t\t\tcode: \"INSUFFICIENT_SCOPE\",\n\t\t\t\tmessage: \"Token lacks required scope: admin\",\n\t\t\t},\n\t\t}),\n\t\t{ status: 403, headers: { \"Content-Type\": \"application/json\", ...MW_CACHE_HEADERS } },\n\t);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"auth.mjs","names":["virtualAuthenticate"],"sources":["../../../src/api/csrf.ts","../../../src/astro/middleware/csp.ts","../../../src/astro/middleware/auth.ts"],"sourcesContent":["/**\n * CSRF protection utilities.\n *\n * Two mechanisms:\n * 1. Custom header check (X-EmDash-Request: 1) — used for authenticated API routes.\n * Browsers block cross-origin custom headers, so presence proves same-origin.\n * 2. Origin check — used for public API routes that skip auth. Compares the Origin\n * header against the request origin. Same approach as Astro's `checkOrigin`.\n */\n\nimport { apiError } from \"./error.js\";\n\n/**\n * Origin-based CSRF check for public API routes that skip auth.\n *\n * State-changing requests (POST/PUT/DELETE) to public endpoints must either:\n * 1. Include the X-EmDash-Request: 1 header (custom header blocked cross-origin), OR\n * 2. Have an Origin header matching the request origin (or the configured public origin)\n *\n * This prevents cross-origin form submissions (which can't set custom headers)\n * and cross-origin fetch (blocked by CORS unless allowed). Same-origin requests\n * always include a matching Origin header.\n *\n * Returns a 403 Response if the check fails, or null if allowed.\n *\n * @param request The incoming request\n * @param url The request URL (internal origin)\n * @param publicOrigin The public-facing origin from config.siteUrl. Must be\n * `undefined` when absent — never `null` or `\"\"` (security invariant H-1a).\n */\nexport function checkPublicCsrf(\n\trequest: Request,\n\turl: URL,\n\tpublicOrigin?: string,\n): Response | null {\n\t// Custom header present — browser blocks cross-origin custom headers\n\tconst csrfHeader = request.headers.get(\"X-EmDash-Request\");\n\tif (csrfHeader === \"1\") return null;\n\n\t// Check Origin header — present on all POST/PUT/DELETE from browsers\n\tconst origin = request.headers.get(\"Origin\");\n\tif (origin) {\n\t\ttry {\n\t\t\tconst originUrl = new URL(origin);\n\t\t\t// Accept if Origin matches either the internal or public origin\n\t\t\tif (originUrl.origin === url.origin) return null;\n\t\t\tif (publicOrigin && originUrl.origin === publicOrigin) return null;\n\t\t} catch {\n\t\t\t// Malformed Origin — fall through to reject\n\t\t}\n\n\t\treturn apiError(\"CSRF_REJECTED\", \"Cross-origin request blocked\", 403);\n\t}\n\n\t// No Origin header — non-browser client (curl, server-to-server).\n\t// Allow these through since CSRF is a browser-specific attack vector.\n\t// Server-to-server requests don't carry ambient credentials (cookies).\n\treturn null;\n}\n","/**\n * Strict Content-Security-Policy for /_emdash routes (admin + API).\n *\n * Applied via middleware header rather than Astro's built-in CSP because\n * Astro's auto-hashing defeats 'unsafe-inline' (CSP3 ignores 'unsafe-inline'\n * when hashes are present), which would break user-facing pages.\n *\n * img-src allows any HTTPS origin because the admin renders user content that\n * may reference external images (migrations, external hosting, embeds).\n * Plugin security does not rely on img-src -- plugins run in V8 isolates with\n * no DOM access. connect-src stays at 'self' unless the experimental registry\n * is configured, in which case the configured aggregator origin is allowed.\n */\nimport type { RegistryConfigInput } from \"../../registry/types.js\";\n\nfunction getRegistryAggregatorOrigin(\n\tregistry: RegistryConfigInput | undefined,\n): string | undefined {\n\tconst aggregatorUrl = typeof registry === \"string\" ? registry : registry?.aggregatorUrl;\n\tif (!aggregatorUrl) return undefined;\n\n\ttry {\n\t\tconst url = new URL(aggregatorUrl);\n\t\tif (url.protocol !== \"http:\" && url.protocol !== \"https:\") return undefined;\n\t\treturn url.origin;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport function buildEmDashCsp(registry?: RegistryConfigInput): string {\n\tconst connectSrc = [\"connect-src 'self'\"];\n\tconst registryAggregatorOrigin = getRegistryAggregatorOrigin(registry);\n\tif (registryAggregatorOrigin) connectSrc.push(registryAggregatorOrigin);\n\n\treturn [\n\t\t\"default-src 'self'\",\n\t\t\"script-src 'self' 'unsafe-inline'\",\n\t\t\"style-src 'self' 'unsafe-inline'\",\n\t\tconnectSrc.join(\" \"),\n\t\t\"form-action 'self'\",\n\t\t\"frame-ancestors 'none'\",\n\t\t\"img-src 'self' https: data: blob:\",\n\t\t\"object-src 'none'\",\n\t\t\"base-uri 'self'\",\n\t].join(\"; \");\n}\n","/**\n * Auth middleware for admin routes\n *\n * Checks if the user is authenticated and has appropriate permissions.\n * Supports two auth modes:\n * - Passkey (default): Session-based auth with passkey login\n * - External providers: JWT-based auth (Cloudflare Access, etc.)\n *\n * This middleware runs AFTER the setup middleware - so if we get here,\n * we know setup is complete and users exist.\n */\n\nimport type { User, RoleLevel } from \"@emdash-cms/auth\";\nimport { createKyselyAdapter } from \"@emdash-cms/auth/adapters/kysely\";\nimport { defineMiddleware } from \"astro:middleware\";\nimport { ulid } from \"ulidx\";\n// Import auth provider via virtual module (statically bundled)\n// This avoids dynamic import issues in Cloudflare Workers\nimport { authenticate as virtualAuthenticate } from \"virtual:emdash/auth\";\n// @ts-ignore - virtual module\nimport virtualConfig from \"virtual:emdash/config\";\n\nimport { checkPublicCsrf } from \"../../api/csrf.js\";\nimport { apiError } from \"../../api/error.js\";\nimport { getPublicOrigin } from \"../../api/public-url.js\";\n\n/** Cache headers for middleware error responses (matches API_CACHE_HEADERS in api/error.ts) */\nconst MW_CACHE_HEADERS = {\n\t\"Cache-Control\": \"private, no-store\",\n} as const;\nimport { resolveApiToken, resolveOAuthToken } from \"../../api/handlers/api-tokens.js\";\nimport { hasScope } from \"../../auth/api-tokens.js\";\nimport { getAuthMode, type ExternalAuthMode } from \"../../auth/mode.js\";\nimport type { ExternalAuthConfig } from \"../../auth/types.js\";\nimport { resolveSessionUser } from \"../session-user.js\";\nimport type { EmDashHandlers } from \"../types.js\";\nimport { buildEmDashCsp } from \"./csp.js\";\n\ndeclare global {\n\tnamespace App {\n\t\tinterface Locals {\n\t\t\tuser?: User;\n\t\t\t/** Token scopes when authenticated via API token or OAuth token. Undefined for session auth. */\n\t\t\ttokenScopes?: string[];\n\t\t\temdash?: EmDashHandlers;\n\t\t}\n\t\tinterface SessionData {\n\t\t\tuser: { id: string };\n\t\t\thasSeenWelcome: boolean;\n\t\t}\n\t}\n}\n\n// Role level constants (matching @emdash-cms/auth)\nconst ROLE_ADMIN = 50;\nconst MCP_ENDPOINT_PATH = \"/_emdash/api/mcp\";\n\nfunction isUnsafeMethod(method: string): boolean {\n\treturn method !== \"GET\" && method !== \"HEAD\" && method !== \"OPTIONS\";\n}\n\nfunction csrfRejectedResponse(): Response {\n\treturn new Response(\n\t\tJSON.stringify({ error: { code: \"CSRF_REJECTED\", message: \"Missing required header\" } }),\n\t\t{\n\t\t\tstatus: 403,\n\t\t\theaders: { \"Content-Type\": \"application/json\", ...MW_CACHE_HEADERS },\n\t\t},\n\t);\n}\n\nfunction mcpUnauthorizedResponse(\n\turl: URL,\n\tconfig?: Parameters<typeof getPublicOrigin>[1],\n): Response {\n\tconst origin = getPublicOrigin(url, config);\n\treturn Response.json(\n\t\t{ error: { code: \"NOT_AUTHENTICATED\", message: \"Not authenticated\" } },\n\t\t{\n\t\t\tstatus: 401,\n\t\t\theaders: {\n\t\t\t\t\"WWW-Authenticate\": `Bearer resource_metadata=\"${origin}/.well-known/oauth-protected-resource\"`,\n\t\t\t\t...MW_CACHE_HEADERS,\n\t\t\t},\n\t\t},\n\t);\n}\n\n/**\n * API routes that skip auth — each handles its own access control.\n *\n * Prefix entries match any path starting with that prefix.\n * Exact entries (no trailing slash or wildcard) match that path only.\n */\nconst PUBLIC_API_PREFIXES = [\n\t\"/_emdash/api/setup\",\n\t\"/_emdash/api/auth/login\",\n\t\"/_emdash/api/auth/register\",\n\t\"/_emdash/api/auth/dev-bypass\",\n\t\"/_emdash/api/auth/signup/\",\n\t\"/_emdash/api/auth/magic-link/\",\n\t\"/_emdash/api/auth/invite/\",\n\t\"/_emdash/api/auth/oauth/\",\n\t\"/_emdash/api/oauth/device/token\",\n\t\"/_emdash/api/oauth/device/code\",\n\t\"/_emdash/api/oauth/token\",\n\t\"/_emdash/api/oauth/register\",\n\t\"/_emdash/api/comments/\",\n\t\"/_emdash/api/media/file/\",\n\t\"/_emdash/.well-known/\",\n];\n\nconst PUBLIC_API_EXACT = new Set([\n\t\"/_emdash/api/auth/passkey/options\",\n\t\"/_emdash/api/auth/passkey/verify\",\n\t\"/_emdash/api/auth/mode\",\n\t\"/_emdash/api/oauth/token\",\n\t\"/_emdash/api/snapshot\",\n\t// Public site search — read-only. The query layer hardcodes status='published'\n\t// so unauthenticated callers only see published content. Admin endpoints\n\t// (/enable, /rebuild, /stats) remain private because they're not in this set.\n\t\"/_emdash/api/search\",\n\t\"/_emdash/api/search/suggest\",\n]);\n\n// Build merged public routes at module load from auth provider descriptors.\n// Routes ending with \"/\" are treated as prefixes; all others are exact matches.\nconst { exact: _providerExactRoutes, prefixes: _providerPrefixRoutes } = (() => {\n\tconst exact = new Set<string>();\n\tconst prefixes: string[] = [];\n\tif (!virtualConfig?.authProviders) return { exact, prefixes };\n\tfor (const route of virtualConfig.authProviders.flatMap((p) => p.publicRoutes ?? [])) {\n\t\tif (route.endsWith(\"/\")) {\n\t\t\tprefixes.push(route);\n\t\t} else {\n\t\t\texact.add(route);\n\t\t}\n\t}\n\treturn { exact, prefixes };\n})();\n\n/**\n * OAuth protocol endpoints that are CSRF-exempt by design.\n *\n * These are RFC-defined endpoints (RFC 6749 §3.2, RFC 7591 §3, RFC 8628 §3.1/§3.4)\n * specified to be called cross-origin by external clients (MCP clients, CLIs,\n * native apps). They authenticate each request on its own merits:\n *\n * - /oauth/token: requires PKCE code_verifier, device_code, or refresh_token\n * - /oauth/register: RFC 7591 dynamic client registration — anonymous by design\n * - /oauth/device/code: RFC 8628 device flow initiation — anonymous by design\n * - /oauth/device/token: requires device_code the client already holds\n *\n * None of these rely on ambient cookie credentials, so browser-based CSRF\n * attacks have nothing to exploit. The endpoints themselves advertise\n * `Access-Control-Allow-Origin: *`. Note: /oauth/device/authorize (the user\n * consent step) is NOT in this list — it is session-authenticated.\n */\nconst CSRF_EXEMPT_PUBLIC_ROUTES = new Set([\n\t\"/_emdash/api/oauth/token\",\n\t\"/_emdash/api/oauth/register\",\n\t\"/_emdash/api/oauth/device/code\",\n\t\"/_emdash/api/oauth/device/token\",\n]);\n\nfunction isPublicEmDashRoute(pathname: string): boolean {\n\tif (PUBLIC_API_EXACT.has(pathname)) return true;\n\tif (PUBLIC_API_PREFIXES.some((p) => pathname.startsWith(p))) return true;\n\tif (_providerExactRoutes.has(pathname)) return true;\n\tif (_providerPrefixRoutes.some((p) => pathname.startsWith(p))) return true;\n\tif (import.meta.env.DEV && pathname === \"/_emdash/api/typegen\") return true;\n\treturn false;\n}\n\nfunction isCsrfExemptPublicRoute(pathname: string): boolean {\n\treturn CSRF_EXEMPT_PUBLIC_ROUTES.has(pathname);\n}\n\nexport const onRequest = defineMiddleware(async (context, next) => {\n\tconst { url } = context;\n\n\t// Only check auth on admin routes and API routes\n\tconst isAdminRoute = url.pathname.startsWith(\"/_emdash/admin\");\n\tconst isSetupRoute = url.pathname.startsWith(\"/_emdash/admin/setup\");\n\tconst isApiRoute = url.pathname.startsWith(\"/_emdash/api\");\n\tconst isPublicApiRoute = isPublicEmDashRoute(url.pathname);\n\n\tconst isPublicRoute = !isAdminRoute && !isApiRoute;\n\n\t// Public API routes skip auth but still need CSRF protection on state-changing methods.\n\t// We check Origin header against the request host (same approach as Astro's checkOrigin).\n\t// This prevents cross-origin form submissions and fetch requests from malicious sites.\n\tif (isPublicApiRoute) {\n\t\tconst method = context.request.method.toUpperCase();\n\t\tif (\n\t\t\tisUnsafeMethod(method) &&\n\t\t\t!isCsrfExemptPublicRoute(url.pathname) // OAuth protocol endpoints — cross-origin by design\n\t\t) {\n\t\t\tconst publicOrigin = getPublicOrigin(url, context.locals.emdash?.config);\n\t\t\tconst csrfError = checkPublicCsrf(context.request, url, publicOrigin);\n\t\t\tif (csrfError) return csrfError;\n\t\t}\n\t\treturn next();\n\t}\n\n\t// Plugin routes: soft auth (resolve user if credentials present, but never block).\n\t// The catch-all handler decides per-route whether auth is required (public vs private).\n\t// Public plugin routes that accept POST are vulnerable to cross-origin form submissions,\n\t// so we apply the same Origin-based CSRF check as other public routes.\n\tconst isPluginRoute = url.pathname.startsWith(\"/_emdash/api/plugins/\");\n\tif (isPluginRoute) {\n\t\tconst method = context.request.method.toUpperCase();\n\t\tif (method !== \"GET\" && method !== \"HEAD\" && method !== \"OPTIONS\") {\n\t\t\tconst publicOrigin = getPublicOrigin(url, context.locals.emdash?.config);\n\t\t\tconst csrfError = checkPublicCsrf(context.request, url, publicOrigin);\n\t\t\tif (csrfError) return csrfError;\n\t\t}\n\t\treturn handlePluginRouteAuth(context, next);\n\t}\n\n\t// Setup routes: skip auth but still enforce CSRF on state-changing methods\n\tif (isSetupRoute) {\n\t\tconst method = context.request.method.toUpperCase();\n\t\tif (method !== \"GET\" && method !== \"HEAD\" && method !== \"OPTIONS\") {\n\t\t\tconst csrfHeader = context.request.headers.get(\"X-EmDash-Request\");\n\t\t\tif (csrfHeader !== \"1\") {\n\t\t\t\treturn new Response(\n\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\terror: { code: \"CSRF_REJECTED\", message: \"Missing required header\" },\n\t\t\t\t\t}),\n\t\t\t\t\t{\n\t\t\t\t\t\tstatus: 403,\n\t\t\t\t\t\theaders: { \"Content-Type\": \"application/json\", ...MW_CACHE_HEADERS },\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\treturn next();\n\t}\n\n\t// For public routes: soft auth check (set locals.user if session exists, but never block)\n\tif (isPublicRoute) {\n\t\treturn handlePublicRouteAuth(context, next);\n\t}\n\n\t// --- Everything below is /_emdash (admin + API) ---\n\n\t// Try Bearer token auth first (API tokens and OAuth tokens).\n\t// If successful, skip CSRF (tokens aren't ambient credentials like cookies).\n\tconst bearerResult = await handleBearerAuth(context);\n\n\tif (bearerResult === \"invalid\") {\n\t\tconst headers: Record<string, string> = {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t...MW_CACHE_HEADERS,\n\t\t};\n\t\t// Add WWW-Authenticate header on MCP endpoint 401s to trigger OAuth discovery\n\t\tif (url.pathname === \"/_emdash/api/mcp\") {\n\t\t\tconst origin = getPublicOrigin(url, context.locals.emdash?.config);\n\t\t\theaders[\"WWW-Authenticate\"] =\n\t\t\t\t`Bearer resource_metadata=\"${origin}/.well-known/oauth-protected-resource\"`;\n\t\t}\n\t\treturn new Response(\n\t\t\tJSON.stringify({ error: { code: \"INVALID_TOKEN\", message: \"Invalid or expired token\" } }),\n\t\t\t{ status: 401, headers },\n\t\t);\n\t}\n\n\tconst isTokenAuth = bearerResult === \"authenticated\";\n\n\t// MCP discovery/tooling is bearer-only. Session/external auth should never\n\t// be consulted for this endpoint, and unauthenticated requests must return\n\t// the OAuth discovery-style 401 response.\n\tconst method = context.request.method.toUpperCase();\n\tconst isMcpEndpoint = url.pathname === MCP_ENDPOINT_PATH;\n\tif (isMcpEndpoint && !isTokenAuth) {\n\t\treturn mcpUnauthorizedResponse(url, context.locals.emdash?.config);\n\t}\n\n\t// CSRF protection: require X-EmDash-Request header on state-changing requests.\n\t// Skip for token-authenticated requests (tokens aren't ambient credentials).\n\t// Browsers block cross-origin custom headers, so this prevents CSRF without tokens.\n\t// OAuth authorize consent is exempt: it's a standard HTML form POST that can't\n\t// include custom headers. The consent flow is protected by session + single-use codes.\n\tconst isOAuthConsent = url.pathname.startsWith(\"/_emdash/oauth/authorize\");\n\tif (\n\t\tisApiRoute &&\n\t\t!isTokenAuth &&\n\t\t!isOAuthConsent &&\n\t\tisUnsafeMethod(method) &&\n\t\t!isPublicApiRoute\n\t) {\n\t\tconst csrfHeader = context.request.headers.get(\"X-EmDash-Request\");\n\t\tif (csrfHeader !== \"1\") {\n\t\t\treturn csrfRejectedResponse();\n\t\t}\n\t}\n\n\t// If already authenticated via Bearer token, enforce scope then skip session/external auth\n\tif (isTokenAuth) {\n\t\t// Enforce API token scopes based on URL pattern + HTTP method\n\t\tconst scopeError = enforceTokenScope(url.pathname, method, context.locals.tokenScopes);\n\t\tif (scopeError) return scopeError;\n\n\t\tconst response = await next();\n\t\tif (!import.meta.env.DEV) {\n\t\t\tresponse.headers.set(\n\t\t\t\t\"Content-Security-Policy\",\n\t\t\t\tbuildEmDashCsp(context.locals.emdash?.config.experimental?.registry),\n\t\t\t);\n\t\t}\n\t\treturn response;\n\t}\n\n\tconst response = await handleEmDashAuth(context, next);\n\n\t// Set strict CSP on all /_emdash responses (prod only)\n\tif (!import.meta.env.DEV) {\n\t\tresponse.headers.set(\n\t\t\t\"Content-Security-Policy\",\n\t\t\tbuildEmDashCsp(context.locals.emdash?.config.experimental?.registry),\n\t\t);\n\t}\n\n\treturn response;\n});\n\n/**\n * Auth handling for /_emdash routes. Returns a Response from either\n * an auth error/redirect or the downstream route handler.\n */\nasync function handleEmDashAuth(\n\tcontext: Parameters<Parameters<typeof defineMiddleware>[0]>[0],\n\tnext: Parameters<Parameters<typeof defineMiddleware>[0]>[1],\n): Promise<Response> {\n\tconst { url, locals } = context;\n\tconst { emdash } = locals;\n\n\tconst isPublicAdminRoute =\n\t\turl.pathname.startsWith(\"/_emdash/admin/login\") ||\n\t\turl.pathname.startsWith(\"/_emdash/admin/invite/accept\");\n\tconst isApiRoute = url.pathname.startsWith(\"/_emdash/api\");\n\n\tif (!emdash?.db) {\n\t\t// No database - let the admin handle this error\n\t\treturn next();\n\t}\n\n\t// Determine auth mode from config\n\tconst authMode = getAuthMode(emdash.config);\n\n\tif (authMode.type === \"external\") {\n\t\t// In dev mode, fall back to passkey auth since external JWT won't be present\n\t\tif (import.meta.env.DEV) {\n\t\t\tif (isPublicAdminRoute) {\n\t\t\t\treturn next();\n\t\t\t}\n\n\t\t\treturn handlePasskeyAuth(context, next, isApiRoute);\n\t\t}\n\n\t\t// External auth provider (Cloudflare Access, etc.)\n\t\treturn handleExternalAuth(context, next, authMode, isApiRoute);\n\t}\n\n\t// Passkey authentication (default)\n\tif (isPublicAdminRoute) {\n\t\treturn next();\n\t}\n\n\treturn handlePasskeyAuth(context, next, isApiRoute);\n}\n\n/**\n * Soft auth for plugin routes: resolve user from Bearer token or session if present,\n * but never block unauthenticated requests. The catch-all handler checks route\n * metadata to decide whether auth is required (public vs private routes).\n */\nasync function handlePluginRouteAuth(\n\tcontext: Parameters<Parameters<typeof defineMiddleware>[0]>[0],\n\tnext: Parameters<Parameters<typeof defineMiddleware>[0]>[1],\n): Promise<Response> {\n\tconst { locals } = context;\n\tconst { emdash } = locals;\n\n\ttry {\n\t\t// Try Bearer token auth first (API tokens and OAuth tokens)\n\t\tconst bearerResult = await handleBearerAuth(context);\n\t\tif (bearerResult === \"authenticated\") {\n\t\t\t// User and tokenScopes are set on locals by handleBearerAuth\n\t\t\treturn next();\n\t\t}\n\t\tif (bearerResult === \"invalid\") {\n\t\t\t// A token was presented but is invalid/expired — return 401 so the\n\t\t\t// caller knows their token is bad (don't silently downgrade to no-auth).\n\t\t\treturn new Response(\n\t\t\t\tJSON.stringify({ error: { code: \"INVALID_TOKEN\", message: \"Invalid or expired token\" } }),\n\t\t\t\t{\n\t\t\t\t\tstatus: 401,\n\t\t\t\t\theaders: { \"Content-Type\": \"application/json\", ...MW_CACHE_HEADERS },\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\t// \"none\" — no token presented, try session auth below.\n\t} catch (error) {\n\t\tconsole.error(\"Plugin route bearer auth error:\", error);\n\t}\n\n\ttry {\n\t\t// Try session auth (sets locals.user if session exists)\n\t\tconst { session } = context;\n\t\tconst sessionUser = await resolveSessionUser(session);\n\t\tif (sessionUser?.id && emdash?.db) {\n\t\t\tconst adapter = createKyselyAdapter(emdash.db);\n\t\t\tconst user = await adapter.getUserById(sessionUser.id);\n\t\t\tif (user && !user.disabled) {\n\t\t\t\tlocals.user = user;\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\t// Log but don't block — public routes should still work without session\n\t\tconsole.error(\"Plugin route session auth error:\", error);\n\t}\n\n\treturn next();\n}\n\n/**\n * Soft auth check for public routes with edit mode cookie.\n * Checks the session and sets locals.user if valid, but never blocks the request.\n */\nasync function handlePublicRouteAuth(\n\tcontext: Parameters<Parameters<typeof defineMiddleware>[0]>[0],\n\tnext: Parameters<Parameters<typeof defineMiddleware>[0]>[1],\n): Promise<Response> {\n\tconst { locals, session } = context;\n\tconst { emdash } = locals;\n\n\ttry {\n\t\tconst sessionUser = await resolveSessionUser(session);\n\t\tif (sessionUser?.id && emdash?.db) {\n\t\t\tconst adapter = createKyselyAdapter(emdash.db);\n\t\t\tconst user = await adapter.getUserById(sessionUser.id);\n\t\t\tif (user && !user.disabled) {\n\t\t\t\tlocals.user = user;\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Silently continue — public page should render normally\n\t}\n\n\treturn next();\n}\n\n/**\n * Handle external auth provider authentication (Cloudflare Access, etc.)\n */\nasync function handleExternalAuth(\n\tcontext: Parameters<Parameters<typeof defineMiddleware>[0]>[0],\n\tnext: Parameters<Parameters<typeof defineMiddleware>[0]>[1],\n\tauthMode: ExternalAuthMode,\n\t_isApiRoute: boolean,\n): Promise<Response> {\n\tconst { locals, request } = context;\n\tconst { emdash } = locals;\n\n\ttry {\n\t\t// Use the authenticate function from the virtual module\n\t\t// (statically imported at build time to work with Cloudflare Workers)\n\t\tif (typeof virtualAuthenticate !== \"function\") {\n\t\t\tthrow new Error(\n\t\t\t\t`Auth provider ${authMode.entrypoint} does not export an authenticate function`,\n\t\t\t);\n\t\t}\n\n\t\t// Authenticate via the provider\n\t\tconst authResult = await virtualAuthenticate(request, authMode.config);\n\n\t\t// Get external auth config for auto-provision settings\n\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- narrowing AuthModeConfig to ExternalAuthConfig after provider check\n\t\tconst externalConfig = authMode.config as ExternalAuthConfig;\n\n\t\t// Find or create user\n\t\tconst adapter = createKyselyAdapter(emdash.db);\n\t\tlet user = await adapter.getUserByEmail(authResult.email);\n\n\t\tif (!user) {\n\t\t\t// User doesn't exist\n\t\t\tif (externalConfig.autoProvision === false) {\n\t\t\t\treturn new Response(\"User not authorized\", {\n\t\t\t\t\tstatus: 403,\n\t\t\t\t\theaders: { \"Content-Type\": \"text/plain\", ...MW_CACHE_HEADERS },\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Check if this is the first user (they become admin)\n\t\t\tconst userCount = await emdash.db\n\t\t\t\t.selectFrom(\"users\")\n\t\t\t\t.select(emdash.db.fn.count(\"id\").as(\"count\"))\n\t\t\t\t.executeTakeFirst();\n\n\t\t\tconst isFirstUser = Number(userCount?.count ?? 0) === 0;\n\t\t\tconst role = isFirstUser ? ROLE_ADMIN : authResult.role;\n\n\t\t\t// Create user\n\t\t\tconst now = new Date().toISOString();\n\t\t\tconst newUser = {\n\t\t\t\tid: ulid(),\n\t\t\t\temail: authResult.email,\n\t\t\t\tname: authResult.name,\n\t\t\t\trole,\n\t\t\t\temail_verified: 1,\n\t\t\t\tcreated_at: now,\n\t\t\t\tupdated_at: now,\n\t\t\t};\n\n\t\t\tawait emdash.db.insertInto(\"users\").values(newUser).execute();\n\n\t\t\tuser = await adapter.getUserByEmail(authResult.email);\n\n\t\t\tconsole.log(\n\t\t\t\t`[external-auth] Provisioned user: ${authResult.email} (role: ${role}, first: ${isFirstUser})`,\n\t\t\t);\n\t\t} else {\n\t\t\t// User exists - check if we need to sync anything\n\t\t\tconst updates: Record<string, unknown> = {};\n\t\t\tlet newName: string | undefined;\n\t\t\tlet newRole: RoleLevel | undefined;\n\n\t\t\t// Sync name from provider if provider provides one and local differs\n\t\t\tif (authResult.name && user.name !== authResult.name) {\n\t\t\t\tnewName = authResult.name;\n\t\t\t\tupdates.name = newName;\n\t\t\t}\n\n\t\t\t// Sync role if enabled\n\t\t\tif (externalConfig.syncRoles && user.role !== authResult.role) {\n\t\t\t\tnewRole = authResult.role;\n\t\t\t\tupdates.role = newRole;\n\t\t\t}\n\n\t\t\tif (Object.keys(updates).length > 0) {\n\t\t\t\tupdates.updated_at = new Date().toISOString();\n\t\t\t\tawait emdash.db.updateTable(\"users\").set(updates).where(\"id\", \"=\", user.id).execute();\n\n\t\t\t\tuser = {\n\t\t\t\t\t...user,\n\t\t\t\t\t...(newName ? { name: newName } : {}),\n\t\t\t\t\t...(newRole ? { role: newRole } : {}),\n\t\t\t\t};\n\n\t\t\t\tconsole.log(\n\t\t\t\t\t`[external-auth] Updated user ${authResult.email}:`,\n\t\t\t\t\tObject.keys(updates).filter((k) => k !== \"updated_at\"),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (!user) {\n\t\t\t// This shouldn't happen, but handle it gracefully\n\t\t\treturn new Response(\"Failed to provision user\", {\n\t\t\t\tstatus: 500,\n\t\t\t\theaders: { \"Content-Type\": \"text/plain\", ...MW_CACHE_HEADERS },\n\t\t\t});\n\t\t}\n\n\t\t// Check if user is disabled locally\n\t\tif (user.disabled) {\n\t\t\treturn new Response(\"Account disabled\", {\n\t\t\t\tstatus: 403,\n\t\t\t\theaders: { \"Content-Type\": \"text/plain\", ...MW_CACHE_HEADERS },\n\t\t\t});\n\t\t}\n\n\t\t// Set user in locals\n\t\tlocals.user = user;\n\n\t\t// Persist to session so public pages can identify the user\n\t\t// (external auth headers are only verified on /_emdash routes)\n\t\tconst { session } = context;\n\t\tsession?.set(\"user\", { id: user.id });\n\n\t\treturn next();\n\t} catch (error) {\n\t\tconsole.error(\"[external-auth] Auth error:\", error);\n\n\t\treturn new Response(\"Authentication failed\", {\n\t\t\tstatus: 401,\n\t\t\theaders: { \"Content-Type\": \"text/plain\", ...MW_CACHE_HEADERS },\n\t\t});\n\t}\n}\n\n/**\n * Try to authenticate via Bearer token (API token or OAuth token).\n *\n * Returns:\n * - \"authenticated\" if token is valid and user is resolved\n * - \"invalid\" if a token was provided but is invalid/expired\n * - \"none\" if no Bearer token was provided\n */\nasync function handleBearerAuth(\n\tcontext: Parameters<Parameters<typeof defineMiddleware>[0]>[0],\n): Promise<\"authenticated\" | \"invalid\" | \"none\"> {\n\tconst authHeader = context.request.headers.get(\"Authorization\");\n\tif (!authHeader?.startsWith(\"Bearer \")) return \"none\";\n\n\tconst token = authHeader.slice(7);\n\tif (!token) return \"none\";\n\n\tconst { locals } = context;\n\tconst { emdash } = locals;\n\tif (!emdash?.db) return \"none\";\n\n\t// Resolve token based on prefix\n\tlet resolved: { userId: string; scopes: string[] } | null = null;\n\n\tif (token.startsWith(\"ec_pat_\")) {\n\t\tresolved = await resolveApiToken(emdash.db, token);\n\t} else if (token.startsWith(\"ec_oat_\")) {\n\t\tresolved = await resolveOAuthToken(emdash.db, token);\n\t} else {\n\t\t// Unknown token format\n\t\treturn \"invalid\";\n\t}\n\n\tif (!resolved) return \"invalid\";\n\n\t// Look up the user\n\tconst adapter = createKyselyAdapter(emdash.db);\n\tconst user = await adapter.getUserById(resolved.userId);\n\n\tif (!user || user.disabled) return \"invalid\";\n\n\t// Set user and scopes on locals\n\tlocals.user = user;\n\tlocals.tokenScopes = resolved.scopes;\n\n\treturn \"authenticated\";\n}\n\n/**\n * Handle passkey (session-based) authentication\n */\nasync function handlePasskeyAuth(\n\tcontext: Parameters<Parameters<typeof defineMiddleware>[0]>[0],\n\tnext: Parameters<Parameters<typeof defineMiddleware>[0]>[1],\n\tisApiRoute: boolean,\n): Promise<Response> {\n\tconst { url, locals, session } = context;\n\tconst { emdash } = locals;\n\n\ttry {\n\t\t// Check session for user (session.get returns a Promise)\n\t\tconst sessionUser = await resolveSessionUser(session);\n\n\t\tif (!sessionUser?.id) {\n\t\t\tif (isApiRoute) {\n\t\t\t\treturn Response.json(\n\t\t\t\t\t{ error: { code: \"NOT_AUTHENTICATED\", message: \"Not authenticated\" } },\n\t\t\t\t\t{ status: 401, headers: MW_CACHE_HEADERS },\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst loginUrl = new URL(\"/_emdash/admin/login\", getPublicOrigin(url, emdash?.config));\n\t\t\tloginUrl.searchParams.set(\"redirect\", url.pathname);\n\t\t\treturn context.redirect(loginUrl.toString());\n\t\t}\n\n\t\t// Get full user from database\n\t\tconst adapter = createKyselyAdapter(emdash.db);\n\t\tconst user = await adapter.getUserById(sessionUser.id);\n\n\t\tif (!user) {\n\t\t\t// User no longer exists - clear session\n\t\t\tsession?.destroy();\n\t\t\tif (isApiRoute) {\n\t\t\t\treturn Response.json(\n\t\t\t\t\t{ error: { code: \"NOT_FOUND\", message: \"User not found\" } },\n\t\t\t\t\t{ status: 401, headers: MW_CACHE_HEADERS },\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst loginUrl = new URL(\"/_emdash/admin/login\", getPublicOrigin(url, emdash?.config));\n\t\t\treturn context.redirect(loginUrl.toString());\n\t\t}\n\n\t\t// Check if user is disabled\n\t\tif (user.disabled) {\n\t\t\tsession?.destroy();\n\t\t\tif (isApiRoute) {\n\t\t\t\treturn apiError(\"ACCOUNT_DISABLED\", \"Account disabled\", 403);\n\t\t\t}\n\t\t\tconst loginUrl = new URL(\"/_emdash/admin/login\", getPublicOrigin(url, emdash?.config));\n\t\t\tloginUrl.searchParams.set(\"error\", \"account_disabled\");\n\t\t\treturn context.redirect(loginUrl.toString());\n\t\t}\n\n\t\t// Set user in locals for use by routes\n\t\tlocals.user = user;\n\t} catch (error) {\n\t\tconsole.error(\"Auth middleware error:\", error);\n\t\t// On error, redirect to login\n\t\treturn context.redirect(\"/_emdash/admin/login\");\n\t}\n\n\treturn next();\n}\n\n// =============================================================================\n// Token scope enforcement\n// =============================================================================\n\n/**\n * Scope rules: ordered list of (pathPrefix, method, requiredScope) tuples.\n * First matching rule wins. Methods: \"*\" = any, \"WRITE\" = POST/PUT/PATCH/DELETE.\n *\n * Routes not matched by any rule default to \"admin\" scope (fail-closed).\n */\nconst SCOPE_RULES: Array<[prefix: string, method: string, scope: string]> = [\n\t// Content routes\n\t[\"/_emdash/api/content\", \"GET\", \"content:read\"],\n\t[\"/_emdash/api/content\", \"WRITE\", \"content:write\"],\n\n\t// Media routes (excluding /file/ which is public)\n\t[\"/_emdash/api/media/file\", \"*\", \"media:read\"], // public anyway, but scope if token-authed\n\t[\"/_emdash/api/media\", \"GET\", \"media:read\"],\n\t[\"/_emdash/api/media\", \"WRITE\", \"media:write\"],\n\n\t// Schema routes\n\t[\"/_emdash/api/schema\", \"GET\", \"schema:read\"],\n\t[\"/_emdash/api/schema\", \"WRITE\", \"schema:write\"],\n\n\t// Taxonomy, menu, section, widget, revision — all content domain\n\t// GET uses content:read (implicit from taxonomies:read / menus:read via role).\n\t// WRITE uses the granular scope so tokens with only taxonomies:manage or\n\t// menus:manage are not rejected. content:write implicitly grants these via\n\t// IMPLICIT_SCOPE_GRANTS in @emdash-cms/auth.\n\t[\"/_emdash/api/taxonomies\", \"GET\", \"content:read\"],\n\t[\"/_emdash/api/taxonomies\", \"WRITE\", \"taxonomies:manage\"],\n\t[\"/_emdash/api/menus\", \"GET\", \"content:read\"],\n\t[\"/_emdash/api/menus\", \"WRITE\", \"menus:manage\"],\n\t[\"/_emdash/api/sections\", \"GET\", \"content:read\"],\n\t[\"/_emdash/api/sections\", \"WRITE\", \"content:write\"],\n\t[\"/_emdash/api/widget-areas\", \"GET\", \"content:read\"],\n\t[\"/_emdash/api/widget-areas\", \"WRITE\", \"content:write\"],\n\t[\"/_emdash/api/revisions\", \"GET\", \"content:read\"],\n\t[\"/_emdash/api/revisions\", \"WRITE\", \"content:write\"],\n\n\t// Search\n\t[\"/_emdash/api/search\", \"GET\", \"content:read\"],\n\t[\"/_emdash/api/search\", \"WRITE\", \"admin\"],\n\n\t// Import, admin, plugins — all require admin scope\n\t[\"/_emdash/api/import\", \"*\", \"admin\"],\n\t[\"/_emdash/api/admin\", \"*\", \"admin\"],\n\t[\"/_emdash/api/plugins\", \"*\", \"admin\"],\n\n\t// Settings — use granular scopes so tokens with settings:read or\n\t// settings:manage are not rejected at the middleware level.\n\t[\"/_emdash/api/settings\", \"GET\", \"settings:read\"],\n\t[\"/_emdash/api/settings\", \"WRITE\", \"settings:manage\"],\n\n\t// MCP endpoint — scopes enforced per-tool inside mcp/server.ts\n\t[\"/_emdash/api/mcp\", \"*\", \"content:read\"],\n];\n\nconst WRITE_METHODS = new Set([\"POST\", \"PUT\", \"PATCH\", \"DELETE\"]);\n\n/**\n * Enforce API token scopes based on the request URL and HTTP method.\n * Returns a 403 Response if the scope is insufficient, or null if allowed.\n *\n * Session-authenticated requests (tokenScopes === undefined) are never checked.\n */\nfunction enforceTokenScope(\n\tpathname: string,\n\tmethod: string,\n\ttokenScopes: string[] | undefined,\n): Response | null {\n\t// Session auth — implicit full access, no scope restrictions\n\tif (!tokenScopes) return null;\n\n\tconst isWrite = WRITE_METHODS.has(method);\n\n\tfor (const [prefix, ruleMethod, scope] of SCOPE_RULES) {\n\t\t// Match exact prefix or prefix followed by /\n\t\tif (pathname !== prefix && !pathname.startsWith(prefix + \"/\")) continue;\n\n\t\t// Check method match\n\t\tif (ruleMethod === \"*\" || (ruleMethod === \"WRITE\" && isWrite) || ruleMethod === method) {\n\t\t\tif (hasScope(tokenScopes, scope)) return null;\n\n\t\t\treturn new Response(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcode: \"INSUFFICIENT_SCOPE\",\n\t\t\t\t\t\tmessage: `Token lacks required scope: ${scope}`,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\t{ status: 403, headers: { \"Content-Type\": \"application/json\", ...MW_CACHE_HEADERS } },\n\t\t\t);\n\t\t}\n\t}\n\n\t// No rule matched — default to admin scope (fail-closed)\n\tif (hasScope(tokenScopes, \"admin\")) return null;\n\n\treturn new Response(\n\t\tJSON.stringify({\n\t\t\terror: {\n\t\t\t\tcode: \"INSUFFICIENT_SCOPE\",\n\t\t\t\tmessage: \"Token lacks required scope: admin\",\n\t\t\t},\n\t\t}),\n\t\t{ status: 403, headers: { \"Content-Type\": \"application/json\", ...MW_CACHE_HEADERS } },\n\t);\n}\n"],"mappings":"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"}
@@ -1,9 +1,9 @@
1
1
  import "../../dialect-helpers-DRI5pyY3.mjs";
2
2
  import "../../base64-CmWvODNW.mjs";
3
- import "../../types-CmI1OyQg.mjs";
4
- import { t as RedirectRepository } from "../../redirect-C1KVGYWe.mjs";
5
- import { a as setCachedRedirects, i as matchCachedPatterns, n as getCachedRedirects } from "../../cache-C35VWD5D.mjs";
6
- import { a as getDb } from "../../loader-DrpofRon.mjs";
3
+ import "../../types-DU4TSFS5.mjs";
4
+ import { t as RedirectRepository } from "../../redirect-lMOzG04S.mjs";
5
+ import { a as setCachedRedirects, i as matchCachedPatterns, n as getCachedRedirects } from "../../cache-Ciu3e3AF.mjs";
6
+ import { a as getDb } from "../../loader-4K37qA-y.mjs";
7
7
  import { n as isTerminalStatus } from "../../status-vUK0SA17.mjs";
8
8
  import { defineMiddleware } from "astro:middleware";
9
9
 
@@ -1,14 +1,7 @@
1
- import "../types-B-gIxFCE.mjs";
2
- import { Kysely } from "kysely";
1
+ import "../types-xIfVRNLp.mjs";
2
+ import { t as PublishedRef } from "../scheduled-publish-BMljtY5G.mjs";
3
3
  import * as astro from "astro";
4
4
 
5
- //#region src/scheduled-publish.d.ts
6
- /** A content item that was promoted to published by a sweep. */
7
- interface PublishedRef {
8
- collection: string;
9
- id: string;
10
- }
11
- //#endregion
12
5
  //#region src/astro/middleware.d.ts
13
6
  /**
14
7
  * Run scheduled maintenance (cron tasks, scheduled publishing, system cleanup)
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.mts","names":[],"sources":["../../src/scheduled-publish.ts","../../src/astro/middleware.ts"],"mappings":";;;;;ACgRA;AAAA,UD5PiB,YAAA;EAChB,UAAA;EACA,EAAA;AAAA;;;;;;;;;AC0PD;;;;;;;;iBAAsB,iBAAA,CACrB,OAAA;EAAW,WAAA,IAAe,IAAA,EAAM,YAAA,OAAmB,OAAA;AAAA,IACjD,OAAA;EAAU,SAAA,EAAW,YAAA;AAAA;AAAA,cAoKX,SAAA,EA4ZX,KAAA,CA5ZoB,iBAAA"}
1
+ {"version":3,"file":"middleware.d.mts","names":[],"sources":["../../src/astro/middleware.ts"],"mappings":";;;;;;;;;;;;;;;;;;;iBAgRsB,iBAAA,CACrB,OAAA;EAAW,WAAA,IAAe,IAAA,EAAM,YAAA,OAAmB,OAAA;AAAA,IACjD,OAAA;EAAU,SAAA,EAAW,YAAA;AAAA;AAAA,cAoKX,SAAA,EA6ZX,KAAA,CA7ZoB,iBAAA"}
@@ -1,57 +1,57 @@
1
- import { i as runMigrations, t as MIGRATION_RACE_WAIT_MS } from "../runner-BsquExlK.mjs";
1
+ import { i as runMigrations, t as MIGRATION_RACE_WAIT_MS } from "../runner-CFngB4WZ.mjs";
2
2
  import { t as validateIdentifier } from "../validate-VPnKoIzW.mjs";
3
3
  import { o as isSqlite } from "../dialect-helpers-DRI5pyY3.mjs";
4
4
  import { i as setI18nConfig } from "../config-CVssduLe.mjs";
5
- import { _ as resolveExclusiveHooks, g as createHookPipeline, m as EmailPipeline, p as PluginRouteRegistry, t as getMenu, v as definePlugin } from "../menus-CxqJ-z-N.mjs";
5
+ import { _ as resolveExclusiveHooks, g as createHookPipeline, m as EmailPipeline, p as PluginRouteRegistry, t as getMenu, v as definePlugin } from "../menus-DzRyC1ID.mjs";
6
6
  import { t as after } from "../after-B1IIdH3Y.mjs";
7
7
  import { createRequestMetrics, getRequestContext, runWithContext } from "../request-context.mjs";
8
- import "../object-cache-SEb2IM4Z.mjs";
9
- import { r as RevisionRepository, t as ContentRepository } from "../content-B43-YrIq.mjs";
8
+ import "../object-cache-CHbHv83-.mjs";
9
+ import { r as RevisionRepository, t as ContentRepository } from "../content-RmfHoWqK.mjs";
10
10
  import "../base64-CmWvODNW.mjs";
11
- import "../types-CmI1OyQg.mjs";
12
- import { t as MediaRepository } from "../media-BjKxjzk8.mjs";
13
- import "../user-JAuc8VUf.mjs";
14
- import "../taxonomy-BBUlGug5.mjs";
15
- import "../comment-BDx50EX5.mjs";
11
+ import "../types-DU4TSFS5.mjs";
12
+ import { t as MediaRepository } from "../media-C0nRRfs1.mjs";
13
+ import "../user-DgGbKL9s.mjs";
14
+ import "../taxonomy-Cru-he6s.mjs";
15
+ import "../comment-CH1IrFFs.mjs";
16
16
  import { t as OptionsRepository } from "../options-DTTML-Tx.mjs";
17
- import "../context-BW3N7wyt.mjs";
18
- import "../menus-8O6xNcYW.mjs";
19
- import "../redirect-C1KVGYWe.mjs";
17
+ import "../context-CzUU_WzL.mjs";
18
+ import "../menus-SL2K0xq2.mjs";
19
+ import "../redirect-lMOzG04S.mjs";
20
20
  import { i as setRequestCacheEntry, r as requestCached } from "../request-cache-KCNHp_RJ.mjs";
21
21
  import "../byline-registry-BOjqDOim.mjs";
22
22
  import { n as initWithLock, t as createInitLock } from "../init-lock-6b309ZrF.mjs";
23
- import "../field-defs-cache-CYYx8xlG.mjs";
24
- import "../byline-C-YajfV7.mjs";
23
+ import "../field-defs-cache-BA-8ZPiT.mjs";
24
+ import "../byline-DEKSVPTy.mjs";
25
25
  import { n as normalizeMediaValue } from "../normalize-CeglrBT9.mjs";
26
26
  import "../enrich-BQ0mxJRs.mjs";
27
- import "../seo-BNzkiK_W.mjs";
27
+ import "../seo-Dd3tbY_k.mjs";
28
28
  import { n as isMissingTableError } from "../db-errors-CK46D-ly.mjs";
29
- import { $ as handleContentGet, B as handleMediaUpdate, G as handleContentAuthors, H as handleRevisionList, I as handleMediaCreate, J as handleContentCountTrashed, K as handleContentCompare, L as handleMediaDelete, Q as handleContentDuplicate, R as handleMediaGet, S as PluginStateRepository, U as handleRevisionRestore, V as handleRevisionGet, X as handleContentDelete, Y as handleContentCreate, Z as handleContentDiscardDraft, _ as loadBundleFromR2, at as handleContentRestore, ct as handleContentUnpublish, dt as validateRev, et as handleContentGetIncludingTrashed, it as handleContentPublish, lt as handleContentUnschedule, nt as handleContentListTrashed, ot as handleContentSchedule, q as handleContentCountScheduled, rt as handleContentPermanentDelete, s as normalizeRegistryConfig, st as handleContentTranslations, tt as handleContentList, ut as handleContentUpdate, z as handleMediaList } from "../api-D2_sbavn.mjs";
30
- import "../dashboard-FIe0S512.mjs";
29
+ import { $ as handleContentGet, B as handleMediaUpdate, G as handleContentAuthors, H as handleRevisionList, I as handleMediaCreate, J as handleContentCountTrashed, K as handleContentCompare, L as handleMediaDelete, Q as handleContentDuplicate, R as handleMediaGet, S as PluginStateRepository, U as handleRevisionRestore, V as handleRevisionGet, X as handleContentDelete, Y as handleContentCreate, Z as handleContentDiscardDraft, _ as loadBundleFromR2, at as handleContentRestore, ct as handleContentUnpublish, dt as validateRev, et as handleContentGetIncludingTrashed, it as handleContentPublish, lt as handleContentUnschedule, nt as handleContentListTrashed, ot as handleContentSchedule, q as handleContentCountScheduled, rt as handleContentPermanentDelete, s as normalizeRegistryConfig, st as handleContentTranslations, tt as handleContentList, ut as handleContentUpdate, z as handleMediaList } from "../api-80kycR9o.mjs";
30
+ import "../dashboard-CVyGhift.mjs";
31
31
  import { n as hashString } from "../hash-DrvzIXcz.mjs";
32
- import { t as FTSManager } from "../fts-manager-DW_NR-Fs.mjs";
33
- import { n as SchemaRegistry } from "../registry-3aOAS4My.mjs";
32
+ import { t as FTSManager } from "../fts-manager-Db9jGavf.mjs";
33
+ import { n as SchemaRegistry } from "../registry-DP-iEtdR.mjs";
34
34
  import { createRecorder, flushRecorder, isInstrumentationEnabled, kyselyLogOption } from "../database/instrumentation.mjs";
35
- import { a as getDb } from "../loader-DrpofRon.mjs";
36
- import "../schema-BGwx8AQY.mjs";
37
- import "../zod-generator-xEHODMJf.mjs";
35
+ import { a as getDb } from "../loader-4K37qA-y.mjs";
36
+ import "../schema-BfJ7bIXH.mjs";
37
+ import "../zod-generator-C5JaV--i.mjs";
38
38
  import "../seo-BkhuuaaE.mjs";
39
- import "../sections-C-umuuLj.mjs";
39
+ import "../sections-Dm0CVX23.mjs";
40
40
  import { r as singleFlightCached, t as createSingleFlightCache } from "../single-flight-cache-Cdfkic3t.mjs";
41
- import { i as getSiteSettings, o as invalidateSiteSettingsCache } from "../settings-C4tXRYiG.mjs";
42
- import "../settings-fPSR6dJa.mjs";
41
+ import { i as getSiteSettings, o as invalidateSiteSettingsCache } from "../settings-BX8cWBus.mjs";
42
+ import "../settings-CZ7ZP9cW.mjs";
43
43
  import "../resolve-C7I0qiR0.mjs";
44
- import { a as getTaxonomyDefs, o as getTaxonomyTerms } from "../taxonomies-n8w72lLQ.mjs";
45
- import "../taxonomies-DbAtL6m4.mjs";
46
- import { r as normalizeManifestRoute } from "../manifest-schema-BOfPXse0.mjs";
44
+ import { a as getTaxonomyDefs, o as getTaxonomyTerms } from "../taxonomies-ckPjz-6d.mjs";
45
+ import "../taxonomies-DChKcVxf.mjs";
46
+ import { r as normalizeManifestRoute } from "../manifest-schema-kPGX7VS-.mjs";
47
47
  import "../types-BvbeGEtr.mjs";
48
48
  import "../ssrf-BRKb343l.mjs";
49
- import "../error-DXnK2wF4.mjs";
50
- import "../parse-DqMWHQ8x.mjs";
49
+ import "../error-BHkLZk-w.mjs";
50
+ import "../parse-CqyZj3Hp.mjs";
51
51
  import "../redirects-Dcbuisoj.mjs";
52
52
  import "../byline-fields-eyJMblSr.mjs";
53
53
  import "../status-vUK0SA17.mjs";
54
- import { a as invalidateUrlPatternCache } from "../query-34pfm02Q.mjs";
54
+ import { a as invalidateUrlPatternCache } from "../query-B0M-9LYR.mjs";
55
55
  import "../import-Bb1T9WJS.mjs";
56
56
  import { t as getTrustedProxyHeaders } from "../trusted-proxy-DZY5WCn2.mjs";
57
57
  import { n as sanitizeHeadersForSandbox, t as extractRequestMeta } from "../request-meta-CmS1tDFf.mjs";
@@ -61,14 +61,14 @@ import { r as devConsoleEmailDeliver, t as DEV_CONSOLE_EMAIL_PLUGIN_ID } from ".
61
61
  import "../utils-B7A57fm9.mjs";
62
62
  import "../tokens-LeuXF9gG.mjs";
63
63
  import "../preview-DKGCt2_p.mjs";
64
- import "../comment-reaction-Gv_pgfI3.mjs";
65
- import "../bylines-CPc_q2Vd.mjs";
66
- import { n as getWidgetAreas } from "../widgets-C2RbFN0G.mjs";
67
- import "../apply-Bk2mAuLn.mjs";
68
- import "../load-DMFi4n1p.mjs";
69
- import "../search-Gl747zMP.mjs";
64
+ import "../comment-reaction-kruNY-kP.mjs";
65
+ import "../bylines-CiqgTHWF.mjs";
66
+ import { n as getWidgetAreas } from "../widgets-DRgtn7Cl.mjs";
67
+ import "../apply-DSypk6Iu.mjs";
68
+ import "../load-DpHICVPh.mjs";
69
+ import "../search-C7t3PH1J.mjs";
70
70
  import "../index.mjs";
71
- import { n as VERSION, t as COMMIT } from "../version-DHdCA24u.mjs";
71
+ import { n as VERSION, t as COMMIT } from "../version-CrqhKGWn.mjs";
72
72
  import { t as getAuthMode } from "../mode-BB0F8xTC.mjs";
73
73
  import { t as cleanupExpiredChallenges } from "../challenge-store-LhiqMccz.mjs";
74
74
  import { a as validateEncryptionKeyAtStartup } from "../secrets-BSf9pRRY.mjs";
@@ -925,9 +925,9 @@ var EmDashRuntime = class EmDashRuntime {
925
925
  const seedHolder = getSeedHolder();
926
926
  try {
927
927
  await initWithLock(seedHolder.lock, () => seedHolder.done.has(seedKey) ? true : void 0, async () => {
928
- const { applySeed } = await import("../apply-Bk2mAuLn.mjs").then((n) => n.n);
929
- const { loadSeed } = await import("../load-DMFi4n1p.mjs").then((n) => n.r);
930
- const { validateSeed } = await import("../validate-BzXHWnkV.mjs").then((n) => n.n);
928
+ const { applySeed } = await import("../apply-DSypk6Iu.mjs").then((n) => n.n);
929
+ const { loadSeed } = await import("../load-DpHICVPh.mjs").then((n) => n.r);
930
+ const { validateSeed } = await import("../validate-CRkcYZAR.mjs").then((n) => n.n);
931
931
  const seed = await loadSeed();
932
932
  if (validateSeed(seed).valid) {
933
933
  await applySeed(db, seed, { onConflict: "skip" });
@@ -1663,7 +1663,7 @@ var EmDashRuntime = class EmDashRuntime {
1663
1663
  if (this.hooks.hasHooks("content:beforeSave")) processedData = (await this.hooks.runContentBeforeSave(body.data, collection, true)).content;
1664
1664
  processedData = await this.runSandboxedBeforeSave(processedData, collection, true);
1665
1665
  processedData = await this.normalizeMediaFields(collection, processedData);
1666
- const { validateContentData } = await import("../validation-B6DKPTtU.mjs");
1666
+ const { validateContentData } = await import("../validation-sR26SOwD.mjs");
1667
1667
  const validation = await validateContentData(this.db, collection, processedData, { partial: false });
1668
1668
  if (!validation.ok) return {
1669
1669
  success: false,
@@ -1679,7 +1679,7 @@ var EmDashRuntime = class EmDashRuntime {
1679
1679
  return result;
1680
1680
  }
1681
1681
  async handleContentUpdate(collection, id, body) {
1682
- const { ContentRepository } = await import("../content-B43-YrIq.mjs").then((n) => n.n);
1682
+ const { ContentRepository } = await import("../content-RmfHoWqK.mjs").then((n) => n.n);
1683
1683
  const repo = new ContentRepository(this.db);
1684
1684
  const resolvedItem = await repo.findByIdOrSlug(collection, id, body.locale);
1685
1685
  const resolvedId = resolvedItem?.id ?? id;
@@ -1706,7 +1706,7 @@ var EmDashRuntime = class EmDashRuntime {
1706
1706
  if (this.hooks.hasHooks("content:beforeSave")) processedData = (await this.hooks.runContentBeforeSave(bodyWithoutRev.data, collection, false)).content;
1707
1707
  processedData = await this.runSandboxedBeforeSave(processedData, collection, false);
1708
1708
  processedData = await this.normalizeMediaFields(collection, processedData);
1709
- const { validateContentData } = await import("../validation-B6DKPTtU.mjs");
1709
+ const { validateContentData } = await import("../validation-sR26SOwD.mjs");
1710
1710
  const validation = await validateContentData(this.db, collection, processedData, { partial: true });
1711
1711
  if (!validation.ok) return {
1712
1712
  success: false,
@@ -1812,10 +1812,14 @@ var EmDashRuntime = class EmDashRuntime {
1812
1812
  return result;
1813
1813
  }
1814
1814
  async handleContentSchedule(collection, id, scheduledAt) {
1815
- return handleContentSchedule(this.db, collection, id, scheduledAt);
1815
+ const result = await handleContentSchedule(this.db, collection, id, scheduledAt);
1816
+ if (result.success && result.data) this.runAfterScheduleHooks(contentItemToRecord(result.data.item), collection);
1817
+ return result;
1816
1818
  }
1817
1819
  async handleContentUnschedule(collection, id) {
1818
- return handleContentUnschedule(this.db, collection, id);
1820
+ const result = await handleContentUnschedule(this.db, collection, id);
1821
+ if (result.success && result.data) this.runAfterUnscheduleHooks(contentItemToRecord(result.data.item), collection);
1822
+ return result;
1819
1823
  }
1820
1824
  async handleContentCountScheduled(collection) {
1821
1825
  return handleContentCountScheduled(this.db, collection);
@@ -2093,12 +2097,29 @@ var EmDashRuntime = class EmDashRuntime {
2093
2097
  }).catch((err) => console.error(`EmDash: Sandboxed plugin ${pluginId} afterDelete error:`, err));
2094
2098
  }
2095
2099
  }
2096
- runAfterPublishHooks(content, collection) {
2100
+ runDeferredContentHook(name, content, collection) {
2101
+ const label = name.slice(8);
2097
2102
  after(async () => {
2098
- if (this.hooks.hasHooks("content:afterPublish")) try {
2099
- await this.hooks.runContentAfterPublish(content, collection);
2103
+ if (this.hooks.hasHooks(name)) try {
2104
+ switch (name) {
2105
+ case "content:afterPublish":
2106
+ await this.hooks.runContentAfterPublish(content, collection);
2107
+ break;
2108
+ case "content:afterUnpublish":
2109
+ await this.hooks.runContentAfterUnpublish(content, collection);
2110
+ break;
2111
+ case "content:afterRestore":
2112
+ await this.hooks.runContentAfterRestore(content, collection);
2113
+ break;
2114
+ case "content:afterSchedule":
2115
+ await this.hooks.runContentAfterSchedule(content, collection);
2116
+ break;
2117
+ case "content:afterUnschedule":
2118
+ await this.hooks.runContentAfterUnschedule(content, collection);
2119
+ break;
2120
+ }
2100
2121
  } catch (err) {
2101
- console.error("EmDash afterPublish hook error:", err);
2122
+ console.error(`EmDash ${label} hook error:`, err);
2102
2123
  }
2103
2124
  const tasks = [];
2104
2125
  for (const [pluginKey, plugin] of this.sandboxedPlugins) {
@@ -2106,53 +2127,32 @@ var EmDashRuntime = class EmDashRuntime {
2106
2127
  if (!pluginId || !this.isPluginEnabled(pluginId)) continue;
2107
2128
  tasks.push((async () => {
2108
2129
  try {
2109
- await plugin.invokeHook("content:afterPublish", {
2130
+ await plugin.invokeHook(name, {
2110
2131
  content,
2111
2132
  collection
2112
2133
  });
2113
2134
  } catch (err) {
2114
- console.error(`EmDash: Sandboxed plugin ${pluginId} afterPublish error:`, err);
2135
+ console.error(`EmDash: Sandboxed plugin ${pluginId} ${label} error:`, err);
2115
2136
  }
2116
2137
  })());
2117
2138
  }
2118
2139
  await Promise.allSettled(tasks);
2119
2140
  });
2120
2141
  }
2142
+ runAfterPublishHooks(content, collection) {
2143
+ this.runDeferredContentHook("content:afterPublish", content, collection);
2144
+ }
2121
2145
  runAfterUnpublishHooks(content, collection) {
2122
- if (this.hooks.hasHooks("content:afterUnpublish")) this.hooks.runContentAfterUnpublish(content, collection).catch((err) => console.error("EmDash afterUnpublish hook error:", err));
2123
- for (const [pluginKey, plugin] of this.sandboxedPlugins) {
2124
- const [pluginId] = pluginKey.split(":");
2125
- if (!pluginId || !this.isPluginEnabled(pluginId)) continue;
2126
- plugin.invokeHook("content:afterUnpublish", {
2127
- content,
2128
- collection
2129
- }).catch((err) => console.error(`EmDash: Sandboxed plugin ${pluginId} afterUnpublish error:`, err));
2130
- }
2146
+ this.runDeferredContentHook("content:afterUnpublish", content, collection);
2131
2147
  }
2132
2148
  runAfterRestoreHooks(content, collection) {
2133
- after(async () => {
2134
- if (this.hooks.hasHooks("content:afterRestore")) try {
2135
- await this.hooks.runContentAfterRestore(content, collection);
2136
- } catch (err) {
2137
- console.error("EmDash afterRestore hook error:", err);
2138
- }
2139
- const tasks = [];
2140
- for (const [pluginKey, plugin] of this.sandboxedPlugins) {
2141
- const [pluginId] = pluginKey.split(":");
2142
- if (!pluginId || !this.isPluginEnabled(pluginId)) continue;
2143
- tasks.push((async () => {
2144
- try {
2145
- await plugin.invokeHook("content:afterRestore", {
2146
- content,
2147
- collection
2148
- });
2149
- } catch (err) {
2150
- console.error(`EmDash: Sandboxed plugin ${pluginId} afterRestore error:`, err);
2151
- }
2152
- })());
2153
- }
2154
- await Promise.allSettled(tasks);
2155
- });
2149
+ this.runDeferredContentHook("content:afterRestore", content, collection);
2150
+ }
2151
+ runAfterScheduleHooks(content, collection) {
2152
+ this.runDeferredContentHook("content:afterSchedule", content, collection);
2153
+ }
2154
+ runAfterUnscheduleHooks(content, collection) {
2155
+ this.runDeferredContentHook("content:afterUnschedule", content, collection);
2156
2156
  }
2157
2157
  async handleSandboxedRoute(plugin, path, request) {
2158
2158
  const routeName = path.replace(LEADING_SLASH_PATTERN, "");
@@ -2818,7 +2818,7 @@ const onRequest = defineMiddleware(async (context, next) => {
2818
2818
  if (!isSetupVerified() && !context.isPrerendered) {
2819
2819
  const t0 = performance.now();
2820
2820
  try {
2821
- const { getDb } = await import("../loader-DrpofRon.mjs").then((n) => n.o);
2821
+ const { getDb } = await import("../loader-4K37qA-y.mjs").then((n) => n.o);
2822
2822
  await (await getDb()).selectFrom("_emdash_migrations").selectAll().limit(1).execute();
2823
2823
  markSetupVerified();
2824
2824
  } catch (error) {
@@ -2959,6 +2959,7 @@ const onRequest = defineMiddleware(async (context, next) => {
2959
2959
  hooks: runtime.hooks,
2960
2960
  email: runtime.email,
2961
2961
  configuredPlugins: runtime.configuredPlugins,
2962
+ sandboxedPluginEntries: runtime.sandboxedPluginEntries,
2962
2963
  config,
2963
2964
  getManifest: runtime.getManifest.bind(runtime),
2964
2965
  invalidateUrlPatternCache,