void 0.20.0 → 0.20.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +5 -1
  2. package/dist/{auth-W9WII-mN.mjs → auth-DPl6kck4.mjs} +46 -26
  3. package/dist/{auth-cmd-CYVhSNFy.mjs → auth-cmd-gniL2fNt.mjs} +5 -4
  4. package/dist/auth-link-NZdjCmSc.mjs +28 -0
  5. package/dist/{build-cmd-CkVD1uOh.mjs → build-cmd-sI18tX_O.mjs} +3 -3
  6. package/dist/{cache-QcUfR-ff.mjs → cache-IHn5MwBC.mjs} +3 -3
  7. package/dist/{cancel-deploy-DsvWKFTe.mjs → cancel-deploy-C5qTOdLi.mjs} +3 -3
  8. package/dist/cf-access-DRsQRe6k.mjs +75 -0
  9. package/dist/cli/cli.mjs +364 -1883
  10. package/dist/cli/env-schema-probe.mjs +11 -2
  11. package/dist/{client-CyCHWSO_.mjs → client-dHfSJvAN.mjs} +336 -58
  12. package/dist/{cloudflare-auth-DRkGe7-s.mjs → cloudflare-auth-6M5llVPC.mjs} +53 -6
  13. package/dist/{cloudflare-cmd-D3ME2GAe.mjs → cloudflare-cmd-4RPGN3KB.mjs} +2 -2
  14. package/dist/{cloudflare-connect-BivMefMA.mjs → cloudflare-connect-t1UU5svD.mjs} +2 -2
  15. package/dist/{cloudflare-operations-CPTpRW6d.mjs → cloudflare-operations-BzWnlC1_.mjs} +1 -1
  16. package/dist/{config-BQFq7QvD.mjs → config-uNGuFsI2.mjs} +1 -1
  17. package/dist/{connect-D9Yr-T5f.mjs → connect-Bfk31O_8.mjs} +6 -6
  18. package/dist/{create-project-DMV-csEm.mjs → create-project-Bk9Z0-Jg.mjs} +6 -5
  19. package/dist/{db-BxoUWL0F.mjs → db-BkRoptAt.mjs} +75 -48
  20. package/dist/{delete-iJOqxBz1.mjs → delete-DouASY9P.mjs} +3 -3
  21. package/dist/{deploy-CcDoeBIf.mjs → deploy-DTaWUS1S.mjs} +137 -128
  22. package/dist/{dev-inbox-DkgRWLkW.mjs → dev-inbox-P0u4tM8Y.mjs} +1 -1
  23. package/dist/{domain-gu_iaHmn.mjs → domain-1RhhOVrC.mjs} +4 -4
  24. package/dist/email-C-lGh51B.mjs +795 -0
  25. package/dist/{env-D4Emu-M_.mjs → env-DBKmK4vc.mjs} +1 -0
  26. package/dist/{env-DmU2To0C.mjs → env-DJHsPE7Z.mjs} +5 -5
  27. package/dist/{env-validation-ENpMy6Ez.mjs → env-validation-CF6KvTRf.mjs} +3 -1
  28. package/dist/{gen-BKw6qHIg.mjs → gen-B_wPnVTK.mjs} +3 -3
  29. package/dist/generate-RTK8_kK1.mjs +47 -0
  30. package/dist/{github-cmd-DJS5Ab-H.mjs → github-cmd-PW7ZnWTp.mjs} +3 -3
  31. package/dist/{headers-D8QfRX9Y.mjs → headers-BAHwgHdW.mjs} +1 -1
  32. package/dist/help-CwOX-zmI.mjs +2216 -0
  33. package/dist/{inbound-BJ70in1n.d.mts → inbound-CH5Mksyy.d.mts} +37 -42
  34. package/dist/{inbound-CNKxb3FY.mjs → inbound-aVHEUhKo.mjs} +143 -101
  35. package/dist/index.mjs +69 -30
  36. package/dist/{init-Dx5cgKqK.mjs → init-BWZ7q5Z4.mjs} +11 -11
  37. package/dist/{link-D_rm5sRH.mjs → link-Rmvu2Wl_.mjs} +4 -4
  38. package/dist/{list-M8XShti7.mjs → list-DEE2S6mY.mjs} +4 -4
  39. package/dist/{local-d1-BE8KBbMy.mjs → local-d1-D2I6Ox5F.mjs} +1 -1
  40. package/dist/{login-C8-UuLnp.mjs → login-Uvferzmm.mjs} +28 -10
  41. package/dist/{logs-DwFU7dMW.mjs → logs-27FenuiC.mjs} +4 -4
  42. package/dist/{mime-BJD7d_qL.mjs → mime-D5Nmdzf7.mjs} +23 -9
  43. package/dist/{node-BkaXcpAc.mjs → node-Ez5KW5rn.mjs} +3 -3
  44. package/dist/operator-auth-B3e08unv.mjs +52 -0
  45. package/dist/operator-client-LUZnlnYk.mjs +82 -0
  46. package/dist/{operator-cmd-DYWRbWUA.mjs → operator-cmd-CjOTmAYE.mjs} +35 -55
  47. package/dist/{output-tFQLLj26.mjs → output-B0cfNSx5.mjs} +316 -2
  48. package/dist/pages/index.mjs +2 -2
  49. package/dist/platform-auth-config-DrbQXXiW.mjs +368 -0
  50. package/dist/platform-auth-protection-Bhtvp0B_.mjs +219 -0
  51. package/dist/platform-auth-recovery-CeOKGVeJ.mjs +310 -0
  52. package/dist/{platform-cmd-BEOVv1PN.mjs → platform-cmd-BFhieCdV.mjs} +16 -6
  53. package/dist/{platform-domain-BszUfiS8.mjs → platform-domain-C74PULqV.mjs} +4 -4
  54. package/dist/{platform-lifecycle-CjSr6xf0.mjs → platform-lifecycle-BwAIgz-t.mjs} +1667 -191
  55. package/dist/{platform-management-W30iCaTL.mjs → platform-management-COogu_Se.mjs} +33 -7
  56. package/dist/{platform-recovery-pHHv4ZSg.mjs → platform-recovery-ewqLefp1.mjs} +6 -5
  57. package/dist/{prepare-CBetXvsN.mjs → prepare-CtDJjoOj.mjs} +2 -2
  58. package/dist/{prepare-BfJvFUtJ.mjs → prepare-blNRQvQl.mjs} +2 -2
  59. package/dist/prerender-render.d.mts +11 -0
  60. package/dist/prerender-render.mjs +111 -0
  61. package/dist/{project-cmd-DCpk1cDt.mjs → project-cmd-DmZK9Hxf.mjs} +32 -14
  62. package/dist/project-team-D8jOJMUJ.mjs +130 -0
  63. package/dist/project-token-DA34bf-C.mjs +75 -0
  64. package/dist/{provision-Blnstcm2.mjs → provision-CSJOjjQk.mjs} +2 -0
  65. package/dist/{requests-Dn8Vheh1.mjs → requests-CUExwGQQ.mjs} +3 -3
  66. package/dist/{rollback-CtlPXEBi.mjs → rollback-CDNGU1gr.mjs} +4 -4
  67. package/dist/{runner-mysql-CgRFl3s6.mjs → runner-mysql-7BPUNGmL.mjs} +1 -1
  68. package/dist/{runner-pg-DCkWPsWS.mjs → runner-pg-BkEza-dX.mjs} +1 -1
  69. package/dist/runtime/ai.mjs +3 -2
  70. package/dist/runtime/email/testing.d.mts +1 -1
  71. package/dist/runtime/email/testing.mjs +3 -3
  72. package/dist/runtime/email-protocol.d.mts +15 -0
  73. package/dist/runtime/email-protocol.mjs +70 -0
  74. package/dist/runtime/email.d.mts +2 -2
  75. package/dist/runtime/email.mjs +189 -96
  76. package/dist/runtime/remote/index.mjs +5 -3
  77. package/dist/{secret-BqTxGqki.mjs → secret-Bzzi2e9E.mjs} +5 -5
  78. package/dist/{skills-Q46GZMO-.mjs → skills-C0RvGjeE.mjs} +1 -1
  79. package/dist/sqlite-validation-BzKMWnO4.mjs +25 -0
  80. package/dist/{subcommand-prompt-WfySCQ7S.mjs → subcommand-prompt-Bmyn5Rlc.mjs} +1 -1
  81. package/dist/{validate-Bihr8WBi.mjs → validate-tBBN_dXH.mjs} +1 -0
  82. package/package.json +12 -7
  83. package/skills/void/SKILL.md +35 -4
  84. package/skills/void/docs/guide/deployment.md +2 -0
  85. package/skills/void/docs/guide/email.md +121 -98
  86. package/skills/void/docs/guide/platform-administration.md +214 -2
  87. package/skills/void/docs/guide/platform-development.md +93 -2
  88. package/skills/void/docs/guide/project-collaboration.md +94 -0
  89. package/skills/void/docs/guide/self-hosted-platform.md +184 -30
  90. package/skills/void/docs/reference/cli.md +294 -15
  91. package/dist/cf-access-AJ1ehiFR.mjs +0 -42
  92. package/dist/cf-access-DsSsZUPr.mjs +0 -67
  93. package/dist/email-r6DHJyAB.mjs +0 -262
@@ -0,0 +1,368 @@
1
+ import { I as isCancel, M as select, O as multiselect, P as text, j as password, w as confirm } from "./output-B0cfNSx5.mjs";
2
+ import { f as openBrowser } from "./auth-DPl6kck4.mjs";
3
+ import { n as OperatorClient } from "./operator-client-LUZnlnYk.mjs";
4
+ import { a as resolveOperatorOrigin, r as readOperatorToken } from "./operator-auth-B3e08unv.mjs";
5
+ import { execFile } from "node:child_process";
6
+ import { parsePlatformAdmissionPolicy, parsePlatformLoginConfiguration } from "@void/platform";
7
+ import { stripVTControlCharacters } from "node:util";
8
+ import { setTimeout } from "node:timers/promises";
9
+ import { readFile } from "node:fs/promises";
10
+ //#region src/cli/platform-auth-config.ts
11
+ function value(input) {
12
+ if (isCancel(input) || typeof input === "symbol") throw new Error("platform: Cancelled.");
13
+ return input;
14
+ }
15
+ function print(result) {
16
+ console.log(JSON.stringify(result, null, 2));
17
+ }
18
+ async function apply(client, path, body, args) {
19
+ const preview = await client.request(path, {
20
+ method: "POST",
21
+ body: {
22
+ ...body,
23
+ dryRun: true
24
+ }
25
+ });
26
+ if (args.plan) return preview;
27
+ if (!args.yes) {
28
+ if (!process.stdin.isTTY) throw new Error("platform: Configuration changes require --yes or --plan in non-interactive mode.");
29
+ console.error(JSON.stringify(preview, null, 2));
30
+ if (!value(await confirm({
31
+ message: `Apply this change to ${client.origin}?`,
32
+ initialValue: false
33
+ }))) throw new Error("platform: Cancelled.");
34
+ }
35
+ return client.request(path, {
36
+ method: "POST",
37
+ body
38
+ });
39
+ }
40
+ async function waitForPlatformLoginTest(client, launch, signal) {
41
+ if (typeof launch.id !== "string" || typeof launch.expiresAt !== "string" || typeof launch.url !== "string") throw new Error("platform: Invalid login test response.");
42
+ const url = new URL(launch.url);
43
+ if (url.origin !== client.origin || url.pathname !== "/auth/test" || url.username || url.password || url.hash) throw new Error("platform: The login test belongs to a different platform.");
44
+ const expiry = Date.parse(launch.expiresAt);
45
+ if (!Number.isFinite(expiry) || expiry > Date.now() + 66e4) throw new Error("platform: Invalid login test expiry.");
46
+ console.log(`Complete the login test in your browser: ${url.href}`);
47
+ openBrowser(url.href, execFile);
48
+ while (Date.now() < expiry) {
49
+ signal?.throwIfAborted();
50
+ const result = await client.request(`/config/auth/tests/${encodeURIComponent(launch.id)}`, { signal });
51
+ if (result.complete === true) return result;
52
+ await setTimeout(1e3, void 0, { signal });
53
+ }
54
+ throw new Error("platform: The login test expired. Run it again.");
55
+ }
56
+ async function testMethod(client, id, args) {
57
+ const path = `/config/auth/connections/${encodeURIComponent(id)}/test`;
58
+ if (args.plan) return client.request(path, {
59
+ method: "POST",
60
+ body: { dryRun: true }
61
+ });
62
+ const launch = await client.request(path, {
63
+ method: "POST",
64
+ body: {}
65
+ });
66
+ if (!process.stdin.isTTY || args.json) return launch;
67
+ const controller = new AbortController();
68
+ const abort = () => controller.abort();
69
+ process.once("SIGINT", abort);
70
+ try {
71
+ return await waitForPlatformLoginTest(client, launch, controller.signal);
72
+ } finally {
73
+ process.removeListener("SIGINT", abort);
74
+ }
75
+ }
76
+ async function configurationInput(args, existing) {
77
+ let input = existing ? { ...existing.configuration } : {};
78
+ if (args.file) {
79
+ const file = JSON.parse(await readFile(args.file, "utf8"));
80
+ if (!file || typeof file !== "object" || Array.isArray(file)) throw new Error("platform: Configuration files must contain a JSON object.");
81
+ if ("clientSecret" in file) throw new Error("platform: Supply the client secret with --client-secret-env, not in the configuration file.");
82
+ input = {
83
+ ...input,
84
+ ...file
85
+ };
86
+ }
87
+ const kind = existing?.configuration.kind ?? args.target;
88
+ if (input.kind !== void 0 && input.kind !== kind || existing && input.id !== existing.id || args.id && input.id !== void 0 && input.id !== args.id) throw new Error("platform: The file must match the selected connection ID and kind. Use add for a different connection.");
89
+ const id = existing?.id ?? args.id ?? input.id;
90
+ input.kind = kind;
91
+ input.id = id || input.id || (kind === "github" || kind === "google" ? kind : kind === "oidc" ? "company" : "access");
92
+ const interactive = !!process.stdin.isTTY && !args.file;
93
+ if (interactive) {
94
+ if (!existing && kind !== "github" && kind !== "google" && !args.id) input.id = value(await text({
95
+ message: "Connection ID:",
96
+ initialValue: String(input.id)
97
+ }));
98
+ input.label = value(await text({
99
+ message: "Login button label:",
100
+ initialValue: String(input.label ?? {
101
+ github: "GitHub",
102
+ google: "Google",
103
+ oidc: "Company SSO",
104
+ "cloudflare-access": "Cloudflare Access"
105
+ }[kind])
106
+ }));
107
+ if (kind === "oidc" || kind === "cloudflare-access") input.issuer = value(await text({
108
+ message: "OIDC issuer URL:",
109
+ initialValue: String(input.issuer ?? "")
110
+ }));
111
+ input.clientId = value(await text({
112
+ message: "Client ID:",
113
+ initialValue: String(input.clientId ?? "")
114
+ }));
115
+ if (kind === "google") {
116
+ const domains = value(await text({
117
+ message: "Allowed Workspace domains (comma-separated; blank allows any Google account):",
118
+ initialValue: Array.isArray(input.allowedDomains) ? input.allowedDomains.join(", ") : ""
119
+ }));
120
+ input.allowedDomains = domains.split(",").map((domain) => domain.trim()).filter(Boolean);
121
+ }
122
+ if (kind === "oidc" || kind === "cloudflare-access") {
123
+ const groups = value(await text({
124
+ message: "Allowed groups (comma-separated; blank uses the provider’s access policy):",
125
+ initialValue: Array.isArray(input.requiredGroups) ? input.requiredGroups.join(", ") : ""
126
+ }));
127
+ input.requiredGroups = groups.split(",").map((group) => group.trim()).filter(Boolean);
128
+ }
129
+ }
130
+ const configuration = parsePlatformLoginConfiguration(input);
131
+ if (existing && configuration.id !== existing.id) throw new Error("platform: Use add to create a connection with a new ID.");
132
+ let secret = args.clientSecretEnv ? process.env[args.clientSecretEnv] : void 0;
133
+ if (args.clientSecretEnv && !secret) throw new Error(`platform: ${args.clientSecretEnv} must contain the client secret.`);
134
+ if (!args.clientSecretEnv && interactive) secret = value(await password({ message: existing ? "Client secret (leave blank to keep the saved secret):" : "Client secret:" })) || void 0;
135
+ if (!existing && !secret) throw new Error("platform: Provide --client-secret-env or enter a client secret interactively.");
136
+ return {
137
+ configuration,
138
+ ...secret ? { clientSecret: secret } : {}
139
+ };
140
+ }
141
+ async function runPlatformAuthConfig(args) {
142
+ if (args.action === "recover") {
143
+ const { runPlatformAuthRecovery } = await import("./platform-auth-recovery-CeOKGVeJ.mjs");
144
+ return runPlatformAuthRecovery(args);
145
+ }
146
+ if (args.action === "protection") {
147
+ const { runPlatformProtectionConfig } = await import("./platform-auth-protection-Bhtvp0B_.mjs");
148
+ return runPlatformProtectionConfig(args);
149
+ }
150
+ const origin = resolveOperatorOrigin(args.connection);
151
+ const client = new OperatorClient(origin, readOperatorToken(origin));
152
+ const listing = await client.request("/config/auth");
153
+ if (!args.action) {
154
+ if (!process.stdin.isTTY || args.json) {
155
+ print(listing);
156
+ return;
157
+ }
158
+ if (!listing.initialized) {
159
+ print(await apply(client, "/config/auth/initialize", {}, args));
160
+ console.log("Run void platform auth login, then return to void platform config auth.");
161
+ return;
162
+ }
163
+ const chosen = value(await select({
164
+ message: "Platform authentication:",
165
+ options: [
166
+ ...listing.methods.map((method) => ({
167
+ value: method.id,
168
+ label: `${stripVTControlCharacters(method.label)} (${method.enabled ? "enabled" : "disabled"})`
169
+ })),
170
+ {
171
+ value: ":add",
172
+ label: "Add login method"
173
+ },
174
+ {
175
+ value: ":admission",
176
+ label: "Who can join?"
177
+ },
178
+ {
179
+ value: ":protection",
180
+ label: "Platform protection (Cloudflare Access)"
181
+ }
182
+ ]
183
+ }));
184
+ if (chosen === ":protection") {
185
+ const target = value(await select({
186
+ message: "Platform protection:",
187
+ options: [
188
+ {
189
+ value: "show",
190
+ label: "Show current protection"
191
+ },
192
+ {
193
+ value: "enable",
194
+ label: "Create or connect Access protection"
195
+ },
196
+ {
197
+ value: "disable",
198
+ label: "Remove runtime protection and revoke sessions"
199
+ }
200
+ ]
201
+ }));
202
+ return runPlatformAuthConfig({
203
+ ...args,
204
+ action: "protection",
205
+ target
206
+ });
207
+ }
208
+ if (chosen === ":admission") return runPlatformAuthConfig({
209
+ ...args,
210
+ action: "admission"
211
+ });
212
+ if (chosen === ":add") {
213
+ const kind = value(await select({
214
+ message: "Login method:",
215
+ options: [
216
+ {
217
+ value: "github",
218
+ label: "GitHub"
219
+ },
220
+ {
221
+ value: "google",
222
+ label: "Google"
223
+ },
224
+ {
225
+ value: "oidc",
226
+ label: "Generic OIDC"
227
+ },
228
+ {
229
+ value: "cloudflare-access",
230
+ label: "Cloudflare Access"
231
+ }
232
+ ]
233
+ }));
234
+ return runPlatformAuthConfig({
235
+ ...args,
236
+ action: "add",
237
+ target: kind
238
+ });
239
+ }
240
+ const method = listing.methods.find((method) => method.id === chosen);
241
+ const action = value(await select({
242
+ message: stripVTControlCharacters(method.label),
243
+ options: [
244
+ {
245
+ value: "test",
246
+ label: "Test login"
247
+ },
248
+ {
249
+ value: "configure",
250
+ label: "Edit configuration"
251
+ },
252
+ {
253
+ value: method.enabled ? "disable" : "enable",
254
+ label: method.enabled ? "Disable" : "Enable"
255
+ },
256
+ {
257
+ value: "link",
258
+ label: "Link this login to my account"
259
+ }
260
+ ]
261
+ }));
262
+ return runPlatformAuthConfig({
263
+ ...args,
264
+ action,
265
+ target: chosen
266
+ });
267
+ }
268
+ if (args.action === "list") {
269
+ print(listing);
270
+ return;
271
+ }
272
+ if (args.action === "initialize") {
273
+ print(await apply(client, "/config/auth/initialize", {}, args));
274
+ return;
275
+ }
276
+ if (!listing.initialized) throw new Error("platform: Run void platform config auth initialize, then sign in again.");
277
+ if (args.action === "admission") {
278
+ let admission;
279
+ if (args.file) admission = JSON.parse(await readFile(args.file, "utf8"));
280
+ else {
281
+ if (!process.stdin.isTTY || args.json) throw new Error("platform: Supply --file with the signup policy in non-interactive mode.");
282
+ const mode = value(await select({
283
+ message: "Who can create an account?",
284
+ initialValue: listing.admission?.mode ?? "restricted",
285
+ options: [
286
+ {
287
+ value: "restricted",
288
+ label: "Invited or allowlisted users"
289
+ },
290
+ {
291
+ value: "company",
292
+ label: "Company-approved users — automatic accounts"
293
+ },
294
+ {
295
+ value: "public",
296
+ label: "Public signup — all enabled logins"
297
+ }
298
+ ]
299
+ }));
300
+ if (mode === "company") {
301
+ const access = !!listing.protection && value(await confirm({
302
+ message: "Allow everyone approved by Cloudflare Access protection?",
303
+ initialValue: true
304
+ }));
305
+ const eligible = listing.methods.filter((method) => method.enabled && method.configuration.kind !== "github" && (method.configuration.kind !== "google" || method.configuration.allowedDomains.length > 0));
306
+ if (!access && eligible.length === 0) throw new Error("platform: Configure a company-restricted login or Access protection first.");
307
+ admission = {
308
+ mode,
309
+ access,
310
+ connections: eligible.length ? value(await multiselect({
311
+ message: "Login methods whose company policies qualify new users:",
312
+ required: !access,
313
+ options: eligible.map((method) => ({
314
+ value: method.id,
315
+ label: stripVTControlCharacters(method.label)
316
+ }))
317
+ })) : []
318
+ };
319
+ } else admission = { mode };
320
+ }
321
+ print(await apply(client, "/config/auth/admission", {
322
+ revision: listing.revision,
323
+ admission: parsePlatformAdmissionPolicy(admission)
324
+ }, args));
325
+ return;
326
+ }
327
+ const method = listing.methods.find((method) => method.id === args.target);
328
+ if (args.action !== "add" && !method) throw new Error("platform: Login method not found. Run void platform config auth list.");
329
+ if (args.action === "show") {
330
+ print(method);
331
+ return;
332
+ }
333
+ if (args.action === "test") {
334
+ print(await testMethod(client, args.target, args));
335
+ return;
336
+ }
337
+ if (args.action === "add" || args.action === "configure") {
338
+ console.error(`Register this callback URL with the provider: ${listing.callbackUrl ?? `${origin}/auth/callback`}`);
339
+ const input = await configurationInput(args, args.action === "configure" ? method : void 0);
340
+ print(await apply(client, `/config/auth/connections/${encodeURIComponent(input.configuration.id)}/configure`, {
341
+ ...input,
342
+ revision: args.action === "add" ? null : method.revision
343
+ }, args));
344
+ if (!args.plan && !args.json) console.log(`Test and enable it with void platform config auth enable ${input.configuration.id}.`);
345
+ return;
346
+ }
347
+ let testId = args.testId;
348
+ if ((args.action === "enable" || args.action === "link") && !testId) {
349
+ if (!process.stdin.isTTY || args.plan || args.json) throw new Error("platform: Complete a login test and supply its --test-id.");
350
+ const tested = await testMethod(client, args.target, args);
351
+ print(tested);
352
+ if (tested.complete !== true || typeof tested.id !== "string") throw new Error("platform: Complete the browser test before enabling or linking this login.");
353
+ testId = tested.id;
354
+ if (args.action === "enable" && value(await confirm({
355
+ message: "Link this verified identity to your administrator account?",
356
+ initialValue: true
357
+ }))) await apply(client, `/config/auth/connections/${encodeURIComponent(args.target)}/link`, {
358
+ revision: method.revision,
359
+ testId
360
+ }, args);
361
+ }
362
+ print(await apply(client, `/config/auth/connections/${encodeURIComponent(args.target)}/${args.action}`, {
363
+ revision: method.revision,
364
+ ...testId ? { testId } : {}
365
+ }, args));
366
+ }
367
+ //#endregion
368
+ export { runPlatformAuthConfig };
@@ -0,0 +1,219 @@
1
+ import { $ as promoteInstallationSecrets, I as isCancel, V as ensurePlatformAccessToken, W as acquirePlatformLifecycleLock, X as normalizePlatformApiUrl, ct as writePendingInstallationSecrets, et as readInstallationRecoverySecrets, it as resolveInstallationManifest, st as writeInstallationManifest, w as confirm } from "./output-B0cfNSx5.mjs";
2
+ import { t as cfAccessHeaders } from "./cf-access-DRsQRe6k.mjs";
3
+ import { n as OperatorClient } from "./operator-client-LUZnlnYk.mjs";
4
+ import { a as resolveOperatorOrigin, r as readOperatorToken } from "./operator-auth-B3e08unv.mjs";
5
+ import { C as PlatformCloudflareClient, S as preparePlatformAccessSetup, b as checkpointPlatformAccessResources, n as acquirePlatformDatabaseLifecycleLock, resolvePlatformInstallDatabaseUrl, x as parsePlatformAccessSetupSpec, y as resolvePlatformCloudflareCredential } from "./platform-lifecycle-BwAIgz-t.mjs";
6
+ import { selectAuthoritativePlatformManifest, verifyPlatformInstallationOwnership } from "./platform-management-COogu_Se.mjs";
7
+ import { createHmac } from "node:crypto";
8
+ import { parsePlatformAccessProtection, platformProtectionProofMessage } from "@void/platform";
9
+ import { readFile } from "node:fs/promises";
10
+ //#region src/cli/platform-auth-protection.ts
11
+ /** Read every externally routed control-plane Worker before changing its preview settings. */
12
+ async function verifyProtectionWorkerOrigins(client, manifest, origins, disablePreviews, options = {}) {
13
+ const domains = await client.listWorkerDomains(manifest.account.id);
14
+ const allowed = new Set(origins.map((origin) => new URL(origin).hostname));
15
+ const workers = [
16
+ manifest.deployment.resources.apiWorker,
17
+ manifest.deployment.resources.proxyWorker,
18
+ ...manifest.resources.some((resource) => resource.name === manifest.deployment.resources.dashboardWorker && resource.state === "ready") || domains.some((domain) => domain.service === manifest.deployment.resources.dashboardWorker) ? [manifest.deployment.resources.dashboardWorker] : []
19
+ ];
20
+ const updates = [];
21
+ for (const worker of workers) {
22
+ if (options.workerNames && !options.workerNames.includes(worker)) continue;
23
+ const path = `/accounts/${encodeURIComponent(manifest.account.id)}/workers/scripts/${encodeURIComponent(worker)}/subdomain`;
24
+ const subdomain = await client.request(path);
25
+ const expectedWorkersDev = origins.some((origin) => new URL(origin).hostname.startsWith(`${worker}.`) && new URL(origin).hostname.endsWith(".workers.dev"));
26
+ if (typeof subdomain.enabled !== "boolean" || typeof subdomain.previews_enabled !== "boolean") throw new Error("platform: Cannot verify Worker alternate origins. Check Workers read permissions.");
27
+ if (subdomain.enabled && !expectedWorkersDev) updates.push({
28
+ path,
29
+ enabled: false
30
+ });
31
+ else if (subdomain.previews_enabled) updates.push({
32
+ path,
33
+ enabled: expectedWorkersDev && subdomain.enabled
34
+ });
35
+ for (const domain of domains.filter((domain) => domain.service === worker)) if (!allowed.has(domain.hostname)) throw new Error(`platform: Protect or remove the additional Worker domain ${domain.hostname} before enabling Access.`);
36
+ for (const route of await client.listWorkerServiceRoutes(manifest.account.id, worker)) {
37
+ const hostname = route.pattern.replace(/^https?:\/\//, "").split("/")[0];
38
+ if (!allowed.has(hostname)) throw new Error("platform: A control-plane Worker has an additional or wildcard route. Protect or remove it before enabling Access.");
39
+ }
40
+ }
41
+ if (updates.length && !disablePreviews) throw new Error("platform: Worker alternate and preview URLs must be disabled before enabling Access.");
42
+ for (const update of updates) {
43
+ await client.request(update.path, {
44
+ method: "POST",
45
+ body: JSON.stringify({
46
+ enabled: update.enabled,
47
+ previews_enabled: false
48
+ }),
49
+ headers: { "Cloudflare-Workers-Script-Api-Date": "2025-08-01" }
50
+ });
51
+ const current = await client.request(update.path);
52
+ if (current.enabled !== update.enabled || current.previews_enabled !== false) throw new Error("platform: Worker alternate origins changed during protection setup. Reconcile them before retrying.");
53
+ }
54
+ }
55
+ async function runPlatformProtectionConfig(args) {
56
+ const manifest = args.target === "show" ? void 0 : resolveInstallationManifest(args.installation);
57
+ const origin = args.connection ? resolveOperatorOrigin(args.connection) : manifest?.endpoints.apiUrl ? normalizePlatformApiUrl(manifest.endpoints.apiUrl) : resolveOperatorOrigin("");
58
+ const operator = new OperatorClient(origin, readOperatorToken(origin));
59
+ if (manifest) {
60
+ const saved = readInstallationRecoverySecrets(manifest);
61
+ const provisioning = saved?.accessProvisioningJson ? JSON.parse(saved.accessProvisioningJson) : void 0;
62
+ if (manifest.deployment.authentication.accessProtection || provisioning?.resources?.protection?.id) ensurePlatformAccessToken(origin, false, true);
63
+ }
64
+ let context = await operator.request("/config/auth/protection");
65
+ if (args.target === "show") {
66
+ console.log(JSON.stringify({
67
+ protection: context.protection,
68
+ origins: context.origins
69
+ }, null, 2));
70
+ return;
71
+ }
72
+ if (!manifest || manifest.status !== "installed" || !manifest.endpoints.proxyUrl || normalizePlatformApiUrl(manifest.endpoints.apiUrl ?? "") !== origin || context.installationId !== manifest.id || typeof context.userId !== "string" || typeof context.sessionId !== "string" || typeof context.revision !== "number" || typeof context.lifecycleVersion !== "string" || !Number.isSafeInteger(context.revision) || !Array.isArray(context.origins) || context.origins.some((value) => typeof value !== "string")) throw new Error("platform: Protection settings must belong to this completed installation. Repair or upgrade it first.");
73
+ let lifecycleVersion = context.lifecycleVersion;
74
+ let origins = context.origins;
75
+ if (!origins.includes(origin) || !origins.includes(new URL(manifest.endpoints.proxyUrl).origin)) throw new Error("platform: The live API and proxy origins differ from installation recovery state. Repair the installation first.");
76
+ const spec = args.file ? parsePlatformAccessSetupSpec(JSON.parse(await readFile(args.file, "utf8"))) : void 0;
77
+ if (args.target === "disable" && spec) throw new Error("platform: Removing protection does not accept an Access application configuration.");
78
+ const reviewed = {
79
+ action: args.target,
80
+ installationId: manifest.id,
81
+ origin,
82
+ origins,
83
+ ...spec ? { cloudflareAccess: spec } : {},
84
+ revokeHumanSessions: true,
85
+ retainAccessApplications: true,
86
+ disableUnprotectedWorkerAlternateOrigins: args.target === "enable"
87
+ };
88
+ if (args.plan) {
89
+ console.log(JSON.stringify({
90
+ dryRun: true,
91
+ ...reviewed
92
+ }, null, 2));
93
+ return;
94
+ }
95
+ if (!args.yes) {
96
+ console.error(JSON.stringify(reviewed, null, 2));
97
+ if (!process.stdin.isTTY) throw new Error("platform: Protection changes require --yes or --plan in non-interactive mode.");
98
+ const accepted = await confirm({
99
+ message: "Apply this protection change and revoke human sessions?",
100
+ initialValue: false
101
+ });
102
+ if (isCancel(accepted) || !accepted) throw new Error("platform: Cancelled.");
103
+ }
104
+ const release = acquirePlatformLifecycleLock(manifest.account.id, manifest.deployment.installationSlug);
105
+ try {
106
+ let currentManifest = resolveInstallationManifest(manifest.id);
107
+ const recovery = readInstallationRecoverySecrets(currentManifest);
108
+ if (!recovery?.jwtSecret) throw new Error("platform: Restore the original encrypted installation recovery credentials before changing protection.");
109
+ const credential = await resolvePlatformCloudflareCredential();
110
+ const cloudflare = new PlatformCloudflareClient(credential.token);
111
+ let protection = null;
112
+ const checkpoint = { ...recovery };
113
+ const save = (values) => {
114
+ Object.assign(checkpoint, values);
115
+ writePendingInstallationSecrets(currentManifest, checkpoint);
116
+ };
117
+ if (args.target === "disable") await verifyPlatformInstallationOwnership(cloudflare, currentManifest);
118
+ if (args.target === "enable") {
119
+ const databaseUrl = resolvePlatformInstallDatabaseUrl(recovery, currentManifest);
120
+ if (!databaseUrl) throw new Error("platform: DATABASE_URL is required to checkpoint Access resource ownership safely.");
121
+ const releaseDatabase = await acquirePlatformDatabaseLifecycleLock(databaseUrl, currentManifest.id, currentManifest.ownershipNonce);
122
+ try {
123
+ currentManifest = selectAuthoritativePlatformManifest(currentManifest, releaseDatabase.authoritativeManifest);
124
+ if (!releaseDatabase.authoritativeManifest) await releaseDatabase.checkpoint?.(currentManifest);
125
+ else writeInstallationManifest(currentManifest);
126
+ context = await operator.request("/config/auth/protection");
127
+ const rawLockedOrigins = context.origins;
128
+ const lockedOrigins = Array.isArray(rawLockedOrigins) ? rawLockedOrigins.filter((value) => typeof value === "string") : [];
129
+ if (normalizePlatformApiUrl(currentManifest.endpoints.apiUrl ?? "") !== origin || !currentManifest.endpoints.proxyUrl || context.installationId !== currentManifest.id || lockedOrigins.length !== (Array.isArray(rawLockedOrigins) ? rawLockedOrigins.length : -1) || !lockedOrigins.includes(origin) || !lockedOrigins.includes(new URL(currentManifest.endpoints.proxyUrl).origin)) throw new Error("platform: Platform origins changed before Access provisioning. Review and retry.");
130
+ origins = lockedOrigins;
131
+ await verifyPlatformInstallationOwnership(cloudflare, currentManifest);
132
+ await verifyProtectionWorkerOrigins(cloudflare, currentManifest, origins, true);
133
+ const originalCredentials = process.env.VOID_ACCESS_CREDENTIALS;
134
+ try {
135
+ const prepared = await preparePlatformAccessSetup({
136
+ plan: {
137
+ account: currentManifest.account,
138
+ installationId: currentManifest.id,
139
+ apiUrl: origin,
140
+ proxyUrl: currentManifest.endpoints.proxyUrl
141
+ },
142
+ ownershipNonce: currentManifest.ownershipNonce,
143
+ protection: true,
144
+ additionalOrigins: origins,
145
+ spec,
146
+ client: cloudflare,
147
+ checkpoint,
148
+ save,
149
+ yes: args.yes
150
+ });
151
+ protection = parsePlatformAccessProtection(prepared?.protection);
152
+ if (!protection) throw new Error("platform: Access protection could not be verified.");
153
+ } finally {
154
+ if (originalCredentials === void 0) delete process.env.VOID_ACCESS_CREDENTIALS;
155
+ else process.env.VOID_ACCESS_CREDENTIALS = originalCredentials;
156
+ }
157
+ } finally {
158
+ try {
159
+ currentManifest = checkpointPlatformAccessResources(currentManifest, checkpoint.accessProvisioningJson, (/* @__PURE__ */ new Date()).toISOString());
160
+ writeInstallationManifest(currentManifest);
161
+ await releaseDatabase.checkpoint?.(currentManifest);
162
+ } finally {
163
+ await releaseDatabase();
164
+ }
165
+ }
166
+ ensurePlatformAccessToken(origin, false, true);
167
+ if (!cfAccessHeaders(process.env, origin)["cf-access-token"]) throw new Error("platform: Sign in interactively through company Access to enable protection. Service credentials cannot authorize this change.");
168
+ context = await operator.request("/config/auth/protection");
169
+ if (context.installationId !== currentManifest.id || typeof context.userId !== "string" || typeof context.sessionId !== "string" || typeof context.revision !== "number" || typeof context.lifecycleVersion !== "string" || !Array.isArray(context.origins) || context.origins.some((value) => typeof value !== "string") || JSON.stringify(context.origins) !== JSON.stringify(origins)) throw new Error("platform: Protection settings changed while Access resources were being checkpointed. Review and retry.");
170
+ lifecycleVersion = context.lifecycleVersion;
171
+ await verifyProtectionWorkerOrigins(cloudflare, currentManifest, origins, false);
172
+ }
173
+ const expiresAt = new Date(Date.now() + 3e5).toISOString();
174
+ const signature = createHmac("sha256", recovery.jwtSecret).update(platformProtectionProofMessage({
175
+ installationId: currentManifest.id,
176
+ origin,
177
+ userId: context.userId,
178
+ sessionId: context.sessionId,
179
+ revision: context.revision,
180
+ lifecycleVersion,
181
+ protection,
182
+ expiresAt
183
+ })).digest("base64url");
184
+ const body = {
185
+ revision: context.revision,
186
+ lifecycleVersion,
187
+ protection,
188
+ expiresAt,
189
+ signature
190
+ };
191
+ const preview = await operator.request("/config/auth/protection", {
192
+ method: "POST",
193
+ body: {
194
+ ...body,
195
+ dryRun: true
196
+ }
197
+ });
198
+ if (!args.json) console.error(JSON.stringify(preview, null, 2));
199
+ const result = await operator.request("/config/auth/protection", {
200
+ method: "POST",
201
+ body
202
+ });
203
+ save({ protectionUpdateJson: JSON.stringify({
204
+ revision: result.revision,
205
+ protection
206
+ }) });
207
+ promoteInstallationSecrets(currentManifest);
208
+ console.log(JSON.stringify({
209
+ ...result,
210
+ protection,
211
+ retainedAccessApplications: true
212
+ }, null, 2));
213
+ if (!args.json) console.log("Sign in again with void platform auth login. Existing Access applications and policies were retained.");
214
+ } finally {
215
+ release();
216
+ }
217
+ }
218
+ //#endregion
219
+ export { runPlatformProtectionConfig, verifyProtectionWorkerOrigins };