void 0.20.1 → 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 (87) 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-CAH62yDU.mjs → auth-cmd-gniL2fNt.mjs} +5 -4
  4. package/dist/auth-link-NZdjCmSc.mjs +28 -0
  5. package/dist/{build-cmd-CJvZvPQO.mjs → build-cmd-sI18tX_O.mjs} +3 -3
  6. package/dist/{cache-BlNeQjuP.mjs → cache-IHn5MwBC.mjs} +3 -3
  7. package/dist/{cancel-deploy-CmlAZ9P6.mjs → cancel-deploy-C5qTOdLi.mjs} +3 -3
  8. package/dist/cf-access-DRsQRe6k.mjs +75 -0
  9. package/dist/cli/cli.mjs +308 -1958
  10. package/dist/cli/env-schema-probe.mjs +11 -2
  11. package/dist/{client-Clirrol3.mjs → client-dHfSJvAN.mjs} +300 -81
  12. package/dist/{cloudflare-auth-B1QtTO1b.mjs → cloudflare-auth-6M5llVPC.mjs} +2 -2
  13. package/dist/{cloudflare-cmd-B6_OZx2V.mjs → cloudflare-cmd-4RPGN3KB.mjs} +2 -2
  14. package/dist/{cloudflare-connect-j5D4hhrG.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-C04Wdy_h.mjs → connect-Bfk31O_8.mjs} +6 -6
  18. package/dist/{create-project-ChGZ1DFd.mjs → create-project-Bk9Z0-Jg.mjs} +6 -5
  19. package/dist/{db-D2d_mUsB.mjs → db-BkRoptAt.mjs} +47 -30
  20. package/dist/{delete-D8GigDk8.mjs → delete-DouASY9P.mjs} +3 -3
  21. package/dist/{deploy-iXZ3F0N6.mjs → deploy-DTaWUS1S.mjs} +121 -110
  22. package/dist/{dev-inbox-DkgRWLkW.mjs → dev-inbox-P0u4tM8Y.mjs} +1 -1
  23. package/dist/{domain-B1VmoSr0.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-BcQzYgoG.mjs → env-DJHsPE7Z.mjs} +5 -5
  27. package/dist/{env-validation-ENpMy6Ez.mjs → env-validation-CF6KvTRf.mjs} +3 -1
  28. package/dist/{gen-DI2YwdBM.mjs → gen-B_wPnVTK.mjs} +2 -2
  29. package/dist/{github-cmd-xItS5Zwf.mjs → github-cmd-PW7ZnWTp.mjs} +3 -3
  30. package/dist/{headers-D8QfRX9Y.mjs → headers-BAHwgHdW.mjs} +1 -1
  31. package/dist/help-CwOX-zmI.mjs +2216 -0
  32. package/dist/{inbound-afAcWeQ9.d.mts → inbound-CH5Mksyy.d.mts} +34 -48
  33. package/dist/{inbound-2d0zi2yS.mjs → inbound-aVHEUhKo.mjs} +130 -100
  34. package/dist/index.mjs +54 -17
  35. package/dist/{init-BD-9THgn.mjs → init-BWZ7q5Z4.mjs} +11 -11
  36. package/dist/{link-RMdgjF1v.mjs → link-Rmvu2Wl_.mjs} +4 -4
  37. package/dist/{list-3F52R_yO.mjs → list-DEE2S6mY.mjs} +4 -4
  38. package/dist/{login-pV69H-ZO.mjs → login-Uvferzmm.mjs} +28 -10
  39. package/dist/{logs-DFHHD6wE.mjs → logs-27FenuiC.mjs} +4 -4
  40. package/dist/{mime-BJD7d_qL.mjs → mime-D5Nmdzf7.mjs} +23 -9
  41. package/dist/{node-Dk3H2jmU.mjs → node-Ez5KW5rn.mjs} +2 -2
  42. package/dist/operator-auth-B3e08unv.mjs +52 -0
  43. package/dist/operator-client-LUZnlnYk.mjs +82 -0
  44. package/dist/{operator-cmd-DYWRbWUA.mjs → operator-cmd-CjOTmAYE.mjs} +35 -55
  45. package/dist/{output-tFQLLj26.mjs → output-B0cfNSx5.mjs} +316 -2
  46. package/dist/pages/index.mjs +2 -2
  47. package/dist/platform-auth-config-DrbQXXiW.mjs +368 -0
  48. package/dist/platform-auth-protection-Bhtvp0B_.mjs +219 -0
  49. package/dist/platform-auth-recovery-CeOKGVeJ.mjs +310 -0
  50. package/dist/{platform-cmd-DxJ2FRwR.mjs → platform-cmd-BFhieCdV.mjs} +16 -6
  51. package/dist/{platform-domain-ChvbJkdy.mjs → platform-domain-C74PULqV.mjs} +4 -4
  52. package/dist/{platform-lifecycle-DN4MzJF_.mjs → platform-lifecycle-BwAIgz-t.mjs} +1667 -191
  53. package/dist/{platform-management-Db2PXw0B.mjs → platform-management-COogu_Se.mjs} +33 -7
  54. package/dist/{platform-recovery-C_YO-tIs.mjs → platform-recovery-ewqLefp1.mjs} +6 -5
  55. package/dist/{prepare-CBetXvsN.mjs → prepare-CtDJjoOj.mjs} +2 -2
  56. package/dist/{prepare-BfJvFUtJ.mjs → prepare-blNRQvQl.mjs} +2 -2
  57. package/dist/prerender-render.d.mts +11 -0
  58. package/dist/prerender-render.mjs +111 -0
  59. package/dist/{project-cmd-Mo0V9yKS.mjs → project-cmd-DmZK9Hxf.mjs} +32 -14
  60. package/dist/project-team-D8jOJMUJ.mjs +130 -0
  61. package/dist/project-token-DA34bf-C.mjs +75 -0
  62. package/dist/{provision-Blnstcm2.mjs → provision-CSJOjjQk.mjs} +2 -0
  63. package/dist/{requests-BcKOVpRg.mjs → requests-CUExwGQQ.mjs} +3 -3
  64. package/dist/{rollback-Bx85-0xh.mjs → rollback-CDNGU1gr.mjs} +4 -4
  65. package/dist/runtime/ai.mjs +3 -2
  66. package/dist/runtime/email/testing.d.mts +1 -1
  67. package/dist/runtime/email/testing.mjs +3 -3
  68. package/dist/runtime/email-protocol.d.mts +15 -0
  69. package/dist/runtime/email-protocol.mjs +70 -0
  70. package/dist/runtime/email.d.mts +2 -2
  71. package/dist/runtime/email.mjs +189 -96
  72. package/dist/runtime/remote/index.mjs +5 -3
  73. package/dist/{secret-ByhJ9AMl.mjs → secret-Bzzi2e9E.mjs} +5 -5
  74. package/dist/{skills-Q46GZMO-.mjs → skills-C0RvGjeE.mjs} +1 -1
  75. package/dist/{subcommand-prompt-WfySCQ7S.mjs → subcommand-prompt-Bmyn5Rlc.mjs} +1 -1
  76. package/package.json +12 -7
  77. package/skills/void/SKILL.md +35 -4
  78. package/skills/void/docs/guide/deployment.md +2 -0
  79. package/skills/void/docs/guide/email.md +102 -110
  80. package/skills/void/docs/guide/platform-administration.md +214 -2
  81. package/skills/void/docs/guide/platform-development.md +64 -2
  82. package/skills/void/docs/guide/project-collaboration.md +94 -0
  83. package/skills/void/docs/guide/self-hosted-platform.md +184 -30
  84. package/skills/void/docs/reference/cli.md +249 -21
  85. package/dist/cf-access-AJ1ehiFR.mjs +0 -42
  86. package/dist/cf-access-DsSsZUPr.mjs +0 -67
  87. package/dist/email-uKyQYUVY.mjs +0 -1016
@@ -0,0 +1,310 @@
1
+ import { I as isCancel, W as acquirePlatformLifecycleLock, et as readInstallationRecoverySecrets, it as resolveInstallationManifest, w as confirm } from "./output-B0cfNSx5.mjs";
2
+ import { f as openBrowser } from "./auth-DPl6kck4.mjs";
3
+ import { a as resolveOperatorOrigin } from "./operator-auth-B3e08unv.mjs";
4
+ import { C as PlatformCloudflareClient, c as platformDatabaseClientUrl, r as acquirePlatformDatabaseLifecycleLockConnection, resolvePlatformInstallDatabaseUrl, y as resolvePlatformCloudflareCredential } from "./platform-lifecycle-BwAIgz-t.mjs";
5
+ import { selectAuthoritativePlatformManifest, verifyPlatformInstallationOwnership } from "./platform-management-COogu_Se.mjs";
6
+ import { createCipheriv, createDecipheriv, createHash, createHmac, hkdfSync, randomBytes, randomUUID } from "node:crypto";
7
+ import { execFile } from "node:child_process";
8
+ import { parsePlatformAccessProtection, parsePlatformAdmissionPolicy, parsePlatformLoginConfiguration, platformAuthenticationRecoveryMessage, validatePlatformAdmissionPolicy } from "@void/platform";
9
+ import { setTimeout } from "node:timers/promises";
10
+ import { readFile } from "node:fs/promises";
11
+ //#region src/cli/platform-auth-recovery-core.ts
12
+ function parseAuthenticationRecoveryInput(value, connectionId) {
13
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("platform: Recovery requires a JSON object.");
14
+ const input = value;
15
+ if (Object.keys(input).some((key) => ![
16
+ "administratorUserId",
17
+ "expectedIdentity",
18
+ "configuration"
19
+ ].includes(key)) || typeof input.administratorUserId !== "string" || !input.administratorUserId.trim()) throw new Error("platform: Recovery requires an existing administratorUserId and accepts no plaintext secrets.");
20
+ let expectedIdentity;
21
+ if (input.expectedIdentity !== void 0) {
22
+ const identity = input.expectedIdentity;
23
+ if (!identity || typeof identity !== "object" || typeof identity.issuer !== "string" || !identity.issuer || typeof identity.subject !== "string" || !identity.subject || Object.keys(identity).some((key) => key !== "issuer" && key !== "subject")) throw new Error("platform: expectedIdentity requires the exact issuer and subject.");
24
+ expectedIdentity = {
25
+ issuer: identity.issuer,
26
+ subject: identity.subject
27
+ };
28
+ }
29
+ const configuration = input.configuration === void 0 ? void 0 : parsePlatformLoginConfiguration(input.configuration);
30
+ if (configuration && configuration.id !== connectionId) throw new Error("platform: Recovery configuration must match the selected connection.");
31
+ return {
32
+ administratorUserId: input.administratorUserId,
33
+ expectedIdentity,
34
+ configuration
35
+ };
36
+ }
37
+ function credentialKey(keysJson, version) {
38
+ const keys = JSON.parse(keysJson);
39
+ const raw = typeof keys[version] === "string" ? Buffer.from(keys[version], "base64") : null;
40
+ if (!raw || raw.length !== 32) throw new Error("platform: Restore the original platform encryption keyring before recovery.");
41
+ return Buffer.from(hkdfSync("sha256", raw, "void-platform-auth-v1", "login-client-credentials", 32));
42
+ }
43
+ function recoverAuthenticationCredential(keysJson, installationId, connectionId, revision, saved) {
44
+ try {
45
+ const encrypted = Buffer.from(saved.ciphertext, "base64");
46
+ const decipher = createDecipheriv("aes-256-gcm", credentialKey(keysJson, saved.keyVersion), Buffer.from(saved.iv, "base64"));
47
+ decipher.setAAD(Buffer.from(JSON.stringify([installationId, JSON.stringify([connectionId, revision])])));
48
+ decipher.setAuthTag(encrypted.subarray(-16));
49
+ return Buffer.concat([decipher.update(encrypted.subarray(0, -16)), decipher.final()]).toString("utf8");
50
+ } catch {
51
+ throw new Error("platform: The original platform encryption keyring is required for authentication recovery.");
52
+ }
53
+ }
54
+ function encryptCredential(keysJson, version, installationId, connectionId, revision, secret) {
55
+ const iv = Buffer.from(randomBytes(12));
56
+ const cipher = createCipheriv("aes-256-gcm", credentialKey(keysJson, version), iv);
57
+ cipher.setAAD(Buffer.from(JSON.stringify([installationId, JSON.stringify([connectionId, revision])])));
58
+ const ciphertext = Buffer.concat([
59
+ cipher.update(secret, "utf8"),
60
+ cipher.final(),
61
+ cipher.getAuthTag()
62
+ ]);
63
+ return {
64
+ keyVersion: version,
65
+ iv: iv.toString("base64"),
66
+ ciphertext: ciphertext.toString("base64")
67
+ };
68
+ }
69
+ async function transaction(db, action) {
70
+ await db.query("BEGIN");
71
+ try {
72
+ const result = await action();
73
+ await db.query("COMMIT");
74
+ return result;
75
+ } catch (error) {
76
+ await db.query("ROLLBACK");
77
+ throw error;
78
+ }
79
+ }
80
+ async function requireAdministrator(db, id) {
81
+ if (!(await db.query("SELECT id FROM users WHERE id = $1 AND role = 'admin' AND (suspended_reason IS NULL OR suspended_reason IN ('requests_exceeded', 'sandbox_runtime_exceeded')) FOR UPDATE", [id])).rows[0]) throw new Error("platform: Recovery can only restore an existing active administrator.");
82
+ }
83
+ async function readConnection(db, id) {
84
+ return (await db.query("SELECT c.id, c.kind, c.revision, c.generation, r.configuration, r.secret FROM platform_login_connections c JOIN platform_login_revisions r ON r.connection_id = c.id AND r.revision = c.revision WHERE c.id = $1", [id])).rows[0];
85
+ }
86
+ async function validateRecoveryAdmission(db, connectionId, revision) {
87
+ const settings = (await db.query("SELECT admission, protection FROM platform_auth_settings WHERE singleton = true FOR UPDATE")).rows[0];
88
+ if (!settings) throw new Error("platform: Authentication settings are unavailable for recovery.");
89
+ const prospective = await db.query("SELECT r.configuration FROM platform_login_connections c JOIN platform_login_revisions r ON r.connection_id = c.id AND r.revision = CASE WHEN c.id = $1 THEN $2 ELSE c.active_revision END WHERE c.enabled = true OR c.id = $1", [connectionId, revision]);
90
+ validatePlatformAdmissionPolicy(parsePlatformAdmissionPolicy(settings.admission), prospective.rows.map((row) => parsePlatformLoginConfiguration(row.configuration)), parsePlatformAccessProtection(settings.protection));
91
+ }
92
+ async function commitAuthenticationRecovery(db, input) {
93
+ const { recovery, identity } = input;
94
+ await transaction(db, async () => {
95
+ await db.query("LOCK TABLE platform_auth_settings IN SHARE ROW EXCLUSIVE MODE");
96
+ await requireAdministrator(db, recovery.administratorUserId);
97
+ const connection = await readConnection(db, recovery.connectionId);
98
+ if (!connection || connection.revision !== recovery.revision || connection.generation !== recovery.generation || Date.parse(recovery.expiresAt) <= Date.now()) throw new Error("platform: Recovery expired or configuration changed. Start again.");
99
+ await validateRecoveryAdmission(db, recovery.connectionId, recovery.revision);
100
+ if (!(await db.query("DELETE FROM platform_login_tests WHERE id = $1 AND user_id = $2 AND connection_id = $3 AND revision = $4 AND issuer = $5 AND subject = $6 AND expires_at > now() RETURNING id", [
101
+ recovery.testId,
102
+ recovery.administratorUserId,
103
+ recovery.connectionId,
104
+ recovery.revision,
105
+ identity.issuer,
106
+ identity.subject
107
+ ])).rows[0]) throw new Error("platform: Recovery requires a fresh successful login test of this exact identity.");
108
+ const owner = (await db.query("SELECT user_id FROM platform_login_identities WHERE connection_id = $1 AND issuer = $2 AND subject = $3", [
109
+ recovery.connectionId,
110
+ identity.issuer,
111
+ identity.subject
112
+ ])).rows[0];
113
+ if (owner && owner.user_id !== recovery.administratorUserId) throw new Error("platform: This identity belongs to another account. Recovery cannot take it over.");
114
+ if (connection.kind === "github" || connection.kind === "google") {
115
+ const legacyOwner = (await db.query("SELECT user_id FROM oauth_accounts WHERE provider = $1 AND provider_id = $2", [connection.kind, identity.subject])).rows[0];
116
+ if (legacyOwner && legacyOwner.user_id !== recovery.administratorUserId) throw new Error("platform: This identity belongs to another account.");
117
+ await db.query("DELETE FROM oauth_accounts WHERE provider = $1 AND user_id = $2", [connection.kind, recovery.administratorUserId]);
118
+ await db.query("INSERT INTO oauth_accounts (id, user_id, provider, provider_id) VALUES ($1,$2,$3,$4)", [
119
+ randomUUID(),
120
+ recovery.administratorUserId,
121
+ connection.kind,
122
+ identity.subject
123
+ ]);
124
+ }
125
+ await db.query("DELETE FROM platform_login_identities WHERE connection_id = $1 AND user_id = $2", [recovery.connectionId, recovery.administratorUserId]);
126
+ await db.query("INSERT INTO platform_login_identities (id, user_id, connection_id, issuer, subject) VALUES ($1,$2,$3,$4,$5)", [
127
+ randomUUID(),
128
+ recovery.administratorUserId,
129
+ recovery.connectionId,
130
+ identity.issuer,
131
+ identity.subject
132
+ ]);
133
+ await db.query("UPDATE platform_login_connections SET enabled = true, active_revision = $2, generation = generation + 1 WHERE id = $1", [recovery.connectionId, recovery.revision]);
134
+ await db.query("UPDATE platform_auth_settings SET revision = revision + 1, sessions_required = true WHERE singleton = true");
135
+ await db.query("UPDATE platform_human_sessions SET revoked_at = now() WHERE (connection_id = $1 OR user_id = $2) AND revoked_at IS NULL", [recovery.connectionId, recovery.administratorUserId]);
136
+ await db.query("DELETE FROM operator_sessions WHERE user_id = $1 OR human_session_id IS NULL", [recovery.administratorUserId]);
137
+ await db.query("DELETE FROM platform_login_tests WHERE connection_id = $1", [recovery.connectionId]);
138
+ await db.query("INSERT INTO operator_events (id,user_id,login,action,target,status,http_status) VALUES ($1,$2,$3,$4,$5,$6,$7)", [
139
+ randomUUID(),
140
+ recovery.administratorUserId,
141
+ "Installation owner recovery",
142
+ "auth.recovery.confirm",
143
+ JSON.stringify({
144
+ installationId: recovery.installationId,
145
+ connectionId: recovery.connectionId,
146
+ revision: recovery.revision,
147
+ identity
148
+ }),
149
+ "success",
150
+ 200
151
+ ]);
152
+ });
153
+ }
154
+ //#endregion
155
+ //#region src/cli/platform-auth-recovery.ts
156
+ async function runPlatformAuthRecovery(args) {
157
+ if (!args.file) throw new Error("platform: Recovery requires --file with administratorUserId and optional expectedIdentity/configuration.");
158
+ const input = parseAuthenticationRecoveryInput(JSON.parse(await readFile(args.file, "utf8")), args.target);
159
+ if (!args.plan && (!process.stdin.isTTY || args.yes) && (!args.yes || !input.expectedIdentity)) throw new Error("platform: Unattended recovery requires --yes and the exact expectedIdentity issuer and subject in --file.");
160
+ let manifest = resolveInstallationManifest(args.installation ?? "");
161
+ if (args.connection && resolveOperatorOrigin(args.connection) !== manifest.endpoints.apiUrl) throw new Error("platform: The selected connection does not match the recovery installation.");
162
+ const secrets = readInstallationRecoverySecrets(manifest);
163
+ if (!secrets?.jwtSecret || !secrets.projectSecretKeysJson || !secrets.projectSecretActiveKeyVersion) throw new Error("platform: Restore the original encrypted installation recovery secrets before recovering a login.");
164
+ const databaseUrl = resolvePlatformInstallDatabaseUrl(secrets, manifest);
165
+ const credential = await resolvePlatformCloudflareCredential();
166
+ const releaseLocal = acquirePlatformLifecycleLock(manifest.account.id, manifest.deployment.installationSlug);
167
+ const { Pool } = await import("pg");
168
+ const pool = new Pool({
169
+ connectionString: platformDatabaseClientUrl(databaseUrl),
170
+ max: 1
171
+ });
172
+ try {
173
+ const db = await pool.connect();
174
+ try {
175
+ const releaseDatabase = await acquirePlatformDatabaseLifecycleLockConnection(db, manifest.id, manifest.ownershipNonce);
176
+ try {
177
+ manifest = selectAuthoritativePlatformManifest(manifest, releaseDatabase.authoritativeManifest);
178
+ await verifyPlatformInstallationOwnership(new PlatformCloudflareClient(credential.token), manifest, { databaseUrl });
179
+ if (!manifest.endpoints.apiUrl) throw new Error("platform: Recover or repair the installed API endpoint first.");
180
+ const existing = await readConnection(db, args.target);
181
+ const keyProbe = existing ?? (await db.query("SELECT c.id,c.kind,r.revision,c.generation,r.configuration,r.secret FROM platform_login_connections c JOIN platform_login_revisions r ON c.id = r.connection_id LIMIT 1")).rows[0];
182
+ if (!keyProbe) throw new Error("platform: Finish first-administrator setup before using provider recovery.");
183
+ recoverAuthenticationCredential(secrets.projectSecretKeysJson, manifest.id, keyProbe.id, keyProbe.revision, keyProbe.secret);
184
+ const configuration = input.configuration ?? existing?.configuration;
185
+ if (!configuration) throw new Error("platform: A replacement connection requires configuration in --file.");
186
+ if (existing && (configuration.kind !== existing.kind || configuration.clientId !== existing.configuration.clientId || configuration.issuer !== existing.configuration.issuer)) throw new Error("platform: A different identity namespace requires a new connection ID.");
187
+ const suppliedSecret = args.clientSecretEnv ? process.env[args.clientSecretEnv] : void 0;
188
+ if (args.clientSecretEnv && !suppliedSecret || !existing && !suppliedSecret) throw new Error("platform: Supply the replacement client secret using --client-secret-env.");
189
+ if (suppliedSecret && suppliedSecret.length > 8192) throw new Error("platform: The client secret is too long.");
190
+ await requireAdministrator(db, input.administratorUserId);
191
+ const preview = {
192
+ installationId: manifest.id,
193
+ administratorUserId: input.administratorUserId,
194
+ connectionId: args.target,
195
+ configuration,
196
+ expectedIdentity: input.expectedIdentity,
197
+ effect: "Test this login, explicitly confirm the identity, and restore the existing administrator. Revoke affected human sessions."
198
+ };
199
+ console.log(JSON.stringify(preview, null, 2));
200
+ if (args.plan) return;
201
+ const proof = randomBytes(32).toString("hex");
202
+ const testId = `test.${randomUUID()}`;
203
+ const expiresAt = new Date(Date.now() + 6e5).toISOString();
204
+ const recovery = await transaction(db, async () => {
205
+ await db.query("LOCK TABLE platform_auth_settings IN SHARE ROW EXCLUSIVE MODE");
206
+ await requireAdministrator(db, input.administratorUserId);
207
+ const latest = await readConnection(db, args.target);
208
+ if (JSON.stringify(latest) !== JSON.stringify(existing)) throw new Error("platform: Login configuration changed. Start recovery again.");
209
+ let revision = existing?.revision ?? 1;
210
+ if (suppliedSecret || input.configuration) {
211
+ revision = existing ? existing.revision + 1 : 1;
212
+ const secret = suppliedSecret ?? recoverAuthenticationCredential(secrets.projectSecretKeysJson, manifest.id, existing.id, existing.revision, existing.secret);
213
+ const encrypted = encryptCredential(secrets.projectSecretKeysJson, secrets.projectSecretActiveKeyVersion, manifest.id, args.target, revision, secret);
214
+ if (!existing) await db.query("INSERT INTO platform_login_connections (id,kind) VALUES ($1,$2)", [args.target, configuration.kind]);
215
+ await db.query("INSERT INTO platform_login_revisions (connection_id,revision,configuration,secret) VALUES ($1,$2,$3::jsonb,$4::jsonb)", [
216
+ args.target,
217
+ revision,
218
+ JSON.stringify(configuration),
219
+ JSON.stringify(encrypted)
220
+ ]);
221
+ await db.query("UPDATE platform_login_connections SET revision = $2 WHERE id = $1", [args.target, revision]);
222
+ await db.query("UPDATE platform_auth_settings SET revision = revision + 1 WHERE singleton = true");
223
+ }
224
+ const unsigned = {
225
+ installationId: manifest.id,
226
+ administratorUserId: input.administratorUserId,
227
+ connectionId: args.target,
228
+ revision,
229
+ generation: existing?.generation ?? 1,
230
+ testId,
231
+ expiresAt
232
+ };
233
+ const recovery = {
234
+ ...unsigned,
235
+ signature: createHmac("sha256", secrets.jwtSecret).update(platformAuthenticationRecoveryMessage(unsigned)).digest("base64url")
236
+ };
237
+ await db.query("INSERT INTO platform_auth_attempts (id,connection_id,revision,generation,purpose,browser_hash,context,expires_at) VALUES ($1,$2,$3,$4,'test',$5,$6::jsonb,$7)", [
238
+ testId,
239
+ args.target,
240
+ revision,
241
+ recovery.generation,
242
+ createHash("sha256").update(proof).digest("hex"),
243
+ JSON.stringify({
244
+ phase: "launch",
245
+ actor: {
246
+ userId: input.administratorUserId,
247
+ sessionId: "",
248
+ audience: "api"
249
+ },
250
+ recovery
251
+ }),
252
+ expiresAt
253
+ ]);
254
+ await db.query("INSERT INTO operator_events (id,user_id,login,action,target,status,http_status) VALUES ($1,$2,$3,$4,$5,$6,$7)", [
255
+ randomUUID(),
256
+ input.administratorUserId,
257
+ "Installation owner recovery",
258
+ "auth.recovery.start",
259
+ JSON.stringify({
260
+ connectionId: args.target,
261
+ revision,
262
+ testId
263
+ }),
264
+ "success",
265
+ 200
266
+ ]);
267
+ return recovery;
268
+ });
269
+ const url = new URL("/auth/test", manifest.endpoints.apiUrl);
270
+ url.searchParams.set("id", testId);
271
+ url.searchParams.set("proof", proof);
272
+ console.log(`Complete the recovery login in your browser: ${url.href}`);
273
+ if (process.stdin.isTTY) openBrowser(url.href, execFile);
274
+ let tested;
275
+ while (Date.now() < Date.parse(expiresAt)) {
276
+ tested = (await db.query("SELECT issuer,subject,login FROM platform_login_tests WHERE id = $1 AND user_id = $2 AND expires_at > now()", [testId, input.administratorUserId])).rows[0];
277
+ if (tested) break;
278
+ await setTimeout(1e3);
279
+ }
280
+ if (!tested) throw new Error("platform: The recovery login expired. Run recovery again.");
281
+ console.log(JSON.stringify({
282
+ administratorUserId: input.administratorUserId,
283
+ authenticatedIdentity: tested
284
+ }, null, 2));
285
+ if (input.expectedIdentity && (input.expectedIdentity.issuer !== tested.issuer || input.expectedIdentity.subject !== tested.subject)) throw new Error("platform: The authenticated identity does not match expectedIdentity. No administrator link was changed.");
286
+ if (!args.yes) {
287
+ const accepted = await confirm({
288
+ message: `Bind issuer ${JSON.stringify(tested.issuer)}, subject ${JSON.stringify(tested.subject)} to existing administrator ${JSON.stringify(input.administratorUserId)}?`,
289
+ initialValue: false
290
+ });
291
+ if (isCancel(accepted) || !accepted) throw new Error("platform: Recovery cancelled.");
292
+ }
293
+ await commitAuthenticationRecovery(db, {
294
+ recovery,
295
+ identity: tested
296
+ });
297
+ console.log("Administrator login recovered. Sign in again with this login method.");
298
+ } finally {
299
+ await releaseDatabase();
300
+ }
301
+ } finally {
302
+ db.release();
303
+ }
304
+ } finally {
305
+ await pool.end();
306
+ releaseLocal();
307
+ }
308
+ }
309
+ //#endregion
310
+ export { runPlatformAuthRecovery };
@@ -1,12 +1,11 @@
1
- import { D as log, F as isCancel, G as normalizePlatformApiUrl, N as text, V as getActivePlatformConnection, W as listPlatformConnections, c as import_picocolors, et as savePlatformConnection, j as select, tt as selectPlatformConnection } from "./output-tFQLLj26.mjs";
2
- import { t as cfAccessHeaders } from "./cf-access-AJ1ehiFR.mjs";
3
- import { t as ensureCloudflaredToken } from "./cf-access-DsSsZUPr.mjs";
1
+ import { B as ensureCloudflaredToken, D as log, I as isCancel, K as getActivePlatformConnection, M as select, P as text, U as isCloudflareAccessChallenge, V as ensurePlatformAccessToken, X as normalizePlatformApiUrl, Y as listPlatformConnections, at as savePlatformConnection, c as import_picocolors, ot as selectPlatformConnection } from "./output-B0cfNSx5.mjs";
2
+ import { t as cfAccessHeaders } from "./cf-access-DRsQRe6k.mjs";
4
3
  import { parsePlatformDiscoveryDocument } from "@void/platform";
5
4
  //#region src/cli/platform-cmd.ts
6
5
  async function discoverPlatform(input, fetcher = fetch) {
7
6
  const apiUrl = normalizePlatformApiUrl(input);
8
7
  ensureCloudflaredToken(apiUrl);
9
- const response = await fetcher(`${apiUrl}/.well-known/void-platform`, {
8
+ const request = () => fetcher(`${apiUrl}/.well-known/void-platform`, {
10
9
  headers: {
11
10
  Accept: "application/json",
12
11
  ...cfAccessHeaders(process.env, apiUrl)
@@ -14,6 +13,12 @@ async function discoverPlatform(input, fetcher = fetch) {
14
13
  redirect: "manual",
15
14
  signal: AbortSignal.timeout(15e3)
16
15
  });
16
+ let response = await request();
17
+ if (isCloudflareAccessChallenge(response)) {
18
+ await response.body?.cancel();
19
+ ensurePlatformAccessToken(apiUrl, true);
20
+ response = await request();
21
+ }
17
22
  if (!response.ok) throw new Error(`platform: Discovery failed at ${apiUrl} (HTTP ${response.status}).`);
18
23
  let value;
19
24
  try {
@@ -83,8 +88,13 @@ function saveDiscoveredPlatform(discovery) {
83
88
  });
84
89
  }
85
90
  async function runPlatformCommand(_root, args) {
91
+ if (args.subcommand === "config") {
92
+ const { runPlatformAuthConfig } = await import("./platform-auth-config-DrbQXXiW.mjs");
93
+ await runPlatformAuthConfig(args);
94
+ return;
95
+ }
86
96
  if (args.subcommand === "operator") {
87
- const { runOperatorCommand } = await import("./operator-cmd-DYWRbWUA.mjs");
97
+ const { runOperatorCommand } = await import("./operator-cmd-CjOTmAYE.mjs");
88
98
  await runOperatorCommand(args);
89
99
  return;
90
100
  }
@@ -116,7 +126,7 @@ async function runPlatformCommand(_root, args) {
116
126
  console.log(`Capabilities: ${Object.entries(discovery.capabilities).filter(([, enabled]) => enabled).map(([name]) => name).join(", ")}`);
117
127
  return;
118
128
  }
119
- const { runPlatformLifecycleCommand } = await import("./platform-lifecycle-DN4MzJF_.mjs");
129
+ const { runPlatformLifecycleCommand } = await import("./platform-lifecycle-BwAIgz-t.mjs");
120
130
  await runPlatformLifecycleCommand(args);
121
131
  }
122
132
  //#endregion
@@ -1,7 +1,7 @@
1
- import { D as log, F as isCancel, N as text, O as note, Y as readInstallationRecoverySecrets, et as savePlatformConnection, nt as writeInstallationManifest, w as confirm, z as acquirePlatformLifecycleLock } from "./output-tFQLLj26.mjs";
2
- import { t as cfAccessHeaders } from "./cf-access-AJ1ehiFR.mjs";
3
- import { S as resolvePlatformCloudflareCredential, assertNoninteractivePlatformMutationAcknowledged, d as createPlatformInstallPlan, g as PlatformCloudflareClient, l as recordPlatformWorkerTraffic, n as acquirePlatformDatabaseLifecycleLock, r as createPlatformHealthProbeToken, resolvePlatformInstallDatabaseUrl, s as recordResource, x as assertPlatformOperatorCredentialCanApplyPlan } from "./platform-lifecycle-DN4MzJF_.mjs";
4
- import { resolveInstalledPlatformManifest, selectAuthoritativePlatformManifest, verifyPlatformInstallationOwnership } from "./platform-management-Db2PXw0B.mjs";
1
+ import { D as log, I as isCancel, P as text, W as acquirePlatformLifecycleLock, at as savePlatformConnection, et as readInstallationRecoverySecrets, k as note, st as writeInstallationManifest, w as confirm } from "./output-B0cfNSx5.mjs";
2
+ import { t as cfAccessHeaders } from "./cf-access-DRsQRe6k.mjs";
3
+ import { C as PlatformCloudflareClient, assertNoninteractivePlatformMutationAcknowledged, d as recordPlatformWorkerTraffic, i as createPlatformHealthProbeToken, l as recordResource, n as acquirePlatformDatabaseLifecycleLock, p as createPlatformInstallPlan, resolvePlatformInstallDatabaseUrl, v as assertPlatformOperatorCredentialCanApplyPlan, y as resolvePlatformCloudflareCredential } from "./platform-lifecycle-BwAIgz-t.mjs";
4
+ import { resolveInstalledPlatformManifest, selectAuthoritativePlatformManifest, verifyPlatformInstallationOwnership } from "./platform-management-COogu_Se.mjs";
5
5
  import { randomUUID } from "node:crypto";
6
6
  import { PLATFORM_APPLICATION_KEY, PLATFORM_REMOTE_INSTALLATION_MANIFEST_KEY, createApplicationRoutes, createPlatformDeploymentManifest, normalizeApplicationDomain } from "@void/platform";
7
7
  //#region src/cli/platform-domain.ts