dineway 0.1.49 → 0.2.1
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.
- package/dist/activity-DUaz_w7p.mjs +79 -0
- package/dist/{activity-events-CwM7yxPW.mjs → activity-events-CJDvE2uc.mjs} +5 -113
- package/dist/actor-0QJ1Gbtf.mjs +37 -0
- package/dist/after-BfniwgIf.mjs +9 -0
- package/dist/{allowed-origins-CyGnbgBd.mjs → allowed-origins--OwHHiY8.mjs} +1 -1
- package/dist/api/route-utils.d.mts +9 -10
- package/dist/api/route-utils.mjs +16 -13
- package/dist/api/schemas/index.d.mts +2 -2
- package/dist/api/schemas/index.mjs +5 -4
- package/dist/{api-BMIxRIEN.mjs → api-B4PHIF4V.mjs} +93 -41
- package/dist/{api-tokens-DtvdtIkm.mjs → api-tokens-tbK_sORH.mjs} +6 -2
- package/dist/{apply-D_RlTYtC.mjs → apply-DjTqq6Ae.mjs} +117 -77
- package/dist/astro/image-endpoint.d.mts +7 -0
- package/dist/astro/image-endpoint.mjs +54 -0
- package/dist/astro/index.d.mts +34 -11
- package/dist/astro/index.mjs +163 -24
- package/dist/astro/integration/shims/use-sync-external-store-with-selector.js +71 -0
- package/dist/astro/integration/shims/use-sync-external-store.js +1 -0
- package/dist/astro/middleware/auth.d.mts +8 -18
- package/dist/astro/middleware/auth.mjs +42 -24
- package/dist/astro/middleware/redirect.mjs +13 -4
- package/dist/astro/middleware/request-context.mjs +8 -5
- package/dist/astro/middleware/seed.d.mts +1 -1
- package/dist/astro/middleware/seed.mjs +2 -2
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.mjs +389 -206
- package/dist/astro/routes/admin.astro +6 -1
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +7 -6
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs +7 -6
- package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +4 -4
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +6 -6
- package/dist/astro/routes/api/admin/briefing.mjs +21 -17
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +4 -4
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +9 -8
- package/dist/astro/routes/api/admin/byline-fields/index.mjs +9 -8
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +9 -8
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +17 -14
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +17 -14
- package/dist/astro/routes/api/admin/bylines/index.mjs +17 -14
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +14 -12
- package/dist/astro/routes/api/admin/comments/_id_.mjs +6 -5
- package/dist/astro/routes/api/admin/comments/bulk.mjs +11 -9
- package/dist/astro/routes/api/admin/comments/counts.mjs +6 -5
- package/dist/astro/routes/api/admin/comments/index.mjs +11 -9
- package/dist/astro/routes/api/admin/context/_id_/history.mjs +27 -24
- package/dist/astro/routes/api/admin/context/_id_/index.mjs +27 -24
- package/dist/astro/routes/api/admin/context/_id_/review.mjs +32 -28
- package/dist/astro/routes/api/admin/context/_id_/supersede.mjs +32 -28
- package/dist/astro/routes/api/admin/context/diff.mjs +31 -27
- package/dist/astro/routes/api/admin/context/index.mjs +32 -28
- package/dist/astro/routes/api/admin/context/stale.mjs +31 -27
- package/dist/astro/routes/api/admin/hitl-requests/_id_/index.mjs +27 -24
- package/dist/astro/routes/api/admin/hitl-requests/_id_/resolve.mjs +31 -27
- package/dist/astro/routes/api/admin/hitl-requests/index.mjs +31 -27
- package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +28 -25
- package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
- package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +6 -6
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs +6 -6
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +56 -51
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +56 -51
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +47 -43
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +54 -49
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +53 -48
- package/dist/astro/routes/api/admin/plugins/index.mjs +47 -43
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +2 -2
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +46 -42
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +53 -48
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +46 -42
- package/dist/astro/routes/api/admin/plugins/updates.mjs +46 -42
- package/dist/astro/routes/api/admin/review-requests/_id_/index.mjs +5 -5
- package/dist/astro/routes/api/admin/review-requests/_id_/resolve.mjs +32 -63
- package/dist/astro/routes/api/admin/review-requests/index.d.mts +2 -1
- package/dist/astro/routes/api/admin/review-requests/index.mjs +58 -10
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +46 -42
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +2 -2
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +46 -42
- package/dist/astro/routes/api/admin/users/_id_/disable.mjs +3 -3
- package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
- package/dist/astro/routes/api/admin/users/_id_/index.mjs +8 -7
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +4 -4
- package/dist/astro/routes/api/admin/users/index.mjs +7 -6
- package/dist/astro/routes/api/auth/dev-bypass.mjs +4 -4
- package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
- package/dist/astro/routes/api/auth/invite/complete.mjs +12 -11
- package/dist/astro/routes/api/auth/invite/index.mjs +9 -8
- package/dist/astro/routes/api/auth/invite/register-options.mjs +11 -10
- package/dist/astro/routes/api/auth/logout.mjs +2 -2
- package/dist/astro/routes/api/auth/magic-link/send.mjs +11 -10
- package/dist/astro/routes/api/auth/magic-link/verify.mjs +3 -3
- package/dist/astro/routes/api/auth/me.mjs +9 -8
- package/dist/astro/routes/api/auth/mode.mjs +1 -1
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +3 -3
- package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/_id_.mjs +6 -5
- package/dist/astro/routes/api/auth/passkey/index.mjs +1 -1
- package/dist/astro/routes/api/auth/passkey/options.mjs +12 -11
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +10 -9
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +11 -10
- package/dist/astro/routes/api/auth/passkey/verify.mjs +11 -10
- package/dist/astro/routes/api/auth/setup-token/index.mjs +5 -5
- package/dist/astro/routes/api/auth/setup-token/verify.mjs +2 -2
- package/dist/astro/routes/api/auth/signup/complete.mjs +12 -11
- package/dist/astro/routes/api/auth/signup/request.mjs +11 -10
- package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +15 -13
- package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.d.mts +8 -0
- package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +160 -0
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +11 -10
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +49 -20
- package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +67 -32
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +16 -14
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +11 -9
- package/dist/astro/routes/api/content/_collection_/authors.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/index.mjs +11 -9
- package/dist/astro/routes/api/content/_collection_/trash.mjs +7 -6
- package/dist/astro/routes/api/dashboard.mjs +9 -8
- package/dist/astro/routes/api/dev/emails.mjs +2 -2
- package/dist/astro/routes/api/health.mjs +1 -1
- package/dist/astro/routes/api/import/probe.d.mts +3 -3
- package/dist/astro/routes/api/import/probe.mjs +22 -10
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +9 -6
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +18 -1
- package/dist/astro/routes/api/import/wordpress/execute.mjs +85 -37
- package/dist/astro/routes/api/import/wordpress/media.d.mts +12 -1
- package/dist/astro/routes/api/import/wordpress/media.mjs +20 -14
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +36 -32
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +17 -1
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +121 -100
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +24 -12
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +55 -2
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +284 -55
- package/dist/astro/routes/api/manifest.mjs +14 -5
- package/dist/astro/routes/api/mcp.mjs +753 -170
- package/dist/astro/routes/api/media/_id_/confirm.mjs +38 -10
- package/dist/astro/routes/api/media/_id_.mjs +11 -7
- package/dist/astro/routes/api/media/file/_...key_.mjs +1 -1
- package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +2 -2
- package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +2 -2
- package/dist/astro/routes/api/media/providers/index.mjs +2 -2
- package/dist/astro/routes/api/media/upload-url.mjs +11 -10
- package/dist/astro/routes/api/media.mjs +28 -35
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +32 -28
- package/dist/astro/routes/api/menus/_name_/items.mjs +32 -28
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +32 -28
- package/dist/astro/routes/api/menus/_name_/translations.mjs +32 -28
- package/dist/astro/routes/api/menus/_name_.mjs +32 -28
- package/dist/astro/routes/api/menus/index.mjs +32 -28
- package/dist/astro/routes/api/oauth/authorize.mjs +7 -7
- package/dist/astro/routes/api/oauth/device/authorize.mjs +6 -6
- package/dist/astro/routes/api/oauth/device/code.mjs +9 -9
- package/dist/astro/routes/api/oauth/device/token.mjs +8 -8
- package/dist/astro/routes/api/oauth/register.mjs +4 -4
- package/dist/astro/routes/api/oauth/token/refresh.mjs +6 -6
- package/dist/astro/routes/api/oauth/token/revoke.mjs +6 -6
- package/dist/astro/routes/api/oauth/token.mjs +6 -6
- package/dist/astro/routes/api/openapi.json.mjs +4 -3
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +46 -31
- package/dist/astro/routes/api/redirects/404s/index.mjs +11 -10
- package/dist/astro/routes/api/redirects/404s/summary.mjs +11 -10
- package/dist/astro/routes/api/redirects/_id_.mjs +33 -29
- package/dist/astro/routes/api/redirects/index.mjs +33 -29
- package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +2 -2
- package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +2 -2
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +48 -43
- package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +48 -43
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +48 -43
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +48 -43
- package/dist/astro/routes/api/schema/collections/index.mjs +48 -43
- package/dist/astro/routes/api/schema/index.mjs +14 -15
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +46 -42
- package/dist/astro/routes/api/schema/orphans/index.mjs +46 -42
- package/dist/astro/routes/api/search/enable.mjs +11 -10
- package/dist/astro/routes/api/search/index.mjs +10 -9
- package/dist/astro/routes/api/search/rebuild.mjs +11 -10
- package/dist/astro/routes/api/search/stats.mjs +6 -6
- package/dist/astro/routes/api/search/suggest.mjs +10 -9
- package/dist/astro/routes/api/sections/_slug_.mjs +32 -28
- package/dist/astro/routes/api/sections/index.mjs +32 -28
- package/dist/astro/routes/api/settings/email.mjs +4 -4
- package/dist/astro/routes/api/settings.mjs +32 -28
- package/dist/astro/routes/api/setup/admin-verify.mjs +14 -13
- package/dist/astro/routes/api/setup/admin.mjs +12 -11
- package/dist/astro/routes/api/setup/dev-bypass.mjs +31 -25
- package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
- package/dist/astro/routes/api/setup/index.mjs +29 -25
- package/dist/astro/routes/api/setup/status.mjs +3 -3
- package/dist/astro/routes/api/snapshot.mjs +8 -7
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +35 -31
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +35 -31
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +35 -31
- package/dist/astro/routes/api/taxonomies/index.mjs +35 -31
- package/dist/astro/routes/api/themes/preview.mjs +5 -5
- package/dist/astro/routes/api/typegen.mjs +4 -4
- package/dist/astro/routes/api/well-known/auth.mjs +3 -3
- package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +3 -3
- package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +3 -3
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +31 -27
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +32 -28
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +32 -28
- package/dist/astro/routes/api/widget-areas/_name_.mjs +29 -26
- package/dist/astro/routes/api/widget-areas/index.mjs +32 -28
- package/dist/astro/routes/api/widget-components.mjs +2 -2
- package/dist/astro/routes/robots.txt.mjs +8 -7
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +23 -14
- package/dist/astro/routes/sitemap.xml.mjs +9 -8
- package/dist/astro/session-driver.d.mts +1 -1
- package/dist/astro/session-driver.mjs +2 -2
- package/dist/astro/types.d.mts +22 -13
- package/dist/{audit-BeNsSyfU.mjs → audit-B2pRxZ20.mjs} +1 -1
- package/dist/auth/providers/github.d.mts +1 -1
- package/dist/auth/providers/google.d.mts +1 -1
- package/dist/{auth-control-guard-Dr14I98m.mjs → auth-control-guard-D71jourk.mjs} +1 -1
- package/dist/{authorize-BLO0m7fW.mjs → authorize-ZXPGPwKn.mjs} +1 -1
- package/dist/{briefing-CkTee1ex.mjs → briefing-0rf-su3-.mjs} +90 -28
- package/dist/{briefing-DJX6irto.mjs → briefing-kRhegBoF.mjs} +2 -1
- package/dist/bundle-CEcdUa1s.mjs +602 -0
- package/dist/{byline-BVC1lV6G.mjs → byline-7xR185Ly.mjs} +30 -62
- package/dist/{byline-fields-Cj39WhXC.mjs → byline-fields-BKzA0fJU.mjs} +1 -1
- package/dist/{byline-fields-uJlygdzM.d.mts → byline-fields-Ckeqd1OY.d.mts} +88 -35
- package/dist/{byline-fields-cNrLJVRs.mjs → byline-fields-WN7RQT4q.mjs} +10 -4
- package/dist/{byline-registry-4v6tXMNX.mjs → byline-registry-Ba54M74c.mjs} +2 -2
- package/dist/{bylines-LN9XISRo.mjs → bylines-C-EL6dh3.mjs} +9 -3
- package/dist/{bylines-DwokOFj6.mjs → bylines-CmZFCMr0.mjs} +5 -5
- package/dist/{cache-Bac05gBd.mjs → cache-DkVCKGio.mjs} +2 -2
- package/dist/{chunks-CuUt7K50.mjs → chunks-ChGNDY8r.mjs} +1 -1
- package/dist/cli/index.d.mts +29 -1
- package/dist/cli/index.mjs +1466 -1763
- package/dist/cli/plugin-kit.d.mts +75 -0
- package/dist/cli/plugin-kit.mjs +4 -0
- package/dist/client/external-auth-headers.d.mts +1 -1
- package/dist/client/index.d.mts +27 -52
- package/dist/client/index.mjs +34 -6
- package/dist/client-factory-BDD9azPJ.mjs +233 -0
- package/dist/{comment-Beue8-15.mjs → comment-DFALgsJD.mjs} +13 -3
- package/dist/comment-reaction-C37bf99u.mjs +58 -0
- package/dist/{comments-DcjFIfqX.mjs → comments-B3NwDBxl.mjs} +1 -1
- package/dist/{content-CBxYGWfL.mjs → content-DJcAYFhz.mjs} +51 -34
- package/dist/content-refresh-B4ZCmxgd.mjs +1576 -0
- package/dist/{context-Df1xVYyr.mjs → context-DnpXcp6Y.mjs} +2 -2
- package/dist/{context-CQVNZaR8.mjs → context-fSCYLL3c.mjs} +125 -53
- package/dist/{context-route-helpers-B9wj8Rmh.mjs → context-route-helpers-CI8XzcsK.mjs} +2 -1
- package/dist/{cron-DxCAU_Sn.mjs → cron-B1SL_lsb.mjs} +7 -3
- package/dist/{dashboard-B7ZUNuyF.mjs → dashboard-D0nj5oWg.mjs} +4 -4
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +1 -1
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/{device-flow-BZVGftqi.mjs → device-flow-BJe6VEhP.mjs} +5 -5
- package/dist/{placeholder-d54Qr_7n.mjs → enrich-Ck9tfMqe.mjs} +36 -9
- package/dist/{error-DCSuUAZW.mjs → error-CSVaQNZy.mjs} +5 -0
- package/dist/{experimental-workflows-CSMffP6q.mjs → experimental-workflows-GVhUgzEs.mjs} +1 -1
- package/dist/field-defs-cache-DL-T2XVS.mjs +49 -0
- package/dist/forgeway-seo-client-G5ddhVHd.mjs +456 -0
- package/dist/{fts-manager-B5l_qwrA.mjs → fts-manager-ewTGohf4.mjs} +1 -1
- package/dist/{hitl-requests-7NlzVeUA.mjs → hitl-requests-DEFj8e6u.mjs} +2 -2
- package/dist/{hitl-route-helpers-k---_H3-.mjs → hitl-route-helpers-C23JHaWz.mjs} +5 -4
- package/dist/import-CXXL5pip.mjs +1354 -0
- package/dist/index-DcnOCD1e.d.mts +375 -0
- package/dist/index.d.mts +15 -16
- package/dist/index.mjs +85 -70
- package/dist/{jsonld-DZ67souc.d.mts → jsonld-DzKbhRZ4.d.mts} +8 -4
- package/dist/{load-D9OiGr2m.mjs → load-CFtJRT3c.mjs} +1 -1
- package/dist/{loader--GOYux8M.mjs → loader-aWkkc1_o.mjs} +117 -31
- package/dist/{manifest-schema-qh2P7RDX.mjs → manifest-schema-uA6pRFXi.mjs} +38 -1
- package/dist/mcp-reviewed-executor-Dn52L_yY.mjs +558 -0
- package/dist/media/image-endpoint.d.mts +32 -0
- package/dist/media/image-endpoint.mjs +111 -0
- package/dist/media/index.d.mts +27 -2
- package/dist/media/index.mjs +5 -3
- package/dist/media/local-runtime.d.mts +15 -11
- package/dist/media/local-runtime.mjs +9 -7
- package/dist/{media-CbKyYukG.mjs → media-CVMOoywq.mjs} +3 -1
- package/dist/{media-allowlist-BYxJIcTO.mjs → media-allowlist-zDNJmXCo.mjs} +1 -1
- package/dist/media-url-BdoLPvg8.mjs +20 -0
- package/dist/{menus-C7F0sCrC.mjs → menus-CCG2z5um.mjs} +80 -15
- package/dist/{menus-BZbXf-ej.mjs → menus-CmyIkRG5.mjs} +19 -5
- package/dist/{normalize-ByemhhfW.mjs → normalize-Cyt7JdF7.mjs} +8 -1
- package/dist/{oauth-authorization-DF-LCsIA.mjs → oauth-authorization-B9H7X85L.mjs} +5 -5
- package/dist/{oauth-clients-Du-V0fEm.mjs → oauth-clients-Dwl32-Uj.mjs} +2 -2
- package/dist/object-cache/memory.d.mts +14 -0
- package/dist/object-cache/memory.mjs +55 -0
- package/dist/object-cache-D51qOseS.mjs +419 -0
- package/dist/output-DhBe5_Pi.mjs +68 -0
- package/dist/page/index.d.mts +10 -3
- package/dist/page/index.mjs +2 -2
- package/dist/{parse-CneSgl7-.mjs → parse-i15EFSAE.mjs} +1 -1
- package/dist/{parser-DxfsARXL.mjs → parser-06LLLL4E.mjs} +151 -9
- package/dist/{placeholder-CXfoCtCd.d.mts → placeholder-DkWkXfDI.d.mts} +12 -4
- package/dist/plugin-utils.d.mts +8 -9
- package/dist/plugins/adapt-sandbox-entry.d.mts +8 -9
- package/dist/plugins/adapt-sandbox-entry.mjs +13 -2
- package/dist/{plugins-DzfqRREq.mjs → plugins-CPA2PtfY.mjs} +919 -66
- package/dist/{transport-LSdZVXHF.mjs → portable-text-BASHceim.mjs} +1 -134
- package/dist/portable-text-E79LyjGo.d.mts +45 -0
- package/dist/{preview-C-3YTX2U.mjs → preview-BjedZm8G.mjs} +4 -3
- package/dist/process-cache-CCDgti7j.mjs +106 -0
- package/dist/project-env-DKibmuP8.mjs +95 -0
- package/dist/{provider-loader-9it4vd6L.d.mts → provider-loader-DC2JdWeO.d.mts} +1 -1
- package/dist/{public-url-B4xV4rGa.d.mts → public-url-DQJCnYi9.d.mts} +1 -1
- package/dist/publication-authorization-RKiHXKtq.mjs +985 -0
- package/dist/{query-CZ3TH_Uu.mjs → query-C2U5u5_p.mjs} +11 -6
- package/dist/{query-Ds0EnMFX.mjs → query-DEv5x6yB.mjs} +281 -76
- package/dist/{rate-limit-i0G3IAOX.mjs → rate-limit-DglsAJSw.mjs} +2 -2
- package/dist/{redirect-CEACczxo.mjs → redirect-ubhEqQCQ.mjs} +2 -2
- package/dist/{redirects-ocRJmQHP.mjs → redirects-Bhpm8bRe.mjs} +35 -19
- package/dist/{redirects-DYj1yPxs.mjs → redirects-xs2yahLq.mjs} +47 -24
- package/dist/{registry-D7HACema.mjs → registry-BYYyb2tF.mjs} +148 -81
- package/dist/request-context.d.mts +2 -0
- package/dist/{request-meta-DAb6xaP9.mjs → request-meta-CFe1tW2M.mjs} +1 -1
- package/dist/review-payload-BysRWpxS.mjs +111 -0
- package/dist/{review-requests-BW2NxY-l.mjs → review-requests-CTWGnWUt.mjs} +1 -1
- package/dist/review-requests-D42VEP1l.mjs +142 -0
- package/dist/review-route-helpers-C31vO6SI.mjs +41 -0
- package/dist/runner-BgmttQmD.mjs +7444 -0
- package/dist/{runner-09sTqtv0.d.mts → runner-D6hzs8L9.d.mts} +1 -1
- package/dist/runtime.d.mts +9 -10
- package/dist/runtime.mjs +3 -3
- package/dist/schema-DUtnZxjZ.mjs +8 -0
- package/dist/{search-Gdrqj582.mjs → search-DUKgZUlm.mjs} +5 -3
- package/dist/{secrets-CeGpBFV2.mjs → secrets-ZV5rd8yV.mjs} +14 -11
- package/dist/{sections-BOjU13Y-.mjs → sections-DvFgsnda.mjs} +2 -2
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +21 -18
- package/dist/seo/index.d.mts +1 -1
- package/dist/seo/index.mjs +3 -12
- package/dist/{seo-5lJOtB5J.mjs → seo-BLszyJxd.mjs} +22 -4
- package/dist/{seo-CLj8lS7Q.mjs → seo-BU8MLCS9.mjs} +5 -2
- package/dist/{seo-contributions-CqVvReEj.mjs → seo-contributions-GnCuL1ML.mjs} +11 -3
- package/dist/{service-jGlhrICs.mjs → service-FL3fB0Rx.mjs} +2 -2
- package/dist/session-user-Ct6GBLvn.mjs +22 -0
- package/dist/{settings-CUXOxzsT.mjs → settings-BECcAIRH.mjs} +28 -55
- package/dist/{settings-DlBsT2VS.mjs → settings-EzADu-dd.mjs} +2 -2
- package/dist/{setup-complete-BRrdPYjZ.mjs → setup-complete-Da0axZEE.mjs} +1 -1
- package/dist/{site-activity-DONFrMBJ.mjs → site-activity-h8smOlYI.mjs} +1 -1
- package/dist/{site-context-C8Az_P_I.mjs → site-context-B8T1kpUP.mjs} +337 -530
- package/dist/{site-url-DIMD68go.mjs → site-url-DJEid0ml.mjs} +1 -1
- package/dist/stable-hash-DshcXvRE.mjs +19 -0
- package/dist/status-NqWOSFAD.mjs +21 -0
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +1 -1
- package/dist/storage/s3.d.mts +1 -1
- package/dist/storage/s3.mjs +1 -1
- package/dist/{taxonomies-D9Sw4wWM.mjs → taxonomies-BiM9NtgW.mjs} +36 -29
- package/dist/taxonomies-DSnY5nOa.mjs +443 -0
- package/dist/{taxonomy-B169Nm6B.mjs → taxonomy-0k_vRVsD.mjs} +44 -9
- package/dist/transport-C46KHLMP.mjs +134 -0
- package/dist/types-A8fPOJnl.mjs +69 -0
- package/dist/types-BLQCz46p.d.mts +118 -0
- package/dist/{types-CwawBUTa.d.mts → types-B_FlxIIl.d.mts} +4 -1
- package/dist/{types-CSgR9nQB.mjs → types-ChxYDRvx.mjs} +1 -1
- package/dist/{types-GaCtZ1k9.d.mts → types-Cw0PeCT_.d.mts} +18 -0
- package/dist/types-DuQn1G7-.d.mts +2973 -0
- package/dist/{runtime-C4-2lSFg.d.mts → types-DwoUifGE.d.mts} +5668 -3000
- package/dist/{types-DRIKLmJI.d.mts → types-opjX_kz6.d.mts} +17 -2
- package/dist/ui/server-runtime.d.mts +13 -15
- package/dist/ui/server-runtime.mjs +24 -20
- package/dist/url-guoXQQcV.mjs +33 -0
- package/dist/{user-DTLoZs16.mjs → user-BH-n01y3.mjs} +2 -2
- package/dist/{utils-B8hBJ9Oz.mjs → utils-D1SrKGtP.mjs} +129 -7
- package/dist/{validate-DzjCdy6v.d.mts → validate-CbVFoJgc.d.mts} +65 -8
- package/dist/{validate-DWmPO6oj.mjs → validate-D1HL-oLR.mjs} +11 -6
- package/dist/version-C3iVRuaF.mjs +6 -0
- package/dist/{widgets-BpJ8EpPd.mjs → widgets-BZrLrNgS.mjs} +46 -43
- package/dist/{wordpress-slugs-DHNR9LpG.mjs → wordpress-slugs-C3PWcowq.mjs} +1 -1
- package/dist/{zod-generator-K91-g665.mjs → zod-generator-C7AsLpSB.mjs} +42 -14
- package/package.json +38 -8
- package/src/components/Block.astro +50 -0
- package/src/components/Comments.astro +143 -1
- package/src/components/DinewayHead.astro +19 -2
- package/src/components/DinewayImage.astro +66 -33
- package/src/components/DinewayMedia.astro +1 -2
- package/src/components/Image.astro +85 -40
- package/src/components/InlinePortableTextEditor.tsx +12 -2
- package/src/components/JsonLdScript.astro +9 -0
- package/src/components/LiveSearch.astro +40 -4
- package/src/components/index.ts +4 -0
- package/src/components/live-search-routing.ts +30 -0
- package/src/components/portable-text-text-align.ts +15 -0
- package/src/ui.ts +2 -0
- package/dist/entity-aliases-4y93Yjuc.mjs +0 -51
- package/dist/import-4YBXbDkI.mjs +0 -684
- package/dist/index-CZiWCQDp.d.mts +0 -227
- package/dist/review-requests-Qum4713s.mjs +0 -79
- package/dist/runner-DzIpHGND.mjs +0 -2836
- package/dist/schema-Bsi-VpYv.mjs +0 -8
- package/dist/taxonomies-CZ-_DitR.mjs +0 -327
- package/dist/types-CuJOqHfM.d.mts +0 -758
- package/dist/types-DSPGWjxO.d.mts +0 -1247
- package/dist/types-nP9vGUFq.d.mts +0 -56
- package/dist/version-C4gfn0PJ.mjs +0 -6
- /package/dist/{adapters-OQCImOL7.d.mts → adapters-BiRymx02.d.mts} +0 -0
- /package/dist/{api-tokens-CYUoFGo7.mjs → api-tokens-Ypaly90d.mjs} +0 -0
- /package/dist/{challenge-store-DFxX0_15.mjs → challenge-store-D5pSY2S1.mjs} +0 -0
- /package/dist/{components--pg6zBvu.mjs → components-DImzlLhb.mjs} +0 -0
- /package/dist/{context-types-r8oFvyOq.mjs → context-types-eBuMDAg-.mjs} +0 -0
- /package/dist/{default-B6iaLvP7.mjs → default-D4Vygcy_.mjs} +0 -0
- /package/dist/{email-console-BOVqIYpB.mjs → email-console-Bp0r68iF.mjs} +0 -0
- /package/dist/{escape-BDgouFFY.mjs → escape-DN4Qmjsl.mjs} +0 -0
- /package/dist/{hash-BPmX6P8I.mjs → hash-mxxx2oPW.mjs} +0 -0
- /package/dist/{mime-CbKsKDCB.mjs → mime-C0vOHFm7.mjs} +0 -0
- /package/dist/{mode-CZqCF9dZ.mjs → mode-C8yZnIlR.mjs} +0 -0
- /package/dist/{oauth-state-store-qncPB_IU.mjs → oauth-state-store-CndT35iw.mjs} +0 -0
- /package/dist/{oauth-user-lookup-BFjWF8wK.mjs → oauth-user-lookup-CMehAaKs.mjs} +0 -0
- /package/dist/{options-LsPjZlqD.mjs → options-DQPTH5n0.mjs} +0 -0
- /package/dist/{passkey-config-DzHQK90v.mjs → passkey-config-NYukYGed.mjs} +0 -0
- /package/dist/{patterns-DKAX6oqN.mjs → patterns-CoqlXUvk.mjs} +0 -0
- /package/dist/{provider-loader-DzUxejk7.mjs → provider-loader-BtfrrzfD.mjs} +0 -0
- /package/dist/{public-url-B_m9IwdV.mjs → public-url-Cp6hyjVR.mjs} +0 -0
- /package/dist/{redirect-BjIBtDWY.mjs → redirect-DrbEmU9a.mjs} +0 -0
- /package/dist/{request-cache-ho99schz.mjs → request-cache-CBFjCwSt.mjs} +0 -0
- /package/dist/{resolve-DTuDyYKs.mjs → resolve-DE71WcL9.mjs} +0 -0
- /package/dist/{setup-nonce-pyGfc9hV.mjs → setup-nonce-BtkKnvxQ.mjs} +0 -0
- /package/dist/{sidecar-client-mJxwayYN.mjs → sidecar-client-Ctzfyyp-.mjs} +0 -0
- /package/dist/{slugify-BqebcqBg.mjs → slugify-KSsVD_D3.mjs} +0 -0
- /package/dist/{ssrf-DiKqWd3N.mjs → ssrf-FJqwQzj4.mjs} +0 -0
- /package/dist/{tokens-SLmgowfd.mjs → tokens-BDQU29Vd.mjs} +0 -0
- /package/dist/{transaction-CFLhEOuH.mjs → transaction-0LEAAy8R.mjs} +0 -0
- /package/dist/{transport-BnsNxh2l.d.mts → transport-D9pKAzFP.d.mts} +0 -0
- /package/dist/{trusted-proxy-BgIIcbsN.mjs → trusted-proxy-Dm7Mxvt6.mjs} +0 -0
- /package/dist/{types-CJ8DkL0q.mjs → types-B7TDDFMG.mjs} +0 -0
- /package/dist/{types-Dryvv7LH.d.mts → types-g0zMtraY.d.mts} +0 -0
- /package/dist/{types-Cg276-3z.mjs → types-mVVgRPaU.mjs} +0 -0
- /package/dist/{types-DZpT-PnG.d.mts → types-wFu80FrO.d.mts} +0 -0
- /package/dist/{url-w7Mp5qAD.mjs → url-EMAguGVk.mjs} +0 -0
|
@@ -1,14 +1,75 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { n as
|
|
4
|
-
import { t as
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { r as validatePluginIdentifier, t as validateIdentifier } from "./validate-VPnKoIzW.mjs";
|
|
2
|
+
import { i as encodeBase64url, n as decodeBase64url } from "./base64-F8-DUraK.mjs";
|
|
3
|
+
import { n as chunks, t as SQL_BATCH_SIZE } from "./chunks-ChGNDY8r.mjs";
|
|
4
|
+
import { t as OptionsRepository } from "./options-DQPTH5n0.mjs";
|
|
5
|
+
import { n as createPluginContext, t as PluginContextFactory } from "./context-fSCYLL3c.mjs";
|
|
6
|
+
import { t as extractRequestMeta } from "./request-meta-CFe1tW2M.mjs";
|
|
7
|
+
import { r as setCronTasksEnabled } from "./cron-B1SL_lsb.mjs";
|
|
8
|
+
import { i as parseEncryptionKeys, t as DinewaySecretsError } from "./secrets-ZV5rd8yV.mjs";
|
|
9
|
+
import { Migrator, sql } from "kysely";
|
|
7
10
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
8
11
|
import { ulid } from "ulidx";
|
|
9
12
|
import { z } from "astro/zod";
|
|
13
|
+
import { Permissions } from "@dineway-ai/auth";
|
|
10
14
|
import { Worker } from "node:worker_threads";
|
|
11
15
|
|
|
16
|
+
//#region src/database/repositories/plugin-secrets.ts
|
|
17
|
+
var PluginSecretRepository = class {
|
|
18
|
+
constructor(db) {
|
|
19
|
+
this.db = db;
|
|
20
|
+
}
|
|
21
|
+
async put(input) {
|
|
22
|
+
const now = input.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
23
|
+
const row = {
|
|
24
|
+
plugin_id: input.pluginId,
|
|
25
|
+
secret_name: input.name,
|
|
26
|
+
encryption_version: input.encryptionVersion,
|
|
27
|
+
key_id: input.keyId,
|
|
28
|
+
nonce: input.nonce,
|
|
29
|
+
ciphertext: input.ciphertext,
|
|
30
|
+
fingerprint: input.fingerprint,
|
|
31
|
+
created_at: now,
|
|
32
|
+
updated_at: now
|
|
33
|
+
};
|
|
34
|
+
await this.db.insertInto("_dineway_plugin_secrets").values(row).onConflict((conflict) => conflict.columns(["plugin_id", "secret_name"]).doUpdateSet({
|
|
35
|
+
encryption_version: row.encryption_version,
|
|
36
|
+
key_id: row.key_id,
|
|
37
|
+
nonce: row.nonce,
|
|
38
|
+
ciphertext: row.ciphertext,
|
|
39
|
+
fingerprint: row.fingerprint,
|
|
40
|
+
updated_at: now
|
|
41
|
+
})).execute();
|
|
42
|
+
const stored = await this.find(input.pluginId, input.name);
|
|
43
|
+
if (!stored) throw new Error("Plugin secret write did not persist.");
|
|
44
|
+
return stored;
|
|
45
|
+
}
|
|
46
|
+
async find(pluginId, name) {
|
|
47
|
+
const row = await this.db.selectFrom("_dineway_plugin_secrets").selectAll().where("plugin_id", "=", pluginId).where("secret_name", "=", name).executeTakeFirst();
|
|
48
|
+
return row ? rowToStoredPluginSecret(row) : null;
|
|
49
|
+
}
|
|
50
|
+
async list(pluginId) {
|
|
51
|
+
return (await this.db.selectFrom("_dineway_plugin_secrets").selectAll().where("plugin_id", "=", pluginId).orderBy("secret_name", "asc").execute()).map(rowToStoredPluginSecret);
|
|
52
|
+
}
|
|
53
|
+
async delete(pluginId, name) {
|
|
54
|
+
const result = await this.db.deleteFrom("_dineway_plugin_secrets").where("plugin_id", "=", pluginId).where("secret_name", "=", name).executeTakeFirst();
|
|
55
|
+
return Number(result.numDeletedRows ?? 0) > 0;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
function rowToStoredPluginSecret(row) {
|
|
59
|
+
return {
|
|
60
|
+
pluginId: row.plugin_id,
|
|
61
|
+
name: row.secret_name,
|
|
62
|
+
encryptionVersion: row.encryption_version,
|
|
63
|
+
keyId: row.key_id,
|
|
64
|
+
nonce: row.nonce,
|
|
65
|
+
ciphertext: row.ciphertext,
|
|
66
|
+
fingerprint: row.fingerprint,
|
|
67
|
+
createdAt: row.created_at,
|
|
68
|
+
updatedAt: row.updated_at
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
//#endregion
|
|
12
73
|
//#region src/database/repositories/relation.ts
|
|
13
74
|
var RelationRepository = class {
|
|
14
75
|
constructor(db) {
|
|
@@ -267,6 +328,9 @@ function portableText(options) {
|
|
|
267
328
|
function generateKey() {
|
|
268
329
|
return Math.random().toString(36).substring(2, 11);
|
|
269
330
|
}
|
|
331
|
+
function normalizeTextAlign(value) {
|
|
332
|
+
return value === "center" || value === "right" || value === "justify" ? value : void 0;
|
|
333
|
+
}
|
|
270
334
|
/**
|
|
271
335
|
* Convert ProseMirror document to Portable Text
|
|
272
336
|
*/
|
|
@@ -311,13 +375,15 @@ function convertNode(node) {
|
|
|
311
375
|
*/
|
|
312
376
|
function convertParagraph(node) {
|
|
313
377
|
const { children, markDefs } = convertInlineContent(node.content || []);
|
|
378
|
+
const textAlign = normalizeTextAlign(node.attrs?.textAlign);
|
|
314
379
|
if (children.length === 0) return null;
|
|
315
380
|
return {
|
|
316
381
|
_type: "block",
|
|
317
382
|
_key: generateKey(),
|
|
318
383
|
style: "normal",
|
|
319
384
|
children,
|
|
320
|
-
markDefs: markDefs.length > 0 ? markDefs : void 0
|
|
385
|
+
markDefs: markDefs.length > 0 ? markDefs : void 0,
|
|
386
|
+
...textAlign ? { textAlign } : {}
|
|
321
387
|
};
|
|
322
388
|
}
|
|
323
389
|
/** Map heading level number to Portable Text style */
|
|
@@ -338,13 +404,15 @@ function headingLevelToStyle(level) {
|
|
|
338
404
|
function convertHeading(node) {
|
|
339
405
|
const { children, markDefs } = convertInlineContent(node.content || []);
|
|
340
406
|
const style = headingLevelToStyle(typeof node.attrs?.level === "number" ? node.attrs.level : 1);
|
|
407
|
+
const textAlign = normalizeTextAlign(node.attrs?.textAlign);
|
|
341
408
|
if (children.length === 0) return null;
|
|
342
409
|
return {
|
|
343
410
|
_type: "block",
|
|
344
411
|
_key: generateKey(),
|
|
345
412
|
style,
|
|
346
413
|
children,
|
|
347
|
-
markDefs: markDefs.length > 0 ? markDefs : void 0
|
|
414
|
+
markDefs: markDefs.length > 0 ? markDefs : void 0,
|
|
415
|
+
...textAlign ? { textAlign } : {}
|
|
348
416
|
};
|
|
349
417
|
}
|
|
350
418
|
/**
|
|
@@ -612,6 +680,7 @@ function convertBlock(block) {
|
|
|
612
680
|
*/
|
|
613
681
|
function convertTextBlock(block) {
|
|
614
682
|
const { style = "normal", children, markDefs = [] } = block;
|
|
683
|
+
const textAlignAttrs = block.textAlign ? { textAlign: block.textAlign } : void 0;
|
|
615
684
|
const content = convertSpans(children, markDefs);
|
|
616
685
|
switch (style) {
|
|
617
686
|
case "h1":
|
|
@@ -621,7 +690,10 @@ function convertTextBlock(block) {
|
|
|
621
690
|
case "h5":
|
|
622
691
|
case "h6": return {
|
|
623
692
|
type: "heading",
|
|
624
|
-
attrs: {
|
|
693
|
+
attrs: {
|
|
694
|
+
level: parseInt(style.substring(1), 10),
|
|
695
|
+
...textAlignAttrs
|
|
696
|
+
},
|
|
625
697
|
content: content.length > 0 ? content : void 0
|
|
626
698
|
};
|
|
627
699
|
case "blockquote": return {
|
|
@@ -633,6 +705,7 @@ function convertTextBlock(block) {
|
|
|
633
705
|
};
|
|
634
706
|
default: return {
|
|
635
707
|
type: "paragraph",
|
|
708
|
+
attrs: textAlignAttrs,
|
|
636
709
|
content: content.length > 0 ? content : void 0
|
|
637
710
|
};
|
|
638
711
|
}
|
|
@@ -825,18 +898,343 @@ function convertHtmlBlock(block) {
|
|
|
825
898
|
}
|
|
826
899
|
|
|
827
900
|
//#endregion
|
|
828
|
-
//#region src/
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
901
|
+
//#region src/security/guest-access-tokens.ts
|
|
902
|
+
const TOKEN_VERSION = 1;
|
|
903
|
+
const DEFAULT_TTL_SECONDS = 720 * 60 * 60;
|
|
904
|
+
const MAX_TTL_SECONDS = 3 * 366 * 24 * 60 * 60;
|
|
905
|
+
const MAX_CLOCK_SKEW_SECONDS = 60;
|
|
906
|
+
const MAX_TOKEN_LENGTH = 4096;
|
|
907
|
+
const MIN_SECRET_BYTES = 32;
|
|
908
|
+
const MAX_SECRET_BYTES$1 = 4096;
|
|
909
|
+
const MAX_VERIFICATION_SECRETS = 8;
|
|
910
|
+
const SIGNATURE_BYTES = 32;
|
|
911
|
+
const DURATION_PATTERN = /^(\d+)([smhdw])$/;
|
|
912
|
+
const BASE64URL_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
913
|
+
const TOKEN_ID_PATTERN = /^gt_[A-Za-z0-9_-]{32}$/;
|
|
914
|
+
const PURPOSE_PATTERN = /^[a-z][a-z0-9_.:-]{0,127}$/;
|
|
915
|
+
const SUBJECT_TYPE_PATTERN = /^[a-z][a-z0-9_-]{0,63}$/;
|
|
916
|
+
const OPAQUE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/;
|
|
917
|
+
const SIGNING_DOMAIN = "dineway-guest-access:v1";
|
|
918
|
+
const HASH_DOMAIN = "dineway-guest-access-hash:v1";
|
|
919
|
+
var GuestAccessTokenConfigurationError = class extends Error {
|
|
920
|
+
name = "GuestAccessTokenConfigurationError";
|
|
921
|
+
constructor(message, code) {
|
|
922
|
+
super(message);
|
|
923
|
+
this.code = code;
|
|
924
|
+
}
|
|
925
|
+
};
|
|
926
|
+
/**
|
|
927
|
+
* Issues an expiring capability for one guest purpose and one restaurant target.
|
|
928
|
+
* Persist only tokenHash/tokenId and revocation metadata; never persist or log token.
|
|
929
|
+
*/
|
|
930
|
+
async function issueGuestAccessToken(options) {
|
|
931
|
+
validateSecret(options.secret);
|
|
932
|
+
validateScope({
|
|
933
|
+
purpose: options.purpose,
|
|
934
|
+
subjectType: options.subjectType,
|
|
935
|
+
subjectId: options.subjectId,
|
|
936
|
+
locationId: options.locationId
|
|
832
937
|
});
|
|
938
|
+
if (options.tokenId !== void 0 && !TOKEN_ID_PATTERN.test(options.tokenId)) invalidInput("Guest access token identifier is invalid.");
|
|
939
|
+
const issuedAt = normalizeNow(options.now);
|
|
940
|
+
const ttl = parseDuration(options.expiresIn ?? DEFAULT_TTL_SECONDS);
|
|
941
|
+
const claims = {
|
|
942
|
+
version: TOKEN_VERSION,
|
|
943
|
+
tokenId: options.tokenId ?? randomTokenId(),
|
|
944
|
+
purpose: options.purpose,
|
|
945
|
+
subjectType: options.subjectType,
|
|
946
|
+
subjectId: options.subjectId,
|
|
947
|
+
locationId: options.locationId,
|
|
948
|
+
issuedAt,
|
|
949
|
+
expiresAt: issuedAt + ttl
|
|
950
|
+
};
|
|
951
|
+
const encodedPayload = encodeBase64url(new TextEncoder().encode(JSON.stringify(claims)));
|
|
952
|
+
const token = `${encodedPayload}.${encodeBase64url(await sign(encodedPayload, options.secret))}`;
|
|
953
|
+
return {
|
|
954
|
+
token,
|
|
955
|
+
tokenHash: await hashGuestAccessToken(token),
|
|
956
|
+
claims
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Verifies signature, expiry, and the caller-declared purpose/target scope.
|
|
961
|
+
* A valid result still requires the plugin to check its revocation record by tokenHash.
|
|
962
|
+
*/
|
|
963
|
+
async function verifyGuestAccessToken(options) {
|
|
964
|
+
if (!options.token) return {
|
|
965
|
+
valid: false,
|
|
966
|
+
error: "none"
|
|
967
|
+
};
|
|
968
|
+
validateExpectedScope(options.expected);
|
|
969
|
+
const secrets = normalizeSecrets(options.secrets);
|
|
970
|
+
const token = options.token;
|
|
971
|
+
if (token.length > MAX_TOKEN_LENGTH) return {
|
|
972
|
+
valid: false,
|
|
973
|
+
error: "malformed"
|
|
974
|
+
};
|
|
975
|
+
const parts = token.split(".");
|
|
976
|
+
if (parts.length !== 2) return {
|
|
977
|
+
valid: false,
|
|
978
|
+
error: "malformed"
|
|
979
|
+
};
|
|
980
|
+
const encodedPayload = parts[0];
|
|
981
|
+
const encodedSignature = parts[1];
|
|
982
|
+
if (!encodedPayload || !encodedSignature) return {
|
|
983
|
+
valid: false,
|
|
984
|
+
error: "malformed"
|
|
985
|
+
};
|
|
986
|
+
const signature = decodeCanonicalBase64url(encodedSignature);
|
|
987
|
+
if (!signature || signature.byteLength !== SIGNATURE_BYTES) return {
|
|
988
|
+
valid: false,
|
|
989
|
+
error: "malformed"
|
|
990
|
+
};
|
|
991
|
+
let signatureValid = false;
|
|
992
|
+
for (const secret of secrets) if (await verifySignature(encodedPayload, signature, secret)) {
|
|
993
|
+
signatureValid = true;
|
|
994
|
+
break;
|
|
995
|
+
}
|
|
996
|
+
if (!signatureValid) return {
|
|
997
|
+
valid: false,
|
|
998
|
+
error: "invalid"
|
|
999
|
+
};
|
|
1000
|
+
const claims = decodeClaims(encodedPayload);
|
|
1001
|
+
if (!claims) return {
|
|
1002
|
+
valid: false,
|
|
1003
|
+
error: "malformed"
|
|
1004
|
+
};
|
|
1005
|
+
const now = normalizeNow(options.now);
|
|
1006
|
+
if (claims.issuedAt > now + MAX_CLOCK_SKEW_SECONDS) return {
|
|
1007
|
+
valid: false,
|
|
1008
|
+
error: "invalid"
|
|
1009
|
+
};
|
|
1010
|
+
if (claims.expiresAt <= now) return {
|
|
1011
|
+
valid: false,
|
|
1012
|
+
error: "expired"
|
|
1013
|
+
};
|
|
1014
|
+
if (!matchesExpectedScope(claims, options.expected)) return {
|
|
1015
|
+
valid: false,
|
|
1016
|
+
error: "scope_mismatch"
|
|
1017
|
+
};
|
|
1018
|
+
return {
|
|
1019
|
+
valid: true,
|
|
1020
|
+
claims,
|
|
1021
|
+
tokenHash: await hashGuestAccessToken(token)
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
/** Returns the non-reversible identifier stored for revocation and lookup. */
|
|
1025
|
+
async function hashGuestAccessToken(token) {
|
|
1026
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(`${HASH_DOMAIN}.${token}`));
|
|
1027
|
+
return encodeBase64url(new Uint8Array(digest));
|
|
1028
|
+
}
|
|
1029
|
+
async function sign(encodedPayload, secret) {
|
|
1030
|
+
const key = await importHmacKey(secret, ["sign"]);
|
|
1031
|
+
const signature = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(`${SIGNING_DOMAIN}.${encodedPayload}`));
|
|
1032
|
+
return new Uint8Array(signature);
|
|
1033
|
+
}
|
|
1034
|
+
async function verifySignature(encodedPayload, signature, secret) {
|
|
1035
|
+
const key = await importHmacKey(secret, ["verify"]);
|
|
1036
|
+
return crypto.subtle.verify("HMAC", key, toArrayBuffer$2(signature), new TextEncoder().encode(`${SIGNING_DOMAIN}.${encodedPayload}`));
|
|
1037
|
+
}
|
|
1038
|
+
async function importHmacKey(secret, usages) {
|
|
1039
|
+
return crypto.subtle.importKey("raw", new TextEncoder().encode(secret), {
|
|
1040
|
+
name: "HMAC",
|
|
1041
|
+
hash: "SHA-256"
|
|
1042
|
+
}, false, usages);
|
|
1043
|
+
}
|
|
1044
|
+
function decodeClaims(encodedPayload) {
|
|
1045
|
+
const bytes = decodeCanonicalBase64url(encodedPayload);
|
|
1046
|
+
if (!bytes) return null;
|
|
1047
|
+
let value;
|
|
1048
|
+
try {
|
|
1049
|
+
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
1050
|
+
} catch {
|
|
1051
|
+
return null;
|
|
1052
|
+
}
|
|
1053
|
+
if (!isRecord$1(value) || !hasExactClaimKeys(value)) return null;
|
|
1054
|
+
if (value.version !== TOKEN_VERSION || typeof value.tokenId !== "string" || typeof value.purpose !== "string" || typeof value.subjectType !== "string" || typeof value.subjectId !== "string" || typeof value.locationId !== "string" || typeof value.issuedAt !== "number" || typeof value.expiresAt !== "number") return null;
|
|
1055
|
+
if (!TOKEN_ID_PATTERN.test(value.tokenId) || !PURPOSE_PATTERN.test(value.purpose) || !SUBJECT_TYPE_PATTERN.test(value.subjectType) || !OPAQUE_ID_PATTERN.test(value.subjectId) || !OPAQUE_ID_PATTERN.test(value.locationId) || !Number.isSafeInteger(value.issuedAt) || !Number.isSafeInteger(value.expiresAt) || value.expiresAt <= value.issuedAt || value.expiresAt - value.issuedAt > MAX_TTL_SECONDS) return null;
|
|
1056
|
+
return {
|
|
1057
|
+
version: TOKEN_VERSION,
|
|
1058
|
+
tokenId: value.tokenId,
|
|
1059
|
+
purpose: value.purpose,
|
|
1060
|
+
subjectType: value.subjectType,
|
|
1061
|
+
subjectId: value.subjectId,
|
|
1062
|
+
locationId: value.locationId,
|
|
1063
|
+
issuedAt: value.issuedAt,
|
|
1064
|
+
expiresAt: value.expiresAt
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
function hasExactClaimKeys(value) {
|
|
1068
|
+
const keys = Object.keys(value).toSorted();
|
|
1069
|
+
return keys.length === 8 && keys.join(",") === "expiresAt,issuedAt,locationId,purpose,subjectId,subjectType,tokenId,version";
|
|
1070
|
+
}
|
|
1071
|
+
function matchesExpectedScope(claims, expected) {
|
|
1072
|
+
return claims.purpose === expected.purpose && claims.subjectType === expected.subjectType && (expected.subjectId === void 0 || claims.subjectId === expected.subjectId) && (expected.locationId === void 0 || claims.locationId === expected.locationId);
|
|
1073
|
+
}
|
|
1074
|
+
function validateExpectedScope(expected) {
|
|
1075
|
+
validatePurpose(expected.purpose);
|
|
1076
|
+
validateSubjectType(expected.subjectType);
|
|
1077
|
+
if (expected.subjectId !== void 0) validateOpaqueId(expected.subjectId, "subject id");
|
|
1078
|
+
if (expected.locationId !== void 0) validateOpaqueId(expected.locationId, "location id");
|
|
1079
|
+
}
|
|
1080
|
+
function validateScope(scope) {
|
|
1081
|
+
validatePurpose(scope.purpose);
|
|
1082
|
+
validateSubjectType(scope.subjectType);
|
|
1083
|
+
validateOpaqueId(scope.subjectId, "subject id");
|
|
1084
|
+
validateOpaqueId(scope.locationId, "location id");
|
|
1085
|
+
}
|
|
1086
|
+
function validatePurpose(value) {
|
|
1087
|
+
if (!PURPOSE_PATTERN.test(value)) invalidInput("Guest access purpose is invalid.");
|
|
1088
|
+
}
|
|
1089
|
+
function validateSubjectType(value) {
|
|
1090
|
+
if (!SUBJECT_TYPE_PATTERN.test(value)) invalidInput("Guest access subject type is invalid.");
|
|
1091
|
+
}
|
|
1092
|
+
function validateOpaqueId(value, label) {
|
|
1093
|
+
if (!OPAQUE_ID_PATTERN.test(value)) invalidInput(`Guest access ${label} is invalid.`);
|
|
1094
|
+
}
|
|
1095
|
+
function parseDuration(value) {
|
|
1096
|
+
let seconds;
|
|
1097
|
+
if (typeof value === "number") seconds = value;
|
|
1098
|
+
else {
|
|
1099
|
+
const match = DURATION_PATTERN.exec(value);
|
|
1100
|
+
if (!match) invalidInput("Guest access expiry duration is invalid.");
|
|
1101
|
+
seconds = Number(match[1]) * ({
|
|
1102
|
+
s: 1,
|
|
1103
|
+
m: 60,
|
|
1104
|
+
h: 3600,
|
|
1105
|
+
d: 1440 * 60,
|
|
1106
|
+
w: 10080 * 60
|
|
1107
|
+
}[match[2]] ?? 0);
|
|
1108
|
+
}
|
|
1109
|
+
if (!Number.isSafeInteger(seconds) || seconds <= 0 || seconds > MAX_TTL_SECONDS) invalidInput("Guest access expiry must be between one second and 366 days.");
|
|
1110
|
+
return seconds;
|
|
1111
|
+
}
|
|
1112
|
+
function normalizeSecrets(value) {
|
|
1113
|
+
const secrets = typeof value === "string" ? [value] : [...new Set(value)];
|
|
1114
|
+
if (secrets.length === 0 || secrets.length > MAX_VERIFICATION_SECRETS) throw new GuestAccessTokenConfigurationError(`Guest access verification requires 1-${MAX_VERIFICATION_SECRETS} secrets.`, "INVALID_GUEST_TOKEN_SECRET");
|
|
1115
|
+
for (const secret of secrets) validateSecret(secret);
|
|
1116
|
+
return secrets;
|
|
1117
|
+
}
|
|
1118
|
+
function validateSecret(secret) {
|
|
1119
|
+
const secretBytes = new TextEncoder().encode(secret).byteLength;
|
|
1120
|
+
if (secretBytes < MIN_SECRET_BYTES || secretBytes > MAX_SECRET_BYTES$1) throw new GuestAccessTokenConfigurationError(`Guest access signing secrets must contain ${MIN_SECRET_BYTES}-${MAX_SECRET_BYTES$1} UTF-8 bytes.`, "INVALID_GUEST_TOKEN_SECRET");
|
|
1121
|
+
}
|
|
1122
|
+
function normalizeNow(value) {
|
|
1123
|
+
const now = value ?? Math.floor(Date.now() / 1e3);
|
|
1124
|
+
if (!Number.isSafeInteger(now) || now < 0) invalidInput("Guest access clock value is invalid.");
|
|
1125
|
+
return now;
|
|
1126
|
+
}
|
|
1127
|
+
function randomTokenId() {
|
|
1128
|
+
return `gt_${encodeBase64url(crypto.getRandomValues(new Uint8Array(24)))}`;
|
|
1129
|
+
}
|
|
1130
|
+
function decodeCanonicalBase64url(value) {
|
|
1131
|
+
if (!BASE64URL_PATTERN.test(value)) return null;
|
|
1132
|
+
try {
|
|
1133
|
+
const decoded = decodeBase64url(value);
|
|
1134
|
+
return encodeBase64url(decoded) === value ? decoded : null;
|
|
1135
|
+
} catch {
|
|
1136
|
+
return null;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
function toArrayBuffer$2(bytes) {
|
|
1140
|
+
const buffer = new ArrayBuffer(bytes.byteLength);
|
|
1141
|
+
new Uint8Array(buffer).set(bytes);
|
|
1142
|
+
return buffer;
|
|
1143
|
+
}
|
|
1144
|
+
function isRecord$1(value) {
|
|
1145
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1146
|
+
}
|
|
1147
|
+
function invalidInput(message) {
|
|
1148
|
+
throw new GuestAccessTokenConfigurationError(message, "INVALID_GUEST_TOKEN_INPUT");
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
//#endregion
|
|
1152
|
+
//#region src/plugins/route-body.ts
|
|
1153
|
+
const CONTENT_LENGTH_PATTERN = /^(?:0|[1-9][0-9]*)$/;
|
|
1154
|
+
const DEFAULT_PLUGIN_RAW_BODY_LIMIT_BYTES = 1024 * 1024;
|
|
1155
|
+
const MAX_PLUGIN_RAW_BODY_LIMIT_BYTES = 10 * 1024 * 1024;
|
|
1156
|
+
async function readBoundedPluginRouteBody(request, limit) {
|
|
1157
|
+
const declaredLength = request.headers.get("content-length");
|
|
1158
|
+
if (declaredLength && CONTENT_LENGTH_PATTERN.test(declaredLength) && Number(declaredLength) > limit) return {
|
|
1159
|
+
ok: false,
|
|
1160
|
+
limit
|
|
1161
|
+
};
|
|
1162
|
+
if (!request.body) return {
|
|
1163
|
+
ok: true,
|
|
1164
|
+
bytes: new Uint8Array()
|
|
1165
|
+
};
|
|
1166
|
+
const reader = request.body.getReader();
|
|
1167
|
+
const chunks = [];
|
|
1168
|
+
let total = 0;
|
|
1169
|
+
try {
|
|
1170
|
+
while (true) {
|
|
1171
|
+
const chunk = await reader.read();
|
|
1172
|
+
if (chunk.done) break;
|
|
1173
|
+
if (total + chunk.value.byteLength > limit) {
|
|
1174
|
+
await reader.cancel().catch(() => void 0);
|
|
1175
|
+
return {
|
|
1176
|
+
ok: false,
|
|
1177
|
+
limit
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
chunks.push(chunk.value);
|
|
1181
|
+
total += chunk.value.byteLength;
|
|
1182
|
+
}
|
|
1183
|
+
} finally {
|
|
1184
|
+
reader.releaseLock();
|
|
1185
|
+
}
|
|
1186
|
+
const bytes = new Uint8Array(total);
|
|
1187
|
+
let offset = 0;
|
|
1188
|
+
for (const chunk of chunks) {
|
|
1189
|
+
bytes.set(chunk, offset);
|
|
1190
|
+
offset += chunk.byteLength;
|
|
1191
|
+
}
|
|
1192
|
+
return {
|
|
1193
|
+
ok: true,
|
|
1194
|
+
bytes
|
|
1195
|
+
};
|
|
833
1196
|
}
|
|
834
1197
|
|
|
835
1198
|
//#endregion
|
|
836
1199
|
//#region src/plugins/define-plugin.ts
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
1200
|
+
/**
|
|
1201
|
+
* definePlugin() Helper
|
|
1202
|
+
*
|
|
1203
|
+
* Creates a properly typed and normalized plugin definition.
|
|
1204
|
+
* Supports two formats:
|
|
1205
|
+
*
|
|
1206
|
+
* 1. **Native format** -- full PluginDefinition with id, version, capabilities, etc.
|
|
1207
|
+
* Returns a ResolvedPlugin.
|
|
1208
|
+
*
|
|
1209
|
+
* 2. **Standard format** -- just { hooks, routes }. No id/version/capabilities.
|
|
1210
|
+
* Returns the same object (identity function for type inference).
|
|
1211
|
+
* Metadata comes from the descriptor at config time.
|
|
1212
|
+
*
|
|
1213
|
+
*/
|
|
1214
|
+
const MCP_NAME_PATTERN = /^[a-z][a-z0-9_]{2,127}$/;
|
|
1215
|
+
const MCP_RISK_FACTORS = new Set([
|
|
1216
|
+
"content",
|
|
1217
|
+
"availability",
|
|
1218
|
+
"time",
|
|
1219
|
+
"money",
|
|
1220
|
+
"allergen",
|
|
1221
|
+
"pii",
|
|
1222
|
+
"batch",
|
|
1223
|
+
"payment",
|
|
1224
|
+
"destructive",
|
|
1225
|
+
"external_side_effect"
|
|
1226
|
+
]);
|
|
1227
|
+
const STRONG_RISK_FACTORS = new Set([
|
|
1228
|
+
"availability",
|
|
1229
|
+
"time",
|
|
1230
|
+
"money",
|
|
1231
|
+
"allergen",
|
|
1232
|
+
"pii",
|
|
1233
|
+
"batch",
|
|
1234
|
+
"payment",
|
|
1235
|
+
"destructive",
|
|
1236
|
+
"external_side_effect"
|
|
1237
|
+
]);
|
|
840
1238
|
function definePlugin(definition) {
|
|
841
1239
|
if (!("id" in definition) || !("version" in definition)) {
|
|
842
1240
|
if (!("hooks" in definition) && !("routes" in definition)) throw new Error("Standard plugin format requires at least `hooks` or `routes`. For native format, provide `id` and `version`.");
|
|
@@ -848,13 +1246,20 @@ function definePlugin(definition) {
|
|
|
848
1246
|
* Internal: define a native-format plugin with full validation and normalization.
|
|
849
1247
|
*/
|
|
850
1248
|
function defineNativePlugin(definition) {
|
|
851
|
-
const
|
|
1249
|
+
const SIMPLE_ID = /^[a-z0-9-]+$/;
|
|
1250
|
+
const SCOPED_ID = /^@[a-z0-9-]+\/[a-z0-9-]+$/;
|
|
1251
|
+
const SEMVER_PATTERN = /^\d+\.\d+\.\d+/;
|
|
1252
|
+
const MIGRATION_NAME_PATTERN = /^\d{3}_[a-z][a-z0-9_]*$/;
|
|
1253
|
+
const { id, version, capabilities = [], allowedHosts = [], migrations = {}, hooks = {}, routes = {}, publicationReleasePolicies = [], admin = {} } = definition;
|
|
1254
|
+
const mcp = resolveMcp(definition.mcp, id);
|
|
852
1255
|
const storage = definition.storage ?? {};
|
|
1256
|
+
const normalizedMigrations = Object.fromEntries(Object.entries(migrations).toSorted(([left], [right]) => left.localeCompare(right)));
|
|
853
1257
|
if (!SIMPLE_ID.test(id) && !SCOPED_ID.test(id)) throw new Error(`Invalid plugin id "${id}". Must be lowercase alphanumeric with dashes (e.g., "my-plugin" or "@scope/my-plugin").`);
|
|
854
1258
|
if (!SEMVER_PATTERN.test(version)) throw new Error(`Invalid plugin version "${version}". Must be semver format (e.g., "1.0.0").`);
|
|
855
1259
|
const validCapabilities = new Set([
|
|
856
1260
|
"network:request",
|
|
857
1261
|
"network:request:unrestricted",
|
|
1262
|
+
"network:request:development-loopback",
|
|
858
1263
|
"content:read",
|
|
859
1264
|
"content:write",
|
|
860
1265
|
"media:read",
|
|
@@ -866,21 +1271,125 @@ function defineNativePlugin(definition) {
|
|
|
866
1271
|
"hooks.page-fragments:register"
|
|
867
1272
|
]);
|
|
868
1273
|
for (const cap of capabilities) if (!validCapabilities.has(cap)) throw new Error(`Invalid capability "${cap}" in plugin "${id}".`);
|
|
1274
|
+
for (const [name, migration] of Object.entries(normalizedMigrations)) {
|
|
1275
|
+
if (!MIGRATION_NAME_PATTERN.test(name)) throw new Error(`Invalid migration name "${name}" in plugin "${id}". Use NNN_descriptive_name.`);
|
|
1276
|
+
if (typeof migration.up !== "function") throw new Error(`Plugin "${id}" migration "${name}" requires an up function.`);
|
|
1277
|
+
if (migration.down !== void 0 && typeof migration.down !== "function") throw new Error(`Plugin "${id}" migration "${name}" has an invalid down function.`);
|
|
1278
|
+
}
|
|
869
1279
|
const normalizedCapabilities = [...capabilities];
|
|
870
1280
|
if (capabilities.includes("content:write") && !capabilities.includes("content:read")) normalizedCapabilities.push("content:read");
|
|
871
1281
|
if (capabilities.includes("media:write") && !capabilities.includes("media:read")) normalizedCapabilities.push("media:read");
|
|
872
1282
|
if (capabilities.includes("network:request:unrestricted") && !capabilities.includes("network:request")) normalizedCapabilities.push("network:request");
|
|
1283
|
+
if (capabilities.includes("network:request:development-loopback") && !capabilities.includes("network:request:unrestricted")) throw new Error(`Plugin "${id}" must also declare "network:request:unrestricted" when using "network:request:development-loopback".`);
|
|
1284
|
+
for (const [routeName, route] of Object.entries(routes)) {
|
|
1285
|
+
if (route.body !== void 0 && route.body !== "json" && route.body !== "raw") throw new Error(`Plugin "${id}" route "${routeName}" has an invalid body mode.`);
|
|
1286
|
+
if (route.body === "raw" && route.input) throw new Error(`Plugin "${id}" raw-body route "${routeName}" cannot declare an input schema.`);
|
|
1287
|
+
if (route.body !== "raw" && route.maxBodyBytes !== void 0) throw new Error(`Plugin "${id}" JSON route "${routeName}" cannot declare a raw body-size limit.`);
|
|
1288
|
+
if (route.body === "raw" && route.maxBodyBytes !== void 0 && (!Number.isSafeInteger(route.maxBodyBytes) || route.maxBodyBytes < 1 || route.maxBodyBytes > MAX_PLUGIN_RAW_BODY_LIMIT_BYTES)) throw new Error(`Plugin "${id}" raw-body route "${routeName}" has an invalid body-size limit.`);
|
|
1289
|
+
if (route.public === true && route.authorization) throw new Error(`Plugin "${id}" public route "${routeName}" cannot declare private authorization.`);
|
|
1290
|
+
if (route.apiTokenPolicy && (route.public === true || !route.authorization)) throw new Error(`Plugin "${id}" route "${routeName}" API-token policy requires private authorization.`);
|
|
1291
|
+
if (route.apiTokenPolicy?.effect === "write" && (route.apiTokenPolicy.idempotencyField.trim().length === 0 || !route.input)) throw new Error(`Plugin "${id}" route "${routeName}" API-token write policy requires a parsed input schema and idempotency field.`);
|
|
1292
|
+
if (route.authorization && (route.authorization.anyOfTokenScopes.length === 0 || route.authorization.anyOfTokenScopes.some((scope) => scope.trim().length === 0))) throw new Error(`Plugin "${id}" route "${routeName}" requires at least one valid token scope.`);
|
|
1293
|
+
const restaurantPermission = route.authorization?.restaurantPermission;
|
|
1294
|
+
if (restaurantPermission !== void 0 && (restaurantPermission.trim().length === 0 || typeof route.resolveTarget !== "function" || typeof route.authorizeTarget !== "function")) throw new Error(`Plugin "${id}" route "${routeName}" requires a restaurant target resolver and authorizer.`);
|
|
1295
|
+
if (!restaurantPermission && (route.resolveTarget || route.authorizeTarget)) throw new Error(`Plugin "${id}" route "${routeName}" cannot declare target authorization without a restaurant permission.`);
|
|
1296
|
+
}
|
|
1297
|
+
const resolvedHooks = resolveHooks(hooks, id);
|
|
1298
|
+
const releasePolicyIds = /* @__PURE__ */ new Set();
|
|
1299
|
+
for (const policy of publicationReleasePolicies) {
|
|
1300
|
+
if (!policy.id.trim()) throw new Error(`Plugin "${id}" publication release policy requires an ID.`);
|
|
1301
|
+
if (releasePolicyIds.has(policy.id)) throw new Error(`Plugin "${id}" has duplicate publication release policy "${policy.id}".`);
|
|
1302
|
+
releasePolicyIds.add(policy.id);
|
|
1303
|
+
}
|
|
873
1304
|
return {
|
|
874
1305
|
id,
|
|
875
1306
|
version,
|
|
876
1307
|
capabilities: normalizedCapabilities,
|
|
877
1308
|
allowedHosts,
|
|
878
1309
|
storage,
|
|
879
|
-
|
|
1310
|
+
migrations: Object.freeze(normalizedMigrations),
|
|
1311
|
+
hooks: resolvedHooks,
|
|
880
1312
|
routes,
|
|
1313
|
+
mcp,
|
|
1314
|
+
publicationReleasePolicies: [...publicationReleasePolicies].toSorted((left, right) => left.id.localeCompare(right.id)),
|
|
881
1315
|
admin
|
|
882
1316
|
};
|
|
883
1317
|
}
|
|
1318
|
+
function resolveMcp(mcp, pluginId) {
|
|
1319
|
+
const tools = [...mcp?.tools ?? []];
|
|
1320
|
+
const resources = [...mcp?.resources ?? []];
|
|
1321
|
+
const namespace = `${pluginId.split("/").at(-1).replaceAll("-", "_")}_`;
|
|
1322
|
+
const toolNames = /* @__PURE__ */ new Set();
|
|
1323
|
+
const resourceNames = /* @__PURE__ */ new Set();
|
|
1324
|
+
const resourceUris = /* @__PURE__ */ new Set();
|
|
1325
|
+
const authorizeTarget = mcp?.authorizeTarget;
|
|
1326
|
+
for (const tool of tools) {
|
|
1327
|
+
validateMcpName(tool.name, "tool", pluginId, namespace, MCP_NAME_PATTERN);
|
|
1328
|
+
if (toolNames.has(tool.name)) throw new Error(`Duplicate MCP tool "${tool.name}" in plugin "${pluginId}".`);
|
|
1329
|
+
toolNames.add(tool.name);
|
|
1330
|
+
validateMcpText(tool.title, "title", tool.name, pluginId);
|
|
1331
|
+
validateMcpText(tool.description, "description", tool.name, pluginId);
|
|
1332
|
+
validateMcpAuthorization(tool.authorization, tool.name, pluginId);
|
|
1333
|
+
validateZodSchema(tool.inputSchema, "inputSchema", tool.name, pluginId);
|
|
1334
|
+
if (tool.outputSchema) validateZodSchema(tool.outputSchema, "outputSchema", tool.name, pluginId);
|
|
1335
|
+
if (typeof tool.handler !== "function") throw new Error(`MCP tool "${tool.name}" in plugin "${pluginId}" requires a handler.`);
|
|
1336
|
+
if (tool.authorization.restaurantPermission && !tool.resolveTarget) throw new Error(`MCP tool "${tool.name}" in plugin "${pluginId}" declares a restaurant permission but no resolveTarget function.`);
|
|
1337
|
+
if (tool.authorization.restaurantPermission && typeof authorizeTarget !== "function") throw new Error(`MCP tool "${tool.name}" in plugin "${pluginId}" requires an MCP target authorizer.`);
|
|
1338
|
+
if (tool.kind === "command") {
|
|
1339
|
+
if (typeof tool.preview !== "function") throw new Error(`MCP command "${tool.name}" in plugin "${pluginId}" requires preview.`);
|
|
1340
|
+
if (typeof tool.actionFingerprint !== "function" || tool.idempotency?.required !== true) throw new Error(`MCP command "${tool.name}" in plugin "${pluginId}" requires actionFingerprint and required idempotency.`);
|
|
1341
|
+
if ((tool.pii.input === "sensitive" || tool.pii.output === "sensitive") && typeof tool.redactPreview !== "function") throw new Error(`Sensitive MCP command "${tool.name}" in plugin "${pluginId}" requires redactPreview.`);
|
|
1342
|
+
validateMcpText(tool.risk.reason, "risk reason", tool.name, pluginId);
|
|
1343
|
+
validateCommandRisk(tool, pluginId);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
for (const resource of resources) {
|
|
1347
|
+
validateMcpName(resource.name, "resource", pluginId, namespace, MCP_NAME_PATTERN);
|
|
1348
|
+
if (resourceNames.has(resource.name)) throw new Error(`Duplicate MCP resource "${resource.name}" in plugin "${pluginId}".`);
|
|
1349
|
+
resourceNames.add(resource.name);
|
|
1350
|
+
validateMcpText(resource.title, "title", resource.name, pluginId);
|
|
1351
|
+
validateMcpText(resource.description, "description", resource.name, pluginId);
|
|
1352
|
+
validateMcpAuthorization(resource.authorization, resource.name, pluginId);
|
|
1353
|
+
if (resource.authorization.restaurantPermission && !resource.target) throw new Error(`MCP resource "${resource.name}" in plugin "${pluginId}" declares a restaurant permission but no target.`);
|
|
1354
|
+
if (resource.authorization.restaurantPermission && typeof authorizeTarget !== "function") throw new Error(`MCP resource "${resource.name}" in plugin "${pluginId}" requires an MCP target authorizer.`);
|
|
1355
|
+
try {
|
|
1356
|
+
if (!new URL(resource.uri).protocol) throw new Error("Missing URI protocol");
|
|
1357
|
+
} catch {
|
|
1358
|
+
throw new Error(`Invalid MCP resource URI "${resource.uri}" in plugin "${pluginId}".`);
|
|
1359
|
+
}
|
|
1360
|
+
if (resourceUris.has(resource.uri)) throw new Error(`Duplicate MCP resource URI "${resource.uri}" in plugin "${pluginId}".`);
|
|
1361
|
+
resourceUris.add(resource.uri);
|
|
1362
|
+
if (typeof resource.handler !== "function") throw new Error(`MCP resource "${resource.name}" in plugin "${pluginId}" requires a handler.`);
|
|
1363
|
+
}
|
|
1364
|
+
return authorizeTarget ? {
|
|
1365
|
+
tools,
|
|
1366
|
+
resources,
|
|
1367
|
+
authorizeTarget
|
|
1368
|
+
} : {
|
|
1369
|
+
tools,
|
|
1370
|
+
resources
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
function validateCommandRisk(tool, pluginId) {
|
|
1374
|
+
if (tool.risk.mandatory !== true) throw new Error(`MCP command "${tool.name}" in plugin "${pluginId}" must declare a mandatory risk floor.`);
|
|
1375
|
+
if (!Array.isArray(tool.risk.factors) || tool.risk.factors.length === 0 || tool.risk.factors.some((factor) => !MCP_RISK_FACTORS.has(factor))) throw new Error(`MCP command "${tool.name}" in plugin "${pluginId}" requires valid risk factors.`);
|
|
1376
|
+
if (tool.risk.factors.some((factor) => STRONG_RISK_FACTORS.has(factor)) && tool.risk.level !== "strong_confirmation") throw new Error(`MCP command "${tool.name}" in plugin "${pluginId}" requires strong confirmation for its declared risk factors.`);
|
|
1377
|
+
if (tool.risk.level === "strong_confirmation" && tool.audit !== "strict") throw new Error(`Strong-confirmation MCP command "${tool.name}" in plugin "${pluginId}" requires strict audit.`);
|
|
1378
|
+
if ((tool.pii.input === "sensitive" || tool.pii.output === "sensitive" || tool.reversibility !== "reversible") && (tool.risk.level !== "strong_confirmation" || tool.audit !== "strict")) throw new Error(`Sensitive or externally compensating MCP command "${tool.name}" in plugin "${pluginId}" requires strong confirmation and strict audit.`);
|
|
1379
|
+
}
|
|
1380
|
+
function validateMcpName(name, kind, pluginId, namespace, pattern) {
|
|
1381
|
+
if (!pattern.test(name) || !name.startsWith(namespace)) throw new Error(`Invalid MCP ${kind} name "${name}" in plugin "${pluginId}". Names must match ${pattern} and start with "${namespace}".`);
|
|
1382
|
+
}
|
|
1383
|
+
function validateMcpText(value, field, name, pluginId) {
|
|
1384
|
+
if (typeof value !== "string" || value.trim().length === 0) throw new Error(`MCP contribution "${name}" in plugin "${pluginId}" requires ${field}.`);
|
|
1385
|
+
}
|
|
1386
|
+
function validateMcpAuthorization(authorization, name, pluginId) {
|
|
1387
|
+
if (!authorization || !Object.hasOwn(Permissions, authorization.permission)) throw new Error(`MCP contribution "${name}" in plugin "${pluginId}" declares an invalid permission.`);
|
|
1388
|
+
if (!Array.isArray(authorization.anyOfTokenScopes) || authorization.anyOfTokenScopes.length === 0 || authorization.anyOfTokenScopes.some((scope) => typeof scope !== "string" || scope.trim().length === 0)) throw new Error(`MCP contribution "${name}" in plugin "${pluginId}" requires at least one token scope.`);
|
|
1389
|
+
}
|
|
1390
|
+
function validateZodSchema(schema, field, name, pluginId) {
|
|
1391
|
+
if (typeof schema !== "object" || schema === null || !("safeParse" in schema) || typeof schema.safeParse !== "function") throw new Error(`MCP tool "${name}" in plugin "${pluginId}" requires a Zod ${field}.`);
|
|
1392
|
+
}
|
|
884
1393
|
/**
|
|
885
1394
|
* Resolve hooks to normalized format with defaults.
|
|
886
1395
|
*
|
|
@@ -929,6 +1438,69 @@ function resolveHook(hook, pluginId) {
|
|
|
929
1438
|
};
|
|
930
1439
|
}
|
|
931
1440
|
|
|
1441
|
+
//#endregion
|
|
1442
|
+
//#region src/plugins/migrations.ts
|
|
1443
|
+
const LEADING_AT = /^@/;
|
|
1444
|
+
var StaticPluginMigrationProvider = class {
|
|
1445
|
+
constructor(migrations) {
|
|
1446
|
+
this.migrations = migrations;
|
|
1447
|
+
}
|
|
1448
|
+
async getMigrations() {
|
|
1449
|
+
return this.migrations;
|
|
1450
|
+
}
|
|
1451
|
+
};
|
|
1452
|
+
/**
|
|
1453
|
+
* Run migrations contributed by configured trusted native plugins.
|
|
1454
|
+
*
|
|
1455
|
+
* Each plugin receives isolated Kysely migration and lock tables. This keeps migration history valid
|
|
1456
|
+
* when another plugin is removed and lets different plugins safely reuse local migration names.
|
|
1457
|
+
*/
|
|
1458
|
+
async function runPluginMigrations(db, plugins) {
|
|
1459
|
+
const applied = [];
|
|
1460
|
+
for (const plugin of [...plugins].toSorted((left, right) => left.id.localeCompare(right.id))) {
|
|
1461
|
+
if (Object.keys(plugin.migrations).length === 0) continue;
|
|
1462
|
+
const tables = pluginMigrationTableNames(plugin.id);
|
|
1463
|
+
const { error, results } = await new Migrator({
|
|
1464
|
+
db,
|
|
1465
|
+
provider: new StaticPluginMigrationProvider(plugin.migrations),
|
|
1466
|
+
migrationTableName: tables.migrations,
|
|
1467
|
+
migrationLockTableName: tables.lock
|
|
1468
|
+
}).migrateToLatest();
|
|
1469
|
+
const successful = results?.filter((result) => result.status === "Success").map((result) => result.migrationName) ?? [];
|
|
1470
|
+
if (error) {
|
|
1471
|
+
const failed = results?.find((result) => result.status === "Error")?.migrationName;
|
|
1472
|
+
throw new Error(`Plugin migration failed for ${plugin.id}${failed ? ` (${failed})` : ""}: ${errorMessage(error)}`, { cause: error });
|
|
1473
|
+
}
|
|
1474
|
+
applied.push({
|
|
1475
|
+
pluginId: plugin.id,
|
|
1476
|
+
applied: successful
|
|
1477
|
+
});
|
|
1478
|
+
}
|
|
1479
|
+
return applied;
|
|
1480
|
+
}
|
|
1481
|
+
function pluginMigrationTableNames(pluginId) {
|
|
1482
|
+
const slug = pluginId.replace(LEADING_AT, "").replaceAll("/", "_").replaceAll("-", "_").slice(0, 20);
|
|
1483
|
+
const suffix = fnv1a(pluginId);
|
|
1484
|
+
return {
|
|
1485
|
+
migrations: `_dineway_plugin_migration_${slug}_${suffix}`,
|
|
1486
|
+
lock: `_dineway_plugin_migration_lock_${slug}_${suffix}`
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
function fnv1a(value) {
|
|
1490
|
+
let hash = 2166136261;
|
|
1491
|
+
for (let index = 0; index < value.length; index++) {
|
|
1492
|
+
hash ^= value.charCodeAt(index);
|
|
1493
|
+
hash = Math.imul(hash, 16777619);
|
|
1494
|
+
}
|
|
1495
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
1496
|
+
}
|
|
1497
|
+
function errorMessage(error) {
|
|
1498
|
+
if (!(error instanceof Error)) return JSON.stringify(error);
|
|
1499
|
+
if (error.message) return error.message;
|
|
1500
|
+
if (error.cause instanceof Error && error.cause.message) return error.cause.message;
|
|
1501
|
+
return "unknown error";
|
|
1502
|
+
}
|
|
1503
|
+
|
|
932
1504
|
//#endregion
|
|
933
1505
|
//#region src/plugins/hooks.ts
|
|
934
1506
|
/**
|
|
@@ -1023,6 +1595,9 @@ var HookPipeline = class HookPipeline {
|
|
|
1023
1595
|
this.registerPluginHook(plugin, "content:afterDelete");
|
|
1024
1596
|
this.registerPluginHook(plugin, "content:afterPublish");
|
|
1025
1597
|
this.registerPluginHook(plugin, "content:afterUnpublish");
|
|
1598
|
+
this.registerPluginHook(plugin, "content:afterRestore");
|
|
1599
|
+
this.registerPluginHook(plugin, "content:afterSchedule");
|
|
1600
|
+
this.registerPluginHook(plugin, "content:afterUnschedule");
|
|
1026
1601
|
this.registerPluginHook(plugin, "media:beforeUpload");
|
|
1027
1602
|
this.registerPluginHook(plugin, "media:afterUpload");
|
|
1028
1603
|
this.registerPluginHook(plugin, "cron");
|
|
@@ -1055,6 +1630,9 @@ var HookPipeline = class HookPipeline {
|
|
|
1055
1630
|
["content:afterDelete", "content:read"],
|
|
1056
1631
|
["content:afterPublish", "content:read"],
|
|
1057
1632
|
["content:afterUnpublish", "content:read"],
|
|
1633
|
+
["content:afterRestore", "content:read"],
|
|
1634
|
+
["content:afterSchedule", "content:read"],
|
|
1635
|
+
["content:afterUnschedule", "content:read"],
|
|
1058
1636
|
["media:beforeUpload", "media:write"],
|
|
1059
1637
|
["media:afterUpload", "media:read"],
|
|
1060
1638
|
["comment:beforeCreate", "users:read"],
|
|
@@ -1348,40 +1926,34 @@ var HookPipeline = class HookPipeline {
|
|
|
1348
1926
|
* Run content:afterPublish hooks (fire-and-forget).
|
|
1349
1927
|
*/
|
|
1350
1928
|
async runContentAfterPublish(content, collection) {
|
|
1351
|
-
|
|
1352
|
-
const results = [];
|
|
1353
|
-
for (const hook of hooks) {
|
|
1354
|
-
const { handler } = hook;
|
|
1355
|
-
const event = {
|
|
1356
|
-
content,
|
|
1357
|
-
collection
|
|
1358
|
-
};
|
|
1359
|
-
const ctx = this.getContext(hook.pluginId);
|
|
1360
|
-
const start = Date.now();
|
|
1361
|
-
try {
|
|
1362
|
-
await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
|
|
1363
|
-
results.push({
|
|
1364
|
-
success: true,
|
|
1365
|
-
pluginId: hook.pluginId,
|
|
1366
|
-
duration: Date.now() - start
|
|
1367
|
-
});
|
|
1368
|
-
} catch (error) {
|
|
1369
|
-
results.push({
|
|
1370
|
-
success: false,
|
|
1371
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
1372
|
-
pluginId: hook.pluginId,
|
|
1373
|
-
duration: Date.now() - start
|
|
1374
|
-
});
|
|
1375
|
-
if (hook.errorPolicy === "abort") throw error;
|
|
1376
|
-
}
|
|
1377
|
-
}
|
|
1378
|
-
return results;
|
|
1929
|
+
return this.runContentStateChangeHook("content:afterPublish", content, collection);
|
|
1379
1930
|
}
|
|
1380
1931
|
/**
|
|
1381
1932
|
* Run content:afterUnpublish hooks (fire-and-forget).
|
|
1382
1933
|
*/
|
|
1383
1934
|
async runContentAfterUnpublish(content, collection) {
|
|
1384
|
-
|
|
1935
|
+
return this.runContentStateChangeHook("content:afterUnpublish", content, collection);
|
|
1936
|
+
}
|
|
1937
|
+
/**
|
|
1938
|
+
* Run content:afterRestore hooks (fire-and-forget).
|
|
1939
|
+
*/
|
|
1940
|
+
async runContentAfterRestore(content, collection) {
|
|
1941
|
+
return this.runContentStateChangeHook("content:afterRestore", content, collection);
|
|
1942
|
+
}
|
|
1943
|
+
/**
|
|
1944
|
+
* Run content:afterSchedule hooks (fire-and-forget).
|
|
1945
|
+
*/
|
|
1946
|
+
async runContentAfterSchedule(content, collection) {
|
|
1947
|
+
return this.runContentStateChangeHook("content:afterSchedule", content, collection);
|
|
1948
|
+
}
|
|
1949
|
+
/**
|
|
1950
|
+
* Run content:afterUnschedule hooks (fire-and-forget).
|
|
1951
|
+
*/
|
|
1952
|
+
async runContentAfterUnschedule(content, collection) {
|
|
1953
|
+
return this.runContentStateChangeHook("content:afterUnschedule", content, collection);
|
|
1954
|
+
}
|
|
1955
|
+
async runContentStateChangeHook(name, content, collection) {
|
|
1956
|
+
const hooks = this.getTypedHooks(name);
|
|
1385
1957
|
const results = [];
|
|
1386
1958
|
for (const hook of hooks) {
|
|
1387
1959
|
const { handler } = hook;
|
|
@@ -2022,6 +2594,23 @@ var EmailPipeline = class {
|
|
|
2022
2594
|
* - Error handling
|
|
2023
2595
|
*
|
|
2024
2596
|
*/
|
|
2597
|
+
const CONSUMED_BODY_METHODS = new Set([
|
|
2598
|
+
"json",
|
|
2599
|
+
"text",
|
|
2600
|
+
"arrayBuffer",
|
|
2601
|
+
"blob",
|
|
2602
|
+
"formData",
|
|
2603
|
+
"bytes"
|
|
2604
|
+
]);
|
|
2605
|
+
function guardConsumedRequestBody(request, bodyField) {
|
|
2606
|
+
return new Proxy(request, { get(target, prop) {
|
|
2607
|
+
if (typeof prop === "string" && CONSUMED_BODY_METHODS.has(prop)) return () => {
|
|
2608
|
+
throw new Error(`[dineway] ctx.request.${prop}() is not available inside a plugin route handler: Dineway has already consumed the request body and exposes it as ${bodyField}. Read ${bodyField} instead of ctx.request.${prop}().`);
|
|
2609
|
+
};
|
|
2610
|
+
const value = Reflect.get(target, prop, target);
|
|
2611
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
2612
|
+
} });
|
|
2613
|
+
}
|
|
2025
2614
|
/**
|
|
2026
2615
|
* Route handler for a plugin
|
|
2027
2616
|
*/
|
|
@@ -2047,8 +2636,20 @@ var PluginRouteHandler = class {
|
|
|
2047
2636
|
},
|
|
2048
2637
|
status: 404
|
|
2049
2638
|
};
|
|
2639
|
+
const requestMethod = options.request.method.toUpperCase();
|
|
2640
|
+
const allowedMethods = route.methods;
|
|
2641
|
+
if (allowedMethods && !allowedMethods.includes(requestMethod)) return {
|
|
2642
|
+
success: false,
|
|
2643
|
+
error: {
|
|
2644
|
+
code: "METHOD_NOT_ALLOWED",
|
|
2645
|
+
message: `Method ${requestMethod} is not allowed for this plugin route`
|
|
2646
|
+
},
|
|
2647
|
+
status: 405
|
|
2648
|
+
};
|
|
2649
|
+
const bodyMode = route.body ?? "json";
|
|
2050
2650
|
let validatedInput;
|
|
2051
|
-
if (
|
|
2651
|
+
if (bodyMode === "raw") validatedInput = void 0;
|
|
2652
|
+
else if (route.input) {
|
|
2052
2653
|
const parseResult = route.input.safeParse(options.body);
|
|
2053
2654
|
if (!parseResult.success) return {
|
|
2054
2655
|
success: false,
|
|
@@ -2061,11 +2662,72 @@ var PluginRouteHandler = class {
|
|
|
2061
2662
|
};
|
|
2062
2663
|
validatedInput = parseResult.data;
|
|
2063
2664
|
} else validatedInput = options.body;
|
|
2665
|
+
const baseContext = this.contextFactory.createContext(this.plugin);
|
|
2666
|
+
const actor = options.actor ?? null;
|
|
2667
|
+
const rawBody = bodyMode === "raw" ? options.rawBody?.slice() ?? new Uint8Array() : null;
|
|
2668
|
+
const request = guardConsumedRequestBody(options.request, bodyMode === "raw" ? "ctx.rawBody" : "ctx.input");
|
|
2669
|
+
const requestMeta = extractRequestMeta(options.request, this.trustedProxyHeaders);
|
|
2670
|
+
let target = null;
|
|
2671
|
+
if (route.authorization && !actor) return {
|
|
2672
|
+
success: false,
|
|
2673
|
+
error: {
|
|
2674
|
+
code: "UNAUTHORIZED",
|
|
2675
|
+
message: "Authentication required"
|
|
2676
|
+
},
|
|
2677
|
+
status: 401
|
|
2678
|
+
};
|
|
2679
|
+
const restaurantPermission = route.authorization?.restaurantPermission;
|
|
2680
|
+
if (restaurantPermission) {
|
|
2681
|
+
if (!actor) return {
|
|
2682
|
+
success: false,
|
|
2683
|
+
error: {
|
|
2684
|
+
code: "UNAUTHORIZED",
|
|
2685
|
+
message: "Authentication required"
|
|
2686
|
+
},
|
|
2687
|
+
status: 401
|
|
2688
|
+
};
|
|
2689
|
+
try {
|
|
2690
|
+
const resolvedTarget = await route.resolveTarget(validatedInput, {
|
|
2691
|
+
...baseContext,
|
|
2692
|
+
actor,
|
|
2693
|
+
request,
|
|
2694
|
+
requestMeta
|
|
2695
|
+
});
|
|
2696
|
+
validateTarget(resolvedTarget);
|
|
2697
|
+
target = Object.freeze({ ...resolvedTarget });
|
|
2698
|
+
const authorizationContext = {
|
|
2699
|
+
...baseContext,
|
|
2700
|
+
input: validatedInput,
|
|
2701
|
+
rawBody,
|
|
2702
|
+
actor,
|
|
2703
|
+
target,
|
|
2704
|
+
request,
|
|
2705
|
+
requestMeta
|
|
2706
|
+
};
|
|
2707
|
+
await route.authorizeTarget({
|
|
2708
|
+
restaurantPermission,
|
|
2709
|
+
target,
|
|
2710
|
+
context: authorizationContext
|
|
2711
|
+
});
|
|
2712
|
+
} catch {
|
|
2713
|
+
return {
|
|
2714
|
+
success: false,
|
|
2715
|
+
error: {
|
|
2716
|
+
code: "FORBIDDEN",
|
|
2717
|
+
message: "Restaurant target authorization denied for this operation"
|
|
2718
|
+
},
|
|
2719
|
+
status: 403
|
|
2720
|
+
};
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2064
2723
|
const routeContext = {
|
|
2065
|
-
...
|
|
2724
|
+
...baseContext,
|
|
2066
2725
|
input: validatedInput,
|
|
2067
|
-
|
|
2068
|
-
|
|
2726
|
+
rawBody,
|
|
2727
|
+
actor,
|
|
2728
|
+
target,
|
|
2729
|
+
request,
|
|
2730
|
+
requestMeta
|
|
2069
2731
|
};
|
|
2070
2732
|
try {
|
|
2071
2733
|
return {
|
|
@@ -2113,9 +2775,22 @@ var PluginRouteHandler = class {
|
|
|
2113
2775
|
getRouteMeta(name) {
|
|
2114
2776
|
const route = this.plugin.routes[name];
|
|
2115
2777
|
if (!route) return null;
|
|
2116
|
-
return {
|
|
2778
|
+
return {
|
|
2779
|
+
public: route.public === true,
|
|
2780
|
+
body: route.body ?? "json",
|
|
2781
|
+
...route.body === "raw" ? { maxBodyBytes: route.maxBodyBytes ?? DEFAULT_PLUGIN_RAW_BODY_LIMIT_BYTES } : {},
|
|
2782
|
+
authorization: route.authorization,
|
|
2783
|
+
apiTokenPolicy: route.apiTokenPolicy
|
|
2784
|
+
};
|
|
2117
2785
|
}
|
|
2118
2786
|
};
|
|
2787
|
+
function validateTarget(target) {
|
|
2788
|
+
if (![
|
|
2789
|
+
"plugin",
|
|
2790
|
+
"location",
|
|
2791
|
+
"entity"
|
|
2792
|
+
].includes(target.type) || typeof target.id !== "string" || target.id.trim().length === 0 || typeof target.scope !== "string" || target.scope.trim().length === 0 || target.locationId !== void 0 && (typeof target.locationId !== "string" || target.locationId.trim().length === 0)) throw new Error("Invalid plugin route target");
|
|
2793
|
+
}
|
|
2119
2794
|
/**
|
|
2120
2795
|
* Error class for plugin routes
|
|
2121
2796
|
* Allows plugins to return structured errors with specific HTTP status codes
|
|
@@ -2224,6 +2899,164 @@ var PluginRouteRegistry = class {
|
|
|
2224
2899
|
}
|
|
2225
2900
|
};
|
|
2226
2901
|
|
|
2902
|
+
//#endregion
|
|
2903
|
+
//#region src/plugins/secrets.ts
|
|
2904
|
+
const ENCRYPTION_VERSION = 1;
|
|
2905
|
+
const AES_GCM_NONCE_BYTES = 12;
|
|
2906
|
+
const MAX_SECRET_BYTES = 65536;
|
|
2907
|
+
/**
|
|
2908
|
+
* Encrypted storage for trusted plugin credentials.
|
|
2909
|
+
*
|
|
2910
|
+
* Management surfaces must use set/status/list/delete and never echo the value
|
|
2911
|
+
* passed to set. resolveForRuntime is reserved for trusted server-side provider
|
|
2912
|
+
* calls and must not be exposed through plugin routes, MCP, logs, or audit data.
|
|
2913
|
+
*/
|
|
2914
|
+
var PluginSecretStore = class {
|
|
2915
|
+
repository;
|
|
2916
|
+
keysPromise;
|
|
2917
|
+
constructor(db, options = {}) {
|
|
2918
|
+
this.repository = new PluginSecretRepository(db);
|
|
2919
|
+
this.keysPromise = options.keys ? Promise.resolve(options.keys) : parseEncryptionKeys(readEncryptionKey(options.env));
|
|
2920
|
+
}
|
|
2921
|
+
async set(pluginId, name, value) {
|
|
2922
|
+
validateSecretIdentity(pluginId, name);
|
|
2923
|
+
validateSecretValue(value);
|
|
2924
|
+
const primaryKey = (await this.requireKeys())[0];
|
|
2925
|
+
if (!primaryKey) throw encryptionKeyRequired();
|
|
2926
|
+
const encrypted = await encryptSecret(primaryKey, pluginId, name, value);
|
|
2927
|
+
return toStatus(await this.repository.put({
|
|
2928
|
+
pluginId,
|
|
2929
|
+
name,
|
|
2930
|
+
encryptionVersion: ENCRYPTION_VERSION,
|
|
2931
|
+
keyId: primaryKey.kid,
|
|
2932
|
+
nonce: encrypted.nonce,
|
|
2933
|
+
ciphertext: encrypted.ciphertext,
|
|
2934
|
+
fingerprint: await fingerprintSecret(primaryKey, value)
|
|
2935
|
+
}));
|
|
2936
|
+
}
|
|
2937
|
+
async status(pluginId, name) {
|
|
2938
|
+
validateSecretIdentity(pluginId, name);
|
|
2939
|
+
const stored = await this.repository.find(pluginId, name);
|
|
2940
|
+
return stored ? toStatus(stored) : null;
|
|
2941
|
+
}
|
|
2942
|
+
async list(pluginId) {
|
|
2943
|
+
validatePluginIdentifier(pluginId, "plugin id");
|
|
2944
|
+
return (await this.repository.list(pluginId)).map(toStatus);
|
|
2945
|
+
}
|
|
2946
|
+
async delete(pluginId, name) {
|
|
2947
|
+
validateSecretIdentity(pluginId, name);
|
|
2948
|
+
return this.repository.delete(pluginId, name);
|
|
2949
|
+
}
|
|
2950
|
+
async resolveForRuntime(pluginId, name) {
|
|
2951
|
+
validateSecretIdentity(pluginId, name);
|
|
2952
|
+
const stored = await this.repository.find(pluginId, name);
|
|
2953
|
+
if (!stored) return null;
|
|
2954
|
+
if (stored.encryptionVersion !== ENCRYPTION_VERSION) throw new DinewaySecretsError("The stored plugin secret uses an unsupported encryption version.", "PLUGIN_SECRET_VERSION_UNSUPPORTED");
|
|
2955
|
+
const keys = await this.requireKeys();
|
|
2956
|
+
const decryptionKey = keys.find((key) => key.kid === stored.keyId);
|
|
2957
|
+
if (!decryptionKey) throw new DinewaySecretsError("The stored plugin secret cannot be decrypted by the configured key ring.", "PLUGIN_SECRET_KEY_UNAVAILABLE");
|
|
2958
|
+
let value;
|
|
2959
|
+
try {
|
|
2960
|
+
value = await decryptSecret(decryptionKey, stored);
|
|
2961
|
+
} catch {
|
|
2962
|
+
throw new DinewaySecretsError("The stored plugin secret failed authenticated decryption.", "PLUGIN_SECRET_DECRYPT_FAILED");
|
|
2963
|
+
}
|
|
2964
|
+
const primaryKey = keys[0];
|
|
2965
|
+
if (primaryKey && primaryKey.kid !== stored.keyId) {
|
|
2966
|
+
const encrypted = await encryptSecret(primaryKey, pluginId, name, value);
|
|
2967
|
+
await this.repository.put({
|
|
2968
|
+
pluginId,
|
|
2969
|
+
name,
|
|
2970
|
+
encryptionVersion: ENCRYPTION_VERSION,
|
|
2971
|
+
keyId: primaryKey.kid,
|
|
2972
|
+
nonce: encrypted.nonce,
|
|
2973
|
+
ciphertext: encrypted.ciphertext,
|
|
2974
|
+
fingerprint: await fingerprintSecret(primaryKey, value)
|
|
2975
|
+
});
|
|
2976
|
+
}
|
|
2977
|
+
return value;
|
|
2978
|
+
}
|
|
2979
|
+
async requireKeys() {
|
|
2980
|
+
const keys = await this.keysPromise;
|
|
2981
|
+
if (!keys || keys.length === 0) throw encryptionKeyRequired();
|
|
2982
|
+
return keys;
|
|
2983
|
+
}
|
|
2984
|
+
};
|
|
2985
|
+
async function encryptSecret(key, pluginId, name, value) {
|
|
2986
|
+
const nonce = crypto.getRandomValues(new Uint8Array(AES_GCM_NONCE_BYTES));
|
|
2987
|
+
const cryptoKey = await importAesKey(key, ["encrypt"]);
|
|
2988
|
+
const ciphertext = await crypto.subtle.encrypt({
|
|
2989
|
+
name: "AES-GCM",
|
|
2990
|
+
iv: toArrayBuffer$1(nonce),
|
|
2991
|
+
additionalData: aad(pluginId, name, ENCRYPTION_VERSION)
|
|
2992
|
+
}, cryptoKey, new TextEncoder().encode(value));
|
|
2993
|
+
return {
|
|
2994
|
+
nonce: encodeBase64url(nonce),
|
|
2995
|
+
ciphertext: encodeBase64url(new Uint8Array(ciphertext))
|
|
2996
|
+
};
|
|
2997
|
+
}
|
|
2998
|
+
async function decryptSecret(key, stored) {
|
|
2999
|
+
const cryptoKey = await importAesKey(key, ["decrypt"]);
|
|
3000
|
+
const plaintext = await crypto.subtle.decrypt({
|
|
3001
|
+
name: "AES-GCM",
|
|
3002
|
+
iv: toArrayBuffer$1(decodeBase64url(stored.nonce)),
|
|
3003
|
+
additionalData: aad(stored.pluginId, stored.name, stored.encryptionVersion)
|
|
3004
|
+
}, cryptoKey, toArrayBuffer$1(decodeBase64url(stored.ciphertext)));
|
|
3005
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(plaintext);
|
|
3006
|
+
}
|
|
3007
|
+
async function importAesKey(key, usages) {
|
|
3008
|
+
return crypto.subtle.importKey("raw", toArrayBuffer$1(key.key), { name: "AES-GCM" }, false, usages);
|
|
3009
|
+
}
|
|
3010
|
+
function aad(pluginId, name, encryptionVersion) {
|
|
3011
|
+
return toArrayBuffer$1(new TextEncoder().encode(JSON.stringify([
|
|
3012
|
+
"dineway-plugin-secret",
|
|
3013
|
+
encryptionVersion,
|
|
3014
|
+
pluginId,
|
|
3015
|
+
name
|
|
3016
|
+
])));
|
|
3017
|
+
}
|
|
3018
|
+
async function fingerprintSecret(key, value) {
|
|
3019
|
+
const hmacKey = await crypto.subtle.importKey("raw", toArrayBuffer$1(key.key), {
|
|
3020
|
+
name: "HMAC",
|
|
3021
|
+
hash: "SHA-256"
|
|
3022
|
+
}, false, ["sign"]);
|
|
3023
|
+
const digest = await crypto.subtle.sign("HMAC", hmacKey, new TextEncoder().encode(`dineway-plugin-secret-fingerprint:v1:${value}`));
|
|
3024
|
+
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("").slice(0, 12);
|
|
3025
|
+
}
|
|
3026
|
+
function toArrayBuffer$1(bytes) {
|
|
3027
|
+
const buffer = new ArrayBuffer(bytes.byteLength);
|
|
3028
|
+
new Uint8Array(buffer).set(bytes);
|
|
3029
|
+
return buffer;
|
|
3030
|
+
}
|
|
3031
|
+
function validateSecretIdentity(pluginId, name) {
|
|
3032
|
+
validatePluginIdentifier(pluginId, "plugin id");
|
|
3033
|
+
validateIdentifier(name, "plugin secret name");
|
|
3034
|
+
}
|
|
3035
|
+
function validateSecretValue(value) {
|
|
3036
|
+
const byteLength = new TextEncoder().encode(value).byteLength;
|
|
3037
|
+
if (byteLength === 0 || byteLength > MAX_SECRET_BYTES) throw new DinewaySecretsError(`Plugin secret values must contain between 1 and ${MAX_SECRET_BYTES} UTF-8 bytes.`, "INVALID_PLUGIN_SECRET_VALUE");
|
|
3038
|
+
}
|
|
3039
|
+
function encryptionKeyRequired() {
|
|
3040
|
+
return new DinewaySecretsError("DINEWAY_ENCRYPTION_KEY is required before plugin secrets can be written or decrypted.", "ENCRYPTION_KEY_REQUIRED");
|
|
3041
|
+
}
|
|
3042
|
+
function readEncryptionKey(env) {
|
|
3043
|
+
if (env) return env.DINEWAY_ENCRYPTION_KEY;
|
|
3044
|
+
const meta = import.meta.env ?? {};
|
|
3045
|
+
const proc = typeof process !== "undefined" && process.env ? process.env : {};
|
|
3046
|
+
return meta.DINEWAY_ENCRYPTION_KEY ?? proc.DINEWAY_ENCRYPTION_KEY;
|
|
3047
|
+
}
|
|
3048
|
+
function toStatus(stored) {
|
|
3049
|
+
return {
|
|
3050
|
+
pluginId: stored.pluginId,
|
|
3051
|
+
name: stored.name,
|
|
3052
|
+
present: true,
|
|
3053
|
+
fingerprint: stored.fingerprint,
|
|
3054
|
+
keyId: stored.keyId,
|
|
3055
|
+
createdAt: stored.createdAt,
|
|
3056
|
+
updatedAt: stored.updatedAt
|
|
3057
|
+
};
|
|
3058
|
+
}
|
|
3059
|
+
|
|
2227
3060
|
//#endregion
|
|
2228
3061
|
//#region src/plugins/manager.ts
|
|
2229
3062
|
/** Options table key prefix for exclusive hook DB reads via PluginManager */
|
|
@@ -2393,6 +3226,27 @@ var PluginManager = class {
|
|
|
2393
3226
|
return this.hookPipeline.runContentAfterUnpublish(content, collection);
|
|
2394
3227
|
}
|
|
2395
3228
|
/**
|
|
3229
|
+
* Run content:afterRestore hooks across all active plugins
|
|
3230
|
+
*/
|
|
3231
|
+
async runContentAfterRestore(content, collection) {
|
|
3232
|
+
this.ensureInitialized();
|
|
3233
|
+
return this.hookPipeline.runContentAfterRestore(content, collection);
|
|
3234
|
+
}
|
|
3235
|
+
/**
|
|
3236
|
+
* Run content:afterSchedule hooks across all active plugins
|
|
3237
|
+
*/
|
|
3238
|
+
async runContentAfterSchedule(content, collection) {
|
|
3239
|
+
this.ensureInitialized();
|
|
3240
|
+
return this.hookPipeline.runContentAfterSchedule(content, collection);
|
|
3241
|
+
}
|
|
3242
|
+
/**
|
|
3243
|
+
* Run content:afterUnschedule hooks across all active plugins
|
|
3244
|
+
*/
|
|
3245
|
+
async runContentAfterUnschedule(content, collection) {
|
|
3246
|
+
this.ensureInitialized();
|
|
3247
|
+
return this.hookPipeline.runContentAfterUnschedule(content, collection);
|
|
3248
|
+
}
|
|
3249
|
+
/**
|
|
2396
3250
|
* Run media:beforeUpload hooks across all active plugins
|
|
2397
3251
|
*/
|
|
2398
3252
|
async runMediaBeforeUpload(file) {
|
|
@@ -2859,7 +3713,13 @@ export async function invokeRoute(routeName, input, serializedRequest) {
|
|
|
2859
3713
|
throw new Error(\`Route \${routeName} handler is not a function\`);
|
|
2860
3714
|
}
|
|
2861
3715
|
return handler(
|
|
2862
|
-
{
|
|
3716
|
+
{
|
|
3717
|
+
input,
|
|
3718
|
+
actor: serializedRequest.actor ?? null,
|
|
3719
|
+
target: null,
|
|
3720
|
+
request: serializedRequest,
|
|
3721
|
+
requestMeta: serializedRequest.meta,
|
|
3722
|
+
},
|
|
2863
3723
|
ctx,
|
|
2864
3724
|
);
|
|
2865
3725
|
}
|
|
@@ -3039,8 +3899,13 @@ function createResolvedPluginFromManifest(manifest) {
|
|
|
3039
3899
|
capabilities: normalizeCapabilities(manifest.capabilities),
|
|
3040
3900
|
allowedHosts: manifest.allowedHosts,
|
|
3041
3901
|
storage: manifest.storage,
|
|
3902
|
+
migrations: {},
|
|
3042
3903
|
hooks: {},
|
|
3043
3904
|
routes: {},
|
|
3905
|
+
mcp: {
|
|
3906
|
+
tools: [],
|
|
3907
|
+
resources: []
|
|
3908
|
+
},
|
|
3044
3909
|
admin: manifest.admin
|
|
3045
3910
|
};
|
|
3046
3911
|
}
|
|
@@ -3455,16 +4320,4 @@ function createNodeSandboxRunner(options) {
|
|
|
3455
4320
|
}
|
|
3456
4321
|
|
|
3457
4322
|
//#endregion
|
|
3458
|
-
|
|
3459
|
-
/**
|
|
3460
|
-
* Check if a value is a StandardPluginDefinition (has hooks/routes but no id/version).
|
|
3461
|
-
*/
|
|
3462
|
-
function isStandardPluginDefinition(value) {
|
|
3463
|
-
if (typeof value !== "object" || value === null) return false;
|
|
3464
|
-
const hasPluginShape = "hooks" in value || "routes" in value;
|
|
3465
|
-
const hasNativeShape = "id" in value && "version" in value;
|
|
3466
|
-
return hasPluginShape && !hasNativeShape;
|
|
3467
|
-
}
|
|
3468
|
-
|
|
3469
|
-
//#endregion
|
|
3470
|
-
export { RelationRepository as S, portableTextToProsemirror as _, SandboxNotAvailableError as a, reference as b, createPluginManager as c, EmailPipeline as d, HookPipeline as f, after as g, definePlugin as h, NoopSandboxRunner as i, PluginRouteError as l, resolveExclusiveHooks as m, NodeSandboxRunner as n, createNoopSandboxRunner as o, createHookPipeline as p, createNodeSandboxRunner as r, PluginManager as s, isStandardPluginDefinition as t, PluginRouteRegistry as u, prosemirrorToPortableText as v, image as x, portableText as y };
|
|
4323
|
+
export { portableTextToProsemirror as C, image as D, reference as E, RelationRepository as O, verifyGuestAccessToken as S, portableText as T, DEFAULT_PLUGIN_RAW_BODY_LIMIT_BYTES as _, createNoopSandboxRunner as a, hashGuestAccessToken as b, PluginSecretStore as c, EmailPipeline as d, HookPipeline as f, definePlugin as g, runPluginMigrations as h, SandboxNotAvailableError as i, PluginRouteError as l, resolveExclusiveHooks as m, createNodeSandboxRunner as n, PluginManager as o, createHookPipeline as p, NoopSandboxRunner as r, createPluginManager as s, NodeSandboxRunner as t, PluginRouteRegistry as u, readBoundedPluginRouteBody as v, prosemirrorToPortableText as w, issueGuestAccessToken as x, GuestAccessTokenConfigurationError as y };
|