void 0.28.0 → 1.1.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/dist/{account-cmd-Ci00cncT.mjs → account-cmd-BTcUzv9L.mjs} +4 -4
- package/dist/{application-analysis-D72ZQfMZ.mjs → application-analysis-AB_YYWgH.mjs} +1 -1
- package/dist/{application-routing-Dy589Vgn.mjs → application-routing-L7V-TFhZ.mjs} +1 -1
- package/dist/auth-Cbqi1iVX.mjs +1 -0
- package/dist/{auth-link-DWr6Afao.mjs → auth-link-DUJuvRx5.mjs} +4 -4
- package/dist/{auth-router-DP14wVkZ.mjs → auth-router-CIxiMG7i.mjs} +3 -3
- package/dist/{build-cmd-Cr3FJxv0.mjs → build-cmd-BkIv25PA.mjs} +3 -3
- package/dist/{cache-D0Tt28cn.mjs → cache-Ctzfig-W.mjs} +3 -3
- package/dist/{cancel-deploy-CJKvk7AN.mjs → cancel-deploy-CD9o-SsC.mjs} +2 -2
- package/dist/cf-build-output-BU3hqdHu.mjs +1 -0
- package/dist/cli/cli.mjs +31 -31
- package/dist/cli/cloudflare-operation-process.mjs +2 -2
- package/dist/cli/env-schema-probe.d.mts +2 -1
- package/dist/cli/env-schema-probe.mjs +2 -2
- package/dist/client-B60PwCC4.mjs +1 -0
- package/dist/{cloudflare-cmd-CTrn-lXJ.mjs → cloudflare-cmd-BIAF_BIY.mjs} +2 -2
- package/dist/{cloudflare-connect-BosmCNlq.mjs → cloudflare-connect-WjwnVOJA.mjs} +1 -1
- package/dist/{cloudflare-operations-BKdSfNBN.mjs → cloudflare-operations-B42EZeJy.mjs} +3 -3
- package/dist/cloudflare-operations-CrSUFRzJ.mjs +1 -0
- package/dist/collect-Cae8A7Zr.mjs +1 -0
- package/dist/config-QrJWu0IX.mjs +1 -0
- package/dist/{connect-nukQSoEO.mjs → connect-CKtk-EZe.mjs} +5 -5
- package/dist/create-project-B-ewF_Af.mjs +1 -0
- package/dist/{create-project-D4APeBwX.mjs → create-project-yInGo72M.mjs} +3 -3
- package/dist/database-provider.mjs +1 -2
- package/dist/{db-27RztQLy.mjs → db-7I_gfdVq.mjs} +37 -38
- package/dist/db-types-Bb4WBWIs.mjs +49 -0
- package/dist/{delete-BbsZFZIw.mjs → delete-Cy-M1u-r.mjs} +3 -3
- package/dist/deploy-9lgfhrXS.mjs +2 -0
- package/dist/{deploy-B06EQq4t.mjs → deploy-BYYlnsiy.mjs} +176 -21
- package/dist/{deploy-managed-static-CGswbcay.mjs → deploy-managed-static-Ca4TFdmx.mjs} +24 -706
- package/dist/{deploy-transport-BiicfoQW.mjs → deploy-transport-DWlWBe6M.mjs} +1 -1
- package/dist/deploy.mjs +4 -4
- package/dist/discover-ClzFSwns.mjs +1 -0
- package/dist/dispatch-DrTTk8Xg.mjs +431 -0
- package/dist/{dist-D3m88sjf.mjs → dist-BVdsO7Py.mjs} +5 -6
- package/dist/dist-CfgIkieS.mjs +1 -0
- package/dist/{domain-Bfq9n03H.mjs → domain-CZGxPv9u.mjs} +4 -4
- package/dist/edge.mjs +1 -2
- package/dist/{email-DIwaQU4P.mjs → email-DzyNVuoU.mjs} +6 -6
- package/dist/{email-cloudflare-CH053RxK.mjs → email-cloudflare-DfG6UEMm.mjs} +1 -1
- package/dist/{env-BhdWMrNy.mjs → env-0jrMQJI_.mjs} +5 -2
- package/dist/{env-C6ipBLp3.mjs → env-Nx3yf50r.mjs} +4 -4
- package/dist/{env-raw-Cklzcde3.mjs → env-raw-D74P4ebM.mjs} +12 -1
- package/dist/env-validation-BT7z-qpQ.mjs +1 -0
- package/dist/{env-validation-cE0zcmzO.mjs → env-validation-DYxjz_Wo.mjs} +2 -0
- package/dist/{gen-CnN1Xidx.mjs → gen-CufHEf42.mjs} +2 -2
- package/dist/gen-sXyp5DFO.mjs +1 -0
- package/dist/generate-ClI89brV.mjs +1 -0
- package/dist/{github-cmd-BdkU4Nhe.mjs → github-cmd-FU8EjFyU.mjs} +3 -3
- package/dist/{help-Bug7pyK_.mjs → help-BT2hovNI.mjs} +9 -5
- package/dist/help-i2YGGnTH.mjs +1 -0
- package/dist/{inbound-DmkTQeWp.mjs → inbound-BuTfm11U.mjs} +1 -1
- package/dist/index.mjs +551 -303
- package/dist/{info-CujPwtMe.mjs → info-l249eUdH.mjs} +1 -1
- package/dist/{init-ZWOJvc-Z.mjs → init-D0A6M64Q.mjs} +60 -14
- package/dist/{isr-config-DPjXM8J2.mjs → isr-config-fZaJJ8t7.mjs} +1 -30
- package/dist/{link-UEc7bnF4.mjs → link-WMoJFf4i.mjs} +4 -4
- package/dist/{list-CuWeOnzk.mjs → list-CQ2EQEHL.mjs} +4 -4
- package/dist/{live-BxLX-JqJ.mjs → live-BcHoTDFe.mjs} +1 -1
- package/dist/{local-d1-BM3p74mm.mjs → local-d1-BxvQYEFi.mjs} +1 -1
- package/dist/{login-CZ2Cqa-D.mjs → login-C2T1oVG-.mjs} +3 -3
- package/dist/login-XD844Xs-.mjs +1 -0
- package/dist/{logs-TihRcLTk.mjs → logs-DnC1d7xB.mjs} +3 -3
- package/dist/{migrate-uFwojusJ.mjs → migrate-BDIprOYd.mjs} +2 -2
- package/dist/migrate-BRwU-blY.mjs +1 -0
- package/dist/mysql-migration-sql-BIS5vbNk.mjs +241 -0
- package/dist/nitro.d.mts +30 -0
- package/dist/nitro.mjs +88 -0
- package/dist/{node-DoKbo1SZ.mjs → node-cPadMgUO.mjs} +3 -3
- package/dist/operator-cmd-D6gnZsnK.mjs +1 -0
- package/dist/{operator-cmd-C5lCctnc.mjs → operator-cmd-DHieWtFp.mjs} +5 -5
- package/dist/{output-FNHKw5Ac.mjs → output-Ba6ZbrzY.mjs} +1 -1
- package/dist/pages/head.mjs +1 -2
- package/dist/pages/prefetch.mjs +1 -2
- package/dist/pages/serialize.mjs +1 -2
- package/dist/plan-CKX_jWnD.mjs +1 -0
- package/dist/platform-args-B0HeTh0C.mjs +1 -0
- package/dist/{platform-args-C6Mb_I3a.mjs → platform-args-CaCL_1Ch.mjs} +9 -2
- package/dist/{platform-auth-config-CJmNkFT2.mjs → platform-auth-config-Z16-I8nT.mjs} +6 -6
- package/dist/{platform-auth-protection-D5wvsI-Y.mjs → platform-auth-protection-BXwZiJSF.mjs} +4 -4
- package/dist/{platform-auth-recovery-D_0I3n-1.mjs → platform-auth-recovery-CXjfqyOv.mjs} +4 -4
- package/dist/{platform-cmd-DxxZO6e5.mjs → platform-cmd-DaAOiWTl.mjs} +7 -7
- package/dist/platform-cmd-PT4YwMLM.mjs +1 -0
- package/dist/{platform-domain-zLXnW1G_.mjs → platform-domain-CxpvUeo0.mjs} +4 -4
- package/dist/{platform-lifecycle-DOW-wLfx.mjs → platform-lifecycle-DBiS0cJd.mjs} +317 -99
- package/dist/platform-lifecycle-u9pMOvxC.mjs +1 -0
- package/dist/{platform-management-t7HKy4-U.mjs → platform-management-DhEFdQAf.mjs} +67 -9
- package/dist/platform-management-DvBS83rC.mjs +1 -0
- package/dist/{platform-plans-config-CNH6zbPX.mjs → platform-plans-config-BWn1S2Rv.mjs} +2 -2
- package/dist/{platform-recovery-DeucI7A9.mjs → platform-recovery-BUGOB4RC.mjs} +2 -2
- package/dist/{platform-setup-CTf6MMT9.mjs → platform-setup-YvN9VQXV.mjs} +3 -3
- package/dist/prepare-CYT6tAzD.mjs +1 -0
- package/dist/{prepare-CSH9dKTI.mjs → prepare-DEB5SeVJ.mjs} +3 -2
- package/dist/{prepare-C4zfwks4.mjs → prepare-PQCgsny0.mjs} +3 -3
- package/dist/prerender-render.mjs +1 -2
- package/dist/{project-cmd-Dq75AXzy.mjs → project-cmd-DVatD8Ev.mjs} +16 -16
- package/dist/project-slug-6cyz8E7k.mjs +1 -0
- package/dist/{project-team-B3_QcStG.mjs → project-team-DJrWEAG1.mjs} +3 -3
- package/dist/{project-token-CvsRvv2I.mjs → project-token-CBgLBuL6.mjs} +3 -3
- package/dist/project-zdB3fs9G.mjs +1 -0
- package/dist/{project-zero-trust-D1ICiwN_.mjs → project-zero-trust-92gLtZ25.mjs} +3 -3
- package/dist/{provision-DDh7ITVS.mjs → provision-BnSSkiED.mjs} +1 -1
- package/dist/{requests-3CL3BuZh.mjs → requests-7W1Pygt2.mjs} +2 -2
- package/dist/resolve-project-PnhzMtY6.mjs +1 -0
- package/dist/{resolve-project-C_Cu7E8a.mjs → resolve-project-WpwkCB_c.mjs} +1 -1
- package/dist/{rollback-Ce1d_0J9.mjs → rollback-BcASVnwP.mjs} +3 -3
- package/dist/{route-types-IrBuwJ7u.mjs → route-types-BZi26DVS.mjs} +2 -49
- package/dist/{runner-DcVDrzuR.mjs → runner-CabsQ-iM.mjs} +15 -6
- package/dist/runner-So9mYfcm.mjs +1 -0
- package/dist/runner-mysql-BhRaONkO.mjs +146 -0
- package/dist/runner-mysql-DWwE8jKi.mjs +1 -0
- package/dist/{runner-pg-CPd-CB_F.mjs → runner-pg-BRemq_GX.mjs} +2 -2
- package/dist/runner-pg-_Xncr3H5.mjs +1 -0
- package/dist/runtime/ai.mjs +15 -4
- package/dist/runtime/auth-client-react.mjs +1 -2
- package/dist/runtime/auth-client-solid.mjs +1 -2
- package/dist/runtime/auth-client-svelte.mjs +1 -2
- package/dist/runtime/auth-client-vue.mjs +1 -2
- package/dist/runtime/auth-client.mjs +1 -2
- package/dist/runtime/auth.mjs +1 -2
- package/dist/runtime/better-auth-mysql.mjs +4 -1
- package/dist/runtime/db.mjs +6 -2
- package/dist/runtime/durable.d.mts +2 -1
- package/dist/runtime/durable.mjs +1 -1
- package/dist/runtime/email/testing.mjs +1 -1
- package/dist/runtime/email-protocol.mjs +1 -2
- package/dist/runtime/email.mjs +1 -1
- package/dist/runtime/env-public.mjs +2 -2
- package/dist/runtime/env.mjs +1 -2
- package/dist/runtime/isr.mjs +1 -1
- package/dist/runtime/kv.mjs +1 -1
- package/dist/runtime/limits.mjs +1 -2
- package/dist/runtime/live-client.d.mts +2 -1
- package/dist/runtime/live-server.d.mts +2 -1
- package/dist/runtime/live-server.mjs +1 -1
- package/dist/runtime/live.mjs +1 -2
- package/dist/runtime/log.d.mts +2 -1
- package/dist/runtime/migration-handler-mysql.mjs +38 -8
- package/dist/runtime/migration-handler.mjs +1 -2
- package/dist/runtime/queues.mjs +1 -1
- package/dist/runtime/response.mjs +1 -2
- package/dist/runtime/route-url.mjs +1 -2
- package/dist/runtime/routing.d.mts +2 -1
- package/dist/runtime/sandbox-container.mjs +1 -2
- package/dist/runtime/sandbox.mjs +3 -3
- package/dist/runtime/seed.d.mts +2 -1
- package/dist/runtime/sse.mjs +1 -2
- package/dist/runtime/storage.mjs +1 -1
- package/dist/runtime/validator.mjs +1 -2
- package/dist/runtime/ws-server.d.mts +2 -1
- package/dist/runtime/ws-server.mjs +1 -1
- package/dist/{secret-B5J8bBsI.mjs → secret-bg73f1Ge.mjs} +5 -5
- package/dist/{skills-BJsan98C.mjs → skills-C_yRTg0g.mjs} +1 -1
- package/dist/{subcommand-prompt-gQ-NDA-u.mjs → subcommand-prompt-Bm3ZlYgA.mjs} +2 -2
- package/dist/sveltekit.d.mts +2 -1
- package/dist/validate-BQFdf0Pi.mjs +1 -0
- package/dist/wrapper-CwhpNWtH.mjs +663 -0
- package/docs/guide/ai.md +8 -2
- package/docs/guide/database/mysql.md +14 -3
- package/docs/guide/database.md +1 -5
- package/docs/guide/durable-state.md +9 -5
- package/docs/guide/platform/installation/credentials.md +18 -2
- package/docs/guide/platform/installation/domains.md +1 -1
- package/docs/guide/platform/installation/maintenance.md +38 -1
- package/docs/guide/platform/installation/prerequisites.md +6 -2
- package/docs/guide/platform/installation/setup.md +23 -1
- package/docs/guide/platform/installation/uninstall.md +6 -1
- package/docs/guide/sandboxes.md +1 -1
- package/docs/guide/self-hosted-platform.md +1 -1
- package/docs/integrations/cloudflare.md +2 -2
- package/docs/integrations/frameworks/analog.md +3 -11
- package/docs/integrations/frameworks/nuxt.md +7 -10
- package/docs/integrations/nodejs-bun-deno.md +2 -0
- package/docs/reference/api.md +1 -0
- package/docs/reference/cli/platform-installation.md +18 -6
- package/docs/reference/config.md +2 -0
- package/package.json +34 -28
- package/sandbox.Dockerfile +1 -1
- package/skills/void/SKILL.md +1 -1
- package/dist/auth-wn6F8dB8.mjs +0 -2
- package/dist/cf-build-output-2u583yJK.mjs +0 -2
- package/dist/client-C1BjRn3g.mjs +0 -2
- package/dist/cloudflare-operations-Ds-lX3Ts.mjs +0 -2
- package/dist/collect-NUlJMpLY.mjs +0 -2
- package/dist/config-CNnrSMYm.mjs +0 -2
- package/dist/create-project-DL7YXpjj.mjs +0 -2
- package/dist/deploy-Db2ALnQP.mjs +0 -3
- package/dist/discover-CTUiSsfG.mjs +0 -2
- package/dist/dist-Bx1z6Eva.mjs +0 -2
- package/dist/env-validation-CTn-HhG1.mjs +0 -2
- package/dist/gen-F8pSZnhc.mjs +0 -2
- package/dist/generate-Rii9klHB.mjs +0 -2
- package/dist/help-s9q7LTcu.mjs +0 -2
- package/dist/login-DhK7o1oJ.mjs +0 -2
- package/dist/migrate-BgT-hmu8.mjs +0 -2
- package/dist/operator-cmd-Cjk5DiRx.mjs +0 -2
- package/dist/plan-CH2G51je.mjs +0 -2
- package/dist/platform-args-Cm7x-fot.mjs +0 -2
- package/dist/platform-cmd-Cuj4oUVo.mjs +0 -2
- package/dist/platform-lifecycle-_gfnjUBr.mjs +0 -2
- package/dist/platform-management-CYrLvTGH.mjs +0 -2
- package/dist/prepare-dr9uxQSV.mjs +0 -2
- package/dist/project-DiiOBZPF.mjs +0 -2
- package/dist/project-slug-DofjTFd-.mjs +0 -2
- package/dist/resolve-project-B9Hr0D3n.mjs +0 -2
- package/dist/runner-B5huK2eC.mjs +0 -2
- package/dist/runner-mysql-CCtyI0p6.mjs +0 -55
- package/dist/runner-mysql-CaJq-hd6.mjs +0 -2
- package/dist/runner-pg-BJhFZZG4.mjs +0 -2
- package/dist/validate-C9xtFOYR.mjs +0 -2
- package/dist/{sandbox-container-DBKIqDEK.mjs → sandbox-container-a9LRUtii.mjs} +1 -1
|
@@ -3,17 +3,17 @@ import { t as writeFileAtomic } from "./atomic-write-Dq6R0BPN.mjs";
|
|
|
3
3
|
import { S as writePendingInstallationSecrets, a as latestPlatformWorkerCutoverGeneration, c as normalizePlatformApiUrl, d as platformRegistryDirectory, f as platformWorkerSecretUpdateIntentState, h as readPendingInstallationSecrets, m as readInstallationRecoverySecrets, n as assertPlatformInstallationSlugAvailable, o as listInstallationManifests, p as promoteInstallationSecrets, t as acquirePlatformLifecycleLock, v as resolveInstallationManifest, x as writeInstallationManifest, y as savePlatformConnection } from "./platform-registry-B9mWX9LO.mjs";
|
|
4
4
|
import { t as cfAccessHeaders } from "./cf-access-BM9G0lhO.mjs";
|
|
5
5
|
import { t as ensurePlatformAccessToken } from "./cf-access-CWP4avfP.mjs";
|
|
6
|
-
import { A as confirm, H as outro, K as select, L as log, U as password, X as text$1, tt as isCancel, z as multiselect } from "./dist-
|
|
7
|
-
import { f as import_picocolors, l as formatUrl, m as printSuccess, n as cliCommandName, p as note, r as createSpinner, u as getErrorMessage } from "./output-
|
|
6
|
+
import { A as confirm, H as outro, K as select, L as log, U as password, X as text$1, tt as isCancel, z as multiselect } from "./dist-BVdsO7Py.mjs";
|
|
7
|
+
import { f as import_picocolors, l as formatUrl, m as printSuccess, n as cliCommandName, p as note, r as createSpinner, u as getErrorMessage } from "./output-Ba6ZbrzY.mjs";
|
|
8
8
|
import { l as resolveCloudflareOperationArgv } from "./cloudflare-process-CeDWJRhj.mjs";
|
|
9
|
-
import { d as resolvePinnedApiBaseUrl, i as createDefaultRunWrangler } from "./provision-
|
|
10
|
-
import { t as parsePlatformArgs } from "./platform-args-
|
|
9
|
+
import { d as resolvePinnedApiBaseUrl, i as createDefaultRunWrangler } from "./provision-BnSSkiED.mjs";
|
|
10
|
+
import { t as parsePlatformArgs } from "./platform-args-CaCL_1Ch.mjs";
|
|
11
11
|
import { o as runCloudflareAuthCommand } from "./cloudflare-auth-CqFBDo52.mjs";
|
|
12
|
-
import { a as platformManagementSetupPage, c as platformSetupLink, i as platformInstallCommandArguments, l as platformSetupPages, n as formatPlatformSetupPlan, o as platformPromptAnswer, r as openPlatformSetupPage, s as platformSetupFieldHelp, t as formatPlatformInstallCommand } from "./platform-setup-
|
|
12
|
+
import { a as platformManagementSetupPage, c as platformSetupLink, i as platformInstallCommandArguments, l as platformSetupPages, n as formatPlatformSetupPlan, o as platformPromptAnswer, r as openPlatformSetupPage, s as platformSetupFieldHelp, t as formatPlatformInstallCommand } from "./platform-setup-YvN9VQXV.mjs";
|
|
13
13
|
import { copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
15
|
import { isRecord } from "@void/deploy-core";
|
|
16
|
-
import { PLATFORM_APP_ACCESS_SETUP_PROOF_PREFIX, PLATFORM_AUTH_SETUP_PROOF_PREFIX, PLATFORM_DISABLED_KEY, PLATFORM_DISABLED_VALUE, PLATFORM_HEALTH_HOSTNAME_LABEL, PLATFORM_LOGIN_KINDS, PLATFORM_R2_OWNERSHIP_MARKER_KEY, PLATFORM_REMOTE_INSTALLATION_MANIFEST_KEY, PLATFORM_REMOTE_INSTALLATION_MANIFEST_KEY as REMOTE_INSTALLATION_MANIFEST_KEY, createApplicationRoutes, createPlatformDeploymentManifest, createPlatformInstallationManifest, createPlatformResourceNames, createPlatformResourceOwnershipMarker, createPlatformWorkerConfigs, normalizeApplicationDomain, normalizeInstallationSlug, normalizePlatformDashboardUrl, parsePlatformAccessProtection, parsePlatformAdmissionPolicy, parsePlatformAppAccessSetup, parsePlatformAuthenticationSetup, parsePlatformInstallationManifest, parsePlatformLoginConfiguration, parsePlatformResourceOwnershipMarker, platformDashboardUrl, recordInstallationResource, recordInstallationStep, setInstallationEndpoints, transitionInstallation } from "@void/platform";
|
|
16
|
+
import { PLATFORM_APP_ACCESS_SETUP_PROOF_PREFIX, PLATFORM_APP_ACCESS_TURN_OFF_PROOF_PREFIX, PLATFORM_AUTH_SETUP_PROOF_PREFIX, PLATFORM_DISABLED_KEY, PLATFORM_DISABLED_VALUE, PLATFORM_HEALTH_HOSTNAME_LABEL, PLATFORM_LOGIN_KINDS, PLATFORM_R2_OWNERSHIP_MARKER_KEY, PLATFORM_REMOTE_INSTALLATION_MANIFEST_KEY, PLATFORM_REMOTE_INSTALLATION_MANIFEST_KEY as REMOTE_INSTALLATION_MANIFEST_KEY, createApplicationRoutes, createPlatformDeploymentManifest, createPlatformInstallationManifest, createPlatformResourceNames, createPlatformResourceOwnershipMarker, createPlatformWorkerConfigs, isPlatformAccessCompanyIdentityProvider, isPlatformAccessEmailDomain, isPlatformAccessIdentityRule, normalizeApplicationDomain, normalizeInstallationSlug, normalizePlatformDashboardUrl, parsePlatformAccessProtection, parsePlatformAdmissionPolicy, parsePlatformAppAccessSetup, parsePlatformAuthenticationSetup, parsePlatformInstallationManifest, parsePlatformLoginConfiguration, parsePlatformResourceOwnershipMarker, platformDashboardUrl, recordInstallationResource, recordInstallationStep, setInstallationEndpoints, transitionInstallation } from "@void/platform";
|
|
17
17
|
import { createHash, createHmac, randomBytes, randomUUID } from "node:crypto";
|
|
18
18
|
import { tmpdir } from "node:os";
|
|
19
19
|
import { stripVTControlCharacters } from "node:util";
|
|
@@ -910,52 +910,41 @@ var PlatformAccessClient = class {
|
|
|
910
910
|
throw new Error("platform: Access inventory exceeded the supported pagination limit.");
|
|
911
911
|
}
|
|
912
912
|
};
|
|
913
|
-
/**
|
|
914
|
-
|
|
915
|
-
* platform/packages/api/src/zero-trust/cloudflare.ts; review both when adding
|
|
916
|
-
* a rule type.
|
|
917
|
-
*/
|
|
918
|
-
function identityRule(rule) {
|
|
919
|
-
if (rule.email_domain) {
|
|
920
|
-
const domain = object(rule.email_domain, "email domain").domain;
|
|
921
|
-
return typeof domain === "string" && /^(?:[a-z0-9-]+\.)+[a-z]{2,63}$/i.test(domain);
|
|
922
|
-
}
|
|
923
|
-
if (rule.email) {
|
|
924
|
-
const email = object(rule.email, "email rule").email;
|
|
925
|
-
return typeof email === "string" && /^[^\s*@]+@[^\s*@]+\.[^\s*@]+$/.test(email);
|
|
926
|
-
}
|
|
927
|
-
return [
|
|
928
|
-
"email",
|
|
929
|
-
"email_domain",
|
|
930
|
-
"email_list",
|
|
931
|
-
"gsuite",
|
|
932
|
-
"github-organization",
|
|
933
|
-
"saml",
|
|
934
|
-
"okta",
|
|
935
|
-
"group"
|
|
936
|
-
].some((key) => Object.hasOwn(rule, key) && !!rule[key] && typeof rule[key] === "object" && !Array.isArray(rule[key]) && Object.keys(rule[key]).length > 0);
|
|
937
|
-
}
|
|
913
|
+
/** The selected providers or policies cannot protect Void; other errors are not about the selection. */
|
|
914
|
+
var PlatformAccessPolicyRejectedError = class extends Error {};
|
|
938
915
|
/** Preserve corporate MFA/device requirements; reject broad policies rather than rewriting them. */
|
|
939
|
-
async function verifyCompanyAccessPolicies(client, policies) {
|
|
916
|
+
async function verifyCompanyAccessPolicies(client, policies, identityProviderIds, identityProviders) {
|
|
917
|
+
const companyProviders = new Set((identityProviders ?? await client.list("identity_providers")).filter(isPlatformAccessCompanyIdentityProvider).map((provider) => provider.id));
|
|
918
|
+
const selectedCompanyProviders = new Set(identityProviderIds.filter((id) => companyProviders.has(id)));
|
|
919
|
+
const identityRule = (entry, name, providers = selectedCompanyProviders) => {
|
|
920
|
+
const rule = object(entry, name);
|
|
921
|
+
const entra = rule.azureAD || rule.auth_context;
|
|
922
|
+
if (entra) {
|
|
923
|
+
const id = typeof entra === "object" ? entra.identity_provider_id : null;
|
|
924
|
+
return typeof id === "string" && providers.has(id);
|
|
925
|
+
}
|
|
926
|
+
return isPlatformAccessIdentityRule(rule, (id) => providers.has(id));
|
|
927
|
+
};
|
|
928
|
+
const restricted = (target, providers) => Array.isArray(target.include) && target.include.length > 0 && (Array.isArray(target.require) && target.require.some((entry) => identityRule(entry, "policy rule", providers)) || target.include.every((entry) => identityRule(entry, "policy rule", providers)));
|
|
940
929
|
const groups = /* @__PURE__ */ new Set();
|
|
941
930
|
const checkedGroups = /* @__PURE__ */ new Set();
|
|
942
931
|
const checkRules = async (value, depth = 0) => {
|
|
943
932
|
if (value === void 0) return;
|
|
944
|
-
if (!Array.isArray(value) || value.length > 100 || depth > 8) throw new
|
|
933
|
+
if (!Array.isArray(value) || value.length > 100 || depth > 8) throw new PlatformAccessPolicyRejectedError("platform: Unsupported Access policy rule structure.");
|
|
945
934
|
for (const entry of value) {
|
|
946
935
|
const rule = object(entry, "policy rule");
|
|
947
|
-
if (Object.hasOwn(rule, "everyone") || Object.hasOwn(rule, "any_valid_service_token")) throw new
|
|
936
|
+
if (Object.hasOwn(rule, "everyone") || Object.hasOwn(rule, "any_valid_service_token")) throw new PlatformAccessPolicyRejectedError("platform: Company Access policies must not allow Everyone or any valid service token.");
|
|
948
937
|
if (rule.group) {
|
|
949
938
|
const id = text(object(rule.group, "Access group").id, "Access group ID");
|
|
950
|
-
if (groups.has(id)) throw new
|
|
939
|
+
if (groups.has(id)) throw new PlatformAccessPolicyRejectedError("platform: Access groups contain a recursive membership rule.");
|
|
951
940
|
if (checkedGroups.has(id)) continue;
|
|
952
|
-
if (checkedGroups.size >= 100) throw new
|
|
941
|
+
if (checkedGroups.size >= 100) throw new PlatformAccessPolicyRejectedError("platform: Access group policy exceeds the inspection limit.");
|
|
953
942
|
groups.add(id);
|
|
954
943
|
const group = object(await client.get(`groups/${encodeURIComponent(id)}`), "Access group");
|
|
955
944
|
await checkRules(group.include, depth + 1);
|
|
956
945
|
await checkRules(group.require, depth + 1);
|
|
957
|
-
if (!Array.isArray(group.include) || !group.include.length) throw new
|
|
958
|
-
if (!(Array.isArray(group.require) && group.require.some((entry) => identityRule(
|
|
946
|
+
if (!Array.isArray(group.include) || !group.include.length) throw new PlatformAccessPolicyRejectedError("platform: Access groups must contain explicit identity rules.");
|
|
947
|
+
if (!(Array.isArray(group.require) && group.require.some((entry) => identityRule(entry, "group rule"))) && !group.include.every((entry) => identityRule(entry, "group rule"))) throw new PlatformAccessPolicyRejectedError("platform: Access groups must restrict company identities.");
|
|
959
948
|
groups.delete(id);
|
|
960
949
|
checkedGroups.add(id);
|
|
961
950
|
}
|
|
@@ -968,15 +957,15 @@ async function verifyCompanyAccessPolicies(client, policies) {
|
|
|
968
957
|
"deny",
|
|
969
958
|
"non_identity",
|
|
970
959
|
"bypass"
|
|
971
|
-
].includes(String(policy.decision))) throw new
|
|
972
|
-
if (policy.decision === "bypass") throw new
|
|
960
|
+
].includes(String(policy.decision))) throw new PlatformAccessPolicyRejectedError("platform: Unsupported Access policy decision.");
|
|
961
|
+
if (policy.decision === "bypass") throw new PlatformAccessPolicyRejectedError("platform: A bypass policy cannot protect a Void platform.");
|
|
973
962
|
if (policy.decision !== "allow") continue;
|
|
974
963
|
allows++;
|
|
975
964
|
await checkRules(policy.include);
|
|
976
965
|
await checkRules(policy.require);
|
|
977
|
-
if (!
|
|
966
|
+
if (!restricted(policy)) throw new PlatformAccessPolicyRejectedError(restricted(policy, companyProviders) ? "platform: An Access allow policy uses an identity provider that is not one of the selected identity providers." : "platform: Select Access allow policies restricted to company identities.");
|
|
978
967
|
}
|
|
979
|
-
if (!allows) throw new
|
|
968
|
+
if (!allows) throw new PlatformAccessPolicyRejectedError("platform: Select at least one company Access allow policy.");
|
|
980
969
|
}
|
|
981
970
|
function platformAccessPublicDestinations(application) {
|
|
982
971
|
const destinations = Array.isArray(application.destinations) ? application.destinations : [];
|
|
@@ -1001,6 +990,19 @@ function platformAccessCreateRejected(error) {
|
|
|
1001
990
|
429
|
|
1002
991
|
].includes(error.status);
|
|
1003
992
|
}
|
|
993
|
+
/** Read reusable policies by ID and check that together they admit only company identities. */
|
|
994
|
+
async function verifySelectedAccessPolicies(client, policyIds, identityProviderIds, identityProviders) {
|
|
995
|
+
const policies = await Promise.all(policyIds.map(async (id) => {
|
|
996
|
+
try {
|
|
997
|
+
return object(await client.get(`policies/${encodeURIComponent(id)}`), "Access policy");
|
|
998
|
+
} catch (error) {
|
|
999
|
+
if (error instanceof PlatformCloudflareApiError && error.status === 404) throw new PlatformAccessPolicyRejectedError(`platform: The selected Access policy ${encodeURIComponent(id)} is unavailable in this account.`, { cause: error });
|
|
1000
|
+
throw error;
|
|
1001
|
+
}
|
|
1002
|
+
}));
|
|
1003
|
+
await verifyCompanyAccessPolicies(client, policies, identityProviderIds, identityProviders);
|
|
1004
|
+
return policies;
|
|
1005
|
+
}
|
|
1004
1006
|
async function inspectPlatformAccess(client, input) {
|
|
1005
1007
|
const authDomain = text(object(await client.get("organizations"), "Access organization").auth_domain, "Access team domain");
|
|
1006
1008
|
const teamDomain = authDomain.startsWith("https://") ? authDomain : `https://${authDomain}`;
|
|
@@ -1011,15 +1013,14 @@ async function inspectPlatformAccess(client, input) {
|
|
|
1011
1013
|
audience: "validation"
|
|
1012
1014
|
}]
|
|
1013
1015
|
});
|
|
1014
|
-
const
|
|
1015
|
-
|
|
1016
|
-
if (input.identityProviderIds.
|
|
1017
|
-
|
|
1018
|
-
await verifyCompanyAccessPolicies(client, policies);
|
|
1016
|
+
const providers = await client.list("identity_providers");
|
|
1017
|
+
const identityProviders = providers.map(platformAccessIdentityProvider);
|
|
1018
|
+
if (!input.identityProviderIds.length || !input.policyIds.length || input.identityProviderIds.length > 16 || input.policyIds.length > 16 || new Set(input.identityProviderIds).size !== input.identityProviderIds.length || new Set(input.policyIds).size !== input.policyIds.length) throw new PlatformAccessPolicyRejectedError("platform: Select explicit identity providers and company policies for Access.");
|
|
1019
|
+
if (input.identityProviderIds.some((id) => !identityProviders.some((provider) => provider.id === id))) throw new PlatformAccessPolicyRejectedError("platform: A selected Access identity provider is unavailable in this account.");
|
|
1019
1020
|
return {
|
|
1020
1021
|
teamDomain,
|
|
1021
1022
|
identityProviders,
|
|
1022
|
-
policies
|
|
1023
|
+
policies: await verifySelectedAccessPolicies(client, input.policyIds, input.identityProviderIds, providers)
|
|
1023
1024
|
};
|
|
1024
1025
|
}
|
|
1025
1026
|
async function provisionPlatformAccess(client, input, previous, save) {
|
|
@@ -1259,7 +1260,7 @@ async function provisionPlatformAccess(client, input, previous, save) {
|
|
|
1259
1260
|
async function verifyPlatformAccessApplication(client, input, app, type, machineId) {
|
|
1260
1261
|
if (app.type !== type || !sameSet(ids(app.allowed_idps), input.identityProviderIds)) throw new Error("platform: Access application identity-provider configuration does not match the reviewed plan.");
|
|
1261
1262
|
const currentPolicies = await client.list(`apps/${encodeURIComponent(text(app.id, "application ID"))}/policies`);
|
|
1262
|
-
await verifyCompanyAccessPolicies(client, currentPolicies);
|
|
1263
|
+
await verifyCompanyAccessPolicies(client, currentPolicies, input.identityProviderIds);
|
|
1263
1264
|
if (!sameSet(currentPolicies.filter((policy) => policy.decision === "allow" || policy.decision === "deny").map((policy) => text(policy.id, "policy ID")), input.policyIds)) throw new Error("platform: Access application policies do not match the reviewed company policies.");
|
|
1264
1265
|
for (const policy of currentPolicies.filter((policy) => policy.decision === "non_identity")) {
|
|
1265
1266
|
const includes = Array.isArray(policy.include) ? policy.include : [];
|
|
@@ -1275,7 +1276,13 @@ function accessSetupGuide() {
|
|
|
1275
1276
|
url: "https://void.cloud/guide/platform/installation/setup#cloudflare-access"
|
|
1276
1277
|
});
|
|
1277
1278
|
}
|
|
1278
|
-
|
|
1279
|
+
/** Parse up to ten exact company email domains, or return undefined when any entry is invalid. */
|
|
1280
|
+
function companyEmailDomains(value) {
|
|
1281
|
+
const domains = [...new Set(value.split(/[\s,]+/).map((entry) => entry.trim().replace(/^@/, "").toLowerCase()).filter(Boolean))];
|
|
1282
|
+
return domains.length && domains.length <= 10 && domains.every(isPlatformAccessEmailDomain) ? domains : void 0;
|
|
1283
|
+
}
|
|
1284
|
+
async function selectCompanyAccessPolicies(client, initialPolicies, accountId, mode, identityProviderIds) {
|
|
1285
|
+
const emailDomain = "__email_domain__";
|
|
1279
1286
|
const create = "__create__";
|
|
1280
1287
|
const refresh = "__refresh__";
|
|
1281
1288
|
let policies = initialPolicies;
|
|
@@ -1287,14 +1294,22 @@ async function selectCompanyAccessPolicies(client, initialPolicies, accountId, m
|
|
|
1287
1294
|
}));
|
|
1288
1295
|
const available = new Set(options.map((option) => option.value));
|
|
1289
1296
|
selectedIds = selectedIds.filter((id) => available.has(id));
|
|
1290
|
-
const actions = [
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1297
|
+
const actions = [
|
|
1298
|
+
...mode === "create" ? [{
|
|
1299
|
+
value: emailDomain,
|
|
1300
|
+
label: "Allow my company’s email domain",
|
|
1301
|
+
hint: "Void creates this Allow policy for you"
|
|
1302
|
+
}] : [],
|
|
1303
|
+
{
|
|
1304
|
+
value: create,
|
|
1305
|
+
label: "Create in Cloudflare",
|
|
1306
|
+
hint: "Set up a policy in Cloudflare, then return here"
|
|
1307
|
+
},
|
|
1308
|
+
{
|
|
1309
|
+
value: refresh,
|
|
1310
|
+
label: "Refresh list"
|
|
1311
|
+
}
|
|
1312
|
+
];
|
|
1298
1313
|
let choices;
|
|
1299
1314
|
if (options.length === 0) {
|
|
1300
1315
|
log.info("No reusable Allow or Deny policies were found in this account. Create an Allow policy for your company, or refresh after saving one.");
|
|
@@ -1309,7 +1324,26 @@ async function selectCompanyAccessPolicies(client, initialPolicies, accountId, m
|
|
|
1309
1324
|
required: true
|
|
1310
1325
|
}));
|
|
1311
1326
|
selectedIds = choices.filter((id) => available.has(id));
|
|
1312
|
-
if (choices.includes(
|
|
1327
|
+
if (choices.includes(emailDomain)) {
|
|
1328
|
+
const domains = companyEmailDomains(platformPromptAnswer(await text$1({
|
|
1329
|
+
message: "Company email domains (comma-separated):",
|
|
1330
|
+
placeholder: "example.com",
|
|
1331
|
+
validate: (value) => companyEmailDomains(value ?? "") ? void 0 : "Enter up to 10 email domains, such as example.com."
|
|
1332
|
+
})));
|
|
1333
|
+
const name = `Emails ending in @${domains[0]}${domains.length > 1 ? ` and ${domains.length - 1} more` : ""}`;
|
|
1334
|
+
try {
|
|
1335
|
+
const created = await client.create("policies", {
|
|
1336
|
+
name,
|
|
1337
|
+
decision: "allow",
|
|
1338
|
+
include: domains.map((domain) => ({ email_domain: { domain } }))
|
|
1339
|
+
});
|
|
1340
|
+
if (typeof created?.id === "string" && created.id) selectedIds.push(created.id);
|
|
1341
|
+
log.info(`Created the Access policy “${name}” in Cloudflare. It stays there for reuse; you can add MFA or device rules to it later.`);
|
|
1342
|
+
} catch (error) {
|
|
1343
|
+
if (!(error instanceof PlatformCloudflareApiError)) throw error;
|
|
1344
|
+
log.warn(`${error.message} Choose “Create in Cloudflare” instead.`);
|
|
1345
|
+
}
|
|
1346
|
+
} else if (choices.includes(create)) {
|
|
1313
1347
|
note([
|
|
1314
1348
|
"1. Open Zero Trust → Access controls → Policies → Add a policy in this account.",
|
|
1315
1349
|
"2. Name the policy and choose Allow.",
|
|
@@ -1325,7 +1359,13 @@ async function selectCompanyAccessPolicies(client, initialPolicies, accountId, m
|
|
|
1325
1359
|
message: "Have you saved the new policy?",
|
|
1326
1360
|
initialValue: true
|
|
1327
1361
|
}))) continue;
|
|
1328
|
-
} else if (!choices.includes(refresh))
|
|
1362
|
+
} else if (!choices.includes(refresh)) try {
|
|
1363
|
+
await verifySelectedAccessPolicies(client, selectedIds, identityProviderIds);
|
|
1364
|
+
return selectedIds;
|
|
1365
|
+
} catch (error) {
|
|
1366
|
+
if (!(error instanceof PlatformAccessPolicyRejectedError) && !(error instanceof PlatformCloudflareApiError && error.status === 404)) throw error;
|
|
1367
|
+
log.warn(`${error.message.replace(/^platform: /, "")} Choose other policies, or fix the policy in Cloudflare and refresh the list.`);
|
|
1368
|
+
}
|
|
1329
1369
|
policies = await client.list("policies");
|
|
1330
1370
|
}
|
|
1331
1371
|
}
|
|
@@ -1518,7 +1558,7 @@ async function preparePlatformAccessSetup(input) {
|
|
|
1518
1558
|
input.save({ accessSetupSpecJson: JSON.stringify(spec) });
|
|
1519
1559
|
}
|
|
1520
1560
|
if (!spec.policyIds && process.stdin.isTTY) log.info("Choose which existing access rules apply to this platform, including MFA and device requirements.");
|
|
1521
|
-
const policyIds = spec.policyIds ?? (process.stdin.isTTY ? await selectCompanyAccessPolicies(client, policies, accountId, spec.mode) : []);
|
|
1561
|
+
const policyIds = spec.policyIds ?? (process.stdin.isTTY ? await selectCompanyAccessPolicies(client, policies, accountId, spec.mode, identityProviderIds) : []);
|
|
1522
1562
|
if (!identityProviderIds.length || !policyIds.length) throw new Error("platform: Access setup requires explicit identityProviderIds and policyIds.");
|
|
1523
1563
|
spec = {
|
|
1524
1564
|
...spec,
|
|
@@ -1578,6 +1618,7 @@ async function preparePlatformAccessSetup(input) {
|
|
|
1578
1618
|
loginClientSecret = platformPromptAnswer(await password({ message: "Existing Access OIDC client secret:" }));
|
|
1579
1619
|
input.save({ accessExistingLoginSecret: loginClientSecret });
|
|
1580
1620
|
}
|
|
1621
|
+
let provisioning = previous;
|
|
1581
1622
|
const result = await provisionPlatformAccess(client, {
|
|
1582
1623
|
accountId,
|
|
1583
1624
|
installationId: input.plan.installationId,
|
|
@@ -1593,7 +1634,18 @@ async function preparePlatformAccessSetup(input) {
|
|
|
1593
1634
|
} } : {},
|
|
1594
1635
|
...input.protection ? { protection: { applicationId: spec.protectionApplicationId } } : {},
|
|
1595
1636
|
serviceToken
|
|
1596
|
-
}, previous, (state) =>
|
|
1637
|
+
}, previous, (state) => {
|
|
1638
|
+
provisioning = state;
|
|
1639
|
+
input.save({ accessProvisioningJson: JSON.stringify(state) });
|
|
1640
|
+
}).catch((error) => {
|
|
1641
|
+
if (!(error instanceof PlatformAccessPolicyRejectedError)) throw error;
|
|
1642
|
+
if (!input.spec && process.stdin.isTTY && !Object.keys(provisioning.resources).length) {
|
|
1643
|
+
const { identityProviderIds: _providers, policyIds: _policies, ...unpicked } = spec;
|
|
1644
|
+
input.save({ accessSetupSpecJson: JSON.stringify(unpicked) });
|
|
1645
|
+
throw new PlatformAccessPolicyRejectedError(`${error.message} Choose other policies when you run the command again, or fix the policy in Cloudflare.`, { cause: error });
|
|
1646
|
+
}
|
|
1647
|
+
throw new PlatformAccessPolicyRejectedError(`${error.message} Fix the selected policy in Cloudflare, then resume.`, { cause: error });
|
|
1648
|
+
});
|
|
1597
1649
|
if (result.credentials) {
|
|
1598
1650
|
const accessCredentialsJson = JSON.stringify(result.credentials);
|
|
1599
1651
|
input.save({ accessCredentialsJson });
|
|
@@ -1894,7 +1946,7 @@ async function prepareInstallationAppAccess(input) {
|
|
|
1894
1946
|
}
|
|
1895
1947
|
if (!policyIds.length) {
|
|
1896
1948
|
if (!process.stdin.isTTY) throw new Error("platform: Set appAccess.policyIds in --auth-config for the hosting account.");
|
|
1897
|
-
policyIds = await selectCompanyAccessPolicies(access, await access.list("policies"), input.plan.account.id, "create");
|
|
1949
|
+
policyIds = await selectCompanyAccessPolicies(access, await access.list("policies"), input.plan.account.id, "create", identityProviderIds);
|
|
1898
1950
|
input.save({ appAccessPolicyIds: JSON.stringify(policyIds) });
|
|
1899
1951
|
}
|
|
1900
1952
|
} else log.info("This account requires Access. Void will create public rules scoped to app hostnames.");
|
|
@@ -1961,6 +2013,37 @@ async function initializeInstalledAppAccess(input, fetcher = fetch) {
|
|
|
1961
2013
|
}
|
|
1962
2014
|
throw new Error("platform: App access setup is safely checkpointed. Resume installation to finish before app traffic opens.");
|
|
1963
2015
|
}
|
|
2016
|
+
/**
|
|
2017
|
+
* Turn off the project Zero Trust the installer enabled, for an uninstall
|
|
2018
|
+
* before any platform user exists. Signed with the app access setup authority
|
|
2019
|
+
* that turned it on.
|
|
2020
|
+
*/
|
|
2021
|
+
async function turnOffInstalledProjectZeroTrust(input, fetcher = fetch) {
|
|
2022
|
+
const body = JSON.stringify({ version: 1 });
|
|
2023
|
+
const signature = createHmac("sha256", input.setupToken).update(`${PLATFORM_APP_ACCESS_TURN_OFF_PROOF_PREFIX}\n${input.installationId}\n${body}`).digest("base64url");
|
|
2024
|
+
const response = await fetcher(new URL("/internal/platform/app-access/turn-off", input.apiUrl), {
|
|
2025
|
+
method: "POST",
|
|
2026
|
+
headers: {
|
|
2027
|
+
"Content-Type": "application/json",
|
|
2028
|
+
"X-Void-Platform-Probe": input.probeToken,
|
|
2029
|
+
"X-Void-App-Access-Setup": signature,
|
|
2030
|
+
...cfAccessHeaders(process.env, input.apiUrl)
|
|
2031
|
+
},
|
|
2032
|
+
body,
|
|
2033
|
+
redirect: "manual",
|
|
2034
|
+
signal: AbortSignal.timeout(6e4)
|
|
2035
|
+
});
|
|
2036
|
+
let result = {};
|
|
2037
|
+
try {
|
|
2038
|
+
result = await response.json();
|
|
2039
|
+
} catch {}
|
|
2040
|
+
if (response.status === 404 || response.status === 503 && result.error === "Platform disabled" || response.status === 403 && result.error === "access_human_identity_required") throw new Error(`platform: This platform runtime cannot turn off project Zero Trust before an administrator exists. Run \`${input.updateCommand}\`, then retry the uninstall.`);
|
|
2041
|
+
if (result.code === "users_exist") throw new Error("platform: A platform user exists, so an administrator must turn off project Zero Trust. Run `void platform zero-trust disable`, then retry the uninstall.");
|
|
2042
|
+
if (result.code === "app_access_credentials") throw new Error("platform: Cloudflare rejected the saved app access token, so Void cannot turn off project Zero Trust.");
|
|
2043
|
+
if (!response.ok || result.installationId !== input.installationId) throw new Error(`platform: ${result.error || `Turning off project Zero Trust failed (HTTP ${response.status}).`}`);
|
|
2044
|
+
if (!result.turnedOff) throw new Error("platform: Project Zero Trust is still turning off. Rerun the uninstall.");
|
|
2045
|
+
if (result.usersExist) throw new Error("platform: A platform user signed in while project Zero Trust was turning off, so the uninstall stopped. Project Zero Trust is now off and the platform still runs. To keep the platform, an administrator can turn project Zero Trust back on in Admin → Zero Trust or with `void platform zero-trust configure`. To remove it, run the uninstall again.");
|
|
2046
|
+
}
|
|
1964
2047
|
//#endregion
|
|
1965
2048
|
//#region src/cli/platform-auth-bootstrap.ts
|
|
1966
2049
|
/** Checkpoint a replacement before submission so an interrupted response is safely replayable. */
|
|
@@ -2670,7 +2753,7 @@ function addNamedPlan(context, target) {
|
|
|
2670
2753
|
const remotelyOwned = remoteOwnership?.resources.some((resource) => resource.key === key && resource.kind === kind && resource.id === remote.id && resource.name === name && resource.ownership === "created");
|
|
2671
2754
|
const markedProvision = existing !== null && hasUnrecordedProvisionIntent(existing, key, kind, name) && intentProofs.has(key);
|
|
2672
2755
|
const ambiguousProvision = existing !== null && options?.authoritativeProvisionIntents === true && isLegacyUniqueProvisionAttempt(existing, key, kind, name) && hasAmbiguousProvisionIntent(existing, key, kind, remote.id, name);
|
|
2673
|
-
if ((!recorded || recorded.id !== remote.id || recorded.kind !== kind) && !remotelyOwned && !markedProvision && !ambiguousProvision) throw new Error(`platform: ${kind} ${JSON.stringify(name)} already exists but is not owned by this installation. Resume the owning installation, or choose another installation name. Nothing was overwritten.`);
|
|
2756
|
+
if ((!recorded || recorded.id !== remote.id || recorded.kind !== kind) && !remotelyOwned && !markedProvision && !ambiguousProvision) throw new Error(existing !== null && hasUnrecordedProvisionIntent(existing, key, kind, name) ? `platform: ${kind} ${JSON.stringify(name)} already exists, and Void cannot tell whether this installation created it. Nothing was overwritten. If an earlier failed attempt of this installation created it, delete it in the Cloudflare dashboard, then rerun the command. Otherwise, choose another installation name.` : `platform: ${kind} ${JSON.stringify(name)} already exists but is not owned by this installation. Resume the owning installation, or choose another installation name. Nothing was overwritten.`);
|
|
2674
2757
|
if (ambiguousProvision && !markedProvision && !remotelyOwned) {
|
|
2675
2758
|
actions.push({
|
|
2676
2759
|
action: "create",
|
|
@@ -3268,6 +3351,7 @@ function createPlatformInstallationSecrets(input) {
|
|
|
3268
3351
|
return value;
|
|
3269
3352
|
};
|
|
3270
3353
|
const existingInstallation = input.existingInstallation === true;
|
|
3354
|
+
const generateIdentityKeys = input.generateIdentityKeys === true && !existingInstallation;
|
|
3271
3355
|
const restoredIdentity = (explicit, name) => existingInstallation ? checkpoint[name] || explicit || "" : explicit || checkpoint[name] || "";
|
|
3272
3356
|
const configured = (explicit, name) => explicit || checkpoint[name] || "";
|
|
3273
3357
|
const authenticationSetupJson = configured(input.authenticationSetupJson ?? "", "authenticationSetupJson");
|
|
@@ -3279,7 +3363,7 @@ function createPlatformInstallationSecrets(input) {
|
|
|
3279
3363
|
const setup = parsePlatformAuthenticationSetup(JSON.parse(authenticationSetupJson));
|
|
3280
3364
|
if (!existingInstallation && createHash("sha256").update(authBootstrapCode).digest("hex") !== setup.bootstrapCredentialHash) throw new Error("platform: Valid installation setup authority and a matching administrator setup code are required.");
|
|
3281
3365
|
}
|
|
3282
|
-
const jwtSecret = restoredIdentity(input.jwtSecret || "", "jwtSecret");
|
|
3366
|
+
const jwtSecret = restoredIdentity(input.jwtSecret || "", "jwtSecret") || (generateIdentityKeys ? randomBytes(32).toString("base64") : "");
|
|
3283
3367
|
const resolvedJwtSecret = input.existingInstallation ? jwtSecret : required(jwtSecret, "VOID_PLATFORM_JWT_SECRET");
|
|
3284
3368
|
if (!existingInstallation && Buffer.byteLength(resolvedJwtSecret, "utf8") < 32) throw new Error("platform: VOID_PLATFORM_JWT_SECRET must contain at least 32 bytes of secret material unique to this installation.");
|
|
3285
3369
|
const emailEnabled = input.emailEnabled === true;
|
|
@@ -3295,6 +3379,7 @@ function createPlatformInstallationSecrets(input) {
|
|
|
3295
3379
|
const projectSecretKeyring = resolveProjectSecretKeyring({
|
|
3296
3380
|
checkpoint,
|
|
3297
3381
|
existingInstallation,
|
|
3382
|
+
generateIdentityKeys,
|
|
3298
3383
|
projectSecretActiveKeyVersion: input.projectSecretActiveKeyVersion || "",
|
|
3299
3384
|
projectSecretKey: input.projectSecretKey || "",
|
|
3300
3385
|
projectSecretKeysJson: input.projectSecretKeysJson || "",
|
|
@@ -3356,7 +3441,7 @@ function resolveProjectSecretKeyring(input) {
|
|
|
3356
3441
|
if (!explicitFullKeyring && (checkpointKeysJson !== "" || checkpointActiveVersion !== "") && (!checkpointKeysJson || !checkpointActiveVersion)) throw new Error("platform: Encrypted recovery material must contain both the project-secret keyring and its active version.");
|
|
3357
3442
|
if (explicitFullKeyring) return normalizeProjectSecretKeyring(input.projectSecretKeysJson, input.projectSecretActiveKeyVersion, "VOID_PLATFORM_PROJECT_SECRET_KEYS_JSON", "VOID_PLATFORM_PROJECT_SECRET_ACTIVE_KEY_VERSION");
|
|
3358
3443
|
if (checkpointKeysJson) return normalizeProjectSecretKeyring(checkpointKeysJson, checkpointActiveVersion, "encrypted recovery project-secret keyring", "encrypted recovery project-secret active version");
|
|
3359
|
-
const v1Key = input.existingInstallation ? input.checkpoint.projectSecretKey || input.projectSecretKey || "" : input.required(input.projectSecretKey || input.checkpoint.projectSecretKey || "", "VOID_PLATFORM_PROJECT_SECRET_KEY");
|
|
3444
|
+
const v1Key = input.existingInstallation ? input.checkpoint.projectSecretKey || input.projectSecretKey || "" : input.required(input.projectSecretKey || input.checkpoint.projectSecretKey || (input.generateIdentityKeys ? randomBytes(32).toString("base64") : ""), "VOID_PLATFORM_PROJECT_SECRET_KEY");
|
|
3360
3445
|
if (!v1Key) return {
|
|
3361
3446
|
activeVersion: "",
|
|
3362
3447
|
keysJson: "",
|
|
@@ -3559,13 +3644,15 @@ function createPlatformHealthProbeToken(ownershipNonce) {
|
|
|
3559
3644
|
}
|
|
3560
3645
|
//#endregion
|
|
3561
3646
|
//#region src/cli/platform-installer-errors.ts
|
|
3647
|
+
const ANALYTICS_ENGINE_NOT_ENABLED = 10089;
|
|
3562
3648
|
function assertCleanPlatformWrangler(result, operation) {
|
|
3563
3649
|
if (result.status !== 0 || result.signal || result.error) {
|
|
3564
3650
|
const diagnostic = cloudflareCommandDiagnostic({
|
|
3565
3651
|
...result,
|
|
3566
3652
|
stderr: result.stderr.trim() || result.errors?.map((error) => error.message).join("\n") || ""
|
|
3567
3653
|
});
|
|
3568
|
-
|
|
3654
|
+
const analyticsEngineDisabled = result.errorCode === ANALYTICS_ENGINE_NOT_ENABLED || result.errors?.some((error) => error.code === ANALYTICS_ENGINE_NOT_ENABLED) || /\b10089\b/.test(diagnostic ?? "");
|
|
3655
|
+
throw new Error(`platform: Wrangler failed to ${operation} (${diagnostic || `exit ${result.status}`}).` + (analyticsEngineDisabled ? " If the Analytics Engine page offers no way to enable it, follow https://void.cloud/guide/platform/installation/prerequisites#if-cloudflare-rejects-analytics-engine, then retry." : ""));
|
|
3569
3656
|
}
|
|
3570
3657
|
}
|
|
3571
3658
|
function assertPlatformTriggerStabilization(result, workerName, targetVersionId, operation) {
|
|
@@ -5200,7 +5287,7 @@ async function verifyInstalledPlatformAccessProtection(input) {
|
|
|
5200
5287
|
}
|
|
5201
5288
|
const existingWorkers = input.plan.actions.filter((action) => action.kind === "worker" && action.action === "update").map((action) => action.name);
|
|
5202
5289
|
if (existingWorkers.length) {
|
|
5203
|
-
const { verifyProtectionWorkerOrigins } = await import("./platform-auth-protection-
|
|
5290
|
+
const { verifyProtectionWorkerOrigins } = await import("./platform-auth-protection-BXwZiJSF.mjs");
|
|
5204
5291
|
await verifyProtectionWorkerOrigins({
|
|
5205
5292
|
request: input.client.request.bind(input.client),
|
|
5206
5293
|
listWorkerDomains: input.client.listWorkerDomains.bind(input.client),
|
|
@@ -5544,9 +5631,10 @@ function projectZeroTrustUninstallState(setting, projectRows) {
|
|
|
5544
5631
|
/** Read the project Zero Trust state from the platform database. */
|
|
5545
5632
|
async function readPlatformProjectZeroTrustStateConnection(client) {
|
|
5546
5633
|
const present = (await client.query(`SELECT to_regclass('public.platform_settings') IS NOT NULL AS "settings",
|
|
5547
|
-
to_regclass('public.project_zero_trust') IS NOT NULL AS "projects"
|
|
5634
|
+
to_regclass('public.project_zero_trust') IS NOT NULL AS "projects",
|
|
5635
|
+
to_regclass('public.users') IS NOT NULL AS "users"`)).rows[0];
|
|
5548
5636
|
if (!present) throw new Error("the database did not report its Zero Trust tables");
|
|
5549
|
-
|
|
5637
|
+
const state = projectZeroTrustUninstallState(present.settings ? (await client.query("SELECT \"value\" FROM \"platform_settings\" WHERE \"key\" = $1", [ZERO_TRUST_SETTING])).rows[0]?.value : void 0, present.projects ? (await client.query(`SELECT z."project_id", p."slug" AS "project_slug", p."status" AS "project_status",
|
|
5550
5638
|
z."bypass_application_id", z."bypass_application_name",
|
|
5551
5639
|
z."protected_application_id", z."protected_application_name"
|
|
5552
5640
|
FROM "project_zero_trust" z
|
|
@@ -5556,6 +5644,12 @@ async function readPlatformProjectZeroTrustStateConnection(client) {
|
|
|
5556
5644
|
OR z."protected_application_id" IS NOT NULL
|
|
5557
5645
|
OR z."protected_application_name" IS NOT NULL
|
|
5558
5646
|
ORDER BY z."project_id"`)).rows : []);
|
|
5647
|
+
if (!state.active) return state;
|
|
5648
|
+
const usersExist = present.users ? (await client.query("SELECT EXISTS(SELECT 1 FROM \"users\") AS \"present\"")).rows[0]?.present === true : false;
|
|
5649
|
+
return {
|
|
5650
|
+
...state,
|
|
5651
|
+
usersExist
|
|
5652
|
+
};
|
|
5559
5653
|
}
|
|
5560
5654
|
/**
|
|
5561
5655
|
* Take the Zero Trust configuration lock exclusively on this session. Every
|
|
@@ -5594,7 +5688,7 @@ async function liftPlatformProjectZeroTrustFenceConnection(client) {
|
|
|
5594
5688
|
if (!await hasSettingsTable(client)) return;
|
|
5595
5689
|
await client.query("DELETE FROM \"platform_settings\" WHERE \"key\" = $1", [UNINSTALL_SETTING]);
|
|
5596
5690
|
}
|
|
5597
|
-
function
|
|
5691
|
+
function formatZeroTrustApplications(applications) {
|
|
5598
5692
|
const lines = applications.slice(0, LISTED_APPLICATIONS).map((application) => ` ${application.role}: ${application.name ?? "(name not recorded)"}${application.id ? ` (${application.id})` : ""}`);
|
|
5599
5693
|
if (applications.length > LISTED_APPLICATIONS) lines.push(` …and ${applications.length - LISTED_APPLICATIONS} more`);
|
|
5600
5694
|
return lines.join("\n");
|
|
@@ -5607,21 +5701,24 @@ function unfinishedDeletionLines(state) {
|
|
|
5607
5701
|
/**
|
|
5608
5702
|
* Refuse to uninstall while project Zero Trust still owns Access applications,
|
|
5609
5703
|
* or while its state cannot be read. Runs before uninstall changes anything.
|
|
5704
|
+
* With `canTurnOff`, enabled Zero Trust on a platform no user has signed in to
|
|
5705
|
+
* is not refused: the applications uninstall must turn off are returned.
|
|
5610
5706
|
*/
|
|
5611
|
-
async function assertProjectZeroTrustAllowsUninstall(read) {
|
|
5707
|
+
async function assertProjectZeroTrustAllowsUninstall(read, options = {}) {
|
|
5612
5708
|
const state = await readPlatformDatabase(read, (reason) => [
|
|
5613
5709
|
`platform: Refusing to uninstall because the project Zero Trust state could not be read from the platform database: ${reason}`,
|
|
5614
5710
|
"Uninstall turns off the platform API, which Void needs to remove the Cloudflare Access applications that project Zero Trust created.",
|
|
5615
5711
|
"Make the installation database reachable (export VOID_PLATFORM_DATABASE_URL if this machine has not recorded it), then retry. If project Zero Trust was ever enabled, run `void platform zero-trust disable` first."
|
|
5616
5712
|
].join("\n"));
|
|
5713
|
+
if (state.active && state.usersExist === false && options.canTurnOff) return { turnOff: state.applications };
|
|
5617
5714
|
if (state.active) {
|
|
5618
5715
|
const covered = state.destinations.length ? ` on ${state.destinations.join(" and ")}` : " on every hostname under the application domain";
|
|
5619
5716
|
throw new Error([
|
|
5620
5717
|
"platform: Refusing to uninstall while project Zero Trust is enabled or still changing.",
|
|
5621
5718
|
`Uninstall turns off the platform API, so Void could no longer remove its Cloudflare Access applications, and Access would keep asking for sign-in${covered}, including hostnames Void does not serve.`,
|
|
5622
|
-
"Run `void platform zero-trust disable`, rerun it until `void platform zero-trust status` reports Zero Trust disabled with no operation running, then retry the uninstall.",
|
|
5719
|
+
state.usersExist === false ? "No platform user exists yet. Run this command on the computer that installed the platform; it keeps the authority to turn off project Zero Trust." : "Run `void platform zero-trust disable`, rerun it until `void platform zero-trust status` reports Zero Trust disabled with no operation running, then retry the uninstall.",
|
|
5623
5720
|
"If the platform API does not answer, bring it back with `void platform enable` or `void platform repair` first.",
|
|
5624
|
-
...state.applications.length ? ["Void-managed Access applications:",
|
|
5721
|
+
...state.applications.length ? ["Void-managed Access applications:", formatZeroTrustApplications(state.applications)] : [],
|
|
5625
5722
|
...unfinishedDeletionLines(state),
|
|
5626
5723
|
DASHBOARD_HINT
|
|
5627
5724
|
].join("\n"));
|
|
@@ -5630,7 +5727,7 @@ async function assertProjectZeroTrustAllowsUninstall(read) {
|
|
|
5630
5727
|
"platform: Refusing to uninstall while Void-managed Access applications from project Zero Trust remain.",
|
|
5631
5728
|
"Zero Trust is disabled, but it could not remove the applications of some projects. Retry the uninstall once they are gone.",
|
|
5632
5729
|
"Void-managed Access applications:",
|
|
5633
|
-
|
|
5730
|
+
formatZeroTrustApplications(state.applications),
|
|
5634
5731
|
...state.applications.some((application) => !application.deleting) ? [`Hourly maintenance retries ${state.unfinishedDeletions.length ? "the other applications" : "them"} while the platform is enabled, and clears Void's records of any you delete in the dashboard.`] : [],
|
|
5635
5732
|
...unfinishedDeletionLines(state),
|
|
5636
5733
|
DASHBOARD_HINT
|
|
@@ -5797,6 +5894,18 @@ function readPlatformProjectZeroTrustState(databaseUrl, installationId, ownershi
|
|
|
5797
5894
|
function readPlatformProjectSlugs(databaseUrl, installationId, ownershipNonce) {
|
|
5798
5895
|
return readClaimedPlatformDatabase(databaseUrl, installationId, ownershipNonce, readPlatformProjectSlugsConnection);
|
|
5799
5896
|
}
|
|
5897
|
+
/** Read the first administrator setup row and whether any user exists. */
|
|
5898
|
+
async function readPlatformAdministratorSetupConnection(client) {
|
|
5899
|
+
const present = (await client.query(`SELECT to_regclass('public.platform_auth_bootstrap') IS NOT NULL AS "setup",
|
|
5900
|
+
to_regclass('public.users') IS NOT NULL AS "users"`)).rows[0];
|
|
5901
|
+
const setup = present?.setup ? (await client.query("SELECT \"request_hash\", \"consumed_at\" FROM \"platform_auth_bootstrap\" WHERE \"singleton\" = true")).rows[0] : void 0;
|
|
5902
|
+
const usersExist = present?.users ? (await client.query("SELECT EXISTS(SELECT 1 FROM \"users\") AS \"present\"")).rows[0]?.present === true : false;
|
|
5903
|
+
return {
|
|
5904
|
+
pending: !!setup && setup.consumed_at === null,
|
|
5905
|
+
usersExist,
|
|
5906
|
+
requestHash: setup?.request_hash ?? null
|
|
5907
|
+
};
|
|
5908
|
+
}
|
|
5800
5909
|
/** Slugs of every project row, including projects whose deletion stopped partway. */
|
|
5801
5910
|
async function readPlatformProjectSlugsConnection(client) {
|
|
5802
5911
|
if ((await client.query(`SELECT to_regclass('public.projects') IS NOT NULL AS "present"`)).rows[0]?.present !== true) return [];
|
|
@@ -5927,6 +6036,7 @@ async function acquirePlatformDatabaseLifecycleLockConnection(client, installati
|
|
|
5927
6036
|
protection: parsePlatformAccessProtection(settings.protection)
|
|
5928
6037
|
} : null;
|
|
5929
6038
|
};
|
|
6039
|
+
release.readAdministratorSetup = () => readPlatformAdministratorSetupConnection(client);
|
|
5930
6040
|
release.readProjectZeroTrust = () => readPlatformProjectZeroTrustStateConnection(client);
|
|
5931
6041
|
release.readProjects = () => readPlatformProjectSlugsConnection(client);
|
|
5932
6042
|
release.lockProjectZeroTrust = () => lockPlatformProjectZeroTrustConnection(client);
|
|
@@ -6129,7 +6239,7 @@ async function claimPlatformDatabaseConnection(client, installationId, ownership
|
|
|
6129
6239
|
await client.query(`INSERT INTO "__void_platform_installation"
|
|
6130
6240
|
("singleton", "installation_id", "ownership_nonce")
|
|
6131
6241
|
VALUES (true, $1, $2)`, [installationId, ownershipNonce]);
|
|
6132
|
-
} else if (claim.installation_id !== installationId || claim.ownership_nonce !== ownershipNonce) throw new Error(`platform: This PostgreSQL database is already claimed by Void platform installation ${claim.installation_id}.
|
|
6242
|
+
} else if (claim.installation_id !== installationId || claim.ownership_nonce !== ownershipNonce) throw new Error(`platform: This PostgreSQL database is already claimed by Void platform installation ${claim.installation_id}. Uninstall keeps the database and its data, so use a dedicated empty database for each installation.`);
|
|
6133
6243
|
await client.query("COMMIT");
|
|
6134
6244
|
} catch (error) {
|
|
6135
6245
|
await client.query("ROLLBACK").catch(() => ({ rows: [] }));
|
|
@@ -6723,7 +6833,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
|
|
|
6723
6833
|
}
|
|
6724
6834
|
}, dependencies.initializeAuthentication ?? initializeInstalledPlatformAuthentication)).setupPending) {
|
|
6725
6835
|
setupPending = true;
|
|
6726
|
-
dependencies.onProgress?.(
|
|
6836
|
+
dependencies.onProgress?.("Administrator setup prepared; its link and code are shown when this finishes");
|
|
6727
6837
|
}
|
|
6728
6838
|
state.manifest = recordInstallationStep(state.manifest, {
|
|
6729
6839
|
key: authenticationStep,
|
|
@@ -6868,6 +6978,19 @@ async function installPlatform(plan, secrets, existing, dependencies) {
|
|
|
6868
6978
|
function defaultConnectionId(kind) {
|
|
6869
6979
|
return kind === "oidc" ? "company" : kind === "cloudflare-access" ? "access" : kind;
|
|
6870
6980
|
}
|
|
6981
|
+
async function prompt(message, initialValue = "", masked = false) {
|
|
6982
|
+
if (!process.stdin.isTTY) throw new Error(`platform: ${message} Supply it through --auth-config and environment references.`);
|
|
6983
|
+
return masked ? platformPromptAnswer(await password({ message })) : platformPromptAnswer(await text$1({
|
|
6984
|
+
message,
|
|
6985
|
+
initialValue
|
|
6986
|
+
}));
|
|
6987
|
+
}
|
|
6988
|
+
function clientSecretFromEnvironment(name) {
|
|
6989
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) throw new Error("platform: Invalid clientSecretEnv reference.");
|
|
6990
|
+
const secret = process.env[name];
|
|
6991
|
+
if (!secret) throw new Error(`platform: ${name} must contain the login client secret.`);
|
|
6992
|
+
return secret;
|
|
6993
|
+
}
|
|
6871
6994
|
function platformManagementAccessForAuthentication(selection, accountId, checkpoint) {
|
|
6872
6995
|
if (!selection.authentication.accessProtection && (!selection.authentication.providers.includes("cloudflare-access") || selection.file && !selection.file.cloudflareAccess)) return;
|
|
6873
6996
|
const spec = selection.file?.cloudflareAccess ?? (checkpoint?.accessSetupSpecJson ? parsePlatformAccessSetupSpec(JSON.parse(checkpoint.accessSetupSpecJson)) : void 0);
|
|
@@ -6944,13 +7067,6 @@ async function collectInstallationAuthentication(selection, plan, checkpoint, sa
|
|
|
6944
7067
|
authBootstrapCode: checkpoint.authBootstrapCode ?? ""
|
|
6945
7068
|
};
|
|
6946
7069
|
}
|
|
6947
|
-
const prompt = async (message, initialValue = "", masked = false) => {
|
|
6948
|
-
if (!process.stdin.isTTY) throw new Error(`platform: ${message} Supply it through --auth-config and environment references.`);
|
|
6949
|
-
return masked ? platformPromptAnswer(await password({ message })) : platformPromptAnswer(await text$1({
|
|
6950
|
-
message,
|
|
6951
|
-
initialValue
|
|
6952
|
-
}));
|
|
6953
|
-
};
|
|
6954
7070
|
const entries = selection.file?.connections ?? selection.authentication.providers.map((kind) => ({
|
|
6955
7071
|
configuration: {
|
|
6956
7072
|
kind,
|
|
@@ -7000,11 +7116,8 @@ async function collectInstallationAuthentication(selection, plan, checkpoint, sa
|
|
|
7000
7116
|
if (kind === "google" && config.allowedDomains === void 0 && !selection.file) config.allowedDomains = (await prompt("Allowed Google Workspace domains (comma-separated, or blank):")).split(",").map((value) => value.trim()).filter(Boolean);
|
|
7001
7117
|
const parsed = parsePlatformLoginConfiguration(config);
|
|
7002
7118
|
let secret;
|
|
7003
|
-
if (entry.clientSecretEnv)
|
|
7004
|
-
|
|
7005
|
-
secret = process.env[entry.clientSecretEnv];
|
|
7006
|
-
if (!secret) throw new Error(`platform: ${entry.clientSecretEnv} must contain the login client secret.`);
|
|
7007
|
-
} else if (stored?.clientSecret && stored.configuration?.clientId === parsed.clientId && stored.configuration?.issuer === parsed.issuer) secret = stored.clientSecret;
|
|
7119
|
+
if (entry.clientSecretEnv) secret = clientSecretFromEnvironment(entry.clientSecretEnv);
|
|
7120
|
+
else if (stored?.clientSecret && stored.configuration?.clientId === parsed.clientId && stored.configuration?.issuer === parsed.issuer) secret = stored.clientSecret;
|
|
7008
7121
|
else secret = await prompt(`${config.label}: client secret:`, "", true);
|
|
7009
7122
|
const connection = {
|
|
7010
7123
|
configuration: parsed,
|
|
@@ -7109,6 +7222,87 @@ async function collectInstallationAuthentication(selection, plan, checkpoint, sa
|
|
|
7109
7222
|
save(result);
|
|
7110
7223
|
return result;
|
|
7111
7224
|
}
|
|
7225
|
+
/** The hash the platform records for accepted login settings; a reset request does not change it. */
|
|
7226
|
+
function authenticationSetupRequestHash(setupJson) {
|
|
7227
|
+
const { resetPending: _, ...configuration } = parsePlatformAuthenticationSetup(JSON.parse(setupJson));
|
|
7228
|
+
return createHash("sha256").update(JSON.stringify(configuration)).digest("hex");
|
|
7229
|
+
}
|
|
7230
|
+
/**
|
|
7231
|
+
* Read a repair login file. It may change login settings, not methods or Access
|
|
7232
|
+
* protection: it lists the same connection IDs and kinds as the saved setup.
|
|
7233
|
+
*/
|
|
7234
|
+
async function readLoginReplacementFile(file, authentication, setupJson) {
|
|
7235
|
+
const selection = await selectInstallationAuthentication(file);
|
|
7236
|
+
const installed = parsePlatformAuthenticationSetup(JSON.parse(setupJson)).connections;
|
|
7237
|
+
const connections = (entries) => entries.map(({ configuration: { id, kind } }) => JSON.stringify([id || defaultConnectionId(kind), kind])).sort().join("\n");
|
|
7238
|
+
if (connections(selection.file.connections) !== connections(installed) || !!selection.authentication.accessProtection !== !!authentication.accessProtection) throw new Error(`platform: Repair changes login settings, not which login methods or Access protection the platform uses. List each installed connection with its ID: ${installed.map(({ configuration }) => configuration.id).join(", ")}.`);
|
|
7239
|
+
return selection.file;
|
|
7240
|
+
}
|
|
7241
|
+
/**
|
|
7242
|
+
* New login settings for a first administrator setup that is still pending,
|
|
7243
|
+
* from a file or from prompts prefilled with the saved settings. A Cloudflare
|
|
7244
|
+
* Access login and Access protection stay as installed. The result resets
|
|
7245
|
+
* pending setup with a new setup code; the setup authority does not change.
|
|
7246
|
+
*/
|
|
7247
|
+
async function replacePendingInstallationAuthentication(input) {
|
|
7248
|
+
const previous = parsePlatformAuthenticationSetup(JSON.parse(input.setupJson));
|
|
7249
|
+
const file = input.file ? await readLoginReplacementFile(input.file, input.authentication, input.setupJson) : void 0;
|
|
7250
|
+
console.log(`Login callback URL: ${formatUrl(`${input.apiUrl}/auth/callback`)}`);
|
|
7251
|
+
const savedConnection = (id) => previous.connections.find((connection) => connection.configuration.id === id);
|
|
7252
|
+
const connections = [];
|
|
7253
|
+
for (const entry of file?.connections ?? []) {
|
|
7254
|
+
const config = { ...entry.configuration };
|
|
7255
|
+
const kind = config.kind;
|
|
7256
|
+
if (!config.id) config.id = defaultConnectionId(kind);
|
|
7257
|
+
if (kind === "cloudflare-access") {
|
|
7258
|
+
const saved = savedConnection(config.id);
|
|
7259
|
+
if (saved?.configuration.kind !== "cloudflare-access") throw new Error("platform: Repair keeps the installed Cloudflare Access login. Use its installed connection ID.");
|
|
7260
|
+
connections.push(saved);
|
|
7261
|
+
continue;
|
|
7262
|
+
}
|
|
7263
|
+
if (!config.label) config.label = kind;
|
|
7264
|
+
if (kind === "oidc" && !config.issuer) config.issuer = await prompt(`${config.label}: issuer URL:`);
|
|
7265
|
+
if (!config.clientId) config.clientId = await prompt(`${config.label}: client ID:`);
|
|
7266
|
+
const configuration = parsePlatformLoginConfiguration(config);
|
|
7267
|
+
connections.push({
|
|
7268
|
+
configuration,
|
|
7269
|
+
clientSecret: entry.clientSecretEnv ? clientSecretFromEnvironment(entry.clientSecretEnv) : await prompt(`${configuration.label}: client secret:`, "", true)
|
|
7270
|
+
});
|
|
7271
|
+
}
|
|
7272
|
+
for (const saved of file ? [] : previous.connections) {
|
|
7273
|
+
const current = saved.configuration;
|
|
7274
|
+
if (current.kind === "cloudflare-access") {
|
|
7275
|
+
connections.push(saved);
|
|
7276
|
+
continue;
|
|
7277
|
+
}
|
|
7278
|
+
const issuer = current.kind === "oidc" ? await prompt(`${current.label}: issuer URL:`, current.issuer ?? "") : current.issuer;
|
|
7279
|
+
const clientId = await prompt(`${current.label}: client ID:`, current.clientId);
|
|
7280
|
+
const allowedDomains = current.kind === "google" ? (await prompt("Allowed Google Workspace domains (comma-separated, or blank):", current.allowedDomains.join(", "))).split(",").map((value) => value.trim()).filter(Boolean) : current.allowedDomains;
|
|
7281
|
+
connections.push({
|
|
7282
|
+
configuration: parsePlatformLoginConfiguration({
|
|
7283
|
+
...current,
|
|
7284
|
+
...issuer ? { issuer } : {},
|
|
7285
|
+
clientId,
|
|
7286
|
+
allowedDomains
|
|
7287
|
+
}),
|
|
7288
|
+
clientSecret: await prompt(`${current.label}: client secret (leave blank to keep it):`, "", true) || saved.clientSecret
|
|
7289
|
+
});
|
|
7290
|
+
}
|
|
7291
|
+
const ids = connections.map((connection) => connection.configuration.id);
|
|
7292
|
+
const authBootstrapCode = randomBytes(32).toString("hex");
|
|
7293
|
+
const setup = parsePlatformAuthenticationSetup({
|
|
7294
|
+
connections,
|
|
7295
|
+
administratorConnectionId: file?.administratorConnectionId ?? (ids.includes(previous.administratorConnectionId) ? previous.administratorConnectionId : ids[0]),
|
|
7296
|
+
admission: file?.admission ?? previous.admission,
|
|
7297
|
+
protection: previous.protection,
|
|
7298
|
+
resetPending: true,
|
|
7299
|
+
bootstrapCredentialHash: createHash("sha256").update(authBootstrapCode).digest("hex")
|
|
7300
|
+
});
|
|
7301
|
+
return {
|
|
7302
|
+
authenticationSetupJson: JSON.stringify(setup),
|
|
7303
|
+
authBootstrapCode
|
|
7304
|
+
};
|
|
7305
|
+
}
|
|
7112
7306
|
//#endregion
|
|
7113
7307
|
//#region src/cli/platform-saved-plans.ts
|
|
7114
7308
|
function planId(args) {
|
|
@@ -7216,10 +7410,13 @@ function completePlatformSetupDraft(manifest) {
|
|
|
7216
7410
|
}
|
|
7217
7411
|
//#endregion
|
|
7218
7412
|
//#region src/cli/platform-lifecycle.ts
|
|
7413
|
+
function isPlatformInstallDatabaseBound(checkpoint, manifest) {
|
|
7414
|
+
return Boolean(checkpoint?.databaseClaimState === "claimed" || manifest?.resources.some((resource) => resource.state === "ready" && (resource.kind === "hyperdrive" || resource.kind === "worker")) || manifest?.steps.some((step) => step.key.startsWith("database-schema:") || step.key.startsWith("database-migrations:")));
|
|
7415
|
+
}
|
|
7219
7416
|
function resolvePlatformInstallDatabaseUrl(checkpoint, manifest = null) {
|
|
7220
7417
|
const explicit = process.env.VOID_PLATFORM_DATABASE_URL || process.env.DATABASE_URL || "";
|
|
7221
7418
|
const recorded = checkpoint?.databaseUrl || "";
|
|
7222
|
-
if (checkpoint
|
|
7419
|
+
if (isPlatformInstallDatabaseBound(checkpoint, manifest)) {
|
|
7223
7420
|
if (!recorded) {
|
|
7224
7421
|
if (explicit) return explicit;
|
|
7225
7422
|
throw new Error("platform: The claimed PostgreSQL database URL is missing from local recovery state. Export VOID_PLATFORM_DATABASE_URL with the original installation database before continuing.");
|
|
@@ -7231,14 +7428,14 @@ function resolvePlatformInstallDatabaseUrl(checkpoint, manifest = null) {
|
|
|
7231
7428
|
}
|
|
7232
7429
|
async function runPlatformLifecycleCommand(commandArgs) {
|
|
7233
7430
|
if (commandArgs.subcommand === "domain") {
|
|
7234
|
-
const { runPlatformDomainCommand } = await import("./platform-domain-
|
|
7431
|
+
const { runPlatformDomainCommand } = await import("./platform-domain-CxpvUeo0.mjs");
|
|
7235
7432
|
await runPlatformDomainCommand(commandArgs);
|
|
7236
7433
|
return;
|
|
7237
7434
|
}
|
|
7238
7435
|
if (commandArgs.subcommand === "discover") {
|
|
7239
7436
|
const client = new PlatformCloudflareClient((await resolvePlatformCloudflareCredential()).token);
|
|
7240
7437
|
const accountId = await selectAccount(await readAccounts(client), commandArgs.account);
|
|
7241
|
-
const { recoverPlatformInstallations } = await import("./platform-recovery-
|
|
7438
|
+
const { recoverPlatformInstallations } = await import("./platform-recovery-BUGOB4RC.mjs");
|
|
7242
7439
|
const recovered = await recoverPlatformInstallations(client, accountId, void 0, commandArgs.selector);
|
|
7243
7440
|
if (recovered.length === 0) {
|
|
7244
7441
|
log.info(`No recoverable Void platform installations were found in ${accountId}.`);
|
|
@@ -7249,7 +7446,7 @@ async function runPlatformLifecycleCommand(commandArgs) {
|
|
|
7249
7446
|
return;
|
|
7250
7447
|
}
|
|
7251
7448
|
if (commandArgs.subcommand !== "install") {
|
|
7252
|
-
const { runInstalledPlatformCommand } = await import("./platform-management-
|
|
7449
|
+
const { runInstalledPlatformCommand } = await import("./platform-management-DvBS83rC.mjs");
|
|
7253
7450
|
await runInstalledPlatformCommand(commandArgs);
|
|
7254
7451
|
return;
|
|
7255
7452
|
}
|
|
@@ -7406,6 +7603,7 @@ async function runPlatformLifecycleCommand(commandArgs) {
|
|
|
7406
7603
|
active.start(message);
|
|
7407
7604
|
return active;
|
|
7408
7605
|
};
|
|
7606
|
+
let usingUnboundDatabase = false;
|
|
7409
7607
|
try {
|
|
7410
7608
|
if (!existing && !args.plan) assertPlatformInstallationSlugAvailable(installationSlug);
|
|
7411
7609
|
if (existing && !args.plan) {
|
|
@@ -7418,11 +7616,13 @@ async function runPlatformLifecycleCommand(commandArgs) {
|
|
|
7418
7616
|
if (existing && !isPlatformSetupDraft(existing)) {
|
|
7419
7617
|
if (!databaseUrl) throw new Error("platform: DATABASE_URL is required to verify an interrupted installation before resuming it.");
|
|
7420
7618
|
progress = startProgress("Reading the saved installation");
|
|
7619
|
+
usingUnboundDatabase = !isPlatformInstallDatabaseBound(checkpoint, existing);
|
|
7421
7620
|
const authoritative = await readPlatformDatabaseLifecycleManifest(databaseUrl, existing.id, existing.ownershipNonce);
|
|
7621
|
+
usingUnboundDatabase = false;
|
|
7422
7622
|
progress?.stop("Read the saved installation.");
|
|
7423
7623
|
progress = void 0;
|
|
7424
7624
|
if (authoritative) {
|
|
7425
|
-
const { selectAuthoritativePlatformManifest } = await import("./platform-management-
|
|
7625
|
+
const { selectAuthoritativePlatformManifest } = await import("./platform-management-DvBS83rC.mjs");
|
|
7426
7626
|
const selected = selectAuthoritativePlatformManifest(existing, authoritative);
|
|
7427
7627
|
authoritativeProvisionIntents = true;
|
|
7428
7628
|
if (!args.plan && JSON.stringify(selected) !== JSON.stringify(existing)) writeInstallationManifest(selected);
|
|
@@ -7750,6 +7950,8 @@ async function runPlatformLifecycleCommand(commandArgs) {
|
|
|
7750
7950
|
r2SecretAccessKey
|
|
7751
7951
|
});
|
|
7752
7952
|
const hasFullProjectSecretKeyring = Boolean(checkpoint?.projectSecretKeysJson || checkpoint?.projectSecretActiveKeyVersion || process.env.VOID_PLATFORM_PROJECT_SECRET_KEYS_JSON || process.env.VOID_PLATFORM_PROJECT_SECRET_ACTIVE_KEY_VERSION);
|
|
7953
|
+
const generateIdentityKeys = credentialSetup && !!process.stdin.isTTY;
|
|
7954
|
+
const generatedKeys = generateIdentityKeys ? [configured("jwtSecret", "VOID_PLATFORM_JWT_SECRET") ? "" : "JWT signing key", configured("projectSecretKey", "VOID_PLATFORM_PROJECT_SECRET_KEY") || hasFullProjectSecretKeyring ? "" : "project encryption key"].filter(Boolean) : [];
|
|
7753
7955
|
const secrets = createPlatformInstallationSecrets({
|
|
7754
7956
|
configurableAuthentication: !!authenticationSelection.authentication.configurable,
|
|
7755
7957
|
...authenticationSecrets,
|
|
@@ -7766,24 +7968,35 @@ async function runPlatformLifecycleCommand(commandArgs) {
|
|
|
7766
7968
|
r2AccessKeyId,
|
|
7767
7969
|
r2SecretAccessKey,
|
|
7768
7970
|
existingInstallation: !credentialSetup,
|
|
7769
|
-
|
|
7770
|
-
|
|
7971
|
+
generateIdentityKeys,
|
|
7972
|
+
jwtSecret: generateIdentityKeys ? configured("jwtSecret", "VOID_PLATFORM_JWT_SECRET") : await collect("jwtSecret", "VOID_PLATFORM_JWT_SECRET", "JWT signing key:", true, true, credentialSetup ? (value) => Buffer.byteLength(value, "utf8") >= 32 ? void 0 : "The JWT signing secret must contain at least 32 random bytes." : void 0),
|
|
7973
|
+
projectSecretKey: configured("projectSecretKey", "VOID_PLATFORM_PROJECT_SECRET_KEY") || (hasFullProjectSecretKeyring || generateIdentityKeys ? "" : await collect("projectSecretKey", "VOID_PLATFORM_PROJECT_SECRET_KEY", "Project encryption key:", true, true, credentialSetup ? (value) => {
|
|
7771
7974
|
const key = Buffer.from(value, "base64");
|
|
7772
7975
|
return key.length === 32 && key.toString("base64") === value ? void 0 : "The project encryption key must be canonical base64 encoding of exactly 32 bytes.";
|
|
7773
7976
|
} : void 0)),
|
|
7774
7977
|
projectSecretKeysJson: process.env.VOID_PLATFORM_PROJECT_SECRET_KEYS_JSON || "",
|
|
7775
7978
|
projectSecretActiveKeyVersion: process.env.VOID_PLATFORM_PROJECT_SECRET_ACTIVE_KEY_VERSION || ""
|
|
7776
7979
|
});
|
|
7980
|
+
if (generatedKeys.length) log.info(`Void generated the ${generatedKeys.join(" and ")} and keeps ${generatedKeys.length > 1 ? "them" : "it"} in this machine’s encrypted recovery data. ` + platformSetupLink({
|
|
7981
|
+
title: "About these keys",
|
|
7982
|
+
url: "https://void.cloud/guide/platform/installation/credentials#signing-and-encryption-keys"
|
|
7983
|
+
}));
|
|
7777
7984
|
progress = startProgress("Installing the platform");
|
|
7778
7985
|
if (credentialSetup) {
|
|
7779
7986
|
saveValues({
|
|
7987
|
+
jwtSecret: secrets.jwtSecret,
|
|
7780
7988
|
projectSecretKey: secrets.projectSecretKey,
|
|
7781
7989
|
...secrets.emailEnabled ? { emailSigningSecret: secrets.emailSigningSecret } : {}
|
|
7782
7990
|
});
|
|
7783
7991
|
existing = completePlatformSetupDraft(existing);
|
|
7784
7992
|
}
|
|
7993
|
+
usingUnboundDatabase = !isPlatformInstallDatabaseBound(checkpoint, existing);
|
|
7785
7994
|
const result = await installPlatform(plan, secrets, existing, {
|
|
7786
7995
|
acquireDatabaseLifecycleLock: acquirePlatformDatabaseLifecycleLock,
|
|
7996
|
+
claimDatabase: async (...claim) => {
|
|
7997
|
+
await claimPlatformDatabase(...claim);
|
|
7998
|
+
usingUnboundDatabase = false;
|
|
7999
|
+
},
|
|
7787
8000
|
client,
|
|
7788
8001
|
operatorApiToken: managementApiToken || operatorCredential.token,
|
|
7789
8002
|
runtime,
|
|
@@ -7827,7 +8040,7 @@ async function runPlatformLifecycleCommand(commandArgs) {
|
|
|
7827
8040
|
})).actions.filter((action) => action.kind === "worker"),
|
|
7828
8041
|
revalidateBeforeWorkerDeploy: (manifest, action) => revalidatePlatformInstallAction(client, plan, manifest, action),
|
|
7829
8042
|
verifyExistingOwnership: async (manifest, databaseCheckpointExists) => {
|
|
7830
|
-
const { verifyPlatformInstallationOwnership } = await import("./platform-management-
|
|
8043
|
+
const { verifyPlatformInstallationOwnership } = await import("./platform-management-DvBS83rC.mjs");
|
|
7831
8044
|
const retainedReplacementKeys = new Set(plan.actions.filter((action) => {
|
|
7832
8045
|
const previous = manifest.resources.find((resource) => resource.key === action.key);
|
|
7833
8046
|
return action.action === "create" && previous && previous.name !== action.name;
|
|
@@ -7857,7 +8070,9 @@ async function runPlatformLifecycleCommand(commandArgs) {
|
|
|
7857
8070
|
if (result.setupPending && completedAuthCode) {
|
|
7858
8071
|
log.info(`First administrator setup: ${formatUrl(`${plan.apiUrl}/setup`)}`);
|
|
7859
8072
|
console.log(`One-time administrator setup code: ${completedAuthCode}`);
|
|
8073
|
+
log.info(`Login callback URL: ${formatUrl(`${plan.apiUrl}/auth/callback`)}`);
|
|
7860
8074
|
log.info("Confirm your identity in the browser, then test and enable any additional login methods in Settings.");
|
|
8075
|
+
log.info(`If sign-in fails, run \`void platform repair ${result.manifest.id}\` to change the login settings.`);
|
|
7861
8076
|
}
|
|
7862
8077
|
if (!plan.deployment.application.zoneName) log.info("Apps will use workers.dev addresses. When your domain is ready: void platform domain set <domain>");
|
|
7863
8078
|
log.info("Run `void account login`, then deploy an app with `void deploy --platform void`.");
|
|
@@ -7865,7 +8080,10 @@ async function runPlatformLifecycleCommand(commandArgs) {
|
|
|
7865
8080
|
} catch (error) {
|
|
7866
8081
|
progress?.error("Platform installation failed.");
|
|
7867
8082
|
progress = void 0;
|
|
7868
|
-
if (existing && !args.plan && !(error instanceof PlatformAccessBlockedError))
|
|
8083
|
+
if (existing && !args.plan && !(error instanceof PlatformAccessBlockedError)) {
|
|
8084
|
+
log.info(`Continue with: ${cliCommandName()} platform install --resume --name ${existing.id}`);
|
|
8085
|
+
if (usingUnboundDatabase) log.info("To use another PostgreSQL database, set VOID_PLATFORM_DATABASE_URL to its URL first.");
|
|
8086
|
+
}
|
|
7869
8087
|
throw error;
|
|
7870
8088
|
} finally {
|
|
7871
8089
|
try {
|
|
@@ -7979,4 +8197,4 @@ async function promptSecret(message, masked = true, validate, placeholder) {
|
|
|
7979
8197
|
return value;
|
|
7980
8198
|
}
|
|
7981
8199
|
//#endregion
|
|
7982
|
-
export {
|
|
8200
|
+
export { matchesRecordedR2Fingerprint as A, activatePlatformAccessCredentials as B, platformTrafficAttachmentWorker as C, formatPlatformInstallPlan as D, dnsRecordConfigHash as E, platformDatabaseNameFromOwnerUrl as F, PlatformCloudflareClient as G, parsePlatformAccessSetupSpec as H, turnOffInstalledProjectZeroTrust as I, hyperdriveOriginMatches as J, hyperdriveOriginConfigHash as K, assertPlatformOperatorCredentialCanApplyPlan as L, r2BucketConfigHash as M, resourceMarkerProblem as N, hasProvisionIntentStep as O, revalidatePlatformInstallAction as P, resolvePlatformCloudflareCredential as R, recordResource as S, createPlatformInstallPlan as T, preparePlatformAccessSetup as U, checkpointPlatformAccessResources as V, PlatformCloudflareApiError as W, verifyPlatformRuntimeTokenPermissions as Y, readPlatformDatabase as _, authenticationSetupRequestHash as a, createPlatformInstallationSecrets as b, REMOTE_INSTALLATION_MANIFEST_KEY as c, acquirePlatformDatabaseLifecycleLockConnection as d, platformDatabaseClientUrl as f, formatZeroTrustApplications as g, assertProjectZeroTrustAllowsUninstall as h, verifyPlatformAiGatewayPlan as i, platformAdoptionIntentHash as j, legacyResourceConfigHash as k, installPlatform as l, readPlatformProjectZeroTrustState as m, resolvePlatformInstallDatabaseUrl as n, readLoginReplacementFile as o, readPlatformProjectSlugs as p, hyperdriveOriginFromConnectionString as q, runPlatformLifecycleCommand as r, replacePendingInstallationAuthentication as s, assertNoninteractivePlatformMutationAcknowledged as t, acquirePlatformDatabaseLifecycleLock as u, loadPlatformRuntime as v, recordPlatformWorkerTraffic as w, resolveBootstrapGitHubId as x, createPlatformHealthProbeToken as y, verifyPlatformCloudflareCredential as z };
|