@velora-cms/server 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +65 -0
- package/dist/admin-bundle/assets/__virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react__loadShare__.js_commonjs-proxy-Ng0fOpdU.js +1 -0
- package/dist/admin-bundle/assets/__virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react_mf_2_dom__loadShare__.js_commonjs-proxy-oc1i_iBU.js +9 -0
- package/dist/admin-bundle/assets/_virtual_mf-localSharedImportMap___mfe_internal__velora_admin__mf_owner__1-DOKdWYyj.js +1 -0
- package/dist/admin-bundle/assets/_virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare___mf_0_velora_mf_2_cms_mf_1_plugin_mf_2_sdk__loadShare__.js-C1-ZAxwO.js +2 -0
- package/dist/admin-bundle/assets/_virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react__loadShare__.js-BuUBskgj.js +9 -0
- package/dist/admin-bundle/assets/_virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-DMaku-HC.js +9 -0
- package/dist/admin-bundle/assets/_virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react_mf_2_dom__loadShare__.js-DYfqn12F.js +1 -0
- package/dist/admin-bundle/assets/_virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-BPoj3DC9.js +25 -0
- package/dist/admin-bundle/assets/content-trash.schema-DfXHkC-w.js +72 -0
- package/dist/admin-bundle/assets/hostInit--C4xK6nj.js +1 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-400-normal-CyU-ddYS.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-400-normal-EA1o-u_Q.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-500-normal-C4MQITzh.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-500-normal-DPXcppbm.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-600-normal-0pRdybE_.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-600-normal-MJZ0liF5.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-400-normal-Bo5KPYvw.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-400-normal-CFZh1unN.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-500-normal-BletK1Hp.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-500-normal-Cd6jVIg7.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-600-normal-Dk5eISD_.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-600-normal-KrqB56Mw.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-400-normal-BeVLg08x.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-400-normal-BpLfq-63.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-500-normal-Ck_W4G78.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-500-normal-eJxPnL1P.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-600-normal-DT29Q8HW.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-600-normal-DjDAeNQD.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-400-normal-BTotfTJu.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-400-normal-DZqxrq2p.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-500-normal-ByOcLdNv.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-500-normal-CocWQlwt.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-600-normal-71GNu3SW.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-600-normal-BGq0mW3O.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-400-normal-Dsrv2Tcn.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-400-normal-g30qAdWV.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-500-normal-Cs5J6C77.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-500-normal-DB5PtV2g.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-600-normal-Bz0x94Yp.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-600-normal-DUMzJB7m.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-greek-400-normal-D9ESIMu3.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-greek-400-normal-_efipK4i.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-greek-500-normal-CuWXN6rf.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-greek-500-normal-JMMifIXV.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-greek-600-normal-D-CqTdkO.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-greek-600-normal-DzTrcv_p.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-400-normal-C5H60-Va.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-400-normal-RBey6euL.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-500-normal-D0aIdm-b.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-500-normal-DakdToA3.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-600-normal-DIrixKbi.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-600-normal-DOrvGEcy.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-400-normal-DG4YqDda.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-400-normal-fK1oJ5dG.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-500-normal-BEb3_waV.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-500-normal-e4dixQRQ.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-600-normal-DgdngZtN.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-600-normal-DpPYBSTl.woff2 +0 -0
- package/dist/admin-bundle/assets/index-BIAF0ShP.js +542 -0
- package/dist/admin-bundle/assets/index-DhMY-0sC.js +148 -0
- package/dist/admin-bundle/assets/index-DnZV_gsg.css +1 -0
- package/dist/admin-bundle/assets/index-Do71v6NZ.js +7 -0
- package/dist/admin-bundle/assets/literata-cyrillic-ext-opsz-italic-5yUt4G_9.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-cyrillic-ext-opsz-normal-C5tfwALZ.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-cyrillic-opsz-italic-Bj0ndbyp.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-cyrillic-opsz-normal-DgmypKXX.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-greek-ext-opsz-italic-DFpO5j-Z.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-greek-ext-opsz-normal-DGm64vGR.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-greek-opsz-italic-CHERNRFq.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-greek-opsz-normal-DFglUmAd.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-latin-ext-opsz-italic-4y8iAqqs.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-latin-ext-opsz-normal-STiTPs0F.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-latin-opsz-italic-CJ93D38p.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-latin-opsz-normal-BHZsWIlc.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-vietnamese-opsz-italic-BdcrZMcO.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-vietnamese-opsz-normal-Gqw5JQlq.woff2 +0 -0
- package/dist/admin-bundle/assets/mf-entry-bootstrap-0-aef015cd.js +41 -0
- package/dist/admin-bundle/assets/remoteEntry-CNyN68bc.js +14 -0
- package/dist/admin-bundle/assets/virtualExposes-BDCSo5OS.js +1 -0
- package/dist/admin-bundle/assets/vite-preload-helper-Cgledu0r.js +1 -0
- package/dist/admin-bundle/index.html +16 -0
- package/dist/app.d.ts +8 -0
- package/dist/app.js +252 -0
- package/dist/auth/api-content-author.d.ts +2 -0
- package/dist/auth/api-content-author.js +27 -0
- package/dist/auth/api-keys.d.ts +7 -0
- package/dist/auth/api-keys.js +85 -0
- package/dist/auth/jwt-payload.d.ts +16 -0
- package/dist/auth/jwt-payload.js +1 -0
- package/dist/auth/preview-tokens.d.ts +7 -0
- package/dist/auth/preview-tokens.js +76 -0
- package/dist/auth/rate-limit.d.ts +16 -0
- package/dist/auth/rate-limit.js +283 -0
- package/dist/auth/require-auth.d.ts +4 -0
- package/dist/auth/require-auth.js +28 -0
- package/dist/auth/route-rate-limits.d.ts +31 -0
- package/dist/auth/route-rate-limits.js +155 -0
- package/dist/auth/tokens.d.ts +15 -0
- package/dist/auth/tokens.js +38 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +6 -0
- package/dist/boot/core-migration-gate.d.ts +12 -0
- package/dist/boot/core-migration-gate.js +52 -0
- package/dist/boot/resolve-boot-mode.d.ts +6 -0
- package/dist/boot/resolve-boot-mode.js +206 -0
- package/dist/bundled-templates/com.velora.blog/manifest.json +39 -0
- package/dist/bundled-templates/com.velora.blog/package.tar.gz +0 -0
- package/dist/bundled-templates/com.velora.blog/sample-content.json +14 -0
- package/dist/config/admin-ui.d.ts +1 -0
- package/dist/config/admin-ui.js +10 -0
- package/dist/config/cors.d.ts +7 -0
- package/dist/config/cors.js +53 -0
- package/dist/config/env-file-path.d.ts +2 -0
- package/dist/config/env-file-path.js +22 -0
- package/dist/config/graphql-limits.d.ts +2 -0
- package/dist/config/graphql-limits.js +26 -0
- package/dist/config/headless.d.ts +1 -0
- package/dist/config/headless.js +8 -0
- package/dist/config/license-revalidation.d.ts +2 -0
- package/dist/config/license-revalidation.js +16 -0
- package/dist/config/load-env.d.ts +1 -0
- package/dist/config/load-env.js +53 -0
- package/dist/config/marketplace.d.ts +11 -0
- package/dist/config/marketplace.js +19 -0
- package/dist/config/preview.d.ts +3 -0
- package/dist/config/preview.js +39 -0
- package/dist/config/public-docs.d.ts +1 -0
- package/dist/config/public-docs.js +9 -0
- package/dist/config/site.d.ts +7 -0
- package/dist/config/site.js +12 -0
- package/dist/config/trust-proxy.d.ts +2 -0
- package/dist/config/trust-proxy.js +42 -0
- package/dist/data-types/validate-data-type-settings.d.ts +1 -0
- package/dist/data-types/validate-data-type-settings.js +33 -0
- package/dist/db/content-db.d.ts +16 -0
- package/dist/db/content-db.js +109 -0
- package/dist/db/core-migrations/index.d.ts +14 -0
- package/dist/db/core-migrations/index.js +184 -0
- package/dist/db/core-migrations/steps.d.ts +4 -0
- package/dist/db/core-migrations/steps.js +66 -0
- package/dist/db/load-prisma-client.d.ts +2 -0
- package/dist/db/load-prisma-client.js +44 -0
- package/dist/db/prisma.d.ts +3 -0
- package/dist/db/prisma.js +67 -0
- package/dist/db/resolve-client-dir.d.ts +2 -0
- package/dist/db/resolve-client-dir.js +83 -0
- package/dist/docs/filter-public-api-spec.d.ts +2 -0
- package/dist/docs/filter-public-api-spec.js +52 -0
- package/dist/docs-bundle/404.html +41 -0
- package/dist/docs-bundle/_astro/MobileTableOfContents.astro_astro_type_script_index_0_lang.BcSo_yiZ.js +1 -0
- package/dist/docs-bundle/_astro/Search.astro_astro_type_script_index_0_lang.CavldbAs.js +1 -0
- package/dist/docs-bundle/_astro/TableOfContents.astro_astro_type_script_index_0_lang.Csloo1VZ.js +1 -0
- package/dist/docs-bundle/_astro/common.DTXA7sXM.css +3 -0
- package/dist/docs-bundle/_astro/ec.0vx5m.js +3 -0
- package/dist/docs-bundle/_astro/ec.w36nc.css +1 -0
- package/dist/docs-bundle/_astro/page.Dwipeu-R.js +1 -0
- package/dist/docs-bundle/_astro/print.ehPL0gv-.css +1 -0
- package/dist/docs-bundle/_astro/starlight-toc.CTCQXEge.js +1 -0
- package/dist/docs-bundle/_astro/ui-core.D-7Scnhs.js +2 -0
- package/dist/docs-bundle/favicon.svg +1 -0
- package/dist/docs-bundle/index.html +1 -0
- package/dist/docs-bundle/pagefind/fragment/en_18a36d7.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_1d9eb24.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_1ef320c.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_3099406.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_4a5b6fe.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_9aa652e.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_baccdf6.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_d81f3fe.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_d855550.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_e6d88d8.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_e9d3189.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/index/en_5feeed7.pf_index +0 -0
- package/dist/docs-bundle/pagefind/index/en_c6981f9.pf_index +0 -0
- package/dist/docs-bundle/pagefind/index/en_ce4f6bf.pf_index +0 -0
- package/dist/docs-bundle/pagefind/pagefind-component-ui.css +1509 -0
- package/dist/docs-bundle/pagefind/pagefind-component-ui.js +56 -0
- package/dist/docs-bundle/pagefind/pagefind-entry.json +1 -0
- package/dist/docs-bundle/pagefind/pagefind-highlight.js +1070 -0
- package/dist/docs-bundle/pagefind/pagefind-modular-ui.css +214 -0
- package/dist/docs-bundle/pagefind/pagefind-modular-ui.js +8 -0
- package/dist/docs-bundle/pagefind/pagefind-ui.css +1 -0
- package/dist/docs-bundle/pagefind/pagefind-ui.js +2 -0
- package/dist/docs-bundle/pagefind/pagefind-worker.js +6 -0
- package/dist/docs-bundle/pagefind/pagefind.en_1e94abd852.pf_meta +0 -0
- package/dist/docs-bundle/pagefind/pagefind.js +6 -0
- package/dist/docs-bundle/pagefind/wasm.en.pagefind +0 -0
- package/dist/docs-bundle/pagefind/wasm.unknown.pagefind +0 -0
- package/dist/docs-bundle/v1/index.html +86 -0
- package/dist/docs-bundle/v1/using-velora/build-a-headless-frontend/index.html +413 -0
- package/dist/docs-bundle/v1/using-velora/content-model/index.html +858 -0
- package/dist/docs-bundle/v1/using-velora/editing/index.html +748 -0
- package/dist/docs-bundle/v1/using-velora/getting-started/index.html +588 -0
- package/dist/docs-bundle/v1/using-velora/headless-api/index.html +1620 -0
- package/dist/docs-bundle/v1/using-velora/index.html +215 -0
- package/dist/docs-bundle/v1/using-velora/installation/index.html +886 -0
- package/dist/docs-bundle/v1/using-velora/installing-plugins/index.html +836 -0
- package/dist/docs-bundle/v1/using-velora/media/index.html +468 -0
- package/dist/docs-bundle/v1/using-velora/users-and-roles/index.html +561 -0
- package/dist/graphql/field-count-rule.d.ts +5 -0
- package/dist/graphql/field-count-rule.js +59 -0
- package/dist/graphql/schema.d.ts +12 -0
- package/dist/graphql/schema.js +330 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +180 -0
- package/dist/lib.d.ts +2 -0
- package/dist/lib.js +39 -0
- package/dist/marketplace/install-bundle-members.d.ts +33 -0
- package/dist/marketplace/install-bundle-members.js +137 -0
- package/dist/marketplace/install-from-marketplace.d.ts +60 -0
- package/dist/marketplace/install-from-marketplace.js +581 -0
- package/dist/marketplace/license-revalidation-job.d.ts +38 -0
- package/dist/marketplace/license-revalidation-job.js +139 -0
- package/dist/marketplace/marketplace-client.d.ts +43 -0
- package/dist/marketplace/marketplace-client.js +228 -0
- package/dist/marketplace/revalidate-license.d.ts +39 -0
- package/dist/marketplace/revalidate-license.js +103 -0
- package/dist/media/media-service.d.ts +36 -0
- package/dist/media/media-service.js +162 -0
- package/dist/plugins/boot-plugin-loader.d.ts +32 -0
- package/dist/plugins/boot-plugin-loader.js +57 -0
- package/dist/plugins/hook-dispatcher.d.ts +13 -0
- package/dist/plugins/hook-dispatcher.js +68 -0
- package/dist/plugins/install-built-package.d.ts +36 -0
- package/dist/plugins/install-built-package.js +199 -0
- package/dist/plugins/install-package-tarball.d.ts +25 -0
- package/dist/plugins/install-package-tarball.js +73 -0
- package/dist/plugins/plugin-context.d.ts +5 -0
- package/dist/plugins/plugin-context.js +26 -0
- package/dist/plugins/plugin-package-store.d.ts +31 -0
- package/dist/plugins/plugin-package-store.js +64 -0
- package/dist/plugins/plugin-registry-service.d.ts +227 -0
- package/dist/plugins/plugin-registry-service.js +636 -0
- package/dist/plugins/seed-template-content.d.ts +23 -0
- package/dist/plugins/seed-template-content.js +198 -0
- package/dist/plugins/server-serializers.d.ts +3 -0
- package/dist/plugins/server-serializers.js +16 -0
- package/dist/plugins/simple-storage-api.d.ts +9 -0
- package/dist/plugins/simple-storage-api.js +26 -0
- package/dist/plugins/snapshot-groups.d.ts +16 -0
- package/dist/plugins/snapshot-groups.js +81 -0
- package/dist/plugins/structured-query-builder.d.ts +21 -0
- package/dist/plugins/structured-query-builder.js +60 -0
- package/dist/plugins/structured-snapshot-service.d.ts +16 -0
- package/dist/plugins/structured-snapshot-service.js +286 -0
- package/dist/plugins/structured-storage-api.d.ts +22 -0
- package/dist/plugins/structured-storage-api.js +91 -0
- package/dist/plugins/structured-storage-transaction.d.ts +22 -0
- package/dist/plugins/structured-storage-transaction.js +63 -0
- package/dist/plugins/structured-table-naming.d.ts +1 -0
- package/dist/plugins/structured-table-naming.js +18 -0
- package/dist/plugins/trusted-keys.d.ts +11 -0
- package/dist/plugins/trusted-keys.js +67 -0
- package/dist/plugins/trusted-signing-keys.d.ts +27 -0
- package/dist/plugins/trusted-signing-keys.js +40 -0
- package/dist/plugins/uninstall-bundle.d.ts +29 -0
- package/dist/plugins/uninstall-bundle.js +150 -0
- package/dist/plugins/verify-package.d.ts +36 -0
- package/dist/plugins/verify-package.js +121 -0
- package/dist/routes/admin-static.d.ts +8 -0
- package/dist/routes/admin-static.js +196 -0
- package/dist/routes/api-keys.d.ts +2 -0
- package/dist/routes/api-keys.js +95 -0
- package/dist/routes/api-playground.d.ts +2 -0
- package/dist/routes/api-playground.js +21 -0
- package/dist/routes/auth.d.ts +2 -0
- package/dist/routes/auth.js +102 -0
- package/dist/routes/content.d.ts +3 -0
- package/dist/routes/content.js +471 -0
- package/dist/routes/data-types.d.ts +3 -0
- package/dist/routes/data-types.js +100 -0
- package/dist/routes/docs-static.d.ts +8 -0
- package/dist/routes/docs-static.js +137 -0
- package/dist/routes/document-types.d.ts +3 -0
- package/dist/routes/document-types.js +149 -0
- package/dist/routes/headless-bridge.d.ts +10 -0
- package/dist/routes/headless-bridge.js +207 -0
- package/dist/routes/health.d.ts +2 -0
- package/dist/routes/health.js +15 -0
- package/dist/routes/marketplace-proxy.d.ts +8 -0
- package/dist/routes/marketplace-proxy.js +135 -0
- package/dist/routes/media.d.ts +6 -0
- package/dist/routes/media.js +93 -0
- package/dist/routes/plugin-api-routes.d.ts +12 -0
- package/dist/routes/plugin-api-routes.js +224 -0
- package/dist/routes/plugin-assets.d.ts +8 -0
- package/dist/routes/plugin-assets.js +51 -0
- package/dist/routes/plugin-management.d.ts +7 -0
- package/dist/routes/plugin-management.js +525 -0
- package/dist/routes/plugin-storage.d.ts +8 -0
- package/dist/routes/plugin-storage.js +104 -0
- package/dist/routes/plugins.d.ts +2 -0
- package/dist/routes/plugins.js +36 -0
- package/dist/routes/public-api.d.ts +9 -0
- package/dist/routes/public-api.js +545 -0
- package/dist/routes/published-item.d.ts +57 -0
- package/dist/routes/published-item.js +236 -0
- package/dist/routes/setup.d.ts +9 -0
- package/dist/routes/setup.js +477 -0
- package/dist/routes/snapshots.d.ts +3 -0
- package/dist/routes/snapshots.js +65 -0
- package/dist/routes/templates.d.ts +3 -0
- package/dist/routes/templates.js +37 -0
- package/dist/routes/themes.d.ts +2 -0
- package/dist/routes/themes.js +30 -0
- package/dist/routes/users.d.ts +2 -0
- package/dist/routes/users.js +113 -0
- package/dist/sandbox/register-local-plugin.d.ts +36 -0
- package/dist/sandbox/register-local-plugin.js +108 -0
- package/dist/sandbox/seed-sandbox.d.ts +20 -0
- package/dist/sandbox/seed-sandbox.js +122 -0
- package/dist/scripts/copy-admin-bundle.d.ts +1 -0
- package/dist/scripts/copy-admin-bundle.js +24 -0
- package/dist/scripts/copy-docs-bundle.d.ts +1 -0
- package/dist/scripts/copy-docs-bundle.js +22 -0
- package/dist/scripts/create-api-key.d.ts +5 -0
- package/dist/scripts/create-api-key.js +56 -0
- package/dist/scripts/generate-prisma-clients.d.ts +6 -0
- package/dist/scripts/generate-prisma-clients.js +125 -0
- package/dist/scripts/pack-bundled-templates.d.ts +1 -0
- package/dist/scripts/pack-bundled-templates.js +87 -0
- package/dist/setup/build-connection-string.d.ts +19 -0
- package/dist/setup/build-connection-string.js +14 -0
- package/dist/setup/bundled-templates.d.ts +19 -0
- package/dist/setup/bundled-templates.js +121 -0
- package/dist/setup/ensure-prisma-client.d.ts +3 -0
- package/dist/setup/ensure-prisma-client.js +23 -0
- package/dist/setup/install-starter.d.ts +14 -0
- package/dist/setup/install-starter.js +76 -0
- package/dist/setup/normalize-database-url.d.ts +2 -0
- package/dist/setup/normalize-database-url.js +35 -0
- package/dist/setup/persist-env-config.d.ts +2 -0
- package/dist/setup/persist-env-config.js +63 -0
- package/dist/setup/provision-database.d.ts +2 -0
- package/dist/setup/provision-database.js +89 -0
- package/dist/setup-app.d.ts +4 -0
- package/dist/setup-app.js +43 -0
- package/package.json +96 -0
- package/prisma/schema.prisma +303 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { licenseCheckIntervalHours } from "../config/license-revalidation.js";
|
|
2
|
+
import { revalidateLicense } from "./revalidate-license.js";
|
|
3
|
+
/** One sweep over every plugin that has a license row. A single plugin's
|
|
4
|
+
* failure is logged and never aborts the sweep (the Session 91 precedent:
|
|
5
|
+
* one plugin must never take the others down). */
|
|
6
|
+
export async function runLicenseRevalidationPass(deps) {
|
|
7
|
+
const licenses = await deps.prisma.pluginLicense.findMany({ select: { pluginId: true } });
|
|
8
|
+
let checked = 0;
|
|
9
|
+
let revoked = 0;
|
|
10
|
+
let failed = 0;
|
|
11
|
+
for (const { pluginId } of licenses) {
|
|
12
|
+
try {
|
|
13
|
+
const outcome = await revalidateLicense(deps, pluginId);
|
|
14
|
+
if (outcome.checked) {
|
|
15
|
+
checked += 1;
|
|
16
|
+
if (outcome.status === "revoked")
|
|
17
|
+
revoked += 1;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
failed += 1;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
failed += 1;
|
|
25
|
+
console.error(`[license] revalidation threw for ${pluginId}`, error);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return { checked, revoked, failed };
|
|
29
|
+
}
|
|
30
|
+
// A pass that hasn't settled after this long is presumed wedged rather than
|
|
31
|
+
// merely slow. The one genuinely UNBOUNDED await anywhere in this pipeline
|
|
32
|
+
// is a third-party plugin's onDeactivate hook: PluginRegistryService.
|
|
33
|
+
// deactivate() awaits `this.manifests.get(pluginId)?.onDeactivate?.()`
|
|
34
|
+
// directly, with no timeout of its own. Everything else already carries a
|
|
35
|
+
// bound — the upstream license check is capped by marketplace-client.ts's
|
|
36
|
+
// own 8s AbortSignal.timeout, and a plain DB write is sub-second.
|
|
37
|
+
//
|
|
38
|
+
// This value MUST sit above that 8s upstream bound. A single unreachable
|
|
39
|
+
// license server already burns the full 8s for ONE plugin, so a shorter
|
|
40
|
+
// timeout here would trip the "presumed wedged" path during exactly the
|
|
41
|
+
// outage this feature exists to survive — and the log line would then
|
|
42
|
+
// misattribute a slow network to a hung onDeactivate hook. 15s leaves
|
|
43
|
+
// headroom for that 8s call plus a deactivate() (a DB write and a
|
|
44
|
+
// well-behaved hook) while still bounding a genuinely wedged hook to
|
|
45
|
+
// seconds rather than the life of the process.
|
|
46
|
+
//
|
|
47
|
+
// Trade-off, stated plainly: a sweep over several licensed plugins all
|
|
48
|
+
// hitting a slow (not hung) upstream can still exceed 15s in aggregate and
|
|
49
|
+
// trip this path, letting a second pass start alongside the first. That is
|
|
50
|
+
// safe — each plugin's write is independent, and re-checking one twice in
|
|
51
|
+
// close succession is a no-op at worst (deactivate() itself is idempotent,
|
|
52
|
+
// guarded by `if (!row.enabled) return`) — whereas the alternative, a hook
|
|
53
|
+
// that never resolves permanently pinning the overlap guard and silently
|
|
54
|
+
// disabling all future enforcement for the rest of the process's life, is
|
|
55
|
+
// the one thing this constant exists to rule out.
|
|
56
|
+
const DEFAULT_PASS_TIMEOUT_MS = 15_000;
|
|
57
|
+
function delay(ms) {
|
|
58
|
+
return new Promise((resolve) => {
|
|
59
|
+
const handle = setTimeout(resolve, ms);
|
|
60
|
+
handle.unref?.();
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/** Starts the first background timer in apps/server. Two things are
|
|
64
|
+
* non-negotiable: the handle is unref()'d so it can never hold the process
|
|
65
|
+
* open, and the caller MUST stop it on app close (app.ts registers an
|
|
66
|
+
* onClose hook) — otherwise every integration test that builds and closes
|
|
67
|
+
* an app leaves a live timer behind.
|
|
68
|
+
*
|
|
69
|
+
* Passes are intended never to overlap, but forward progress must never
|
|
70
|
+
* depend on a third-party hook actually resolving. What is ACTUALLY
|
|
71
|
+
* guaranteed (corrected from an earlier version of this comment, which
|
|
72
|
+
* claimed the wait was inherently bounded by the upstream 8s timeout
|
|
73
|
+
* alone — true of the license CHECK, but not of registry.deactivate(),
|
|
74
|
+
* which also awaits a plugin's onDeactivate hook with no timeout of its
|
|
75
|
+
* own):
|
|
76
|
+
* - stop() always resolves within ~passTimeoutMs of being called,
|
|
77
|
+
* regardless of whether the in-flight pass (or the hook it's stuck
|
|
78
|
+
* in) ever finishes. If it gives up, it logs clearly that it did.
|
|
79
|
+
* - A pass that's been "in flight" longer than passTimeoutMs is
|
|
80
|
+
* presumed wedged, and the overlap guard stops treating it as a
|
|
81
|
+
* reason to skip the next tick — so a hung onDeactivate hook can
|
|
82
|
+
* disable enforcement for at most one bad tick, not for the rest of
|
|
83
|
+
* the process's life.
|
|
84
|
+
* - Giving up on a wedged pass does not cancel it. It keeps running in
|
|
85
|
+
* the background and will still write whatever verdict it eventually
|
|
86
|
+
* reaches; it just can no longer block anyone else from making
|
|
87
|
+
* progress. */
|
|
88
|
+
export function startLicenseRevalidation(deps, opts = {}) {
|
|
89
|
+
const intervalMs = opts.intervalMs ?? licenseCheckIntervalHours() * 60 * 60 * 1000;
|
|
90
|
+
const passTimeoutMs = opts.passTimeoutMs ?? DEFAULT_PASS_TIMEOUT_MS;
|
|
91
|
+
let inFlight;
|
|
92
|
+
let inFlightStartedAt = 0;
|
|
93
|
+
const pass = async () => {
|
|
94
|
+
// A previous pass is still marked in-flight — but only skip this tick
|
|
95
|
+
// while it's within passTimeoutMs of its own start. Past that, it's
|
|
96
|
+
// presumed wedged (see the comment above startLicenseRevalidation) and
|
|
97
|
+
// a fresh pass is allowed to start rather than skipping forever.
|
|
98
|
+
if (inFlight && Date.now() - inFlightStartedAt < passTimeoutMs)
|
|
99
|
+
return;
|
|
100
|
+
const run = (async () => {
|
|
101
|
+
try {
|
|
102
|
+
await runLicenseRevalidationPass(deps);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
console.error("[license] revalidation pass failed", error);
|
|
106
|
+
}
|
|
107
|
+
})();
|
|
108
|
+
inFlight = run;
|
|
109
|
+
inFlightStartedAt = Date.now();
|
|
110
|
+
try {
|
|
111
|
+
await run;
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
// Only clear if we're still the pass currently being tracked — a
|
|
115
|
+
// later tick may already have presumed us wedged and started its
|
|
116
|
+
// own run, whose own finally is the one responsible for clearing
|
|
117
|
+
// `inFlight` when IT settles. Without this check, a very-late
|
|
118
|
+
// resolution of an abandoned pass could clobber a newer pass's
|
|
119
|
+
// in-flight tracking.
|
|
120
|
+
if (inFlight === run)
|
|
121
|
+
inFlight = undefined;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
void pass(); // one pass at boot
|
|
125
|
+
const handle = setInterval(() => void pass(), intervalMs);
|
|
126
|
+
handle.unref?.();
|
|
127
|
+
return async () => {
|
|
128
|
+
clearInterval(handle);
|
|
129
|
+
const current = inFlight;
|
|
130
|
+
if (!current)
|
|
131
|
+
return;
|
|
132
|
+
const timedOut = await Promise.race([current.then(() => false), delay(passTimeoutMs).then(() => true)]);
|
|
133
|
+
if (timedOut) {
|
|
134
|
+
console.error(`[license] stop() gave up waiting for an in-flight revalidation pass after ${passTimeoutMs}ms — ` +
|
|
135
|
+
"a plugin's onDeactivate hook is likely hung. app.close() is proceeding without it; the pass " +
|
|
136
|
+
"keeps running in the background and will still write its own outcome if/when it settles.");
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export declare class MarketplaceUnavailableError extends Error {
|
|
2
|
+
constructor(reason: string);
|
|
3
|
+
}
|
|
4
|
+
export declare class MarketplaceNotFoundError extends Error {
|
|
5
|
+
constructor(pluginId: string);
|
|
6
|
+
}
|
|
7
|
+
export interface MarketplaceListParams {
|
|
8
|
+
q?: string;
|
|
9
|
+
type?: string;
|
|
10
|
+
db?: string;
|
|
11
|
+
cursor?: string;
|
|
12
|
+
limit?: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function fetchListings(baseUrl: string, params: MarketplaceListParams, opts?: {
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
}): Promise<unknown>;
|
|
17
|
+
export declare function fetchListing(baseUrl: string, pluginId: string, opts?: {
|
|
18
|
+
timeoutMs?: number;
|
|
19
|
+
}): Promise<unknown>;
|
|
20
|
+
export interface LicenseVerdict {
|
|
21
|
+
status: "valid" | "invalid" | "revoked";
|
|
22
|
+
reason?: string;
|
|
23
|
+
}
|
|
24
|
+
/** Writes the package body to destPath. Nothing is written unless the
|
|
25
|
+
* response is a 2xx — a 404 leaves no partial file behind for the
|
|
26
|
+
* installer to trip over. */
|
|
27
|
+
export declare function downloadPackage(baseUrl: string, pluginId: string, version: string, destPath: string, opts?: {
|
|
28
|
+
timeoutMs?: number;
|
|
29
|
+
maxBytes?: number;
|
|
30
|
+
}): Promise<void>;
|
|
31
|
+
/** null means the version is genuinely unsigned upstream (a legitimate
|
|
32
|
+
* answer for rows predating the signing pipeline), not a failure. */
|
|
33
|
+
export declare function downloadSignature(baseUrl: string, pluginId: string, version: string, opts?: {
|
|
34
|
+
timeoutMs?: number;
|
|
35
|
+
maxBytes?: number;
|
|
36
|
+
}): Promise<string | null>;
|
|
37
|
+
export declare function validateLicenseUpstream(baseUrl: string, input: {
|
|
38
|
+
key: string;
|
|
39
|
+
pluginId: string;
|
|
40
|
+
domain: string;
|
|
41
|
+
}, opts?: {
|
|
42
|
+
timeoutMs?: number;
|
|
43
|
+
}): Promise<LicenseVerdict>;
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { writeFile } from "node:fs/promises";
|
|
2
|
+
/** The first outbound HTTP client in apps/server, so its failure
|
|
3
|
+
* contract is explicit: every network-shaped failure (unreachable,
|
|
4
|
+
* timeout, non-2xx, unparseable body) becomes MarketplaceUnavailableError,
|
|
5
|
+
* and nothing ever hangs — a marketplace outage must degrade the admin's
|
|
6
|
+
* browse screen to a clear error state, never a stalled request. */
|
|
7
|
+
const DEFAULT_TIMEOUT_MS = 8000;
|
|
8
|
+
/** H1 (S138 hardening review): a hard byte ceiling for a downloaded
|
|
9
|
+
* package body, enforced by THIS client — independent of whatever the
|
|
10
|
+
* marketplace's own upload path enforces server-side (apps/marketplace's
|
|
11
|
+
* packages route caps uploads at 20 MB). That cap is the honest
|
|
12
|
+
* marketplace's policy, not a property of the wire: this client is the
|
|
13
|
+
* CMS's own trust boundary against a compromised, misconfigured, or
|
|
14
|
+
* simply wrong VELORA_MARKETPLACE_URL, which owes this process no such
|
|
15
|
+
* courtesy. 24 MB — 20% headroom over the marketplace's 20 MB cap, so a
|
|
16
|
+
* legitimate package sitting right at that cap round-trips without
|
|
17
|
+
* tripping this one — while still bounding a single install's worst-case
|
|
18
|
+
* memory use to a small, fixed multiple of what a real package can ever
|
|
19
|
+
* legitimately be, on a fast link or a slow one alike (AbortSignal.timeout
|
|
20
|
+
* bounds time, not bytes — see readBoundedBody below). */
|
|
21
|
+
const MAX_PACKAGE_BYTES = 24 * 1024 * 1024;
|
|
22
|
+
/** Matches verify-package.ts's own MAX_SIDECAR_BYTES ceiling for the
|
|
23
|
+
* exact same artifact once it's on disk — a signature sidecar is a few KB
|
|
24
|
+
* of JSON at most, so this client refuses anything upstream claims (or
|
|
25
|
+
* turns out) to be larger, well before it ever reaches that later,
|
|
26
|
+
* disk-based check. */
|
|
27
|
+
const MAX_SIGNATURE_BYTES = 8192;
|
|
28
|
+
/** Reads a Response body under a hard byte ceiling that a malicious or
|
|
29
|
+
* merely-wrong `content-length` cannot bypass in either direction: a
|
|
30
|
+
* declared length over the ceiling is rejected immediately (never reading
|
|
31
|
+
* a byte of body), and the ACTUAL bytes read are counted as they arrive
|
|
32
|
+
* and the read is aborted the instant they exceed the ceiling — whether or
|
|
33
|
+
* not a header was sent at all, and regardless of whether it told the
|
|
34
|
+
* truth. AbortSignal.timeout (already on `response` via the fetch() call
|
|
35
|
+
* that produced it) bounds how long this can take; this bounds how much of
|
|
36
|
+
* it can land in the heap. */
|
|
37
|
+
async function readBoundedBody(response, maxBytes, label, timeoutMs) {
|
|
38
|
+
const declared = response.headers.get("content-length");
|
|
39
|
+
if (declared !== null) {
|
|
40
|
+
const declaredBytes = Number(declared);
|
|
41
|
+
if (Number.isFinite(declaredBytes) && declaredBytes > maxBytes) {
|
|
42
|
+
// Never read: don't even start pulling a body we already know is
|
|
43
|
+
// too large. Cancelling releases the connection rather than leaving
|
|
44
|
+
// it dangling for the caller to eventually time out on.
|
|
45
|
+
await response.body?.cancel().catch(() => { });
|
|
46
|
+
throw new MarketplaceUnavailableError(`${label} declared content-length ${declaredBytes} bytes, over the ${maxBytes}-byte limit`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (!response.body) {
|
|
50
|
+
// No stream to bound incrementally (shouldn't happen with undici's
|
|
51
|
+
// fetch, but this module never assumes) — fall back to a single
|
|
52
|
+
// bounded read.
|
|
53
|
+
let buf;
|
|
54
|
+
try {
|
|
55
|
+
buf = Buffer.from(await response.arrayBuffer());
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
const reason = error instanceof Error && error.name === "TimeoutError" ? `no response within ${timeoutMs}ms` : String(error);
|
|
59
|
+
throw new MarketplaceUnavailableError(`${label} body read failed: ${reason}`);
|
|
60
|
+
}
|
|
61
|
+
if (buf.byteLength > maxBytes) {
|
|
62
|
+
throw new MarketplaceUnavailableError(`${label} exceeds the ${maxBytes}-byte limit`);
|
|
63
|
+
}
|
|
64
|
+
return buf;
|
|
65
|
+
}
|
|
66
|
+
const reader = response.body.getReader();
|
|
67
|
+
const chunks = [];
|
|
68
|
+
let total = 0;
|
|
69
|
+
try {
|
|
70
|
+
for (;;) {
|
|
71
|
+
const { done, value } = await reader.read();
|
|
72
|
+
if (done)
|
|
73
|
+
break;
|
|
74
|
+
total += value.byteLength;
|
|
75
|
+
if (total > maxBytes) {
|
|
76
|
+
await reader.cancel(`${label} exceeded ${maxBytes} bytes`).catch(() => { });
|
|
77
|
+
throw new MarketplaceUnavailableError(`${label} exceeds the ${maxBytes}-byte limit`);
|
|
78
|
+
}
|
|
79
|
+
chunks.push(value);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
if (error instanceof MarketplaceUnavailableError)
|
|
84
|
+
throw error;
|
|
85
|
+
const reason = error instanceof Error && error.name === "TimeoutError" ? `no response within ${timeoutMs}ms` : String(error);
|
|
86
|
+
throw new MarketplaceUnavailableError(`${label} body read failed: ${reason}`);
|
|
87
|
+
}
|
|
88
|
+
return Buffer.concat(chunks);
|
|
89
|
+
}
|
|
90
|
+
export class MarketplaceUnavailableError extends Error {
|
|
91
|
+
constructor(reason) {
|
|
92
|
+
super(`marketplace unavailable: ${reason}`);
|
|
93
|
+
this.name = "MarketplaceUnavailableError";
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export class MarketplaceNotFoundError extends Error {
|
|
97
|
+
constructor(pluginId) {
|
|
98
|
+
super(`no published listing for "${pluginId}"`);
|
|
99
|
+
this.name = "MarketplaceNotFoundError";
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function getJson(url, timeoutMs, notFound) {
|
|
103
|
+
let response;
|
|
104
|
+
try {
|
|
105
|
+
response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs), headers: { accept: "application/json" } });
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
const reason = error instanceof Error && error.name === "TimeoutError" ? `no response within ${timeoutMs}ms` : String(error);
|
|
109
|
+
throw new MarketplaceUnavailableError(reason);
|
|
110
|
+
}
|
|
111
|
+
if (response.status === 404 && notFound)
|
|
112
|
+
throw notFound();
|
|
113
|
+
if (!response.ok)
|
|
114
|
+
throw new MarketplaceUnavailableError(`upstream responded ${response.status}`);
|
|
115
|
+
try {
|
|
116
|
+
return await response.json();
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
throw new MarketplaceUnavailableError("upstream returned a body that is not JSON");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
export async function fetchListings(baseUrl, params, opts = {}) {
|
|
123
|
+
const query = new URLSearchParams();
|
|
124
|
+
if (params.q)
|
|
125
|
+
query.set("q", params.q);
|
|
126
|
+
if (params.type)
|
|
127
|
+
query.set("type", params.type);
|
|
128
|
+
if (params.db)
|
|
129
|
+
query.set("db", params.db);
|
|
130
|
+
if (params.cursor)
|
|
131
|
+
query.set("cursor", params.cursor);
|
|
132
|
+
if (params.limit)
|
|
133
|
+
query.set("limit", String(params.limit));
|
|
134
|
+
const suffix = query.toString() ? `?${query}` : "";
|
|
135
|
+
return getJson(`${baseUrl}/api/v1/listings${suffix}`, opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
136
|
+
}
|
|
137
|
+
export async function fetchListing(baseUrl, pluginId, opts = {}) {
|
|
138
|
+
return getJson(`${baseUrl}/api/v1/listings/${encodeURIComponent(pluginId)}`, opts.timeoutMs ?? DEFAULT_TIMEOUT_MS, () => new MarketplaceNotFoundError(pluginId));
|
|
139
|
+
}
|
|
140
|
+
/** Writes the package body to destPath. Nothing is written unless the
|
|
141
|
+
* response is a 2xx — a 404 leaves no partial file behind for the
|
|
142
|
+
* installer to trip over. */
|
|
143
|
+
export async function downloadPackage(baseUrl, pluginId, version, destPath, opts = {}) {
|
|
144
|
+
const url = `${baseUrl}/api/v1/packages/${encodeURIComponent(pluginId)}/${encodeURIComponent(version)}`;
|
|
145
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
146
|
+
let response;
|
|
147
|
+
try {
|
|
148
|
+
response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
const reason = error instanceof Error && error.name === "TimeoutError" ? `no response within ${timeoutMs}ms` : String(error);
|
|
152
|
+
throw new MarketplaceUnavailableError(reason);
|
|
153
|
+
}
|
|
154
|
+
if (response.status === 404)
|
|
155
|
+
throw new MarketplaceNotFoundError(`${pluginId}@${version}`);
|
|
156
|
+
if (!response.ok)
|
|
157
|
+
throw new MarketplaceUnavailableError(`upstream responded ${response.status}`);
|
|
158
|
+
// H1: bounded read, not a bare arrayBuffer() — see readBoundedBody and
|
|
159
|
+
// MAX_PACKAGE_BYTES above for why. Nothing is written to destPath unless
|
|
160
|
+
// the whole body arrived under the ceiling.
|
|
161
|
+
const bytes = await readBoundedBody(response, opts.maxBytes ?? MAX_PACKAGE_BYTES, "package artifact", timeoutMs);
|
|
162
|
+
await writeFile(destPath, bytes);
|
|
163
|
+
}
|
|
164
|
+
/** null means the version is genuinely unsigned upstream (a legitimate
|
|
165
|
+
* answer for rows predating the signing pipeline), not a failure. */
|
|
166
|
+
export async function downloadSignature(baseUrl, pluginId, version, opts = {}) {
|
|
167
|
+
const url = `${baseUrl}/api/v1/packages/${encodeURIComponent(pluginId)}/${encodeURIComponent(version)}/signature`;
|
|
168
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
169
|
+
let response;
|
|
170
|
+
try {
|
|
171
|
+
response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
const reason = error instanceof Error && error.name === "TimeoutError" ? `no response within ${timeoutMs}ms` : String(error);
|
|
175
|
+
throw new MarketplaceUnavailableError(reason);
|
|
176
|
+
}
|
|
177
|
+
if (response.status === 404)
|
|
178
|
+
return null;
|
|
179
|
+
if (!response.ok)
|
|
180
|
+
throw new MarketplaceUnavailableError(`upstream responded ${response.status}`);
|
|
181
|
+
// H1: bounded read (MAX_SIGNATURE_BYTES) — a signature sidecar is a few
|
|
182
|
+
// KB of JSON; anything upstream claims or turns out to be larger than
|
|
183
|
+
// that is refused here rather than buffered.
|
|
184
|
+
const bytes = await readBoundedBody(response, opts.maxBytes ?? MAX_SIGNATURE_BYTES, "signature sidecar", timeoutMs);
|
|
185
|
+
return bytes.toString("utf8");
|
|
186
|
+
}
|
|
187
|
+
export async function validateLicenseUpstream(baseUrl, input, opts = {}) {
|
|
188
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
189
|
+
let response;
|
|
190
|
+
try {
|
|
191
|
+
response = await fetch(`${baseUrl}/api/v1/licenses/validate`, {
|
|
192
|
+
method: "POST",
|
|
193
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
194
|
+
body: JSON.stringify(input),
|
|
195
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
const reason = error instanceof Error && error.name === "TimeoutError" ? `no response within ${timeoutMs}ms` : String(error);
|
|
200
|
+
throw new MarketplaceUnavailableError(reason);
|
|
201
|
+
}
|
|
202
|
+
if (!response.ok)
|
|
203
|
+
throw new MarketplaceUnavailableError(`license server responded ${response.status}`);
|
|
204
|
+
let body;
|
|
205
|
+
try {
|
|
206
|
+
body = await response.json();
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
throw new MarketplaceUnavailableError("license server returned a body that is not JSON");
|
|
210
|
+
}
|
|
211
|
+
return parseLicenseVerdict(body);
|
|
212
|
+
}
|
|
213
|
+
const LICENSE_STATUSES = new Set(["valid", "invalid", "revoked"]);
|
|
214
|
+
/** Task 3 gates paid installs on `status === "valid"`, so an unnarrowed
|
|
215
|
+
* response shape at this network trust boundary is a real weak point — a
|
|
216
|
+
* misconfigured base URL or a buggy/compromised license server returning
|
|
217
|
+
* `{}` or `{status: 200}` must not silently sail through as a verdict. */
|
|
218
|
+
function parseLicenseVerdict(body) {
|
|
219
|
+
if (typeof body !== "object" ||
|
|
220
|
+
body === null ||
|
|
221
|
+
!("status" in body) ||
|
|
222
|
+
typeof body.status !== "string" ||
|
|
223
|
+
!LICENSE_STATUSES.has(body.status) ||
|
|
224
|
+
("reason" in body && body.reason !== undefined && typeof body.reason !== "string")) {
|
|
225
|
+
throw new MarketplaceUnavailableError(`license server returned a malformed verdict: ${JSON.stringify(body)}`);
|
|
226
|
+
}
|
|
227
|
+
return body;
|
|
228
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { PrismaClient } from "@prisma/client";
|
|
2
|
+
import type { PluginRegistryService } from "../plugins/plugin-registry-service.js";
|
|
3
|
+
/** Every verdict-to-consequence mapping lives HERE, so the scheduled job,
|
|
4
|
+
* the boot pass, the activation re-check, and the manual re-check cannot
|
|
5
|
+
* drift apart. Two rules are load-bearing:
|
|
6
|
+
* - FAIL OPEN: an unreachable license server (or missing site domain)
|
|
7
|
+
* never changes status and never deactivates. A site must not lose
|
|
8
|
+
* paid functionality because velora.com is down.
|
|
9
|
+
* - domain_mismatch WARNS, it does not deactivate: an honest operator
|
|
10
|
+
* changing VELORA_SITE_DOMAIN can cause it, and killing their paid
|
|
11
|
+
* plugins over a config edit is the wrong trade (S139 decision 1).
|
|
12
|
+
* This function also never RE-ENABLES a plugin — an admin's own manual
|
|
13
|
+
* deactivation must survive a license check. */
|
|
14
|
+
export type RevalidationOutcome = {
|
|
15
|
+
checked: true;
|
|
16
|
+
status: "valid";
|
|
17
|
+
} | {
|
|
18
|
+
checked: true;
|
|
19
|
+
status: "revoked";
|
|
20
|
+
reason: string;
|
|
21
|
+
deactivated: boolean;
|
|
22
|
+
} | {
|
|
23
|
+
checked: true;
|
|
24
|
+
status: "domain_mismatch";
|
|
25
|
+
reason: string;
|
|
26
|
+
} | {
|
|
27
|
+
checked: false;
|
|
28
|
+
reason: string;
|
|
29
|
+
} | {
|
|
30
|
+
checked: false;
|
|
31
|
+
reason: string;
|
|
32
|
+
missingLicense: true;
|
|
33
|
+
};
|
|
34
|
+
export interface RevalidateDeps {
|
|
35
|
+
prisma: PrismaClient;
|
|
36
|
+
registry: PluginRegistryService;
|
|
37
|
+
marketplaceBaseUrl: string;
|
|
38
|
+
}
|
|
39
|
+
export declare function revalidateLicense(deps: RevalidateDeps, pluginId: string): Promise<RevalidationOutcome>;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { getSiteDomain } from "../config/site.js";
|
|
2
|
+
import { MarketplaceUnavailableError, validateLicenseUpstream } from "./marketplace-client.js";
|
|
3
|
+
const DOMAIN_MISMATCH = "domain_mismatch";
|
|
4
|
+
/** S153: revoking a BUNDLE's license switches off the whole set it
|
|
5
|
+
* installed — the members are what the license actually bought (the S152
|
|
6
|
+
* cascade-to-owned-members decision extended to deactivation). Reverse
|
|
7
|
+
* install order, per-member try/catch; deactivate() is idempotent and a
|
|
8
|
+
* member individually uninstalled since (no registry row) just throws
|
|
9
|
+
* PluginNotInstalledError into the catch. Skip-don't-claim members have
|
|
10
|
+
* no ownership rows and are never touched. Never re-enables anything. */
|
|
11
|
+
async function deactivateWithOwnedMembers(deps, pluginId) {
|
|
12
|
+
let deactivated = false;
|
|
13
|
+
try {
|
|
14
|
+
await deps.registry.deactivate(pluginId);
|
|
15
|
+
deactivated = true;
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
console.error(`[license] failed to deactivate ${pluginId} after a negative license verdict`, error);
|
|
19
|
+
}
|
|
20
|
+
const row = await deps.prisma.pluginRegistry.findUnique({ where: { pluginId }, select: { type: true } });
|
|
21
|
+
if (row?.type !== "bundle")
|
|
22
|
+
return deactivated;
|
|
23
|
+
const members = await deps.prisma.pluginBundleMember.findMany({
|
|
24
|
+
where: { bundlePluginId: pluginId },
|
|
25
|
+
orderBy: { position: "desc" },
|
|
26
|
+
});
|
|
27
|
+
for (const member of members) {
|
|
28
|
+
try {
|
|
29
|
+
await deps.registry.deactivate(member.memberPluginId);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
console.error(`[license] failed to deactivate bundle member ${member.memberPluginId} of ${pluginId}`, error);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return deactivated;
|
|
36
|
+
}
|
|
37
|
+
// The license server that produces this reason string is a different app
|
|
38
|
+
// (apps/marketplace's own license-validation route) with no shared constant
|
|
39
|
+
// tying the two together — an exact byte match here is a drift point.
|
|
40
|
+
// Since this service never re-enables a plugin, a MISS on domain_mismatch
|
|
41
|
+
// (a case variant, a hyphenated "domain-mismatch", stray whitespace) falls
|
|
42
|
+
// through to the hard-revoke-and-deactivate branch below and becomes a
|
|
43
|
+
// one-way ratchet an admin has to manually recover from — the unsafe
|
|
44
|
+
// direction for the one verdict an honest operator's own config edit can
|
|
45
|
+
// trigger. Normalize before comparing so those variants still take the
|
|
46
|
+
// WARN path; the stored/returned reason keeps the upstream's original text.
|
|
47
|
+
function normalizeReason(reason) {
|
|
48
|
+
return reason.trim().toLowerCase().replace(/[-_]+/g, "_");
|
|
49
|
+
}
|
|
50
|
+
export async function revalidateLicense(deps, pluginId) {
|
|
51
|
+
const license = await deps.prisma.pluginLicense.findUnique({ where: { pluginId } });
|
|
52
|
+
if (!license)
|
|
53
|
+
return { checked: false, reason: "no license on record", missingLicense: true };
|
|
54
|
+
const domain = getSiteDomain();
|
|
55
|
+
if (!domain) {
|
|
56
|
+
const reason = "VELORA_SITE_DOMAIN is not configured — skipping the license check";
|
|
57
|
+
await deps.prisma.pluginLicense.update({
|
|
58
|
+
where: { pluginId },
|
|
59
|
+
data: { lastAttemptAt: new Date(), lastAttemptError: reason },
|
|
60
|
+
});
|
|
61
|
+
return { checked: false, reason };
|
|
62
|
+
}
|
|
63
|
+
let verdict;
|
|
64
|
+
try {
|
|
65
|
+
verdict = await validateLicenseUpstream(deps.marketplaceBaseUrl, { key: license.key, pluginId, domain });
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (error instanceof MarketplaceUnavailableError) {
|
|
69
|
+
// FAIL OPEN — status and lastReason deliberately untouched.
|
|
70
|
+
await deps.prisma.pluginLicense.update({
|
|
71
|
+
where: { pluginId },
|
|
72
|
+
data: { lastAttemptAt: new Date(), lastAttemptError: error.message },
|
|
73
|
+
});
|
|
74
|
+
return { checked: false, reason: error.message };
|
|
75
|
+
}
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
const now = new Date();
|
|
79
|
+
if (verdict.status === "valid") {
|
|
80
|
+
await deps.prisma.pluginLicense.update({
|
|
81
|
+
where: { pluginId },
|
|
82
|
+
data: { status: "valid", lastReason: null, lastValidatedAt: now, lastAttemptAt: now, lastAttemptError: null },
|
|
83
|
+
});
|
|
84
|
+
return { checked: true, status: "valid" };
|
|
85
|
+
}
|
|
86
|
+
const reason = verdict.reason ?? verdict.status;
|
|
87
|
+
if (verdict.status === "invalid" && normalizeReason(reason) === DOMAIN_MISMATCH) {
|
|
88
|
+
await deps.prisma.pluginLicense.update({
|
|
89
|
+
where: { pluginId },
|
|
90
|
+
data: { status: DOMAIN_MISMATCH, lastReason: reason, lastValidatedAt: now, lastAttemptAt: now, lastAttemptError: null },
|
|
91
|
+
});
|
|
92
|
+
return { checked: true, status: DOMAIN_MISMATCH, reason };
|
|
93
|
+
}
|
|
94
|
+
// Revoke hard: a confirmed negative that an operator's own config cannot
|
|
95
|
+
// have caused. deactivate() is idempotent, tolerates a missing manifest,
|
|
96
|
+
// and persists enabled:false before running onDeactivate.
|
|
97
|
+
await deps.prisma.pluginLicense.update({
|
|
98
|
+
where: { pluginId },
|
|
99
|
+
data: { status: "revoked", lastReason: reason, lastValidatedAt: now, lastAttemptAt: now, lastAttemptError: null },
|
|
100
|
+
});
|
|
101
|
+
const deactivated = await deactivateWithOwnedMembers(deps, pluginId);
|
|
102
|
+
return { checked: true, status: "revoked", reason, deactivated };
|
|
103
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Prisma, PrismaClient } from "@prisma/client";
|
|
2
|
+
import type { MediaFile } from "@velora-cms/api-schemas";
|
|
3
|
+
import type { Dialect } from "@velora-cms/prisma-variants";
|
|
4
|
+
export interface MediaStorageConfig {
|
|
5
|
+
endpoint: string;
|
|
6
|
+
region: string;
|
|
7
|
+
accessKeyId: string;
|
|
8
|
+
secretAccessKey: string;
|
|
9
|
+
bucket: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function mediaStorageConfigFromEnv(): MediaStorageConfig;
|
|
12
|
+
export interface MediaUploadInput {
|
|
13
|
+
filename: string;
|
|
14
|
+
mimeType: string;
|
|
15
|
+
data: Buffer;
|
|
16
|
+
uploadedBy: string;
|
|
17
|
+
}
|
|
18
|
+
export declare class MediaService {
|
|
19
|
+
private readonly prisma;
|
|
20
|
+
private readonly config;
|
|
21
|
+
private readonly dialect;
|
|
22
|
+
private readonly s3;
|
|
23
|
+
constructor(prisma: PrismaClient, config: MediaStorageConfig, dialect: Dialect);
|
|
24
|
+
ensureBucket(): Promise<void>;
|
|
25
|
+
upload(input: MediaUploadInput): Promise<MediaFile>;
|
|
26
|
+
getById(id: string): Promise<MediaFile | null>;
|
|
27
|
+
list(options: {
|
|
28
|
+
cursor?: string;
|
|
29
|
+
limit: number;
|
|
30
|
+
search?: string;
|
|
31
|
+
}): Promise<MediaFile[]>;
|
|
32
|
+
getDownloadUrl(id: string, expiresInSeconds?: number): Promise<string>;
|
|
33
|
+
delete(id: string): Promise<void>;
|
|
34
|
+
private canonicalUrl;
|
|
35
|
+
}
|
|
36
|
+
export declare function buildFilenameSearchFilter(search: string, dialect: Dialect): Prisma.StringFilter;
|