emdash 0.8.0 → 0.10.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 (317) hide show
  1. package/dist/{adapters-BKSf3T9R.d.mts → adapters-BktHA7EO.d.mts} +1 -1
  2. package/dist/{adapters-BKSf3T9R.d.mts.map → adapters-BktHA7EO.d.mts.map} +1 -1
  3. package/dist/{apply-x0eMK1lX.mjs → apply-UsrFuO7l.mjs} +207 -355
  4. package/dist/apply-UsrFuO7l.mjs.map +1 -0
  5. package/dist/astro/index.d.mts +6 -6
  6. package/dist/astro/index.d.mts.map +1 -1
  7. package/dist/astro/index.mjs +118 -4
  8. package/dist/astro/index.mjs.map +1 -1
  9. package/dist/astro/middleware/auth.d.mts +6 -7
  10. package/dist/astro/middleware/auth.d.mts.map +1 -1
  11. package/dist/astro/middleware/auth.mjs +14 -57
  12. package/dist/astro/middleware/auth.mjs.map +1 -1
  13. package/dist/astro/middleware/redirect.d.mts.map +1 -1
  14. package/dist/astro/middleware/redirect.mjs +15 -10
  15. package/dist/astro/middleware/redirect.mjs.map +1 -1
  16. package/dist/astro/middleware/request-context.d.mts.map +1 -1
  17. package/dist/astro/middleware/request-context.mjs +8 -5
  18. package/dist/astro/middleware/request-context.mjs.map +1 -1
  19. package/dist/astro/middleware/setup.mjs +1 -1
  20. package/dist/astro/middleware.d.mts.map +1 -1
  21. package/dist/astro/middleware.mjs +70 -121
  22. package/dist/astro/middleware.mjs.map +1 -1
  23. package/dist/astro/types.d.mts +25 -10
  24. package/dist/astro/types.d.mts.map +1 -1
  25. package/dist/{byline-Chbr2GoP.mjs → byline-C3vnhIpU.mjs} +4 -4
  26. package/dist/{byline-Chbr2GoP.mjs.map → byline-C3vnhIpU.mjs.map} +1 -1
  27. package/dist/bylines-esI7ioa9.mjs +113 -0
  28. package/dist/bylines-esI7ioa9.mjs.map +1 -0
  29. package/dist/cache-fTzxgMFJ.mjs +65 -0
  30. package/dist/cache-fTzxgMFJ.mjs.map +1 -0
  31. package/dist/{chunks-HGz06Soa.mjs → chunks-Da2-b-oA.mjs} +8 -2
  32. package/dist/{chunks-HGz06Soa.mjs.map → chunks-Da2-b-oA.mjs.map} +1 -1
  33. package/dist/cli/index.mjs +456 -90
  34. package/dist/cli/index.mjs.map +1 -1
  35. package/dist/client/cf-access.d.mts +1 -1
  36. package/dist/client/index.d.mts +1 -1
  37. package/dist/client/index.mjs +3 -3
  38. package/dist/client/index.mjs.map +1 -1
  39. package/dist/{config-BXwuX8Bx.mjs → config-CVssduLe.mjs} +1 -1
  40. package/dist/{config-BXwuX8Bx.mjs.map → config-CVssduLe.mjs.map} +1 -1
  41. package/dist/{content-BcQPYxdV.mjs → content-C7G4QXkK.mjs} +42 -14
  42. package/dist/content-C7G4QXkK.mjs.map +1 -0
  43. package/dist/db/index.d.mts +3 -3
  44. package/dist/db/index.mjs +2 -2
  45. package/dist/db/libsql.d.mts +1 -1
  46. package/dist/db/libsql.d.mts.map +1 -1
  47. package/dist/db/libsql.mjs +7 -2
  48. package/dist/db/libsql.mjs.map +1 -1
  49. package/dist/db/postgres.d.mts +1 -1
  50. package/dist/db/sqlite.d.mts +1 -1
  51. package/dist/db/sqlite.d.mts.map +1 -1
  52. package/dist/db/sqlite.mjs +8 -3
  53. package/dist/db/sqlite.mjs.map +1 -1
  54. package/dist/{db-errors-l1Qh2RPR.mjs → db-errors-B7P2pSCn.mjs} +1 -1
  55. package/dist/{db-errors-l1Qh2RPR.mjs.map → db-errors-B7P2pSCn.mjs.map} +1 -1
  56. package/dist/{default-DCVqE5ib.mjs → default-pHuz9WF6.mjs} +1 -1
  57. package/dist/{default-DCVqE5ib.mjs.map → default-pHuz9WF6.mjs.map} +1 -1
  58. package/dist/{dialect-helpers-DhTzaUxP.mjs → dialect-helpers-BKCvISIQ.mjs} +19 -2
  59. package/dist/dialect-helpers-BKCvISIQ.mjs.map +1 -0
  60. package/dist/{error-zG5T1UGA.mjs → error-DqnRMM5z.mjs} +1 -1
  61. package/dist/{error-zG5T1UGA.mjs.map → error-DqnRMM5z.mjs.map} +1 -1
  62. package/dist/{index-DIb-CzNx.d.mts → index-DjPMOfO0.d.mts} +162 -87
  63. package/dist/index-DjPMOfO0.d.mts.map +1 -0
  64. package/dist/index.d.mts +11 -11
  65. package/dist/index.mjs +27 -24
  66. package/dist/{load-CyEoextb.mjs → load-sXRuM7Us.mjs} +2 -2
  67. package/dist/{load-CyEoextb.mjs.map → load-sXRuM7Us.mjs.map} +1 -1
  68. package/dist/{loader-CndGj8kM.mjs → loader-Bx2_9-5e.mjs} +53 -8
  69. package/dist/loader-Bx2_9-5e.mjs.map +1 -0
  70. package/dist/{manifest-schema-DH9xhc6t.mjs → manifest-schema-CXAbd1vH.mjs} +33 -3
  71. package/dist/manifest-schema-CXAbd1vH.mjs.map +1 -0
  72. package/dist/media/index.d.mts +1 -1
  73. package/dist/media/index.mjs +1 -1
  74. package/dist/media/local-runtime.d.mts +7 -7
  75. package/dist/{mode-BnAOqItE.mjs → mode-YhqNVef_.mjs} +1 -1
  76. package/dist/{mode-BnAOqItE.mjs.map → mode-YhqNVef_.mjs.map} +1 -1
  77. package/dist/options-nPxWnrya.mjs +117 -0
  78. package/dist/options-nPxWnrya.mjs.map +1 -0
  79. package/dist/page/index.d.mts +2 -2
  80. package/dist/{patterns-CrCYkMBb.mjs → patterns-DsUZ4uxI.mjs} +1 -1
  81. package/dist/{patterns-CrCYkMBb.mjs.map → patterns-DsUZ4uxI.mjs.map} +1 -1
  82. package/dist/{placeholder-D29tWZ7o.d.mts → placeholder-CDPtkelt.d.mts} +1 -1
  83. package/dist/{placeholder-D29tWZ7o.d.mts.map → placeholder-CDPtkelt.d.mts.map} +1 -1
  84. package/dist/{placeholder-C-fk5hYI.mjs → placeholder-Ci0RLeCk.mjs} +1 -1
  85. package/dist/{placeholder-C-fk5hYI.mjs.map → placeholder-Ci0RLeCk.mjs.map} +1 -1
  86. package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
  87. package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -1
  88. package/dist/plugins/adapt-sandbox-entry.mjs +6 -5
  89. package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -1
  90. package/dist/public-url-B1AxbbbQ.mjs +51 -0
  91. package/dist/public-url-B1AxbbbQ.mjs.map +1 -0
  92. package/dist/{query-fqEdLFms.mjs → query-Bo-msrmu.mjs} +114 -16
  93. package/dist/query-Bo-msrmu.mjs.map +1 -0
  94. package/dist/{redirect-D_pshWdf.mjs → redirect-C5H7VGIX.mjs} +11 -6
  95. package/dist/redirect-C5H7VGIX.mjs.map +1 -0
  96. package/dist/{registry-C3Mr0ODu.mjs → registry-Beb7wxFc.mjs} +39 -5
  97. package/dist/registry-Beb7wxFc.mjs.map +1 -0
  98. package/dist/{request-cache-Ci7f5pBb.mjs → request-cache-C-tIpYIw.mjs} +1 -1
  99. package/dist/{request-cache-Ci7f5pBb.mjs.map → request-cache-C-tIpYIw.mjs.map} +1 -1
  100. package/dist/runner-Clwe4Mme.d.mts +44 -0
  101. package/dist/runner-Clwe4Mme.d.mts.map +1 -0
  102. package/dist/{runner-tQ7BJ4T7.mjs → runner-DMnlIkh4.mjs} +616 -191
  103. package/dist/runner-DMnlIkh4.mjs.map +1 -0
  104. package/dist/runtime.d.mts +6 -6
  105. package/dist/runtime.mjs +2 -2
  106. package/dist/{search-BoZYFuUk.mjs → search-DkN-BqsS.mjs} +270 -152
  107. package/dist/search-DkN-BqsS.mjs.map +1 -0
  108. package/dist/secrets-CZ8rxLX3.mjs +314 -0
  109. package/dist/secrets-CZ8rxLX3.mjs.map +1 -0
  110. package/dist/seed/index.d.mts +2 -2
  111. package/dist/seed/index.mjs +13 -11
  112. package/dist/seo/index.d.mts +1 -1
  113. package/dist/storage/local.d.mts +1 -1
  114. package/dist/storage/local.mjs +1 -1
  115. package/dist/storage/s3.d.mts +1 -1
  116. package/dist/storage/s3.mjs +1 -1
  117. package/dist/taxonomies-CTtewrSQ.mjs +407 -0
  118. package/dist/taxonomies-CTtewrSQ.mjs.map +1 -0
  119. package/dist/taxonomy-DSxx2K2L.mjs +218 -0
  120. package/dist/taxonomy-DSxx2K2L.mjs.map +1 -0
  121. package/dist/{tokens-D9vnZqYS.mjs → tokens-CyRDPVW2.mjs} +1 -1
  122. package/dist/{tokens-D9vnZqYS.mjs.map → tokens-CyRDPVW2.mjs.map} +1 -1
  123. package/dist/{transaction-Cn2rjY78.mjs → transaction-D44LBXvU.mjs} +1 -1
  124. package/dist/{transaction-Cn2rjY78.mjs.map → transaction-D44LBXvU.mjs.map} +1 -1
  125. package/dist/{transport-CUnEL3Vs.d.mts → transport-DX_5rpsq.d.mts} +1 -1
  126. package/dist/{transport-CUnEL3Vs.d.mts.map → transport-DX_5rpsq.d.mts.map} +1 -1
  127. package/dist/{transport-C9ugt2Nr.mjs → transport-xpzIjCIB.mjs} +6 -5
  128. package/dist/{transport-C9ugt2Nr.mjs.map → transport-xpzIjCIB.mjs.map} +1 -1
  129. package/dist/{types-BrA0xf5I.d.mts → types-B_CXXnzh.d.mts} +1 -1
  130. package/dist/{types-BrA0xf5I.d.mts.map → types-B_CXXnzh.d.mts.map} +1 -1
  131. package/dist/{types-DIMwPFub.d.mts → types-C-aFbqmA.d.mts} +1 -1
  132. package/dist/{types-DIMwPFub.d.mts.map → types-C-aFbqmA.d.mts.map} +1 -1
  133. package/dist/types-CoO6mpV3.mjs +68 -0
  134. package/dist/types-CoO6mpV3.mjs.map +1 -0
  135. package/dist/{types-i36XcA_X.d.mts → types-D19uBYWn.d.mts} +83 -7
  136. package/dist/types-D19uBYWn.d.mts.map +1 -0
  137. package/dist/{types-BmPPSUEx.d.mts → types-Dl1fgFjn.d.mts} +24 -2
  138. package/dist/{types-BmPPSUEx.d.mts.map → types-Dl1fgFjn.d.mts.map} +1 -1
  139. package/dist/{types-CS8FIX7L.d.mts → types-Dtx1mSMX.d.mts} +9 -1
  140. package/dist/types-Dtx1mSMX.d.mts.map +1 -0
  141. package/dist/{types-Bm1dn-q3.mjs → types-Eg829jj9.mjs} +1 -1
  142. package/dist/{types-Bm1dn-q3.mjs.map → types-Eg829jj9.mjs.map} +1 -1
  143. package/dist/{types-CgqmmMJB.mjs → types-K-EkEQCI.mjs} +1 -1
  144. package/dist/{types-CgqmmMJB.mjs.map → types-K-EkEQCI.mjs.map} +1 -1
  145. package/dist/{validate-CxVsLehf.mjs → validate-CBIbxM3L.mjs} +14 -10
  146. package/dist/validate-CBIbxM3L.mjs.map +1 -0
  147. package/dist/{validate-DHxmpFJt.d.mts → validate-DHGwADqO.d.mts} +18 -5
  148. package/dist/validate-DHGwADqO.d.mts.map +1 -0
  149. package/dist/{validation-C-ZpN2GI.mjs → validation-B1NYiEos.mjs} +6 -6
  150. package/dist/{validation-C-ZpN2GI.mjs.map → validation-B1NYiEos.mjs.map} +1 -1
  151. package/dist/version-CMD42IRC.mjs +7 -0
  152. package/dist/{version-Bbq8TCrz.mjs.map → version-CMD42IRC.mjs.map} +1 -1
  153. package/dist/{zod-generator-CpwccCIv.mjs → zod-generator-BNJDQBSZ.mjs} +11 -6
  154. package/dist/{zod-generator-CpwccCIv.mjs.map → zod-generator-BNJDQBSZ.mjs.map} +1 -1
  155. package/locals.d.ts +1 -6
  156. package/package.json +9 -8
  157. package/src/api/handlers/comments.ts +6 -4
  158. package/src/api/handlers/content.ts +40 -1
  159. package/src/api/handlers/dashboard.ts +29 -36
  160. package/src/api/handlers/device-flow.ts +5 -0
  161. package/src/api/handlers/marketplace.ts +11 -4
  162. package/src/api/handlers/menus.ts +256 -75
  163. package/src/api/handlers/oauth-authorization.ts +72 -33
  164. package/src/api/handlers/revision.ts +23 -14
  165. package/src/api/handlers/taxonomies.ts +273 -100
  166. package/src/api/public-url.ts +48 -2
  167. package/src/api/schemas/comments.ts +2 -2
  168. package/src/api/schemas/common.ts +7 -0
  169. package/src/api/schemas/content.ts +17 -0
  170. package/src/api/schemas/menus.ts +23 -0
  171. package/src/api/schemas/sections.ts +3 -3
  172. package/src/api/schemas/taxonomies.ts +39 -0
  173. package/src/api/schemas/users.ts +1 -1
  174. package/src/api/types.ts +5 -1
  175. package/src/astro/integration/index.ts +17 -0
  176. package/src/astro/integration/routes.ts +10 -0
  177. package/src/astro/integration/runtime.ts +30 -0
  178. package/src/astro/integration/virtual-modules.ts +32 -2
  179. package/src/astro/integration/vite-config.ts +6 -1
  180. package/src/astro/middleware/auth.ts +13 -6
  181. package/src/astro/middleware/redirect.ts +29 -16
  182. package/src/astro/middleware/request-context.ts +15 -5
  183. package/src/astro/middleware.ts +23 -9
  184. package/src/astro/routes/api/auth/invite/complete.ts +6 -1
  185. package/src/astro/routes/api/auth/passkey/register/verify.ts +6 -1
  186. package/src/astro/routes/api/auth/passkey/verify.ts +6 -1
  187. package/src/astro/routes/api/auth/signup/complete.ts +6 -1
  188. package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +2 -2
  189. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +4 -2
  190. package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +1 -1
  191. package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +34 -12
  192. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +32 -2
  193. package/src/astro/routes/api/content/[collection]/[id]/restore.ts +4 -2
  194. package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +3 -2
  195. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +8 -4
  196. package/src/astro/routes/api/content/[collection]/[id].ts +12 -0
  197. package/src/astro/routes/api/import/wordpress/execute.ts +3 -1
  198. package/src/astro/routes/api/import/wordpress/prepare.ts +7 -8
  199. package/src/astro/routes/api/import/wordpress/rewrite-url-helpers.ts +196 -0
  200. package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +9 -177
  201. package/src/astro/routes/api/import/wordpress-plugin/execute.ts +3 -1
  202. package/src/astro/routes/api/manifest.ts +62 -45
  203. package/src/astro/routes/api/media/[id]/confirm.ts +10 -1
  204. package/src/astro/routes/api/media/providers/[providerId]/index.ts +12 -3
  205. package/src/astro/routes/api/menus/[name]/items.ts +16 -6
  206. package/src/astro/routes/api/menus/[name]/reorder.ts +8 -3
  207. package/src/astro/routes/api/menus/[name]/translations.ts +82 -0
  208. package/src/astro/routes/api/menus/[name].ts +19 -10
  209. package/src/astro/routes/api/menus/index.ts +9 -6
  210. package/src/astro/routes/api/openapi.json.ts +27 -10
  211. package/src/astro/routes/api/redirects/404s/index.ts +10 -4
  212. package/src/astro/routes/api/redirects/404s/summary.ts +4 -2
  213. package/src/astro/routes/api/redirects/[id].ts +10 -4
  214. package/src/astro/routes/api/redirects/index.ts +7 -3
  215. package/src/astro/routes/api/revisions/[revisionId]/index.ts +1 -1
  216. package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +0 -2
  217. package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +0 -1
  218. package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +0 -1
  219. package/src/astro/routes/api/schema/collections/[slug]/index.ts +2 -2
  220. package/src/astro/routes/api/schema/collections/index.ts +1 -1
  221. package/src/astro/routes/api/search/index.ts +10 -2
  222. package/src/astro/routes/api/sections/[slug].ts +10 -4
  223. package/src/astro/routes/api/sections/index.ts +7 -3
  224. package/src/astro/routes/api/setup/admin-verify.ts +6 -1
  225. package/src/astro/routes/api/snapshot.ts +44 -18
  226. package/src/astro/routes/api/taxonomies/[name]/terms/[slug]/translations.ts +89 -0
  227. package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +22 -22
  228. package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +11 -14
  229. package/src/astro/routes/api/taxonomies/index.ts +9 -7
  230. package/src/astro/routes/api/themes/preview.ts +11 -5
  231. package/src/astro/types.ts +23 -3
  232. package/src/auth/allowed-origins.ts +168 -0
  233. package/src/auth/passkey-config.ts +35 -13
  234. package/src/bylines/index.ts +37 -88
  235. package/src/cli/commands/auth.ts +28 -6
  236. package/src/cli/commands/bundle-utils.ts +11 -2
  237. package/src/cli/commands/bundle.ts +28 -8
  238. package/src/cli/commands/content.ts +13 -0
  239. package/src/cli/commands/export-seed.ts +82 -21
  240. package/src/cli/commands/login.ts +8 -1
  241. package/src/cli/commands/plugin-init.ts +216 -90
  242. package/src/cli/commands/publish.ts +24 -0
  243. package/src/cli/commands/secrets.ts +183 -0
  244. package/src/cli/credentials.ts +1 -1
  245. package/src/cli/index.ts +5 -1
  246. package/src/client/index.ts +4 -4
  247. package/src/client/transport.ts +17 -7
  248. package/src/components/Break.astro +2 -2
  249. package/src/components/EmDashHead.astro +18 -13
  250. package/src/components/Embed.astro +1 -1
  251. package/src/components/Gallery.astro +1 -1
  252. package/src/components/Image.astro +1 -1
  253. package/src/components/InlinePortableTextEditor.tsx +104 -18
  254. package/src/config/secrets.ts +528 -0
  255. package/src/database/dialect-helpers.ts +50 -0
  256. package/src/database/migrations/034_published_at_index.ts +1 -1
  257. package/src/database/migrations/035_bounded_404_log.ts +56 -39
  258. package/src/database/migrations/036_i18n_menus_and_taxonomies.ts +477 -0
  259. package/src/database/migrations/runner.ts +158 -23
  260. package/src/database/repositories/content.ts +47 -12
  261. package/src/database/repositories/redirect.ts +14 -3
  262. package/src/database/repositories/taxonomy.ts +212 -82
  263. package/src/database/types.ts +10 -2
  264. package/src/db/libsql.ts +1 -3
  265. package/src/db/sqlite.ts +2 -5
  266. package/src/emdash-runtime.ts +84 -159
  267. package/src/i18n/resolve.ts +37 -0
  268. package/src/index.ts +9 -0
  269. package/src/loader.ts +73 -3
  270. package/src/mcp/server.ts +180 -54
  271. package/src/menus/index.ts +143 -124
  272. package/src/menus/types.ts +15 -1
  273. package/src/page/site-identity.ts +58 -0
  274. package/src/plugins/adapt-sandbox-entry.ts +22 -10
  275. package/src/plugins/context.ts +13 -10
  276. package/src/plugins/define-plugin.ts +40 -12
  277. package/src/plugins/hooks.ts +23 -19
  278. package/src/plugins/index.ts +9 -0
  279. package/src/plugins/manifest-schema.ts +37 -2
  280. package/src/plugins/types.ts +151 -11
  281. package/src/preview/urls.ts +23 -3
  282. package/src/query.ts +148 -5
  283. package/src/redirects/cache.ts +38 -18
  284. package/src/schema/registry.ts +56 -0
  285. package/src/schema/zod-generator.ts +39 -7
  286. package/src/seed/apply.ts +142 -54
  287. package/src/seed/types.ts +14 -1
  288. package/src/seed/validate.ts +27 -13
  289. package/src/settings/index.ts +80 -6
  290. package/src/settings/types.ts +23 -1
  291. package/src/taxonomies/index.ts +237 -210
  292. package/src/taxonomies/types.ts +10 -0
  293. package/dist/apply-x0eMK1lX.mjs.map +0 -1
  294. package/dist/bylines-CRNsVG88.mjs +0 -157
  295. package/dist/bylines-CRNsVG88.mjs.map +0 -1
  296. package/dist/cache-BkKBuIvS.mjs +0 -56
  297. package/dist/cache-BkKBuIvS.mjs.map +0 -1
  298. package/dist/chunk-ClPoSABd.mjs +0 -21
  299. package/dist/content-BcQPYxdV.mjs.map +0 -1
  300. package/dist/dialect-helpers-DhTzaUxP.mjs.map +0 -1
  301. package/dist/index-DIb-CzNx.d.mts.map +0 -1
  302. package/dist/loader-CndGj8kM.mjs.map +0 -1
  303. package/dist/manifest-schema-DH9xhc6t.mjs.map +0 -1
  304. package/dist/query-fqEdLFms.mjs.map +0 -1
  305. package/dist/redirect-D_pshWdf.mjs.map +0 -1
  306. package/dist/registry-C3Mr0ODu.mjs.map +0 -1
  307. package/dist/runner-OURCaApa.d.mts +0 -34
  308. package/dist/runner-OURCaApa.d.mts.map +0 -1
  309. package/dist/runner-tQ7BJ4T7.mjs.map +0 -1
  310. package/dist/search-BoZYFuUk.mjs.map +0 -1
  311. package/dist/taxonomies-B4IAshV8.mjs +0 -308
  312. package/dist/taxonomies-B4IAshV8.mjs.map +0 -1
  313. package/dist/types-CS8FIX7L.d.mts.map +0 -1
  314. package/dist/types-i36XcA_X.d.mts.map +0 -1
  315. package/dist/validate-CxVsLehf.mjs.map +0 -1
  316. package/dist/validate-DHxmpFJt.d.mts.map +0 -1
  317. package/dist/version-Bbq8TCrz.mjs +0 -7
@@ -8,7 +8,7 @@
8
8
  * utilities. Token infrastructure is shared with the device flow.
9
9
  */
10
10
 
11
- import { clampScopes, computeS256Challenge } from "@emdash-cms/auth";
11
+ import { clampScopes, computeS256Challenge, secureCompare } from "@emdash-cms/auth";
12
12
  import type { RoleLevel } from "@emdash-cms/auth";
13
13
  import { generateCodeVerifier } from "arctic";
14
14
  import type { Kysely } from "kysely";
@@ -19,10 +19,12 @@ import {
19
19
  TOKEN_PREFIXES,
20
20
  VALID_SCOPES,
21
21
  } from "../../auth/api-tokens.js";
22
+ import { withTransaction } from "../../database/transaction.js";
22
23
  import type { Database } from "../../database/types.js";
23
24
  import { validateRedirectUri } from "../oauth/redirect-uri.js";
24
25
  import type { ApiResult } from "../types.js";
25
26
  import { lookupOAuthClient, validateClientRedirectUri } from "./oauth-clients.js";
27
+ import { lookupUserRoleAndStatus } from "./oauth-user-lookup.js";
26
28
 
27
29
  // ---------------------------------------------------------------------------
28
30
  // Constants
@@ -296,8 +298,9 @@ export async function handleAuthorizationCodeExchange(
296
298
  }
297
299
 
298
300
  // PKCE verification: SHA256(code_verifier) must match stored code_challenge
301
+ // Use constant-time comparison to prevent timing side-channels
299
302
  const derivedChallenge = computeS256Challenge(params.code_verifier);
300
- if (derivedChallenge !== row.code_challenge) {
303
+ if (!secureCompare(derivedChallenge, row.code_challenge)) {
301
304
  return {
302
305
  success: false,
303
306
  error: { code: "invalid_grant", message: "PKCE verification failed" },
@@ -312,44 +315,80 @@ export async function handleAuthorizationCodeExchange(
312
315
  };
313
316
  }
314
317
 
315
- // Issue tokens (same as device flow)
316
- const scopes = JSON.parse(row.scopes) as string[];
318
+ // Revalidate user role before issuing tokens (same pattern as handleTokenRefresh).
319
+ // The user's role may have changed since the authorization code was issued.
320
+ const userInfo = await lookupUserRoleAndStatus(db, row.user_id);
321
+ if (!userInfo) {
322
+ return {
323
+ success: false,
324
+ error: { code: "invalid_grant", message: "User not found" },
325
+ };
326
+ }
317
327
 
328
+ if (userInfo.disabled) {
329
+ return {
330
+ success: false,
331
+ error: { code: "invalid_grant", message: "User account is disabled" },
332
+ };
333
+ }
334
+
335
+ // Re-clamp scopes against the user's current role
336
+ const storedScopes = JSON.parse(row.scopes) as string[];
337
+ let scopes = clampScopes(storedScopes, userInfo.role);
338
+
339
+ // Intersect with client's registered scopes (if restricted)
340
+ const client = await lookupOAuthClient(db, row.client_id);
341
+ if (client?.scopes?.length) {
342
+ scopes = scopes.filter((s: string) => client.scopes!.includes(s));
343
+ }
344
+
345
+ if (scopes.length === 0) {
346
+ return {
347
+ success: false,
348
+ error: {
349
+ code: "invalid_grant",
350
+ message: "User role no longer supports any of the requested scopes",
351
+ },
352
+ };
353
+ }
354
+
355
+ // Issue tokens (same as device flow)
318
356
  const accessToken = generatePrefixedToken(TOKEN_PREFIXES.OAUTH_ACCESS);
319
357
  const accessExpires = expiresAt(ACCESS_TOKEN_TTL_SECONDS);
320
358
 
321
359
  const refreshToken = generatePrefixedToken(TOKEN_PREFIXES.OAUTH_REFRESH);
322
360
  const refreshExpires = expiresAt(REFRESH_TOKEN_TTL_SECONDS);
323
361
 
324
- // Store access token
325
- await db
326
- .insertInto("_emdash_oauth_tokens")
327
- .values({
328
- token_hash: accessToken.hash,
329
- token_type: "access",
330
- user_id: row.user_id,
331
- scopes: JSON.stringify(scopes),
332
- client_type: "mcp",
333
- expires_at: accessExpires,
334
- refresh_token_hash: refreshToken.hash,
335
- client_id: row.client_id,
336
- })
337
- .execute();
338
-
339
- // Store refresh token
340
- await db
341
- .insertInto("_emdash_oauth_tokens")
342
- .values({
343
- token_hash: refreshToken.hash,
344
- token_type: "refresh",
345
- user_id: row.user_id,
346
- scopes: JSON.stringify(scopes),
347
- client_type: "mcp",
348
- expires_at: refreshExpires,
349
- refresh_token_hash: null,
350
- client_id: row.client_id,
351
- })
352
- .execute();
362
+ // Atomically store both tokens in a transaction
363
+ await withTransaction(db, async (trx) => {
364
+ await trx
365
+ .insertInto("_emdash_oauth_tokens")
366
+ .values({
367
+ token_hash: accessToken.hash,
368
+ token_type: "access",
369
+ user_id: row.user_id,
370
+ scopes: JSON.stringify(scopes),
371
+ client_type: "mcp",
372
+ expires_at: accessExpires,
373
+ refresh_token_hash: refreshToken.hash,
374
+ client_id: row.client_id,
375
+ })
376
+ .execute();
377
+
378
+ await trx
379
+ .insertInto("_emdash_oauth_tokens")
380
+ .values({
381
+ token_hash: refreshToken.hash,
382
+ token_type: "refresh",
383
+ user_id: row.user_id,
384
+ scopes: JSON.stringify(scopes),
385
+ client_type: "mcp",
386
+ expires_at: refreshExpires,
387
+ refresh_token_hash: null,
388
+ client_id: row.client_id,
389
+ })
390
+ .execute();
391
+ });
353
392
 
354
393
  return {
355
394
  success: true,
@@ -6,6 +6,7 @@ import type { Kysely } from "kysely";
6
6
 
7
7
  import { ContentRepository } from "../../database/repositories/content.js";
8
8
  import { RevisionRepository, type Revision } from "../../database/repositories/revision.js";
9
+ import { withTransaction } from "../../database/transaction.js";
9
10
  import type { Database } from "../../database/types.js";
10
11
  import type { ApiResult, ContentResponse } from "../types.js";
11
12
 
@@ -95,7 +96,6 @@ export async function handleRevisionRestore(
95
96
  ): Promise<ApiResult<ContentResponse>> {
96
97
  try {
97
98
  const revisionRepo = new RevisionRepository(db);
98
- const contentRepo = new ContentRepository(db);
99
99
 
100
100
  // Get the revision
101
101
  const revision = await revisionRepo.findById(revisionId);
@@ -112,22 +112,31 @@ export async function handleRevisionRestore(
112
112
  // Extract _slug from revision data (stored as metadata, not a real column)
113
113
  const { _slug, ...fieldData } = revision.data;
114
114
 
115
- // Update the content with the revision's data
116
- const item = await contentRepo.update(revision.collection, revision.entryId, {
117
- data: fieldData,
118
- slug: typeof _slug === "string" ? _slug : undefined,
119
- });
120
-
121
- // Create a new revision to record the restore, attributed to the caller
122
- await revisionRepo.create({
123
- collection: revision.collection,
124
- entryId: revision.entryId,
125
- data: revision.data,
126
- authorId: callerUserId,
115
+ // Atomically update content and create a new revision to record the restore.
116
+ // If either operation fails, neither is committed (on engines that support
117
+ // transactions; on D1, withTransaction falls back to sequential execution).
118
+ const item = await withTransaction(db, async (trx) => {
119
+ const trxContentRepo = new ContentRepository(trx);
120
+ const trxRevisionRepo = new RevisionRepository(trx);
121
+
122
+ const updated = await trxContentRepo.update(revision.collection, revision.entryId, {
123
+ data: fieldData,
124
+ slug: typeof _slug === "string" ? _slug : undefined,
125
+ });
126
+
127
+ await trxRevisionRepo.create({
128
+ collection: revision.collection,
129
+ entryId: revision.entryId,
130
+ data: revision.data,
131
+ authorId: callerUserId,
132
+ });
133
+
134
+ return updated;
127
135
  });
128
136
 
129
137
  // Fire-and-forget: prune old revisions to prevent unbounded growth
130
- void revisionRepo.pruneOldRevisions(revision.collection, revision.entryId, 50).catch(() => {});
138
+ const pruneRepo = new RevisionRepository(db);
139
+ void pruneRepo.pruneOldRevisions(revision.collection, revision.entryId, 50).catch(() => {});
131
140
 
132
141
  return {
133
142
  success: true,