@mavea/mavea 1.0.7
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/CHANGELOG.md +159 -0
- package/DISCLAIMER.md +16 -0
- package/LICENSE +75 -0
- package/PRIVACY.md +127 -0
- package/README.md +248 -0
- package/SECURITY.md +72 -0
- package/SUPPORT.md +13 -0
- package/TERMS.md +122 -0
- package/THIRD-PARTY.txt +639 -0
- package/TRADEMARKS.md +15 -0
- package/bin/mavea.mjs +1079 -0
- package/dist/.vite/manifest.json +4261 -0
- package/dist/_headers +52 -0
- package/dist/assets/AppCommandPalette-DUMSp3rd.js +1 -0
- package/dist/assets/AtlasView-B5fkQhO6.css +1 -0
- package/dist/assets/AtlasView-Dl2fssaG.js +1 -0
- package/dist/assets/BlockEmpty-CLtVQf6c.js +1 -0
- package/dist/assets/BriefingPlayer-DO205Qkh.js +1 -0
- package/dist/assets/BriefingPlayer-TpFZB77N.css +1 -0
- package/dist/assets/CanvasSkeleton-BZZPCuRZ.js +2 -0
- package/dist/assets/CanvasSkeleton-iy6DBvde.css +1 -0
- package/dist/assets/CardEditor-BY1bZiV6.css +1 -0
- package/dist/assets/CardEditor-DzJXb0G7.js +1 -0
- package/dist/assets/CheckLogRail-F4AlgAcc.css +1 -0
- package/dist/assets/CheckLogRail-wUfby67Z.js +1 -0
- package/dist/assets/CodeOutput-Kh6ldxQz.js +97 -0
- package/dist/assets/CommandPalette-Bb-eErih.css +1 -0
- package/dist/assets/CommandPalette-Kl00mMZm.js +1 -0
- package/dist/assets/CourseLessonReader-COmTQdSK.css +1 -0
- package/dist/assets/CourseLessonReader-MBHoknUz.js +2 -0
- package/dist/assets/CourseRail-CUa7uIGG.css +1 -0
- package/dist/assets/CourseRail-q6x5cCpW.js +1 -0
- package/dist/assets/CoursesApp-v89OVJvg.js +2 -0
- package/dist/assets/CrossExamPanel-CZ0ShGma.css +1 -0
- package/dist/assets/CrossExamPanel-DH04p6VS.js +1 -0
- package/dist/assets/DashPresent-BYXbPuGj.js +1 -0
- package/dist/assets/DashPresent-DQYcgp-W.css +1 -0
- package/dist/assets/DashboardDetail-B50ZkeVK.js +2 -0
- package/dist/assets/DashboardLoopRuntime-CFXPx3v_.js +1 -0
- package/dist/assets/DashboardOverview-C-FdGWM5.js +1 -0
- package/dist/assets/DashboardSettings-Dmw6JTbI.js +1 -0
- package/dist/assets/DashboardsApp-Dx-EsaPW.css +1 -0
- package/dist/assets/DashboardsApp-JSY_bVfi.js +2 -0
- package/dist/assets/DeepZoomApp-BFNwli7l.js +37 -0
- package/dist/assets/DeepZoomApp-fRXCJHHZ.css +1 -0
- package/dist/assets/DelegatePanel-BK7rEhIc.js +26 -0
- package/dist/assets/DelegatePanel-_9RZiLHM.css +1 -0
- package/dist/assets/DemoGallery-BPtVG7_y.js +1 -0
- package/dist/assets/DropSelect-DUhdQSta.js +1 -0
- package/dist/assets/ExportModal-D4WpAg2c.css +1 -0
- package/dist/assets/ExportModal-D65cGyj_.js +2 -0
- package/dist/assets/ExtractionPreview-Iwfg2Ogl.js +18 -0
- package/dist/assets/FallbackCard-B489rTd9.js +1 -0
- package/dist/assets/FallbackCard-D_6ps1xA.css +1 -0
- package/dist/assets/FeatureUseNotice-CWt7gOwS.css +1 -0
- package/dist/assets/FeatureUseNotice-DPtQ0OGb.js +1 -0
- package/dist/assets/FlagshipCTA-BXntKhxb.js +1 -0
- package/dist/assets/FlagshipCommandPalette-BDJfasI-.js +1 -0
- package/dist/assets/FlagshipShowcase-DJ8Gxk_G.js +1 -0
- package/dist/assets/FlashcardsApp-CLNo4WX7.js +1 -0
- package/dist/assets/ForecastPanel-Da2ZBUrE.css +1 -0
- package/dist/assets/ForecastPanel-PBO2-dgS.js +1 -0
- package/dist/assets/GalleryApp-B1CB3_81.css +1 -0
- package/dist/assets/GalleryApp-DM03CMRy.js +1 -0
- package/dist/assets/HonestByDesign-Dqyth6nW.js +1 -0
- package/dist/assets/HowItWorks-C0zosIvq.js +1 -0
- package/dist/assets/LegalApp-DZBdExTy.js +1 -0
- package/dist/assets/LegalMarkdownDocument-CSZhRG5r.js +3 -0
- package/dist/assets/LegalPageShell-C_HottbD.css +1 -0
- package/dist/assets/LegalPageShell-DTeXqWyc.js +1 -0
- package/dist/assets/LeverPanel-DhpJY76O.css +1 -0
- package/dist/assets/LeverPanel-aZfhtVJX.js +1 -0
- package/dist/assets/Library-D2XGNXz7.js +1 -0
- package/dist/assets/Library-DmrtHjUf.css +1 -0
- package/dist/assets/LibraryOverlay-B2WQSgwr.js +1 -0
- package/dist/assets/LibraryOverlay-CnKWDMG_.css +1 -0
- package/dist/assets/LiveApp-B9zwXlGC.js +61 -0
- package/dist/assets/LiveApp-BVZGpJ1S.css +1 -0
- package/dist/assets/LiveSettings-CmgrMqvK.css +1 -0
- package/dist/assets/LiveSettings-D9OcWlm6.js +6 -0
- package/dist/assets/MindMapViewerDrawer-YYyZqmAd.js +1 -0
- package/dist/assets/MindShape-Ca3MXEgK.css +1 -0
- package/dist/assets/MindShape-mTlN5mPZ.js +1 -0
- package/dist/assets/Orb-B0NEVfqL.js +1 -0
- package/dist/assets/PinToDashboard-_kIOHs0k.js +1 -0
- package/dist/assets/PlanReview-33V8GTNB.js +1 -0
- package/dist/assets/Presence-CzLJIeSD.js +1 -0
- package/dist/assets/PresentationDeck-BDoGecxr.js +1 -0
- package/dist/assets/PrismApp-PfBTFhMF.js +2 -0
- package/dist/assets/PrismAskController-DBoxQSJR.css +1 -0
- package/dist/assets/PrismAskController-DbQDrlU9.js +37 -0
- package/dist/assets/PrismBriefingPlayer-1fvlfLR8.js +2 -0
- package/dist/assets/PrismOverlay-B7lEspdu.js +131 -0
- package/dist/assets/PrismOverlay-BTfAicBt.css +1 -0
- package/dist/assets/PrivacyApp-CR4lRs0L.js +128 -0
- package/dist/assets/Quiz-CUwtekfu.js +1 -0
- package/dist/assets/Recap-DCiiWNiV.css +1 -0
- package/dist/assets/Recap-H5fhj5Tc.js +1 -0
- package/dist/assets/ReelJelly-3jSVsJsy.js +1 -0
- package/dist/assets/RehearsalPanel-BCU615wg.css +1 -0
- package/dist/assets/RehearsalPanel-BVgjrOPg.js +6 -0
- package/dist/assets/ReplayCard-BpYO5vX-.js +2 -0
- package/dist/assets/ReplayOverlay-DXmVdeIr.js +1 -0
- package/dist/assets/RewindOverlay-BusfWeFq.js +1 -0
- package/dist/assets/RewindOverlay-C92qgvmi.css +1 -0
- package/dist/assets/RippleApp-9KrJPYgN.js +2 -0
- package/dist/assets/RippleAskController-BNAUoIW0.css +1 -0
- package/dist/assets/RippleAskController-CO9wE7hU.js +34 -0
- package/dist/assets/RippleOverlay-DMHLbfh8.js +6 -0
- package/dist/assets/RippleOverlay-_iiZsQVZ.css +1 -0
- package/dist/assets/SeeDontRead-C389viue.js +1 -0
- package/dist/assets/ShareModal-C8S7ySls.css +1 -0
- package/dist/assets/ShareModal-ClnEkudw.js +32 -0
- package/dist/assets/ShipCascade-BUtwvQoW.js +1 -0
- package/dist/assets/ShipCascade-CyFnrlET.css +1 -0
- package/dist/assets/ShipCourse-B7lG6DYW.js +1 -0
- package/dist/assets/ShipCourse-DcT7PUSm.css +1 -0
- package/dist/assets/ShipGate-BVZfCllb.css +1 -0
- package/dist/assets/ShipGate-BYYu9WVq.js +2 -0
- package/dist/assets/ShipHotspots-Bj2CovtT.js +1 -0
- package/dist/assets/ShipHotspots-CLOMutPv.css +1 -0
- package/dist/assets/ShipIncident-BUHvFFiC.css +1 -0
- package/dist/assets/ShipIncident-DcmindmE.js +2 -0
- package/dist/assets/ShipMigration-BznCskNg.css +1 -0
- package/dist/assets/ShipMigration-s0hVjDQl.js +1 -0
- package/dist/assets/ShipOnboarding-BC9Vxkpa.css +1 -0
- package/dist/assets/ShipOnboarding-ekbHL7OH.js +1 -0
- package/dist/assets/ShipRead-BVNgQT6L.js +1 -0
- package/dist/assets/ShipRollout-BdUQzMyP.js +1 -0
- package/dist/assets/ShipRollout-CqeObx5b.css +1 -0
- package/dist/assets/ShipSuggestions-B6fKYh8L.js +1 -0
- package/dist/assets/ShipSuggestions-BKRvk5Mj.css +1 -0
- package/dist/assets/ShipWorkspace-3D3Rrs1p.js +2 -0
- package/dist/assets/ShipWorkspace-D6XQpGEN.css +1 -0
- package/dist/assets/SignatureLoop-C60r8Wm5.js +1 -0
- package/dist/assets/SrsReview-BoaYb6XN.js +1 -0
- package/dist/assets/SrsReview-DmGdGxT0.css +1 -0
- package/dist/assets/SynthesisApp-vJ2Jq0Qe.js +2 -0
- package/dist/assets/SynthesisOverlay-CkERphvG.js +66 -0
- package/dist/assets/TermsApp-C70tW6m6.js +123 -0
- package/dist/assets/TopicCanvas-CT0eSj2M.js +2 -0
- package/dist/assets/TopicCanvas-D2Nax9II.css +1 -0
- package/dist/assets/TourPrism-BvucSkcs.js +1 -0
- package/dist/assets/TrackerTile-CasZNmt4.js +2 -0
- package/dist/assets/TwoSurfaces-C90X7Aj5.js +1 -0
- package/dist/assets/UnderstoodPanel-CHPD2Ttj.js +1 -0
- package/dist/assets/UnderstoodPanel-Q6GX1TFq.css +1 -0
- package/dist/assets/WhyMachineOverlay-BZk3PVTl.css +1 -0
- package/dist/assets/WhyMachineOverlay-CB0_n6Sy.js +1 -0
- package/dist/assets/WowFeatures-Ci_H5QYX.js +1 -0
- package/dist/assets/ZoomDeck-B2SjNwY7.css +1 -0
- package/dist/assets/ZoomDeck-BQHO79ZU.js +1 -0
- package/dist/assets/__vite-browser-external-BufR4LQa.js +1 -0
- package/dist/assets/ai-Ba4lZJ9Y.js +12 -0
- package/dist/assets/alternateFinishes-DZ-rkpkX.js +494 -0
- package/dist/assets/annotationAuto-B0nihA4F.js +1 -0
- package/dist/assets/annotationReel-BcU9Q0Sw.js +1 -0
- package/dist/assets/annotations-D2K9A5GB.js +1 -0
- package/dist/assets/backup-c-Btapzp.js +1 -0
- package/dist/assets/blockLabel-DAqaHKy3.js +1 -0
- package/dist/assets/c-DWGmR78Y.js +1 -0
- package/dist/assets/c-SSSlmE39.js +1 -0
- package/dist/assets/cache-BumECILO.js +1 -0
- package/dist/assets/canvas-QTEIiK23.js +1 -0
- package/dist/assets/catalog-BHjl-1uH.js +2 -0
- package/dist/assets/catalog-Dqaq2YFd.js +1 -0
- package/dist/assets/cfo.generated-Bjl5OGay.js +1 -0
- package/dist/assets/chapters-BiUsDClQ.js +1 -0
- package/dist/assets/charts1-EOoxU_K3.js +1 -0
- package/dist/assets/charts2-tyUiQPff.js +1 -0
- package/dist/assets/chunk-BVpWp9h4.js +1 -0
- package/dist/assets/citation-BcvmjKS8.js +1 -0
- package/dist/assets/code-CpjYCNS8.js +8 -0
- package/dist/assets/codeContext-0UMRSGRr.js +5 -0
- package/dist/assets/colors-D49Req-e.js +1 -0
- package/dist/assets/compose-BJ9EfcQS.js +3 -0
- package/dist/assets/contentVault-DzTYWcbD.js +1 -0
- package/dist/assets/core-DEFyprhs.js +1 -0
- package/dist/assets/corpus.generated-oX9uU93S.js +11 -0
- package/dist/assets/courseSeed-CZdNBTTs.js +1 -0
- package/dist/assets/courseStore-DfafnL-9.js +1 -0
- package/dist/assets/courses-BkH1ntDL.css +1 -0
- package/dist/assets/cpp-BGdv8dzs.js +1 -0
- package/dist/assets/cpp-CNAAXZl-.js +1 -0
- package/dist/assets/csharp-oqKa8noW.js +1 -0
- package/dist/assets/css-CdtkT07m.js +1 -0
- package/dist/assets/css-LbU1hoFO.js +1 -0
- package/dist/assets/dag-C3Xx8pOD.js +2 -0
- package/dist/assets/dashboardEvents-DR9d_c74.js +2 -0
- package/dist/assets/dashboards-rcWxdoXI.css +1 -0
- package/dist/assets/deckLine-NBd3MyGT.js +1 -0
- package/dist/assets/demoPaletteItems-NPCSaSU6.js +1 -0
- package/dist/assets/dev.generated-DYgVxL_4.js +73 -0
- package/dist/assets/diagrams-CaSfjT4I.js +1 -0
- package/dist/assets/diff-woXpYk--.js +1 -0
- package/dist/assets/display-OvC57CMc.js +9 -0
- package/dist/assets/dist-BFx3nZg4.js +2785 -0
- package/dist/assets/dist-xRGLOZwv.js +14 -0
- package/dist/assets/docs-B2aDtm5K.js +1 -0
- package/dist/assets/everyday-CoZa4kDZ.js +1 -0
- package/dist/assets/explode-WBykfhqW.js +13 -0
- package/dist/assets/extractNumbers-CuhagRkd.js +1 -0
- package/dist/assets/facts-BBTt3ImG.js +1 -0
- package/dist/assets/finance-Bxn_Q1p0.js +1 -0
- package/dist/assets/fit-0HxU-rtB.js +1 -0
- package/dist/assets/fitText-CDFmCQzl.js +1 -0
- package/dist/assets/flows-BL81J7yN.js +1 -0
- package/dist/assets/format-DlZTyeMy.js +1 -0
- package/dist/assets/format-hc2QOoIu.js +1 -0
- package/dist/assets/forms-DeoXOxdD.js +1 -0
- package/dist/assets/generate-8odaTyr4.js +17 -0
- package/dist/assets/generateBeats-b64CL54v.js +1 -0
- package/dist/assets/generateCourse-Cj_-vDWu.js +28 -0
- package/dist/assets/generateLive-D4B30fnF.js +52 -0
- package/dist/assets/geometry-DXAbnb0J.js +1 -0
- package/dist/assets/gesture-DabMpPcq.js +2 -0
- package/dist/assets/github-dark-C-LZuMrd.js +1 -0
- package/dist/assets/github-light-EUqPIrTm.js +1 -0
- package/dist/assets/githubBrowser-DywfDeYx.js +1 -0
- package/dist/assets/githubToken-DtcLYqwm.js +11 -0
- package/dist/assets/go-rLFTqkRN.js +1 -0
- package/dist/assets/homeTarget-BgWKI_E1.js +1 -0
- package/dist/assets/html-BcACZWSP.js +1 -0
- package/dist/assets/html-DDNsd9lV.js +1 -0
- package/dist/assets/http-CJCtjYpA.js +2 -0
- package/dist/assets/icons-CHVz6kv_.js +1 -0
- package/dist/assets/index-CepQEz75.css +1 -0
- package/dist/assets/index-y8GL67bB.js +22 -0
- package/dist/assets/index.es-CRLiWwiA.js +5 -0
- package/dist/assets/info-CiqHR5cs.js +1 -0
- package/dist/assets/java-BtIQNnN0.js +1 -0
- package/dist/assets/java-DxlWrXwe.js +1 -0
- package/dist/assets/javascript-BzbQS41l.js +1 -0
- package/dist/assets/javascript-Yelw-5ZN.js +1 -0
- package/dist/assets/json-B0N3XRZU.js +1 -0
- package/dist/assets/json-B88Oqtu5.js +1 -0
- package/dist/assets/json-CaljABEy.js +1 -0
- package/dist/assets/jspdf.es.min-C-Pf79px.js +79 -0
- package/dist/assets/jsx-D3KKoC5L.js +1 -0
- package/dist/assets/jsx-hEbJ82HF.js +1 -0
- package/dist/assets/jsx-runtime-DaSxXjOY.js +1 -0
- package/dist/assets/katex-BMCG0xok.js +257 -0
- package/dist/assets/keyVault-DEsNh9FR.js +1 -0
- package/dist/assets/kokoro-D4GKpSL0.js +1 -0
- package/dist/assets/kotlin-DhhofPvG.js +1 -0
- package/dist/assets/layout-BFQOzXtG.js +1 -0
- package/dist/assets/layout-CkO0Psde.js +1 -0
- package/dist/assets/lazyRetry-D50B1UMY.js +1 -0
- package/dist/assets/leaflet-src-C_xhOQY7.js +1 -0
- package/dist/assets/learn-DVfqN-nf.js +1 -0
- package/dist/assets/lib-DMAxsZKm.css +1 -0
- package/dist/assets/lib-VFqp6Y3w.js +1 -0
- package/dist/assets/links--u_phi0x.js +1 -0
- package/dist/assets/liveSchema-CJ49PX5R.js +98 -0
- package/dist/assets/markdown-BYOwaDjH.js +1 -0
- package/dist/assets/mastery-BPjxP0fN.js +4 -0
- package/dist/assets/media-CsrUl279.js +43 -0
- package/dist/assets/model-BE0UHu1w.js +27 -0
- package/dist/assets/motion-S0NRruNj.js +1 -0
- package/dist/assets/nav-Czm-NzPV.js +1 -0
- package/dist/assets/neighborhoods-C-1Tjr1r.js +1 -0
- package/dist/assets/notes-A9t4jdfx.css +1 -0
- package/dist/assets/notes-lROPCorS.js +1 -0
- package/dist/assets/notify-Bz4wuE4T.js +1 -0
- package/dist/assets/now-C9-UAzTC.js +1 -0
- package/dist/assets/number-DXgzss0v.js +1 -0
- package/dist/assets/officeDoc-B6nEhNp4.js +3 -0
- package/dist/assets/openchemlib-nMLMmg26.js +23 -0
- package/dist/assets/overlays-JxbHtA2o.js +1 -0
- package/dist/assets/owners-FXDGInVq.js +2 -0
- package/dist/assets/pageVisibility-BxUVsw7Z.js +1 -0
- package/dist/assets/pdf-Df7PCH7q.js +11 -0
- package/dist/assets/pdf.worker.min-yatZIOMy.mjs +21 -0
- package/dist/assets/penStrokes-9Q_l7NTt.js +1 -0
- package/dist/assets/perfProbe-COAp9PAa.js +1 -0
- package/dist/assets/php-CYrBA5iX.js +1 -0
- package/dist/assets/pickers-B4-3HOR8.js +1 -0
- package/dist/assets/pin-Bya3BL_a.css +1 -0
- package/dist/assets/pin-CXSuPJYL.js +1 -0
- package/dist/assets/planTracker-7AMCQPNx.js +1 -0
- package/dist/assets/portal-DMw7C32j.js +1 -0
- package/dist/assets/pptxgen.es-CvTILmV9.js +115 -0
- package/dist/assets/predictions-CYS7Vo39.js +1 -0
- package/dist/assets/prewarm--whjQhWJ.js +1 -0
- package/dist/assets/prism-app-CXYElq8-.css +1 -0
- package/dist/assets/prism.generated-BhbHDHR6.js +41 -0
- package/dist/assets/prismExtract.worker-Cws9K1NG.js +13 -0
- package/dist/assets/prismGround.worker-CIvKqxm2.js +1 -0
- package/dist/assets/prismLayout.worker-CZO2CY7B.js +1 -0
- package/dist/assets/providers-wzm7E8Pa.js +1 -0
- package/dist/assets/purify.es-adlwq8Pz.js +3 -0
- package/dist/assets/python-gzcpVVnB.js +1 -0
- package/dist/assets/queue-DHeyJ32F.css +1 -0
- package/dist/assets/queue-J4h7E3uc.js +1 -0
- package/dist/assets/quietHours-BvcsBKKK.js +1 -0
- package/dist/assets/react-BQSORdFI.js +1 -0
- package/dist/assets/reference-sJzyMiL4.js +1 -0
- package/dist/assets/registry-1GuPBfQK.js +1 -0
- package/dist/assets/registry-2GJZ46M8.css +1 -0
- package/dist/assets/registry-2v0jlh5Q.js +2 -0
- package/dist/assets/registry-B8DStMJk.js +1 -0
- package/dist/assets/registry-B940W2j5.js +1 -0
- package/dist/assets/registry-BAoRB8rd.css +1 -0
- package/dist/assets/registry-BPvIPLke.js +1 -0
- package/dist/assets/registry-BVHiKEwE.css +1 -0
- package/dist/assets/registry-BcZXBr3y.css +1 -0
- package/dist/assets/registry-BffpZK5w.js +1 -0
- package/dist/assets/registry-BgGeRiDA.js +2 -0
- package/dist/assets/registry-Buq5uuCv.js +1 -0
- package/dist/assets/registry-BzNLfje1.css +1 -0
- package/dist/assets/registry-C3ezxLR3.css +1 -0
- package/dist/assets/registry-CAusoyBh.js +1 -0
- package/dist/assets/registry-CEVrY72Z.js +1 -0
- package/dist/assets/registry-CKjKq6ct.js +1 -0
- package/dist/assets/registry-CNgybLQv.css +1 -0
- package/dist/assets/registry-CRM7y8QT.css +1 -0
- package/dist/assets/registry-CRisz4Ca.css +1 -0
- package/dist/assets/registry-Cg7qUZPS.js +1 -0
- package/dist/assets/registry-Cix-yqch.js +1 -0
- package/dist/assets/registry-CtbuZWYz.css +1 -0
- package/dist/assets/registry-DCI_BeFJ.js +5 -0
- package/dist/assets/registry-DDLSYDVA.js +1 -0
- package/dist/assets/registry-DI1wSG1Y.js +1 -0
- package/dist/assets/registry-DJinB5ye.js +2 -0
- package/dist/assets/registry-DUvXX_d0.js +1 -0
- package/dist/assets/registry-DVzLacl_.css +1 -0
- package/dist/assets/registry-DWcNVyI2.css +1 -0
- package/dist/assets/registry-D_URPoL4.js +1 -0
- package/dist/assets/registry-Dkc7hN5T.js +1 -0
- package/dist/assets/registry-DmQlNN3y.js +1 -0
- package/dist/assets/registry-DpaaVbmL.css +1 -0
- package/dist/assets/registry-Dpb8EXHn.js +49 -0
- package/dist/assets/registry-Dru0JcTa.css +1 -0
- package/dist/assets/registry-DujBAgn_.js +1 -0
- package/dist/assets/registry-Dye5m3qt.css +1 -0
- package/dist/assets/registry-GeSy4i7R.css +1 -0
- package/dist/assets/registry-QcULZpdE.js +4 -0
- package/dist/assets/registry-T3i2Q9SI.css +1 -0
- package/dist/assets/registry-UFIuwh3i.css +1 -0
- package/dist/assets/registry-Xzq_Rpmm.css +1 -0
- package/dist/assets/registry-ZOWzhOHk.css +1 -0
- package/dist/assets/registry-aIH-Im5Y.css +1 -0
- package/dist/assets/registry-aazwkBJp.js +1 -0
- package/dist/assets/registry-ei6fIgBT.css +1 -0
- package/dist/assets/registry-types-DMuK0vJ6.js +1 -0
- package/dist/assets/registry-upbw6pyj.css +1 -0
- package/dist/assets/registry-zRPjWNl5.js +1 -0
- package/dist/assets/relate-A9ZeaaAI.js +1 -0
- package/dist/assets/richText-BkxJhl9N.js +1 -0
- package/dist/assets/ruby-BhrksQMt.js +1 -0
- package/dist/assets/run-BYP2YlZT.js +23 -0
- package/dist/assets/run-C_0B_41W.js +21 -0
- package/dist/assets/run-DSVvshaB.js +21 -0
- package/dist/assets/rust-Cfkwpbl8.js +1 -0
- package/dist/assets/safeCssColor-di93BXgG.js +1 -0
- package/dist/assets/safeImageUrl-CUkucDUu.js +1 -0
- package/dist/assets/scale-Bcgfo6Hp.js +1 -0
- package/dist/assets/screen-DLR7p_yP.js +1 -0
- package/dist/assets/search-DnjHupRf.js +1 -0
- package/dist/assets/seed-b1xc0dj2.js +1 -0
- package/dist/assets/shapes-RqjqC0ay.js +1 -0
- package/dist/assets/shard000-DM_rqD7W.js +1 -0
- package/dist/assets/shard001-gQEQs7_F.js +1 -0
- package/dist/assets/shard002-DbDas-W-.js +1 -0
- package/dist/assets/shard003-DxMszG4_.js +1 -0
- package/dist/assets/shard004-DZMpLBw1.js +1 -0
- package/dist/assets/shard005-DLVkh9IH.js +1 -0
- package/dist/assets/shard006-B6efKHov.js +1 -0
- package/dist/assets/shard007-AFdZLe38.js +1 -0
- package/dist/assets/shard008-LbSBzwRx.js +1 -0
- package/dist/assets/shard009-D_6-SUDb.js +1 -0
- package/dist/assets/shard010-DbOj-uZW.js +1 -0
- package/dist/assets/shard011-BYSz7EEW.js +1 -0
- package/dist/assets/shard012-B5Ypj7OV.js +1 -0
- package/dist/assets/shard013-BtsliNIe.js +1 -0
- package/dist/assets/shard014-15sjmk68.js +1 -0
- package/dist/assets/shard015-nnXrqru_.js +1 -0
- package/dist/assets/shard016-BayXFE13.js +1 -0
- package/dist/assets/shard017-Cy4cGexs.js +1 -0
- package/dist/assets/shard018-aO-BYJ8H.js +1 -0
- package/dist/assets/shard019-gxaTeA3I.js +1 -0
- package/dist/assets/shard020-BibJdMI0.js +1 -0
- package/dist/assets/shard021-2FE-tze4.js +1 -0
- package/dist/assets/shard022-IXiyG-JK.js +1 -0
- package/dist/assets/shard023-314F6NJS.js +1 -0
- package/dist/assets/shard024-cN5rpQ_X.js +1 -0
- package/dist/assets/shard025-KKTaXOGE.js +1 -0
- package/dist/assets/shard026-C2Dkd1t6.js +1 -0
- package/dist/assets/shard027-Xy-8x9As.js +1 -0
- package/dist/assets/shard028-Bul-klUj.js +1 -0
- package/dist/assets/shard029-BbRSUcUZ.js +1 -0
- package/dist/assets/shard030-GYllsFVC.js +1 -0
- package/dist/assets/shard031-DMigeiyn.js +1 -0
- package/dist/assets/shard032-CcvmzK0l.js +1 -0
- package/dist/assets/shard033-11AU702M.js +1 -0
- package/dist/assets/shard034-Bx3urxjM.js +1 -0
- package/dist/assets/shard035-DBdEeyCt.js +1 -0
- package/dist/assets/shard036-vYgoRBqS.js +1 -0
- package/dist/assets/shard037-DP4MMw91.js +1 -0
- package/dist/assets/shard038-ffxsv7d6.js +1 -0
- package/dist/assets/shard039-C1JHHNp9.js +1 -0
- package/dist/assets/shard040-JbVvCSO_.js +1 -0
- package/dist/assets/shard041-5L_eFwGx.js +1 -0
- package/dist/assets/shard042-C9nsGL79.js +1 -0
- package/dist/assets/shard043-Dx_9HSKI.js +1 -0
- package/dist/assets/shard044-B3QQNC4A.js +1 -0
- package/dist/assets/shard045-B7z0WHO6.js +1 -0
- package/dist/assets/shard046-D1yDablJ.js +1 -0
- package/dist/assets/shard047-DJi3AgeT.js +1 -0
- package/dist/assets/shard048-D_2ipfUr.js +1 -0
- package/dist/assets/shard049-1aGgWZdd.js +1 -0
- package/dist/assets/shard050-Bjwa0Og4.js +1 -0
- package/dist/assets/shard051-Cx_GAk3v.js +1 -0
- package/dist/assets/shard052-LaeOD3V5.js +1 -0
- package/dist/assets/shard053-D-da7Foj.js +1 -0
- package/dist/assets/shard054-CM8eSRQk.js +1 -0
- package/dist/assets/shard055-QRpDl4Vs.js +1 -0
- package/dist/assets/shard056-DkY7CQm3.js +1 -0
- package/dist/assets/shard057-BVCgf7o0.js +1 -0
- package/dist/assets/shard058-93aGtdrX.js +1 -0
- package/dist/assets/shard059-rJE9X1_I.js +1 -0
- package/dist/assets/shard060-Lu_hfl1r.js +1 -0
- package/dist/assets/shard061-C9A8iNiq.js +1 -0
- package/dist/assets/shard062-BBIpEYZO.js +1 -0
- package/dist/assets/shard063-C_uen3Ub.js +1 -0
- package/dist/assets/shard064-DAHUsHST.js +1 -0
- package/dist/assets/shard065-C2edvdvh.js +1 -0
- package/dist/assets/shard066-DFcuVhZm.js +1 -0
- package/dist/assets/shard067-BN8DM8JZ.js +1 -0
- package/dist/assets/shard068-i464K77p.js +1 -0
- package/dist/assets/shard069-Dm9m2a5U.js +1 -0
- package/dist/assets/shard070-BPVvR8GH.js +1 -0
- package/dist/assets/shard071-Bal_0d9L.js +1 -0
- package/dist/assets/shard072-CBQu62kt.js +1 -0
- package/dist/assets/shard073-D5BRae0w.js +1 -0
- package/dist/assets/shard074-DtFvcBnZ.js +1 -0
- package/dist/assets/share-QHGHNDhQ.js +1 -0
- package/dist/assets/shellscript-BiGZg9Om.js +1 -0
- package/dist/assets/shellscript-CqHdAlCp.js +1 -0
- package/dist/assets/shikiHighlight-DUCYbsJ2.js +153 -0
- package/dist/assets/sourceHost-BV3t2XDb.js +1 -0
- package/dist/assets/speed-DwPw9KSv.js +1 -0
- package/dist/assets/spokenText-D4zPX9JY.js +1 -0
- package/dist/assets/sql-0Qcw9ht3.js +1 -0
- package/dist/assets/sql-CP0rg-5b.js +1 -0
- package/dist/assets/src-DbgF7cRH.js +2 -0
- package/dist/assets/stats-BeP5Bws0.js +1 -0
- package/dist/assets/status-BY02LIJt.js +1 -0
- package/dist/assets/stepDriver-DwjFet6U.js +1 -0
- package/dist/assets/store-4JNNn732.js +1 -0
- package/dist/assets/store-BFC7DZcg.js +1 -0
- package/dist/assets/store-BFrJaSxJ.js +1 -0
- package/dist/assets/store-DXmC_fP2.js +0 -0
- package/dist/assets/store-DgG0C1Qx.js +1 -0
- package/dist/assets/streamParse-DRgMHWye.js +2 -0
- package/dist/assets/student.generated-DS_acPaJ.js +1 -0
- package/dist/assets/swift-DonLKvLd.js +1 -0
- package/dist/assets/tables-Dzd3rTqG.js +1 -0
- package/dist/assets/time-BHTmJR1s.js +1 -0
- package/dist/assets/toast-BBNs2h3L.js +1 -0
- package/dist/assets/toml-CcmNWLt0.js +1 -0
- package/dist/assets/traveler.generated-Beun9X7y.js +1 -0
- package/dist/assets/tsx-5f3ulCja.js +1 -0
- package/dist/assets/tsx-BOvsFOI6.js +1 -0
- package/dist/assets/typeof-B5XbjTb1.js +1 -0
- package/dist/assets/typescript-Utq2Cl8c.js +1 -0
- package/dist/assets/typescript-j_1H8WHN.js +1 -0
- package/dist/assets/useDashboardLoop-B6ByHWoE.js +5 -0
- package/dist/assets/useFocusTrap-DbfG6zbM.js +1 -0
- package/dist/assets/useLiveConfig-X9Wxh90k.js +1 -0
- package/dist/assets/useSpatialCanvas-DogK9457.js +1 -0
- package/dist/assets/useSpotlightWalk-rMbmNBKK.js +1 -0
- package/dist/assets/useSrsCards-CagZefwX.js +1 -0
- package/dist/assets/useStudy-Bflreq2a.js +1 -0
- package/dist/assets/useTimeout-BRYvu2jQ.js +1 -0
- package/dist/assets/useValidatedImage-D2fhA198.js +1 -0
- package/dist/assets/useZoomGesture-CYc3tf2z.js +1 -0
- package/dist/assets/verbatim-CBMzUDlf.js +1 -0
- package/dist/assets/worker-BbLKfGzm.js +2 -0
- package/dist/assets/xml-WY0zc4PG.js +1 -0
- package/dist/assets/yaml-CxbubiML.js +1 -0
- package/dist/assets/yaml-DijMHRDA.js +1 -0
- package/dist/demo-assets/CREDITS.md +40 -0
- package/dist/demo-assets/images/basalt-cliffs.jpg +0 -0
- package/dist/demo-assets/images/capelinhos.jpg +0 -0
- package/dist/demo-assets/images/cliff-fields.jpg +0 -0
- package/dist/demo-assets/images/crater-panorama.jpg +0 -0
- package/dist/demo-assets/images/furnas-lake.jpg +0 -0
- package/dist/demo-assets/images/green-coast.jpg +0 -0
- package/dist/demo-assets/images/overcast-coast.jpg +0 -0
- package/dist/demo-assets/images/patchwork-fields.jpg +0 -0
- package/dist/demo-assets/images/ponta-delgada.jpg +0 -0
- package/dist/demo-assets/images/sete-cidades.jpg +0 -0
- package/dist/demo-assets/images/slide-placeholder-light.svg +9 -0
- package/dist/demo-assets/images/slide-placeholder.svg +9 -0
- package/dist/demo-assets/images/village-street.jpg +0 -0
- package/dist/demo-assets/pdf/cfd-primer.pdf +0 -0
- package/dist/demo-assets/video/azores-film.mp4 +0 -0
- package/dist/favicon.svg +19 -0
- package/dist/fonts/LICENSE.txt +34 -0
- package/dist/fonts/archivo-var-latin.woff2 +0 -0
- package/dist/fonts/bodoni-moda-400-italic-latin.woff2 +0 -0
- package/dist/fonts/bodoni-moda-var-latin.woff2 +0 -0
- package/dist/fonts/eb-garamond-var-italic-latin.woff2 +0 -0
- package/dist/fonts/eb-garamond-var-latin.woff2 +0 -0
- package/dist/fonts/export/families/archivo.css +12 -0
- package/dist/fonts/export/families/bodoni-moda.css +23 -0
- package/dist/fonts/export/families/eb-garamond.css +22 -0
- package/dist/fonts/export/families/ibm-plex-sans.css +12 -0
- package/dist/fonts/export/families/instrument-serif.css +22 -0
- package/dist/fonts/export/families/jetbrains-mono.css +12 -0
- package/dist/fonts/export/families/libre-franklin.css +12 -0
- package/dist/fonts/export/families/lora.css +22 -0
- package/dist/fonts/export/families/source-serif-4.css +12 -0
- package/dist/fonts/export/families/space-grotesk.css +12 -0
- package/dist/fonts/export/families/spectral.css +52 -0
- package/dist/fonts/fonts.css +141 -0
- package/dist/fonts/hanken-grotesk-var-latin.woff2 +0 -0
- package/dist/fonts/ibm-plex-sans-var-latin.woff2 +0 -0
- package/dist/fonts/instrument-serif-400-italic-latin.woff2 +0 -0
- package/dist/fonts/instrument-serif-400-latin.woff2 +0 -0
- package/dist/fonts/jetbrains-mono-var-latin.woff2 +0 -0
- package/dist/fonts/libre-franklin-var-latin.woff2 +0 -0
- package/dist/fonts/lora-var-italic-latin.woff2 +0 -0
- package/dist/fonts/lora-var-latin.woff2 +0 -0
- package/dist/fonts/newsreader-var-italic-latin.woff2 +0 -0
- package/dist/fonts/newsreader-var-latin.woff2 +0 -0
- package/dist/fonts/plex-mono-400-latin.woff2 +0 -0
- package/dist/fonts/plex-mono-500-latin.woff2 +0 -0
- package/dist/fonts/plex-mono-600-latin.woff2 +0 -0
- package/dist/fonts/source-serif-4-var-latin.woff2 +0 -0
- package/dist/fonts/space-grotesk-var-latin.woff2 +0 -0
- package/dist/fonts/spectral-400-italic-latin.woff2 +0 -0
- package/dist/fonts/spectral-400-latin.woff2 +0 -0
- package/dist/fonts/spectral-500-italic-latin.woff2 +0 -0
- package/dist/fonts/spectral-500-latin.woff2 +0 -0
- package/dist/fonts/spectral-600-latin.woff2 +0 -0
- package/dist/index.html +117 -0
- package/dist/legal/DISCLAIMER.md +16 -0
- package/dist/legal/LICENSE.txt +75 -0
- package/dist/legal/PRIVACY.md +127 -0
- package/dist/legal/SECURITY.md +72 -0
- package/dist/legal/SUPPORT.md +13 -0
- package/dist/legal/TERMS.md +122 -0
- package/dist/legal/THIRD-PARTY.txt +639 -0
- package/dist/legal/TRADEMARKS.md +15 -0
- package/dist/ort-wasm-simd-threaded.mjs +59 -0
- package/dist/sw.js +30 -0
- package/dist/vad.worklet.bundle.min.js +1 -0
- package/docker-compose.yml +33 -0
- package/gateway/README.md +99 -0
- package/gateway/actions-gateway.mjs +537 -0
- package/gateway/connectors.d.mts +27 -0
- package/gateway/connectors.mjs +463 -0
- package/gateway/guard.mjs +110 -0
- package/gateway/tokenStore.mjs +134 -0
- package/package.json +143 -0
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
// actions-gateway.mjs — same-origin endpoint for action running + OAuth connect flows.
|
|
2
|
+
//
|
|
3
|
+
// Two concerns in one small server so it runs in one container:
|
|
4
|
+
// 1. POST /<id> — run a confirmed action (the existing behavior)
|
|
5
|
+
// 2. /oauth/* — OAuth connect flows so users never paste tokens manually
|
|
6
|
+
//
|
|
7
|
+
// Browser → Vite proxy → /actions/* → here.
|
|
8
|
+
// Run: node gateway/actions-gateway.mjs | Docker: the `actions` service.
|
|
9
|
+
import { createServer } from 'node:http';
|
|
10
|
+
import { pathToFileURL } from 'node:url';
|
|
11
|
+
import { runConnector, SUPPORTED_ACTIONS } from './connectors.mjs';
|
|
12
|
+
import { isConfigured, setToken, clearToken, getToken } from './tokenStore.mjs';
|
|
13
|
+
import {
|
|
14
|
+
assertGatewayConfig,
|
|
15
|
+
createActionConfirmationStore,
|
|
16
|
+
createOAuthStateStore,
|
|
17
|
+
originAllowed,
|
|
18
|
+
secretOk,
|
|
19
|
+
} from './guard.mjs';
|
|
20
|
+
|
|
21
|
+
const PORT = Number(process.env.ACTIONS_PORT) || 8910;
|
|
22
|
+
export const ACTIONS_HOST = process.env.ACTIONS_HOST || '127.0.0.1';
|
|
23
|
+
const APP_URL = process.env.APP_URL || 'http://localhost:5173';
|
|
24
|
+
const MAX_BODY_BYTES = 64 * 1024;
|
|
25
|
+
const MAX_REQUEST_TARGET_BYTES = 8 * 1024;
|
|
26
|
+
const RATE_LIMIT = Number(process.env.ACTIONS_RATE_LIMIT) || 100; // requests per window, per client
|
|
27
|
+
const RATE_WINDOW_MS = Number(process.env.ACTIONS_RATE_WINDOW_MS) || 60 * 1000;
|
|
28
|
+
|
|
29
|
+
// ── CSRF / auth guard ───────────────────────────────────────────────────────────
|
|
30
|
+
// The gateway holds the deployer's OAuth credentials and fires real actions (Slack/Gmail/Calendar/
|
|
31
|
+
// GitHub), so a state-changing request MUST come from the app itself, not a random page the user
|
|
32
|
+
// happens to have open. Defence:
|
|
33
|
+
// 1. Origin allowlist for browser calls. Missing Origin/Referer never counts as authentication.
|
|
34
|
+
// 2. Shared-secret authentication for non-browser calls. It is optional only while the socket is
|
|
35
|
+
// loopback-only; startup refuses any remote bind unless a strong secret is configured.
|
|
36
|
+
// Vite serves 5173 by default but falls back to 5174/5175/… when a port is taken, so the local dev
|
|
37
|
+
// origin isn't always 5173. Allow the standard Vite dev range on both loopback hosts (plus whatever
|
|
38
|
+
// APP_URL names) — the gateway is local-only, and this keeps the anti-CSRF guard while not breaking
|
|
39
|
+
// a dev server that happened to land on the next port.
|
|
40
|
+
const ALLOWED_ORIGINS = new Set(
|
|
41
|
+
[
|
|
42
|
+
APP_URL,
|
|
43
|
+
...[4173, 5173, 5174, 5175, 5176, 5177].flatMap((p) => [
|
|
44
|
+
`http://localhost:${p}`,
|
|
45
|
+
`http://127.0.0.1:${p}`,
|
|
46
|
+
]),
|
|
47
|
+
].filter(Boolean),
|
|
48
|
+
);
|
|
49
|
+
const GATEWAY_SECRET = process.env.GATEWAY_SECRET || '';
|
|
50
|
+
|
|
51
|
+
// ── OAuth CSRF state ──────────────────────────────────────────────────────────
|
|
52
|
+
// The Google redirect lands on a GET callback (not covered by the POST/DELETE guard), so it
|
|
53
|
+
// carries a single-use, TTL'd `state` nonce: an attacker can't forge a callback without one we
|
|
54
|
+
// actually issued. Kept in memory — a short-lived flow, lost only on a gateway restart (retry).
|
|
55
|
+
const oauthState = createOAuthStateStore(10 * 60 * 1000);
|
|
56
|
+
const actionConfirmations = createActionConfirmationStore(2 * 60 * 1000);
|
|
57
|
+
|
|
58
|
+
/** Guard a privileged request. Browser callers must be same-origin; non-browser callers must carry
|
|
59
|
+
* the shared secret. When a secret is configured, both kinds must carry it. */
|
|
60
|
+
function guardPrivileged(req, res) {
|
|
61
|
+
const hasBrowserContext = Boolean(req.headers.origin || req.headers.referer);
|
|
62
|
+
if (hasBrowserContext && !originAllowed(req.headers, ALLOWED_ORIGINS)) {
|
|
63
|
+
send(res, 403, { ok: false, detail: 'Forbidden: cross-origin request blocked.' });
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
if (!hasBrowserContext && !GATEWAY_SECRET) {
|
|
67
|
+
send(res, 403, {
|
|
68
|
+
ok: false,
|
|
69
|
+
detail: 'Forbidden: non-browser requests require gateway authentication.',
|
|
70
|
+
});
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
if (!secretOk(req.headers, GATEWAY_SECRET)) {
|
|
74
|
+
send(res, 403, { ok: false, detail: 'Forbidden: gateway secret missing or invalid.' });
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function audit(event, fields = {}) {
|
|
81
|
+
// Never add request bodies, OAuth codes/tokens, confirmation tokens, or provider response text.
|
|
82
|
+
console.info(JSON.stringify({ timestamp: new Date().toISOString(), event, ...fields }));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ── Rate limiting ───────────────────────────────────────────────────────────────
|
|
86
|
+
// The gateway fires real, irreversible actions (Slack posts, Gmail drafts, PRs) and talks to
|
|
87
|
+
// rate-limited upstreams, so a single client must not be able to hammer it. A per-client sliding
|
|
88
|
+
// window caps bursts: kept in memory (a Map of recent timestamps per key), self-pruning so it
|
|
89
|
+
// stays bounded, and clock-injectable for tests. `now` is in ms; `take()` returns whether the hit
|
|
90
|
+
// is allowed plus, when blocked, the seconds until the oldest hit in the window ages out.
|
|
91
|
+
export function createRateLimiter(limit, windowMs, now = () => Date.now()) {
|
|
92
|
+
const hits = new Map(); // client key → array of hit timestamps, oldest first
|
|
93
|
+
return {
|
|
94
|
+
take(key) {
|
|
95
|
+
const t = now();
|
|
96
|
+
const cutoff = t - windowMs;
|
|
97
|
+
// Drop keys whose entire window has aged out, so idle clients don't accumulate forever.
|
|
98
|
+
for (const [k, times] of hits) {
|
|
99
|
+
if (times.length === 0 || times[times.length - 1] <= cutoff) hits.delete(k);
|
|
100
|
+
}
|
|
101
|
+
const times = hits.get(key) ?? [];
|
|
102
|
+
while (times.length && times[0] <= cutoff) times.shift(); // expire this key's stale hits
|
|
103
|
+
if (times.length >= limit) {
|
|
104
|
+
const retryAfterMs = times[0] + windowMs - t;
|
|
105
|
+
return { allowed: false, retryAfter: Math.max(1, Math.ceil(retryAfterMs / 1000)) };
|
|
106
|
+
}
|
|
107
|
+
times.push(t);
|
|
108
|
+
hits.set(key, times);
|
|
109
|
+
return { allowed: true };
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// One bounded, self-pruning limiter shared by the state-changing routes (see the router).
|
|
115
|
+
const rateLimiter = createRateLimiter(RATE_LIMIT, RATE_WINDOW_MS);
|
|
116
|
+
|
|
117
|
+
/** Identify the calling client for rate limiting: the same-origin proxy is the trusted hop, so we
|
|
118
|
+
* key on the socket peer rather than any client-supplied forwarding header (which is spoofable). */
|
|
119
|
+
function clientKey(req) {
|
|
120
|
+
return req.socket?.remoteAddress || 'unknown';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
124
|
+
function send(res, status, payload) {
|
|
125
|
+
const body = JSON.stringify(payload);
|
|
126
|
+
res.writeHead(status, {
|
|
127
|
+
'content-type': 'application/json',
|
|
128
|
+
'content-length': Buffer.byteLength(body),
|
|
129
|
+
});
|
|
130
|
+
res.end(body);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function sendHtml(res, status, html) {
|
|
134
|
+
res.writeHead(status, { 'content-type': 'text/html; charset=utf-8' });
|
|
135
|
+
res.end(html);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function readJson(req) {
|
|
139
|
+
const declared = Number(req.headers['content-length']);
|
|
140
|
+
if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) {
|
|
141
|
+
req.resume();
|
|
142
|
+
return Promise.reject(Object.assign(new Error('body too large'), { statusCode: 413 }));
|
|
143
|
+
}
|
|
144
|
+
return new Promise((resolve, reject) => {
|
|
145
|
+
let size = 0;
|
|
146
|
+
const chunks = [];
|
|
147
|
+
let settled = false;
|
|
148
|
+
const fail = (error) => {
|
|
149
|
+
if (settled) return;
|
|
150
|
+
settled = true;
|
|
151
|
+
reject(error);
|
|
152
|
+
};
|
|
153
|
+
req.on('data', (c) => {
|
|
154
|
+
size += c.length;
|
|
155
|
+
if (size > MAX_BODY_BYTES) {
|
|
156
|
+
fail(Object.assign(new Error('body too large'), { statusCode: 413 }));
|
|
157
|
+
req.resume();
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
chunks.push(c);
|
|
161
|
+
});
|
|
162
|
+
req.on('end', () => {
|
|
163
|
+
if (settled) return;
|
|
164
|
+
settled = true;
|
|
165
|
+
if (chunks.length === 0) return resolve({});
|
|
166
|
+
try {
|
|
167
|
+
resolve(JSON.parse(Buffer.concat(chunks).toString('utf8')));
|
|
168
|
+
} catch {
|
|
169
|
+
reject(Object.assign(new Error('invalid json'), { statusCode: 400 }));
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
req.on('error', fail);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function parseRequestTarget(rawUrl) {
|
|
177
|
+
if (!rawUrl || !rawUrl.startsWith('/') || Buffer.byteLength(rawUrl) > MAX_REQUEST_TARGET_BYTES) {
|
|
178
|
+
throw new Error('invalid request target');
|
|
179
|
+
}
|
|
180
|
+
const parsed = new URL(rawUrl, 'http://127.0.0.1');
|
|
181
|
+
const path = decodeURIComponent(parsed.pathname);
|
|
182
|
+
if (path.includes('\0')) throw new Error('invalid request target');
|
|
183
|
+
return { path, qs: Object.fromEntries(parsed.searchParams) };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ── OAuth: GitHub Device Flow ─────────────────────────────────────────────────
|
|
187
|
+
async function githubStart(res) {
|
|
188
|
+
const clientId = process.env.GITHUB_OAUTH_CLIENT_ID;
|
|
189
|
+
if (!clientId)
|
|
190
|
+
return send(res, 503, {
|
|
191
|
+
ok: false,
|
|
192
|
+
detail: 'GITHUB_OAUTH_CLIENT_ID not set on this deployment.',
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const r = await fetch('https://github.com/login/device/code', {
|
|
196
|
+
method: 'POST',
|
|
197
|
+
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
|
198
|
+
// Public repositories are the secure default. A self-hoster who intentionally connects a
|
|
199
|
+
// private repository can opt into the classic broader `repo` scope explicitly.
|
|
200
|
+
body: JSON.stringify({
|
|
201
|
+
client_id: clientId,
|
|
202
|
+
scope: process.env.GITHUB_OAUTH_SCOPE === 'repo' ? 'repo' : 'public_repo',
|
|
203
|
+
}),
|
|
204
|
+
});
|
|
205
|
+
const data = await r.json();
|
|
206
|
+
if (!data.device_code) return send(res, 502, { ok: false, detail: 'GitHub device flow failed.' });
|
|
207
|
+
send(res, 200, {
|
|
208
|
+
ok: true,
|
|
209
|
+
device_code: data.device_code,
|
|
210
|
+
user_code: data.user_code,
|
|
211
|
+
verification_uri: data.verification_uri,
|
|
212
|
+
interval: data.interval ?? 5,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function githubPoll(res, qs) {
|
|
217
|
+
const clientId = process.env.GITHUB_OAUTH_CLIENT_ID;
|
|
218
|
+
const clientSecret = process.env.GITHUB_OAUTH_CLIENT_SECRET;
|
|
219
|
+
const deviceCode = qs.device_code;
|
|
220
|
+
if (!clientId || !clientSecret || !deviceCode)
|
|
221
|
+
return send(res, 400, { ok: false, detail: 'Missing params.' });
|
|
222
|
+
|
|
223
|
+
const r = await fetch('https://github.com/login/oauth/access_token', {
|
|
224
|
+
method: 'POST',
|
|
225
|
+
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
|
226
|
+
body: JSON.stringify({
|
|
227
|
+
client_id: clientId,
|
|
228
|
+
client_secret: clientSecret,
|
|
229
|
+
device_code: deviceCode,
|
|
230
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
231
|
+
}),
|
|
232
|
+
});
|
|
233
|
+
const data = await r.json();
|
|
234
|
+
if (data.access_token) {
|
|
235
|
+
setToken('github', { token: data.access_token, repo: getToken('github')?.repo ?? '' });
|
|
236
|
+
audit('oauth.connected', { service: 'github' });
|
|
237
|
+
return send(res, 200, { ok: true, connected: true });
|
|
238
|
+
}
|
|
239
|
+
// authorization_pending / slow_down are normal — keep polling
|
|
240
|
+
return send(res, 200, { ok: true, connected: false, error: data.error });
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function githubSetRepo(res, body) {
|
|
244
|
+
const repo = (body.repo ?? '').trim();
|
|
245
|
+
if (!repo) return send(res, 400, { ok: false, detail: 'Provide a repo as owner/name.' });
|
|
246
|
+
const stored = getToken('github') ?? {};
|
|
247
|
+
setToken('github', { ...stored, repo });
|
|
248
|
+
audit('oauth.configuration.updated', { service: 'github' });
|
|
249
|
+
send(res, 200, { ok: true });
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ── OAuth: Google Auth Code + PKCE-lite (popup flow) ─────────────────────────
|
|
253
|
+
function googleAuthUrl(state) {
|
|
254
|
+
const clientId = process.env.GOOGLE_OAUTH_CLIENT_ID;
|
|
255
|
+
const redirectUri = APP_URL + '/actions/oauth/google/callback';
|
|
256
|
+
const params = new URLSearchParams({
|
|
257
|
+
response_type: 'code',
|
|
258
|
+
client_id: clientId,
|
|
259
|
+
redirect_uri: redirectUri,
|
|
260
|
+
scope: 'https://www.googleapis.com/auth/calendar.events',
|
|
261
|
+
access_type: 'offline',
|
|
262
|
+
prompt: 'consent',
|
|
263
|
+
state,
|
|
264
|
+
});
|
|
265
|
+
return 'https://accounts.google.com/o/oauth2/v2/auth?' + params;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function googleCallback(res, qs) {
|
|
269
|
+
const code = qs.code;
|
|
270
|
+
const clientId = process.env.GOOGLE_OAUTH_CLIENT_ID;
|
|
271
|
+
const clientSecret = process.env.GOOGLE_OAUTH_CLIENT_SECRET;
|
|
272
|
+
const redirectUri = APP_URL + '/actions/oauth/google/callback';
|
|
273
|
+
|
|
274
|
+
// CSRF: the callback must carry a `state` nonce we issued at /oauth/google/start.
|
|
275
|
+
if (!oauthState.consume(qs.state)) {
|
|
276
|
+
return sendHtml(
|
|
277
|
+
res,
|
|
278
|
+
400,
|
|
279
|
+
closingHtml('error', 'Invalid or expired sign-in request. Try again.'),
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
if (!code || !clientId || !clientSecret) {
|
|
283
|
+
return sendHtml(res, 400, closingHtml('error', 'Missing OAuth parameters.'));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const r = await fetch('https://oauth2.googleapis.com/token', {
|
|
287
|
+
method: 'POST',
|
|
288
|
+
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
|
289
|
+
body: new URLSearchParams({
|
|
290
|
+
code,
|
|
291
|
+
client_id: clientId,
|
|
292
|
+
client_secret: clientSecret,
|
|
293
|
+
redirect_uri: redirectUri,
|
|
294
|
+
grant_type: 'authorization_code',
|
|
295
|
+
}),
|
|
296
|
+
});
|
|
297
|
+
const data = await r.json();
|
|
298
|
+
if (!data.access_token) {
|
|
299
|
+
return sendHtml(res, 502, closingHtml('error', 'Token exchange failed.'));
|
|
300
|
+
}
|
|
301
|
+
setToken('google', {
|
|
302
|
+
token: data.access_token,
|
|
303
|
+
refreshToken: data.refresh_token ?? null,
|
|
304
|
+
expiresAt: Date.now() + (data.expires_in ?? 3600) * 1000,
|
|
305
|
+
});
|
|
306
|
+
audit('oauth.connected', { service: 'google' });
|
|
307
|
+
sendHtml(res, 200, closingHtml('google', null));
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function closingHtml(service, errMsg) {
|
|
311
|
+
const payload = errMsg
|
|
312
|
+
? JSON.stringify({ service, ok: false, error: errMsg })
|
|
313
|
+
: JSON.stringify({ service, ok: true });
|
|
314
|
+
// Target the app origin explicitly (never '*') so only Mavéa — not any page that happened
|
|
315
|
+
// to open this popup — receives the OAuth result.
|
|
316
|
+
return `<!doctype html><html><body><script>
|
|
317
|
+
window.opener?.postMessage(${payload}, ${JSON.stringify(APP_URL)});
|
|
318
|
+
window.close();
|
|
319
|
+
</script></body></html>`;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ── Router ────────────────────────────────────────────────────────────────────
|
|
323
|
+
async function handleRequest(req, res) {
|
|
324
|
+
let requestTarget;
|
|
325
|
+
try {
|
|
326
|
+
requestTarget = parseRequestTarget(req.url);
|
|
327
|
+
} catch {
|
|
328
|
+
return send(res, 400, { ok: false, detail: 'Malformed request target.' });
|
|
329
|
+
}
|
|
330
|
+
const { path, qs } = requestTarget;
|
|
331
|
+
const method = req.method || 'GET';
|
|
332
|
+
|
|
333
|
+
// Every privileged request must pass the CSRF/auth guard. That's all POST/DELETE, plus the
|
|
334
|
+
// OAuth INITIATION GETs (/oauth/*/start): a cross-origin link to google/start would otherwise mint
|
|
335
|
+
// a real state nonce and let the callback complete the flow. GitHub poll is privileged too: a
|
|
336
|
+
// successful poll persists the access token, despite being a GET. Status exposes connection and
|
|
337
|
+
// repository metadata. The provider callback remains bound to its single-use OAuth state nonce.
|
|
338
|
+
const isOAuthInit =
|
|
339
|
+
method === 'GET' && (path === '/oauth/google/start' || path === '/oauth/github/start');
|
|
340
|
+
const isMutation = method === 'POST' || method === 'DELETE' || isOAuthInit;
|
|
341
|
+
const isGithubPoll = method === 'GET' && path === '/oauth/github/poll';
|
|
342
|
+
const isOAuthStatus = method === 'GET' && path === '/oauth/status';
|
|
343
|
+
const isPrivileged = isMutation || isGithubPoll || isOAuthStatus;
|
|
344
|
+
if (isPrivileged && !guardPrivileged(req, res)) return;
|
|
345
|
+
|
|
346
|
+
// Throttle state-changing requests per client so one caller can't flood the connectors or the
|
|
347
|
+
// rate-limited upstreams they call. Runs after the CSRF guard but before any handler reads the
|
|
348
|
+
// body or fires an action.
|
|
349
|
+
if (isMutation || isGithubPoll) {
|
|
350
|
+
const verdict = rateLimiter.take(clientKey(req));
|
|
351
|
+
if (!verdict.allowed) {
|
|
352
|
+
res.writeHead(429, {
|
|
353
|
+
'content-type': 'application/json',
|
|
354
|
+
'retry-after': String(verdict.retryAfter),
|
|
355
|
+
});
|
|
356
|
+
return res.end(
|
|
357
|
+
JSON.stringify({ ok: false, detail: 'Too many requests. Slow down and retry shortly.' }),
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Health check
|
|
363
|
+
if (method === 'GET' && path === '/healthz') {
|
|
364
|
+
return send(res, 200, { ok: true, actions: SUPPORTED_ACTIONS });
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// OAuth status
|
|
368
|
+
if (method === 'GET' && path === '/oauth/status') {
|
|
369
|
+
const g = getToken('github');
|
|
370
|
+
return send(res, 200, {
|
|
371
|
+
ok: true,
|
|
372
|
+
github: isConfigured('github'),
|
|
373
|
+
google: isConfigured('google'),
|
|
374
|
+
githubRepo: g?.repo ?? '',
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// GitHub device flow
|
|
379
|
+
if (method === 'POST' && path === '/oauth/github/start') {
|
|
380
|
+
return githubStart(res);
|
|
381
|
+
}
|
|
382
|
+
if (method === 'GET' && path === '/oauth/github/poll') {
|
|
383
|
+
return githubPoll(res, qs);
|
|
384
|
+
}
|
|
385
|
+
if (method === 'POST' && path === '/oauth/github/repo') {
|
|
386
|
+
let body;
|
|
387
|
+
try {
|
|
388
|
+
body = await readJson(req);
|
|
389
|
+
} catch (error) {
|
|
390
|
+
return send(res, error.statusCode || 400, { ok: false, detail: 'Bad JSON.' });
|
|
391
|
+
}
|
|
392
|
+
return githubSetRepo(res, body);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Google OAuth popup
|
|
396
|
+
if (method === 'GET' && path === '/oauth/google/start') {
|
|
397
|
+
// Runs in a popup: reply with a self-closing page that posts the result back to the
|
|
398
|
+
// opener, never raw JSON. An unconfigured gateway therefore surfaces as a clean error
|
|
399
|
+
// on the card instead of a bare error page in the popup.
|
|
400
|
+
if (!process.env.GOOGLE_OAUTH_CLIENT_ID)
|
|
401
|
+
return sendHtml(
|
|
402
|
+
res,
|
|
403
|
+
503,
|
|
404
|
+
closingHtml(
|
|
405
|
+
'google',
|
|
406
|
+
'Google sign-in isn’t set up on this gateway (GOOGLE_OAUTH_CLIENT_ID).',
|
|
407
|
+
),
|
|
408
|
+
);
|
|
409
|
+
res.writeHead(302, { location: googleAuthUrl(oauthState.issue()) });
|
|
410
|
+
return res.end();
|
|
411
|
+
}
|
|
412
|
+
if (method === 'GET' && path === '/oauth/google/callback') {
|
|
413
|
+
return googleCallback(res, qs);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Disconnect
|
|
417
|
+
if (method === 'DELETE' && (path === '/oauth/github' || path === '/oauth/google')) {
|
|
418
|
+
const svc = path.split('/')[2];
|
|
419
|
+
clearToken(svc);
|
|
420
|
+
audit('oauth.disconnected', { service: svc, client: clientKey(req) });
|
|
421
|
+
return send(res, 200, { ok: true });
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// The browser asks for a short-lived token only after the user clicks the confirm button. It is
|
|
425
|
+
// bound to the exact action + args and consumed by the execution route below, so retries or a
|
|
426
|
+
// captured request cannot replay the action or change its arguments.
|
|
427
|
+
if (method === 'POST' && path === '/confirm') {
|
|
428
|
+
let body;
|
|
429
|
+
try {
|
|
430
|
+
body = await readJson(req);
|
|
431
|
+
} catch (error) {
|
|
432
|
+
return send(res, error.statusCode || 400, { ok: false, detail: 'Bad JSON.' });
|
|
433
|
+
}
|
|
434
|
+
const id = typeof body.id === 'string' ? body.id : '';
|
|
435
|
+
const actionArgs = body.args && typeof body.args === 'object' ? body.args : {};
|
|
436
|
+
if (!SUPPORTED_ACTIONS.includes(id)) {
|
|
437
|
+
return send(res, 404, { ok: false, detail: `No connector for “${id}”.` });
|
|
438
|
+
}
|
|
439
|
+
const confirmationToken = actionConfirmations.issue(id, actionArgs);
|
|
440
|
+
audit('action.confirmed', { action: id, client: clientKey(req) });
|
|
441
|
+
return send(res, 200, { ok: true, confirmationToken, expiresInMs: 2 * 60 * 1000 });
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Run a confirmed action — POST /<id>
|
|
445
|
+
if (method === 'POST') {
|
|
446
|
+
const id = path.replace(/^\//, '');
|
|
447
|
+
if (!id) return send(res, 404, { ok: false, detail: 'No action id in the path.' });
|
|
448
|
+
let body;
|
|
449
|
+
try {
|
|
450
|
+
body = await readJson(req);
|
|
451
|
+
} catch (err) {
|
|
452
|
+
return send(res, err.statusCode || 400, {
|
|
453
|
+
ok: false,
|
|
454
|
+
detail: `Couldn't read the request (${err.message}).`,
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
const actionArgs = body.args && typeof body.args === 'object' ? body.args : {};
|
|
458
|
+
if (!actionConfirmations.consume(req.headers['x-action-confirmation'], id, actionArgs)) {
|
|
459
|
+
return send(res, 403, {
|
|
460
|
+
ok: false,
|
|
461
|
+
detail: 'Action confirmation is missing, expired, already used, or does not match.',
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
const started = Date.now();
|
|
465
|
+
const result = await runConnector(id, actionArgs);
|
|
466
|
+
audit('action.executed', {
|
|
467
|
+
action: id,
|
|
468
|
+
ok: result.ok,
|
|
469
|
+
status: result.status,
|
|
470
|
+
durationMs: Date.now() - started,
|
|
471
|
+
client: clientKey(req),
|
|
472
|
+
});
|
|
473
|
+
// `payload` carries read-only data a connector returns (e.g. a fetched PR diff for Ripple);
|
|
474
|
+
// write actions omit it, so the wire shape is unchanged for them.
|
|
475
|
+
return send(res, result.status, {
|
|
476
|
+
ok: result.ok,
|
|
477
|
+
detail: result.detail,
|
|
478
|
+
...(result.payload !== undefined ? { payload: result.payload } : {}),
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
send(res, 405, { ok: false, detail: 'Method not allowed.' });
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
export function createActionsGatewayServer() {
|
|
486
|
+
const gateway = createServer((req, res) => {
|
|
487
|
+
void handleRequest(req, res).catch(() => {
|
|
488
|
+
if (!res.headersSent) {
|
|
489
|
+
send(res, 500, { ok: false, detail: 'The gateway could not complete the request.' });
|
|
490
|
+
} else {
|
|
491
|
+
res.destroy();
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
gateway.headersTimeout = 10_000;
|
|
496
|
+
gateway.requestTimeout = 15_000;
|
|
497
|
+
gateway.keepAliveTimeout = 5_000;
|
|
498
|
+
gateway.maxHeadersCount = 64;
|
|
499
|
+
gateway.maxRequestsPerSocket = 500;
|
|
500
|
+
return gateway;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
export function listenGateway(gateway, { port = PORT, host = ACTIONS_HOST } = {}) {
|
|
504
|
+
// Validate the same module-scoped secret the request guard will enforce. Accepting a separate
|
|
505
|
+
// listen-time value would let startup validate one secret while requests checked another.
|
|
506
|
+
assertGatewayConfig(host, GATEWAY_SECRET);
|
|
507
|
+
return new Promise((resolve, reject) => {
|
|
508
|
+
const onError = (error) => {
|
|
509
|
+
gateway.off('listening', onListening);
|
|
510
|
+
reject(error);
|
|
511
|
+
};
|
|
512
|
+
const onListening = () => {
|
|
513
|
+
gateway.off('error', onError);
|
|
514
|
+
resolve(gateway.address());
|
|
515
|
+
};
|
|
516
|
+
gateway.once('error', onError);
|
|
517
|
+
gateway.once('listening', onListening);
|
|
518
|
+
gateway.listen(port, host);
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
export const server = createActionsGatewayServer();
|
|
523
|
+
|
|
524
|
+
// Bind the port only when run as the entry point, not when a test imports the pure helpers above.
|
|
525
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
526
|
+
void listenGateway(server)
|
|
527
|
+
.then((address) => {
|
|
528
|
+
const actualPort = typeof address === 'object' && address ? address.port : PORT;
|
|
529
|
+
console.log(
|
|
530
|
+
`actions gateway ${ACTIONS_HOST}:${actualPort} — connectors: ${SUPPORTED_ACTIONS.join(', ')}`,
|
|
531
|
+
);
|
|
532
|
+
})
|
|
533
|
+
.catch((error) => {
|
|
534
|
+
console.error(`actions gateway refused to start: ${error.message}`);
|
|
535
|
+
process.exitCode = 1;
|
|
536
|
+
});
|
|
537
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Types for the dependency-free gateway connector core, so the app's tests get real
|
|
2
|
+
// checking when they import it. The gateway itself stays plain ESM with no build step.
|
|
3
|
+
|
|
4
|
+
/** The outcome of running one connector — mirrors what the HTTP layer sends back. */
|
|
5
|
+
export interface ConnectorResult {
|
|
6
|
+
ok: boolean;
|
|
7
|
+
/** HTTP status to return (200 ok, 4xx bad input / unconfigured, 5xx upstream). */
|
|
8
|
+
status: number;
|
|
9
|
+
/** A short line for the confirm card — what happened, or what to fix. */
|
|
10
|
+
detail: string;
|
|
11
|
+
/** Read-only data a connector returns (e.g. Ripple's fetched PR diff). Write actions omit it. */
|
|
12
|
+
payload?: unknown;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** The action ids this gateway can run (the proxy routes the app POSTs to). */
|
|
16
|
+
export const SUPPORTED_ACTIONS: string[];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Run a confirmed action. Pure and never throws: pass the env holding the credentials and a
|
|
20
|
+
* fetch implementation (both default to the process global / runtime `fetch`).
|
|
21
|
+
*/
|
|
22
|
+
export function runConnector(
|
|
23
|
+
id: string,
|
|
24
|
+
args: Record<string, unknown> | undefined,
|
|
25
|
+
env?: Record<string, string | undefined>,
|
|
26
|
+
fetchImpl?: typeof fetch,
|
|
27
|
+
): Promise<ConnectorResult>;
|