dineway 0.1.3 → 0.1.5

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 (296) hide show
  1. package/README.md +6 -3
  2. package/dist/{apply-CAPvMfoU.mjs → apply-iVSqz2qs.mjs} +132 -39
  3. package/dist/astro/index.d.mts +18 -9
  4. package/dist/astro/index.mjs +238 -16
  5. package/dist/astro/middleware/auth.d.mts +16 -5
  6. package/dist/astro/middleware/auth.mjs +74 -37
  7. package/dist/astro/middleware/redirect.mjs +24 -8
  8. package/dist/astro/middleware/request-context.mjs +18 -5
  9. package/dist/astro/middleware/setup.mjs +1 -1
  10. package/dist/astro/middleware.mjs +411 -169
  11. package/dist/astro/types.d.mts +25 -8
  12. package/dist/{byline-DeWCMU_i.mjs → byline-OhH2dlRu.mjs} +6 -21
  13. package/dist/{bylines-DyqBV9EQ.mjs → bylines-BGpD9_hy.mjs} +16 -6
  14. package/dist/cache-BdSY-gQN.mjs +42 -0
  15. package/dist/chunks--4F8ddV4.mjs +18 -0
  16. package/dist/cli/index.mjs +935 -15
  17. package/dist/client/external-auth-headers.d.mts +1 -1
  18. package/dist/client/index.d.mts +11 -3
  19. package/dist/client/index.mjs +4 -3
  20. package/dist/{connection-C9pxzuag.mjs → connection-BCNICDWN.mjs} +22 -5
  21. package/dist/{content-zSgdNmnt.mjs → content-DWi4d0rT.mjs} +41 -2
  22. package/dist/database/instrumentation.d.mts +34 -0
  23. package/dist/database/instrumentation.mjs +53 -0
  24. package/dist/db/index.d.mts +3 -3
  25. package/dist/db/index.mjs +2 -2
  26. package/dist/db/libsql.d.mts +1 -1
  27. package/dist/db/libsql.mjs +11 -5
  28. package/dist/db/postgres.d.mts +1 -1
  29. package/dist/db/sqlite.d.mts +1 -1
  30. package/dist/db/sqlite.mjs +7 -1
  31. package/dist/db-errors-CEqD7qH9.mjs +23 -0
  32. package/dist/{default-WYlzADZL.mjs → default-VjJyuuG9.mjs} +2 -0
  33. package/dist/{dialect-helpers-B9uSp2GJ.mjs → dialect-helpers-DhTzaUxP.mjs} +3 -0
  34. package/dist/{error-DrxtnGPg.mjs → error-BmL6QipT.mjs} +7 -3
  35. package/dist/{index-C-jx21qs.d.mts → index-yvc6E_17.d.mts} +157 -30
  36. package/dist/index.d.mts +11 -11
  37. package/dist/index.mjs +24 -22
  38. package/dist/{loader-qKmo0wAY.mjs → loader-sMG4TZ-u.mjs} +9 -3
  39. package/dist/media/index.d.mts +1 -1
  40. package/dist/media/index.mjs +1 -1
  41. package/dist/media/local-runtime.d.mts +7 -7
  42. package/dist/page/index.d.mts +10 -2
  43. package/dist/page/index.mjs +22 -1
  44. package/dist/patterns-CrCYkMBb.mjs +92 -0
  45. package/dist/{placeholder-bOx1xCTY.d.mts → placeholder--wOi4TbO.d.mts} +1 -1
  46. package/dist/{placeholder-B3knXwNc.mjs → placeholder-Cp8g5Emj.mjs} +1 -1
  47. package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
  48. package/dist/plugins/adapt-sandbox-entry.mjs +1 -1
  49. package/dist/{query-BiaPl_g2.mjs → query-kDmwCsHh.mjs} +118 -50
  50. package/dist/{redirect-JPqLAbxa.mjs → redirect-DnEWAkVg.mjs} +43 -99
  51. package/dist/{registry-DSd1GWB8.mjs → registry-C0zjeB9P.mjs} +191 -123
  52. package/dist/request-cache-Dk5qPSOx.mjs +66 -0
  53. package/dist/request-context.d.mts +4 -16
  54. package/dist/{runner-B5l1JfOj.d.mts → runner-CFI6B6J2.d.mts} +1 -1
  55. package/dist/{runner-BGUGywgG.mjs → runner-DWZm2KQm.mjs} +589 -137
  56. package/dist/runtime.d.mts +6 -6
  57. package/dist/runtime.mjs +2 -2
  58. package/dist/{search-BNruJHDL.mjs → search-ByRGV2pq.mjs} +570 -424
  59. package/dist/seed/index.d.mts +2 -2
  60. package/dist/seed/index.mjs +11 -10
  61. package/dist/seo/index.d.mts +1 -1
  62. package/dist/storage/local.d.mts +1 -1
  63. package/dist/storage/local.mjs +1 -1
  64. package/dist/storage/s3.d.mts +11 -3
  65. package/dist/storage/s3.mjs +78 -15
  66. package/dist/taxonomies-1s5PaS_8.mjs +266 -0
  67. package/dist/transaction-Cn2rjY78.mjs +27 -0
  68. package/dist/{types-BgQeVaPj.d.mts → types-BuMDPy5C.d.mts} +52 -3
  69. package/dist/{types-DuNbGKjF.mjs → types-COeOq9nK.mjs} +6 -1
  70. package/dist/{types-ju-_ORz7.d.mts → types-CWbdtiux.d.mts} +13 -5
  71. package/dist/{types-D38djUXv.d.mts → types-Cj0KMIZV.d.mts} +16 -3
  72. package/dist/{types-DkvMXalq.d.mts → types-DOrVigru.d.mts} +159 -0
  73. package/dist/{validate-CXnRKfJK.mjs → validate-BZ5wnLLp.mjs} +2 -1
  74. package/dist/{validate-DVKJJ-M_.d.mts → validate-IPf8n4Fj.d.mts} +4 -51
  75. package/dist/{validate-CqRJb_xU.mjs → validate-VPnKoIzW.mjs} +10 -10
  76. package/dist/version-BKXPsfmJ.mjs +6 -0
  77. package/package.json +53 -39
  78. package/src/astro/routes/PluginRegistry.tsx +21 -0
  79. package/src/astro/routes/admin.astro +99 -0
  80. package/src/astro/routes/api/admin/allowed-domains/[domain].ts +112 -0
  81. package/src/astro/routes/api/admin/allowed-domains/index.ts +108 -0
  82. package/src/astro/routes/api/admin/api-tokens/[id].ts +44 -0
  83. package/src/astro/routes/api/admin/api-tokens/index.ts +90 -0
  84. package/src/astro/routes/api/admin/briefing.ts +76 -0
  85. package/src/astro/routes/api/admin/bylines/[id]/index.ts +90 -0
  86. package/src/astro/routes/api/admin/bylines/index.ts +74 -0
  87. package/src/astro/routes/api/admin/comments/[id]/status.ts +120 -0
  88. package/src/astro/routes/api/admin/comments/[id].ts +64 -0
  89. package/src/astro/routes/api/admin/comments/bulk.ts +42 -0
  90. package/src/astro/routes/api/admin/comments/counts.ts +30 -0
  91. package/src/astro/routes/api/admin/comments/index.ts +46 -0
  92. package/src/astro/routes/api/admin/context/[id]/history.ts +35 -0
  93. package/src/astro/routes/api/admin/context/[id]/index.ts +35 -0
  94. package/src/astro/routes/api/admin/context/[id]/review.ts +57 -0
  95. package/src/astro/routes/api/admin/context/[id]/supersede.ts +58 -0
  96. package/src/astro/routes/api/admin/context/diff.ts +35 -0
  97. package/src/astro/routes/api/admin/context/index.ts +69 -0
  98. package/src/astro/routes/api/admin/context/stale.ts +35 -0
  99. package/src/astro/routes/api/admin/hitl-requests/[id]/index.ts +38 -0
  100. package/src/astro/routes/api/admin/hitl-requests/[id]/resolve.ts +54 -0
  101. package/src/astro/routes/api/admin/hitl-requests/index.ts +38 -0
  102. package/src/astro/routes/api/admin/hooks/exclusive/[hookName].ts +132 -0
  103. package/src/astro/routes/api/admin/hooks/exclusive/index.ts +51 -0
  104. package/src/astro/routes/api/admin/oauth-clients/[id].ts +137 -0
  105. package/src/astro/routes/api/admin/oauth-clients/index.ts +95 -0
  106. package/src/astro/routes/api/admin/plugins/[id]/disable.ts +91 -0
  107. package/src/astro/routes/api/admin/plugins/[id]/enable.ts +91 -0
  108. package/src/astro/routes/api/admin/plugins/[id]/index.ts +38 -0
  109. package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +98 -0
  110. package/src/astro/routes/api/admin/plugins/[id]/update.ts +154 -0
  111. package/src/astro/routes/api/admin/plugins/index.ts +32 -0
  112. package/src/astro/routes/api/admin/plugins/marketplace/[id]/icon.ts +62 -0
  113. package/src/astro/routes/api/admin/plugins/marketplace/[id]/index.ts +33 -0
  114. package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +135 -0
  115. package/src/astro/routes/api/admin/plugins/marketplace/index.ts +38 -0
  116. package/src/astro/routes/api/admin/plugins/updates.ts +28 -0
  117. package/src/astro/routes/api/admin/review-requests/[id]/index.ts +35 -0
  118. package/src/astro/routes/api/admin/review-requests/[id]/resolve.ts +52 -0
  119. package/src/astro/routes/api/admin/review-requests/index.ts +35 -0
  120. package/src/astro/routes/api/admin/themes/marketplace/[id]/index.ts +33 -0
  121. package/src/astro/routes/api/admin/themes/marketplace/[id]/thumbnail.ts +62 -0
  122. package/src/astro/routes/api/admin/themes/marketplace/index.ts +45 -0
  123. package/src/astro/routes/api/admin/users/[id]/disable.ts +72 -0
  124. package/src/astro/routes/api/admin/users/[id]/enable.ts +48 -0
  125. package/src/astro/routes/api/admin/users/[id]/index.ts +166 -0
  126. package/src/astro/routes/api/admin/users/[id]/send-recovery.ts +72 -0
  127. package/src/astro/routes/api/admin/users/index.ts +66 -0
  128. package/src/astro/routes/api/auth/dev-bypass.ts +139 -0
  129. package/src/astro/routes/api/auth/invite/accept.ts +52 -0
  130. package/src/astro/routes/api/auth/invite/complete.ts +86 -0
  131. package/src/astro/routes/api/auth/invite/index.ts +99 -0
  132. package/src/astro/routes/api/auth/invite/register-options.ts +73 -0
  133. package/src/astro/routes/api/auth/logout.ts +40 -0
  134. package/src/astro/routes/api/auth/magic-link/send.ts +90 -0
  135. package/src/astro/routes/api/auth/magic-link/verify.ts +71 -0
  136. package/src/astro/routes/api/auth/me.ts +60 -0
  137. package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +221 -0
  138. package/src/astro/routes/api/auth/oauth/[provider].ts +120 -0
  139. package/src/astro/routes/api/auth/passkey/[id].ts +124 -0
  140. package/src/astro/routes/api/auth/passkey/index.ts +54 -0
  141. package/src/astro/routes/api/auth/passkey/options.ts +85 -0
  142. package/src/astro/routes/api/auth/passkey/register/options.ts +88 -0
  143. package/src/astro/routes/api/auth/passkey/register/verify.ts +119 -0
  144. package/src/astro/routes/api/auth/passkey/verify.ts +72 -0
  145. package/src/astro/routes/api/auth/signup/complete.ts +87 -0
  146. package/src/astro/routes/api/auth/signup/request.ts +89 -0
  147. package/src/astro/routes/api/auth/signup/verify.ts +53 -0
  148. package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +310 -0
  149. package/src/astro/routes/api/content/[collection]/[id]/compare.ts +28 -0
  150. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +68 -0
  151. package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +77 -0
  152. package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +42 -0
  153. package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +107 -0
  154. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +100 -0
  155. package/src/astro/routes/api/content/[collection]/[id]/restore.ts +64 -0
  156. package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +31 -0
  157. package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +129 -0
  158. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +143 -0
  159. package/src/astro/routes/api/content/[collection]/[id]/translations.ts +50 -0
  160. package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +69 -0
  161. package/src/astro/routes/api/content/[collection]/[id].ts +173 -0
  162. package/src/astro/routes/api/content/[collection]/index.ts +103 -0
  163. package/src/astro/routes/api/content/[collection]/trash.ts +33 -0
  164. package/src/astro/routes/api/dashboard.ts +32 -0
  165. package/src/astro/routes/api/dev/emails.ts +36 -0
  166. package/src/astro/routes/api/health.ts +54 -0
  167. package/src/astro/routes/api/import/probe.ts +47 -0
  168. package/src/astro/routes/api/import/wordpress/analyze.ts +523 -0
  169. package/src/astro/routes/api/import/wordpress/execute.ts +330 -0
  170. package/src/astro/routes/api/import/wordpress/media.ts +338 -0
  171. package/src/astro/routes/api/import/wordpress/prepare.ts +212 -0
  172. package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +425 -0
  173. package/src/astro/routes/api/import/wordpress-plugin/analyze.ts +111 -0
  174. package/src/astro/routes/api/import/wordpress-plugin/callback.ts +58 -0
  175. package/src/astro/routes/api/import/wordpress-plugin/execute.ts +399 -0
  176. package/src/astro/routes/api/manifest.ts +75 -0
  177. package/src/astro/routes/api/mcp.ts +125 -0
  178. package/src/astro/routes/api/media/[id]/confirm.ts +93 -0
  179. package/src/astro/routes/api/media/[id].ts +145 -0
  180. package/src/astro/routes/api/media/file/[...key].ts +79 -0
  181. package/src/astro/routes/api/media/providers/[providerId]/[itemId].ts +91 -0
  182. package/src/astro/routes/api/media/providers/[providerId]/index.ts +111 -0
  183. package/src/astro/routes/api/media/providers/index.ts +30 -0
  184. package/src/astro/routes/api/media/upload-url.ts +146 -0
  185. package/src/astro/routes/api/media.ts +204 -0
  186. package/src/astro/routes/api/menus/[name]/items.ts +206 -0
  187. package/src/astro/routes/api/menus/[name]/reorder.ts +79 -0
  188. package/src/astro/routes/api/menus/[name].ts +145 -0
  189. package/src/astro/routes/api/menus/index.ts +91 -0
  190. package/src/astro/routes/api/oauth/authorize.ts +430 -0
  191. package/src/astro/routes/api/oauth/device/authorize.ts +45 -0
  192. package/src/astro/routes/api/oauth/device/code.ts +56 -0
  193. package/src/astro/routes/api/oauth/device/token.ts +70 -0
  194. package/src/astro/routes/api/oauth/register.ts +182 -0
  195. package/src/astro/routes/api/oauth/token/refresh.ts +38 -0
  196. package/src/astro/routes/api/oauth/token/revoke.ts +38 -0
  197. package/src/astro/routes/api/oauth/token.ts +195 -0
  198. package/src/astro/routes/api/openapi.json.ts +33 -0
  199. package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +109 -0
  200. package/src/astro/routes/api/redirects/404s/index.ts +72 -0
  201. package/src/astro/routes/api/redirects/404s/summary.ts +33 -0
  202. package/src/astro/routes/api/redirects/[id].ts +183 -0
  203. package/src/astro/routes/api/redirects/index.ts +100 -0
  204. package/src/astro/routes/api/revisions/[revisionId]/index.ts +29 -0
  205. package/src/astro/routes/api/revisions/[revisionId]/restore.ts +62 -0
  206. package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +104 -0
  207. package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +67 -0
  208. package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +45 -0
  209. package/src/astro/routes/api/schema/collections/[slug]/index.ts +107 -0
  210. package/src/astro/routes/api/schema/collections/index.ts +61 -0
  211. package/src/astro/routes/api/schema/index.ts +109 -0
  212. package/src/astro/routes/api/schema/orphans/[slug].ts +36 -0
  213. package/src/astro/routes/api/schema/orphans/index.ts +26 -0
  214. package/src/astro/routes/api/search/enable.ts +64 -0
  215. package/src/astro/routes/api/search/index.ts +52 -0
  216. package/src/astro/routes/api/search/rebuild.ts +72 -0
  217. package/src/astro/routes/api/search/stats.ts +35 -0
  218. package/src/astro/routes/api/search/suggest.ts +50 -0
  219. package/src/astro/routes/api/sections/[slug].ts +203 -0
  220. package/src/astro/routes/api/sections/index.ts +107 -0
  221. package/src/astro/routes/api/settings/email.ts +150 -0
  222. package/src/astro/routes/api/settings.ts +116 -0
  223. package/src/astro/routes/api/setup/admin-verify.ts +122 -0
  224. package/src/astro/routes/api/setup/admin.ts +104 -0
  225. package/src/astro/routes/api/setup/dev-bypass.ts +200 -0
  226. package/src/astro/routes/api/setup/dev-reset.ts +40 -0
  227. package/src/astro/routes/api/setup/index.ts +128 -0
  228. package/src/astro/routes/api/setup/status.ts +122 -0
  229. package/src/astro/routes/api/snapshot.ts +76 -0
  230. package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +232 -0
  231. package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +131 -0
  232. package/src/astro/routes/api/taxonomies/index.ts +114 -0
  233. package/src/astro/routes/api/themes/preview.ts +78 -0
  234. package/src/astro/routes/api/typegen.ts +114 -0
  235. package/src/astro/routes/api/well-known/auth.ts +71 -0
  236. package/src/astro/routes/api/well-known/oauth-authorization-server.ts +48 -0
  237. package/src/astro/routes/api/well-known/oauth-protected-resource.ts +39 -0
  238. package/src/astro/routes/api/widget-areas/[name]/reorder.ts +114 -0
  239. package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +213 -0
  240. package/src/astro/routes/api/widget-areas/[name]/widgets.ts +126 -0
  241. package/src/astro/routes/api/widget-areas/[name].ts +135 -0
  242. package/src/astro/routes/api/widget-areas/index.ts +149 -0
  243. package/src/astro/routes/api/widget-components.ts +22 -0
  244. package/src/astro/routes/robots.txt.ts +81 -0
  245. package/src/astro/routes/sitemap-[collection].xml.ts +104 -0
  246. package/src/astro/routes/sitemap.xml.ts +92 -0
  247. package/src/components/Break.astro +45 -0
  248. package/src/components/Button.astro +71 -0
  249. package/src/components/Buttons.astro +49 -0
  250. package/src/components/Code.astro +59 -0
  251. package/src/components/Columns.astro +59 -0
  252. package/src/components/CommentForm.astro +315 -0
  253. package/src/components/Comments.astro +232 -0
  254. package/src/components/Cover.astro +128 -0
  255. package/src/components/DinewayBodyEnd.astro +32 -0
  256. package/src/components/DinewayBodyStart.astro +32 -0
  257. package/src/components/DinewayHead.astro +61 -0
  258. package/src/components/DinewayImage.astro +178 -0
  259. package/src/components/DinewayMedia.astro +167 -0
  260. package/src/components/Embed.astro +128 -0
  261. package/src/components/File.astro +122 -0
  262. package/src/components/Gallery.astro +93 -0
  263. package/src/components/HtmlBlock.astro +33 -0
  264. package/src/components/Image.astro +178 -0
  265. package/src/components/InlineEditor.astro +27 -0
  266. package/src/components/InlinePortableTextEditor.tsx +1937 -0
  267. package/src/components/LiveSearch.astro +614 -0
  268. package/src/components/PortableText.astro +51 -0
  269. package/src/components/Pullquote.astro +51 -0
  270. package/src/components/Table.astro +135 -0
  271. package/src/components/WidgetArea.astro +22 -0
  272. package/src/components/WidgetRenderer.astro +72 -0
  273. package/src/components/index.ts +106 -0
  274. package/src/components/marks/Link.astro +31 -0
  275. package/src/components/marks/StrikeThrough.astro +7 -0
  276. package/src/components/marks/Subscript.astro +7 -0
  277. package/src/components/marks/Superscript.astro +7 -0
  278. package/src/components/marks/Underline.astro +7 -0
  279. package/src/components/marks.ts +19 -0
  280. package/src/components/widgets/Archives.astro +65 -0
  281. package/src/components/widgets/Categories.astro +35 -0
  282. package/src/components/widgets/RecentPosts.astro +51 -0
  283. package/src/components/widgets/Search.astro +18 -0
  284. package/src/components/widgets/Tags.astro +38 -0
  285. package/src/ui.ts +75 -0
  286. package/LICENSE +0 -9
  287. /package/dist/{adapters-BlzWJG82.d.mts → adapters-C2ypTrZZ.d.mts} +0 -0
  288. /package/dist/{config-Cq8H0SfX.mjs → config-BXwuX8Bx.mjs} +0 -0
  289. /package/dist/{load-C6FCD1FU.mjs → load-Coc9HpHH.mjs} +0 -0
  290. /package/dist/{manifest-schema-CTSEyIJ3.mjs → manifest-schema-D1MSVnoI.mjs} +0 -0
  291. /package/dist/{mode-BlyYtIFO.mjs → mode-47goXBBK.mjs} +0 -0
  292. /package/dist/{tokens-4vgYuXsZ.mjs → tokens-CJz9ubV6.mjs} +0 -0
  293. /package/dist/{transport-C5FYnid7.mjs → transport-DB5eDN4x.mjs} +0 -0
  294. /package/dist/{transport-gIL-e43D.d.mts → transport-Wge_IzKl.d.mts} +0 -0
  295. /package/dist/{types-CLLdsG3g.d.mts → types-BzcUjoqg.d.mts} +0 -0
  296. /package/dist/{types-DShnjzb6.mjs → types-griIBQOQ.mjs} +0 -0
@@ -0,0 +1,60 @@
1
+ /**
2
+ * GET /_dineway/api/auth/me
3
+ *
4
+ * Returns the current authenticated user's info.
5
+ * Used by the admin UI to display user info in the header.
6
+ */
7
+
8
+ import type { APIRoute } from "astro";
9
+
10
+ export const prerender = false;
11
+
12
+ import { apiError, apiSuccess } from "#api/error.js";
13
+ import { isParseError, parseBody } from "#api/parse.js";
14
+ import { authMeActionBody } from "#api/schemas.js";
15
+
16
+ export const GET: APIRoute = async ({ locals, session }) => {
17
+ const { user } = locals;
18
+
19
+ if (!user) {
20
+ return apiError("NOT_AUTHENTICATED", "Not authenticated", 401);
21
+ }
22
+
23
+ // Check if this is the user's first login (for welcome modal)
24
+ // We track this in the session to show the modal only once
25
+ const hasSeenWelcome = await session?.get("hasSeenWelcome");
26
+ const isFirstLogin = !hasSeenWelcome;
27
+
28
+ // Return safe user info (no sensitive data)
29
+ return apiSuccess({
30
+ id: user.id,
31
+ email: user.email,
32
+ name: user.name,
33
+ role: user.role,
34
+ avatarUrl: user.avatarUrl,
35
+ isFirstLogin,
36
+ });
37
+ };
38
+
39
+ /**
40
+ * POST /_dineway/api/auth/me
41
+ *
42
+ * Mark that the user has seen the welcome modal.
43
+ */
44
+ export const POST: APIRoute = async ({ request, locals, session }) => {
45
+ const { user } = locals;
46
+
47
+ if (!user) {
48
+ return apiError("NOT_AUTHENTICATED", "Not authenticated", 401);
49
+ }
50
+
51
+ const body = await parseBody(request, authMeActionBody);
52
+ if (isParseError(body)) return body;
53
+
54
+ if (body.action === "dismissWelcome") {
55
+ session?.set("hasSeenWelcome", true);
56
+ return apiSuccess({ success: true });
57
+ }
58
+
59
+ return apiError("UNKNOWN_ACTION", "Unknown action", 400);
60
+ };
@@ -0,0 +1,221 @@
1
+ /**
2
+ * GET /_dineway/api/auth/oauth/[provider]/callback
3
+ *
4
+ * Handle OAuth callback from provider
5
+ */
6
+
7
+ import type { APIRoute } from "astro";
8
+
9
+ export const prerender = false;
10
+
11
+ import {
12
+ handleOAuthCallback,
13
+ OAuthError,
14
+ Role,
15
+ type OAuthConsumerConfig,
16
+ type RoleLevel,
17
+ } from "@dineway-ai/auth";
18
+ import { createKyselyAdapter } from "@dineway-ai/auth/adapters/kysely";
19
+
20
+ import { getPublicOrigin } from "#api/public-url.js";
21
+ import { createOAuthStateStore } from "#auth/oauth-state-store.js";
22
+
23
+ type ProviderName = "github" | "google";
24
+
25
+ const VALID_PROVIDERS = new Set<string>(["github", "google"]);
26
+
27
+ function isValidProvider(provider: string): provider is ProviderName {
28
+ return VALID_PROVIDERS.has(provider);
29
+ }
30
+
31
+ /** Safely extract a string value from an env-like record */
32
+ function envString(env: Record<string, unknown>, ...keys: string[]): string | undefined {
33
+ for (const key of keys) {
34
+ const val = env[key];
35
+ if (typeof val === "string" && val) return val;
36
+ }
37
+ return undefined;
38
+ }
39
+
40
+ /**
41
+ * Get OAuth config from environment variables
42
+ */
43
+ function getOAuthConfig(env: Record<string, unknown>): OAuthConsumerConfig["providers"] {
44
+ const providers: OAuthConsumerConfig["providers"] = {};
45
+
46
+ // GitHub
47
+ const githubClientId = envString(env, "DINEWAY_OAUTH_GITHUB_CLIENT_ID", "GITHUB_CLIENT_ID");
48
+ const githubClientSecret = envString(
49
+ env,
50
+ "DINEWAY_OAUTH_GITHUB_CLIENT_SECRET",
51
+ "GITHUB_CLIENT_SECRET",
52
+ );
53
+ if (githubClientId && githubClientSecret) {
54
+ providers.github = {
55
+ clientId: githubClientId,
56
+ clientSecret: githubClientSecret,
57
+ };
58
+ }
59
+
60
+ // Google
61
+ const googleClientId = envString(env, "DINEWAY_OAUTH_GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_ID");
62
+ const googleClientSecret = envString(
63
+ env,
64
+ "DINEWAY_OAUTH_GOOGLE_CLIENT_SECRET",
65
+ "GOOGLE_CLIENT_SECRET",
66
+ );
67
+ if (googleClientId && googleClientSecret) {
68
+ providers.google = {
69
+ clientId: googleClientId,
70
+ clientSecret: googleClientSecret,
71
+ };
72
+ }
73
+
74
+ return providers;
75
+ }
76
+
77
+ export const GET: APIRoute = async ({ params, request, locals, session, redirect }) => {
78
+ const { dineway } = locals;
79
+ const provider = params.provider;
80
+
81
+ // Validate provider
82
+ if (!provider || !isValidProvider(provider)) {
83
+ return redirect(
84
+ `/_dineway/admin/login?error=invalid_provider&message=${encodeURIComponent("Invalid OAuth provider")}`,
85
+ );
86
+ }
87
+
88
+ if (!dineway?.db) {
89
+ return redirect(
90
+ `/_dineway/admin/login?error=server_error&message=${encodeURIComponent("Database not configured")}`,
91
+ );
92
+ }
93
+
94
+ const url = new URL(request.url);
95
+ const code = url.searchParams.get("code");
96
+ const state = url.searchParams.get("state");
97
+ const error = url.searchParams.get("error");
98
+ const errorDescription = url.searchParams.get("error_description");
99
+
100
+ // Handle OAuth errors from provider
101
+ if (error) {
102
+ const message = errorDescription || error;
103
+ return redirect(
104
+ `/_dineway/admin/login?error=oauth_denied&message=${encodeURIComponent(message)}`,
105
+ );
106
+ }
107
+
108
+ // Validate required params
109
+ if (!code || !state) {
110
+ return redirect(
111
+ `/_dineway/admin/login?error=invalid_callback&message=${encodeURIComponent("Missing code or state parameter")}`,
112
+ );
113
+ }
114
+
115
+ try {
116
+ // Get OAuth providers from the adapter/runtime env when available,
117
+ // otherwise fall back to the Node build env.
118
+ // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- some adapters inject locals.runtime.env at runtime; App.Locals intentionally stays generic
119
+ const runtimeLocals = locals as unknown as { runtime?: { env?: Record<string, unknown> } };
120
+ // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- import.meta.env is typed as ImportMetaEnv but we need Record<string, unknown> for getOAuthConfig
121
+ const env = runtimeLocals.runtime?.env ?? (import.meta.env as Record<string, unknown>);
122
+ const providers = getOAuthConfig(env);
123
+
124
+ if (!providers[provider]) {
125
+ return redirect(
126
+ `/_dineway/admin/login?error=provider_not_configured&message=${encodeURIComponent(`OAuth provider ${provider} is not configured`)}`,
127
+ );
128
+ }
129
+
130
+ const config: OAuthConsumerConfig = {
131
+ baseUrl: `${getPublicOrigin(url, dineway?.config)}/_dineway`,
132
+ providers,
133
+ canSelfSignup: async (email: string) => {
134
+ // Extract domain from email
135
+ const domain = email.split("@")[1]?.toLowerCase();
136
+ if (!domain) {
137
+ return null;
138
+ }
139
+
140
+ // Check allowed_domains table for a matching, enabled entry
141
+ const entry = await dineway.db
142
+ .selectFrom("allowed_domains")
143
+ .selectAll()
144
+ .where("domain", "=", domain)
145
+ .where("enabled", "=", 1)
146
+ .executeTakeFirst();
147
+
148
+ if (!entry) {
149
+ return null;
150
+ }
151
+
152
+ // Map the stored role level to the Role enum
153
+ const roleLevel = entry.default_role;
154
+ const roleMap: Record<number, RoleLevel> = {
155
+ 50: Role.ADMIN,
156
+ 40: Role.EDITOR,
157
+ 30: Role.AUTHOR,
158
+ 20: Role.CONTRIBUTOR,
159
+ 10: Role.SUBSCRIBER,
160
+ };
161
+ const role = roleMap[roleLevel] ?? Role.CONTRIBUTOR;
162
+ if (!roleMap[roleLevel]) {
163
+ console.warn(
164
+ `[oauth] Unknown role level ${roleLevel} for domain ${domain}, defaulting to CONTRIBUTOR`,
165
+ );
166
+ }
167
+
168
+ return { allowed: true, role };
169
+ },
170
+ };
171
+
172
+ const adapter = createKyselyAdapter(dineway.db);
173
+ const stateStore = createOAuthStateStore(dineway.db);
174
+
175
+ const user = await handleOAuthCallback(config, adapter, provider, code, state, stateStore);
176
+
177
+ // Create session
178
+ if (session) {
179
+ session.set("user", { id: user.id });
180
+ }
181
+
182
+ // Redirect to admin dashboard
183
+ return redirect("/_dineway/admin");
184
+ } catch (callbackError) {
185
+ console.error("OAuth callback error:", callbackError);
186
+
187
+ let message = "Authentication failed";
188
+ let errorCode = "oauth_error";
189
+
190
+ if (callbackError instanceof OAuthError) {
191
+ errorCode = callbackError.code;
192
+
193
+ // Map all error codes to user-friendly messages (never expose raw error.message)
194
+ switch (callbackError.code) {
195
+ case "invalid_state":
196
+ message = "OAuth session expired or invalid. Please try again.";
197
+ break;
198
+ case "signup_not_allowed":
199
+ message = "Self-signup is not allowed for your email. Please contact an administrator.";
200
+ break;
201
+ case "user_not_found":
202
+ message = "Your account was not found. It may have been deleted.";
203
+ break;
204
+ case "token_exchange_failed":
205
+ message = "Failed to complete authentication. Please try again.";
206
+ break;
207
+ case "profile_fetch_failed":
208
+ message = "Failed to retrieve your profile. Please try again.";
209
+ break;
210
+ default:
211
+ message = "Authentication failed. Please try again.";
212
+ break;
213
+ }
214
+ }
215
+ // For generic errors, keep the default "Authentication failed" message
216
+
217
+ return redirect(
218
+ `/_dineway/admin/login?error=${errorCode}&message=${encodeURIComponent(message)}`,
219
+ );
220
+ }
221
+ };
@@ -0,0 +1,120 @@
1
+ /**
2
+ * GET /_dineway/api/auth/oauth/[provider]
3
+ *
4
+ * Start OAuth flow - redirects to provider authorization URL
5
+ */
6
+
7
+ import type { APIRoute } from "astro";
8
+
9
+ export const prerender = false;
10
+
11
+ import { createAuthorizationUrl, type OAuthConsumerConfig } from "@dineway-ai/auth";
12
+
13
+ import { getPublicOrigin } from "#api/public-url.js";
14
+ import { createOAuthStateStore } from "#auth/oauth-state-store.js";
15
+
16
+ type ProviderName = "github" | "google";
17
+
18
+ const VALID_PROVIDERS = new Set<string>(["github", "google"]);
19
+
20
+ function isValidProvider(provider: string): provider is ProviderName {
21
+ return VALID_PROVIDERS.has(provider);
22
+ }
23
+
24
+ /** Safely extract a string value from an env-like record */
25
+ function envString(env: Record<string, unknown>, ...keys: string[]): string | undefined {
26
+ for (const key of keys) {
27
+ const val = env[key];
28
+ if (typeof val === "string" && val) return val;
29
+ }
30
+ return undefined;
31
+ }
32
+
33
+ /**
34
+ * Get OAuth config from environment variables
35
+ */
36
+ function getOAuthConfig(env: Record<string, unknown>): OAuthConsumerConfig["providers"] {
37
+ const providers: OAuthConsumerConfig["providers"] = {};
38
+
39
+ // GitHub
40
+ const githubClientId = envString(env, "DINEWAY_OAUTH_GITHUB_CLIENT_ID", "GITHUB_CLIENT_ID");
41
+ const githubClientSecret = envString(
42
+ env,
43
+ "DINEWAY_OAUTH_GITHUB_CLIENT_SECRET",
44
+ "GITHUB_CLIENT_SECRET",
45
+ );
46
+ if (githubClientId && githubClientSecret) {
47
+ providers.github = {
48
+ clientId: githubClientId,
49
+ clientSecret: githubClientSecret,
50
+ };
51
+ }
52
+
53
+ // Google
54
+ const googleClientId = envString(env, "DINEWAY_OAUTH_GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_ID");
55
+ const googleClientSecret = envString(
56
+ env,
57
+ "DINEWAY_OAUTH_GOOGLE_CLIENT_SECRET",
58
+ "GOOGLE_CLIENT_SECRET",
59
+ );
60
+ if (googleClientId && googleClientSecret) {
61
+ providers.google = {
62
+ clientId: googleClientId,
63
+ clientSecret: googleClientSecret,
64
+ };
65
+ }
66
+
67
+ return providers;
68
+ }
69
+
70
+ export const GET: APIRoute = async ({ params, request, locals, redirect }) => {
71
+ const { dineway } = locals;
72
+ const provider = params.provider;
73
+
74
+ // Validate provider
75
+ if (!provider || !isValidProvider(provider)) {
76
+ return redirect(
77
+ `/_dineway/admin/login?error=invalid_provider&message=${encodeURIComponent("Invalid OAuth provider")}`,
78
+ );
79
+ }
80
+
81
+ if (!dineway?.db) {
82
+ return redirect(
83
+ `/_dineway/admin/login?error=server_error&message=${encodeURIComponent("Database not configured")}`,
84
+ );
85
+ }
86
+
87
+ try {
88
+ const url = new URL(request.url);
89
+
90
+ // Get OAuth providers from the adapter/runtime env when available,
91
+ // otherwise fall back to the Node build env.
92
+ // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- some adapters inject locals.runtime.env at runtime; App.Locals intentionally stays generic
93
+ const runtimeLocals = locals as unknown as { runtime?: { env?: Record<string, unknown> } };
94
+ // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- import.meta.env is typed as ImportMetaEnv but we need Record<string, unknown> for getOAuthConfig
95
+ const env = runtimeLocals.runtime?.env ?? (import.meta.env as Record<string, unknown>);
96
+ const providers = getOAuthConfig(env);
97
+
98
+ if (!providers[provider]) {
99
+ return redirect(
100
+ `/_dineway/admin/login?error=provider_not_configured&message=${encodeURIComponent(`OAuth provider ${provider} is not configured`)}`,
101
+ );
102
+ }
103
+
104
+ const config: OAuthConsumerConfig = {
105
+ baseUrl: `${getPublicOrigin(url, dineway?.config)}/_dineway`,
106
+ providers,
107
+ };
108
+
109
+ const stateStore = createOAuthStateStore(dineway.db);
110
+
111
+ const { url: authUrl } = await createAuthorizationUrl(config, provider, stateStore);
112
+
113
+ return redirect(authUrl);
114
+ } catch (error) {
115
+ console.error("OAuth initiation error:", error);
116
+ return redirect(
117
+ `/_dineway/admin/login?error=oauth_error&message=${encodeURIComponent("Failed to start OAuth flow. Please try again.")}`,
118
+ );
119
+ }
120
+ };
@@ -0,0 +1,124 @@
1
+ /**
2
+ * PATCH/DELETE /_dineway/api/auth/passkey/[id]
3
+ *
4
+ * Rename or delete a passkey
5
+ */
6
+
7
+ import type { APIRoute } from "astro";
8
+
9
+ export const prerender = false;
10
+
11
+ import { createKyselyAdapter } from "@dineway-ai/auth/adapters/kysely";
12
+
13
+ import { apiError, apiSuccess, handleError } from "#api/error.js";
14
+ import { isParseError, parseBody } from "#api/parse.js";
15
+ import { passkeyRenameBody } from "#api/schemas.js";
16
+
17
+ interface PasskeyResponse {
18
+ id: string;
19
+ name: string | null;
20
+ deviceType: "singleDevice" | "multiDevice";
21
+ backedUp: boolean;
22
+ createdAt: string;
23
+ lastUsedAt: string;
24
+ }
25
+
26
+ /**
27
+ * PATCH - Rename a passkey
28
+ */
29
+ export const PATCH: APIRoute = async ({ params, request, locals }) => {
30
+ const { dineway, user } = locals;
31
+ const { id } = params;
32
+
33
+ if (!dineway?.db) {
34
+ return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
35
+ }
36
+
37
+ // Require authentication
38
+ if (!user) {
39
+ return apiError("NOT_AUTHENTICATED", "Not authenticated", 401);
40
+ }
41
+
42
+ if (!id) {
43
+ return apiError("MISSING_PARAM", "Passkey ID is required", 400);
44
+ }
45
+
46
+ try {
47
+ const adapter = createKyselyAdapter(dineway.db);
48
+
49
+ // Get the credential and verify ownership
50
+ const credential = await adapter.getCredentialById(id);
51
+
52
+ if (!credential || credential.userId !== user.id) {
53
+ return apiError("NOT_FOUND", "Passkey not found", 404);
54
+ }
55
+
56
+ // Parse request body
57
+ const body = await parseBody(request, passkeyRenameBody);
58
+ if (isParseError(body)) return body;
59
+
60
+ // Update the name
61
+ const trimmedName = body.name.trim() || null;
62
+ await adapter.updateCredentialName(id, trimmedName);
63
+
64
+ // Return updated passkey info
65
+ const passkey: PasskeyResponse = {
66
+ id: credential.id,
67
+ name: trimmedName,
68
+ deviceType: credential.deviceType,
69
+ backedUp: credential.backedUp,
70
+ createdAt: credential.createdAt.toISOString(),
71
+ lastUsedAt: credential.lastUsedAt.toISOString(),
72
+ };
73
+
74
+ return apiSuccess({ passkey });
75
+ } catch (error) {
76
+ return handleError(error, "Failed to rename passkey", "PASSKEY_RENAME_ERROR");
77
+ }
78
+ };
79
+
80
+ /**
81
+ * DELETE - Remove a passkey
82
+ */
83
+ export const DELETE: APIRoute = async ({ params, locals }) => {
84
+ const { dineway, user } = locals;
85
+ const { id } = params;
86
+
87
+ if (!dineway?.db) {
88
+ return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
89
+ }
90
+
91
+ // Require authentication
92
+ if (!user) {
93
+ return apiError("NOT_AUTHENTICATED", "Not authenticated", 401);
94
+ }
95
+
96
+ if (!id) {
97
+ return apiError("MISSING_PARAM", "Passkey ID is required", 400);
98
+ }
99
+
100
+ try {
101
+ const adapter = createKyselyAdapter(dineway.db);
102
+
103
+ // Get the credential and verify ownership
104
+ const credential = await adapter.getCredentialById(id);
105
+
106
+ if (!credential || credential.userId !== user.id) {
107
+ return apiError("NOT_FOUND", "Passkey not found", 404);
108
+ }
109
+
110
+ // Check that this isn't the last passkey
111
+ const count = await adapter.countCredentialsByUserId(user.id);
112
+
113
+ if (count <= 1) {
114
+ return apiError("LAST_PASSKEY", "Cannot remove your last passkey", 400);
115
+ }
116
+
117
+ // Delete the passkey
118
+ await adapter.deleteCredential(id);
119
+
120
+ return apiSuccess({ success: true });
121
+ } catch (error) {
122
+ return handleError(error, "Failed to delete passkey", "PASSKEY_DELETE_ERROR");
123
+ }
124
+ };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * GET /_dineway/api/auth/passkey
3
+ *
4
+ * List all passkeys for the authenticated user
5
+ */
6
+
7
+ import type { APIRoute } from "astro";
8
+
9
+ export const prerender = false;
10
+
11
+ import { createKyselyAdapter } from "@dineway-ai/auth/adapters/kysely";
12
+
13
+ import { apiError, apiSuccess, handleError } from "#api/error.js";
14
+
15
+ interface PasskeyResponse {
16
+ id: string;
17
+ name: string | null;
18
+ deviceType: "singleDevice" | "multiDevice";
19
+ backedUp: boolean;
20
+ createdAt: string;
21
+ lastUsedAt: string;
22
+ }
23
+
24
+ export const GET: APIRoute = async ({ locals }) => {
25
+ const { dineway, user } = locals;
26
+
27
+ if (!dineway?.db) {
28
+ return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
29
+ }
30
+
31
+ // Require authentication
32
+ if (!user) {
33
+ return apiError("NOT_AUTHENTICATED", "Not authenticated", 401);
34
+ }
35
+
36
+ try {
37
+ const adapter = createKyselyAdapter(dineway.db);
38
+ const credentials = await adapter.getCredentialsByUserId(user.id);
39
+
40
+ // Map to public response format (exclude sensitive fields)
41
+ const passkeys: PasskeyResponse[] = credentials.map((cred) => ({
42
+ id: cred.id,
43
+ name: cred.name,
44
+ deviceType: cred.deviceType,
45
+ backedUp: cred.backedUp,
46
+ createdAt: cred.createdAt.toISOString(),
47
+ lastUsedAt: cred.lastUsedAt.toISOString(),
48
+ }));
49
+
50
+ return apiSuccess({ items: passkeys });
51
+ } catch (error) {
52
+ return handleError(error, "Failed to list passkeys", "PASSKEY_LIST_ERROR");
53
+ }
54
+ };
@@ -0,0 +1,85 @@
1
+ /**
2
+ * POST /_dineway/api/auth/passkey/options
3
+ *
4
+ * Get authentication options for passkey login.
5
+ *
6
+ * Rate limited: 10 requests per minute per IP.
7
+ */
8
+
9
+ import type { APIRoute } from "astro";
10
+
11
+ export const prerender = false;
12
+
13
+ import { createKyselyAdapter } from "@dineway-ai/auth/adapters/kysely";
14
+ import { generateAuthenticationOptions } from "@dineway-ai/auth/passkey";
15
+
16
+ import { apiError, apiSuccess, handleError } from "#api/error.js";
17
+ import { isParseError, parseOptionalBody } from "#api/parse.js";
18
+ import { getPublicOrigin } from "#api/public-url.js";
19
+ import { passkeyOptionsBody } from "#api/schemas.js";
20
+ import { createChallengeStore, cleanupExpiredChallenges } from "#auth/challenge-store.js";
21
+ import { getPasskeyConfig } from "#auth/passkey-config.js";
22
+ import { checkRateLimit, getClientIp, rateLimitResponse } from "#auth/rate-limit.js";
23
+ import { getTrustedProxyHeaders } from "#auth/trusted-proxy.js";
24
+ import { OptionsRepository } from "#db/repositories/options.js";
25
+
26
+ export const POST: APIRoute = async ({ request, locals }) => {
27
+ const { dineway } = locals;
28
+
29
+ if (!dineway?.db) {
30
+ return apiError("NOT_CONFIGURED", "Dineway is not initialized", 500);
31
+ }
32
+
33
+ try {
34
+ // Fire-and-forget cleanup of expired challenges -- prevents accumulation
35
+ void cleanupExpiredChallenges(dineway.db).catch(() => {});
36
+
37
+ // Parse body before rate limiting so malformed requests don't consume slots
38
+ const body = await parseOptionalBody(request, passkeyOptionsBody, {});
39
+ if (isParseError(body)) return body;
40
+
41
+ // Rate limit: 10 requests per 60 seconds per IP
42
+ const ip = getClientIp(request, getTrustedProxyHeaders(dineway.config));
43
+ const rateLimit = await checkRateLimit(dineway.db, ip, "passkey/options", 10, 60);
44
+ if (!rateLimit.allowed) {
45
+ return rateLimitResponse(60);
46
+ }
47
+
48
+ const adapter = createKyselyAdapter(dineway.db);
49
+
50
+ // Get credentials to allow
51
+ let credentials: Awaited<ReturnType<typeof adapter.getCredentialsByUserId>> = [];
52
+
53
+ if (body.email) {
54
+ // Get credentials for specific user
55
+ const user = await adapter.getUserByEmail(body.email);
56
+ if (user) {
57
+ credentials = await adapter.getCredentialsByUserId(user.id);
58
+ }
59
+ // Don't reveal if user exists - just return empty allowCredentials
60
+ }
61
+ // If no email provided, allowCredentials will be undefined (allow any discoverable credential)
62
+
63
+ // Get passkey config
64
+ const url = new URL(request.url);
65
+ const options = new OptionsRepository(dineway.db);
66
+ const siteName = (await options.get<string>("dineway:site_title")) ?? undefined;
67
+ const siteUrl = getPublicOrigin(url, dineway?.config);
68
+ const passkeyConfig = getPasskeyConfig(url, siteName, siteUrl);
69
+
70
+ // Generate authentication options
71
+ const challengeStore = createChallengeStore(dineway.db);
72
+ const authOptions = await generateAuthenticationOptions(
73
+ passkeyConfig,
74
+ credentials,
75
+ challengeStore,
76
+ );
77
+
78
+ return apiSuccess({
79
+ success: true,
80
+ options: authOptions,
81
+ });
82
+ } catch (error) {
83
+ return handleError(error, "Failed to generate passkey options", "PASSKEY_OPTIONS_ERROR");
84
+ }
85
+ };