@go-labs-sg/rsvp 0.13.1 → 0.14.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/README.md +11 -1
- package/dist/index.js +322 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -41,6 +41,16 @@ bun add --global @go-labs-sg/rsvp
|
|
|
41
41
|
rsvp version
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
## Secure credential storage
|
|
45
|
+
|
|
46
|
+
`GO_LABS_CLI_CREDENTIAL_ACCOUNT` selects the native credential-store account for this CLI. The default is `default`, preserving existing production logins. The downloadable staging launcher sets it to `staging` so a staging login does not replace the production login; keep the same account value for login and subsequent commands. The launcher also sets `GO_LABS_CLI_SKIP_DOTENV=1`; this is harmless for RSVP, which does not explicitly load the current-directory `.env` file.
|
|
47
|
+
|
|
48
|
+
Browser sessions use `Bun.secrets`: macOS Keychain, Windows Credential Manager, or Linux Secret Service. Linux requires libsecret, a reachable user D-Bus session, and an unlocked Secret Service collection. SSH and containers must provide those native prerequisites themselves; `--no-browser` only lets approval happen on another device. Denied D-Bus access is a host permissions/session issue, not something the CLI bypasses.
|
|
49
|
+
|
|
50
|
+
Before requesting approval, login verifies write/read/delete capability with a unique non-secret probe. Native operations have a 10-second process deadline and sanitized errors. Issued credentials are read back and verified; failed persistence attempts remote revocation and reports local and remote cleanup separately. There is no plaintext fallback or external credential-helper configuration.
|
|
51
|
+
|
|
52
|
+
See the [shared authentication contract](../../cli-auth/README.md) for recovery and verification limits.
|
|
53
|
+
|
|
44
54
|
## Authentication
|
|
45
55
|
|
|
46
56
|
```sh
|
|
@@ -53,7 +63,7 @@ rsvp auth whoami
|
|
|
53
63
|
|
|
54
64
|
Registration CLI access is limited to global administrators. The server rechecks the account's current global role and ban state when issuing, refreshing, and authenticating CLI credentials, so removing the admin role immediately blocks further CLI access.
|
|
55
65
|
|
|
56
|
-
The CLI polls automatically and stores only Registration-issued credentials through `Bun.secrets`. It does not store browser or email-OTP credentials and has no API-key or environment
|
|
66
|
+
The CLI polls automatically and stores only Registration-issued credentials through `Bun.secrets`. It does not store browser or email-OTP credentials and has no raw API-key or token-environment fallback. Use `rsvp auth logout` to revoke the remote session and remove its local credential. For SSH or containers, pass `--no-browser` and open the printed URL on another device.
|
|
57
67
|
|
|
58
68
|
The default API is `https://rsvp.getout.events`. Override it with `RSVP_API_URL` or `--api-url`; non-local overrides must use HTTPS.
|
|
59
69
|
|
package/dist/index.js
CHANGED
|
@@ -1017,6 +1017,8 @@ function datetime(args) {
|
|
|
1017
1017
|
}
|
|
1018
1018
|
var anyString = /^[\s\S]{0,}$/;
|
|
1019
1019
|
var number = /^-?\d+(?:\.\d+)?$/;
|
|
1020
|
+
var boolean = /^(?:true|false)$/i;
|
|
1021
|
+
var _null = /^null$/i;
|
|
1020
1022
|
var lowercase = /^[^A-Z]*$/;
|
|
1021
1023
|
var uppercase = /^[^a-z]*$/;
|
|
1022
1024
|
|
|
@@ -1898,6 +1900,43 @@ var $ZodNumberFormat = /* @__PURE__ */ $constructor("$ZodNumberFormat", (inst, d
|
|
|
1898
1900
|
$ZodCheckNumberFormat.init(inst, def);
|
|
1899
1901
|
$ZodNumber.init(inst, def);
|
|
1900
1902
|
});
|
|
1903
|
+
var $ZodBoolean = /* @__PURE__ */ $constructor("$ZodBoolean", (inst, def) => {
|
|
1904
|
+
$ZodType.init(inst, def);
|
|
1905
|
+
inst._zod.pattern = boolean;
|
|
1906
|
+
inst._zod.parse = (payload, _ctx) => {
|
|
1907
|
+
if (def.coerce)
|
|
1908
|
+
try {
|
|
1909
|
+
payload.value = Boolean(payload.value);
|
|
1910
|
+
} catch (_) {}
|
|
1911
|
+
const input = payload.value;
|
|
1912
|
+
if (typeof input === "boolean")
|
|
1913
|
+
return payload;
|
|
1914
|
+
payload.issues.push({
|
|
1915
|
+
expected: "boolean",
|
|
1916
|
+
code: "invalid_type",
|
|
1917
|
+
input,
|
|
1918
|
+
inst
|
|
1919
|
+
});
|
|
1920
|
+
return payload;
|
|
1921
|
+
};
|
|
1922
|
+
});
|
|
1923
|
+
var $ZodNull = /* @__PURE__ */ $constructor("$ZodNull", (inst, def) => {
|
|
1924
|
+
$ZodType.init(inst, def);
|
|
1925
|
+
inst._zod.pattern = _null;
|
|
1926
|
+
inst._zod.values = new Set([null]);
|
|
1927
|
+
inst._zod.parse = (payload, _ctx) => {
|
|
1928
|
+
const input = payload.value;
|
|
1929
|
+
if (input === null)
|
|
1930
|
+
return payload;
|
|
1931
|
+
payload.issues.push({
|
|
1932
|
+
expected: "null",
|
|
1933
|
+
code: "invalid_type",
|
|
1934
|
+
input,
|
|
1935
|
+
inst
|
|
1936
|
+
});
|
|
1937
|
+
return payload;
|
|
1938
|
+
};
|
|
1939
|
+
});
|
|
1901
1940
|
var $ZodUnknown = /* @__PURE__ */ $constructor("$ZodUnknown", (inst, def) => {
|
|
1902
1941
|
$ZodType.init(inst, def);
|
|
1903
1942
|
inst._zod.parse = (payload) => payload;
|
|
@@ -3435,6 +3474,18 @@ function _int(Class, params) {
|
|
|
3435
3474
|
...normalizeParams(params)
|
|
3436
3475
|
});
|
|
3437
3476
|
}
|
|
3477
|
+
function _boolean(Class, params) {
|
|
3478
|
+
return new Class({
|
|
3479
|
+
type: "boolean",
|
|
3480
|
+
...normalizeParams(params)
|
|
3481
|
+
});
|
|
3482
|
+
}
|
|
3483
|
+
function _null2(Class, params) {
|
|
3484
|
+
return new Class({
|
|
3485
|
+
type: "null",
|
|
3486
|
+
...normalizeParams(params)
|
|
3487
|
+
});
|
|
3488
|
+
}
|
|
3438
3489
|
function _unknown(Class) {
|
|
3439
3490
|
return new Class({
|
|
3440
3491
|
type: "unknown"
|
|
@@ -4317,6 +4368,18 @@ var numberProcessor = (schema, ctx, _json, params) => {
|
|
|
4317
4368
|
json.allOf = [...json.allOf ?? [], ...rest.map((m) => ({ multipleOf: m }))];
|
|
4318
4369
|
}
|
|
4319
4370
|
};
|
|
4371
|
+
var booleanProcessor = (_schema, _ctx, json, _params) => {
|
|
4372
|
+
json.type = "boolean";
|
|
4373
|
+
};
|
|
4374
|
+
var nullProcessor = (_schema, ctx, json, _params) => {
|
|
4375
|
+
if (ctx.target === "openapi-3.0") {
|
|
4376
|
+
json.type = "string";
|
|
4377
|
+
json.nullable = true;
|
|
4378
|
+
json.enum = [null];
|
|
4379
|
+
} else {
|
|
4380
|
+
json.type = "null";
|
|
4381
|
+
}
|
|
4382
|
+
};
|
|
4320
4383
|
var neverProcessor = (_schema, _ctx, json, _params) => {
|
|
4321
4384
|
json.not = {};
|
|
4322
4385
|
};
|
|
@@ -5111,6 +5174,22 @@ var ZodNumberFormat = /* @__PURE__ */ $constructor("ZodNumberFormat", (inst, def
|
|
|
5111
5174
|
function int(params) {
|
|
5112
5175
|
return _int(ZodNumberFormat, params);
|
|
5113
5176
|
}
|
|
5177
|
+
var ZodBoolean = /* @__PURE__ */ $constructor("ZodBoolean", (inst, def) => {
|
|
5178
|
+
$ZodBoolean.init(inst, def);
|
|
5179
|
+
ZodType.init(inst, def);
|
|
5180
|
+
inst._zod.processJSONSchema = (ctx, json, params) => booleanProcessor(inst, ctx, json, params);
|
|
5181
|
+
});
|
|
5182
|
+
function boolean2(params) {
|
|
5183
|
+
return _boolean(ZodBoolean, params);
|
|
5184
|
+
}
|
|
5185
|
+
var ZodNull = /* @__PURE__ */ $constructor("ZodNull", (inst, def) => {
|
|
5186
|
+
$ZodNull.init(inst, def);
|
|
5187
|
+
ZodType.init(inst, def);
|
|
5188
|
+
inst._zod.processJSONSchema = (ctx, json, params) => nullProcessor(inst, ctx, json, params);
|
|
5189
|
+
});
|
|
5190
|
+
function _null3(params) {
|
|
5191
|
+
return _null2(ZodNull, params);
|
|
5192
|
+
}
|
|
5114
5193
|
var ZodUnknown = /* @__PURE__ */ $constructor("ZodUnknown", (inst, def) => {
|
|
5115
5194
|
$ZodUnknown.init(inst, def);
|
|
5116
5195
|
ZodType.init(inst, def);
|
|
@@ -5529,6 +5608,9 @@ var storedCliCredentialsSchema = object({
|
|
|
5529
5608
|
session: cliSessionIdentitySchema
|
|
5530
5609
|
}).strict();
|
|
5531
5610
|
var cliProductSchema = _enum(cliProducts);
|
|
5611
|
+
// ../../cli-auth/src/client/secret-backend.ts
|
|
5612
|
+
import { execFile } from "child_process";
|
|
5613
|
+
|
|
5532
5614
|
// ../../cli-auth/src/client/runtime.ts
|
|
5533
5615
|
var MINIMUM_BUN_VERSION = [1, 4, 0];
|
|
5534
5616
|
var parseBunVersion = (version) => {
|
|
@@ -5554,27 +5636,145 @@ var assertBunRuntime = (runtime = globalThis.Bun) => {
|
|
|
5554
5636
|
}
|
|
5555
5637
|
};
|
|
5556
5638
|
|
|
5557
|
-
// ../../cli-auth/src/client/
|
|
5639
|
+
// ../../cli-auth/src/client/storage-error.ts
|
|
5640
|
+
var guidance = {
|
|
5641
|
+
"missing-library": "Install the runtime's native credential-store library and ensure your own native keyring/session is accessible and unlocked; Linux desktop storage requires libsecret AND an accessible, unlocked Secret Service.",
|
|
5642
|
+
"bus-unavailable": "The Linux user D-Bus session is absent or unreachable. --no-browser does not provide storage. Start or repair your own user session and native keyring before retrying.",
|
|
5643
|
+
"bus-denied": "Access to the Linux user bus was denied. Fix your own native keyring/session permissions or host policy; do not change bus/socket permissions or borrow another user's bus.",
|
|
5644
|
+
"service-unavailable": "The user bus is reachable but your own Secret Service is unavailable. Start or repair your own native keyring session; do not expose another user's store.",
|
|
5645
|
+
locked: "Unlock your own native credential collection/session before retrying. The CLI will not wait indefinitely for an unlock prompt.",
|
|
5646
|
+
timeout: "The native credential backend did not finish within 10 seconds and its process was terminated. Fix or unlock your own native keyring/session outside the CLI; the CLI will not bypass host permissions.",
|
|
5647
|
+
"invalid-response": "The native credential backend returned an invalid response. Check your own keyring/session and configuration; no credential output was disclosed.",
|
|
5648
|
+
"read-failed": "The native credential backend could not read credentials. Check your own keyring/session availability and permissions; this is not evidence that you are logged out.",
|
|
5649
|
+
"write-failed": "The native credential backend could not persist credentials. Check your own keyring/session write permissions and availability; no plaintext fallback is used.",
|
|
5650
|
+
"delete-failed": "The native credential backend could not delete its entry. Check your own keyring/session permissions and remove any non-secret preflight probe through that backend if necessary."
|
|
5651
|
+
};
|
|
5652
|
+
|
|
5558
5653
|
class CliCredentialStoreUnavailableError extends Error {
|
|
5559
5654
|
code = "CLI_CREDENTIAL_STORE_UNAVAILABLE";
|
|
5560
|
-
|
|
5561
|
-
|
|
5655
|
+
reason;
|
|
5656
|
+
constructor(product, reason = "read-failed") {
|
|
5657
|
+
super(`Secure credential storage is unavailable for the ${product} CLI [CLI_CREDENTIAL_STORE_UNAVAILABLE:${reason}]. ${guidance[reason]}`);
|
|
5562
5658
|
this.name = "CliCredentialStoreUnavailableError";
|
|
5563
|
-
|
|
5564
|
-
}
|
|
5565
|
-
|
|
5659
|
+
this.reason = reason;
|
|
5660
|
+
}
|
|
5661
|
+
}
|
|
5662
|
+
var classifyCredentialStoreFailure = (message, fallback) => {
|
|
5663
|
+
if (/libsecret.*(not found|not available|cannot open|load)|dlopen|shared (object|library).*not found/i.test(message))
|
|
5664
|
+
return "missing-library";
|
|
5665
|
+
if (/(d-?bus|user bus|session bus).*(denied|not permitted)|(?:denied|not permitted).*(d-?bus|user bus|session bus)/i.test(message))
|
|
5666
|
+
return "bus-denied";
|
|
5667
|
+
if (/locked|interaction.*not allowed|user.*cancel/i.test(message))
|
|
5668
|
+
return "locked";
|
|
5669
|
+
if (/ServiceUnknown|org\.freedesktop\.secrets.*(not provided|no owner|not available)|secret.?service.*(unavailable|not running)/i.test(message))
|
|
5670
|
+
return "service-unavailable";
|
|
5671
|
+
if (/d-?bus|user bus|session bus|autolaunch/i.test(message))
|
|
5672
|
+
return "bus-unavailable";
|
|
5673
|
+
return fallback;
|
|
5674
|
+
};
|
|
5675
|
+
|
|
5676
|
+
class CliCredentialPersistenceError extends Error {
|
|
5677
|
+
remoteRevoked;
|
|
5678
|
+
localRestored;
|
|
5679
|
+
phase;
|
|
5680
|
+
code = "CLI_CREDENTIAL_PERSISTENCE_FAILED";
|
|
5681
|
+
constructor(remoteRevoked, localRestored, phase) {
|
|
5682
|
+
super(`CLI credentials could not be saved after ${phase} [CLI_CREDENTIAL_PERSISTENCE_FAILED]. ${remoteRevoked ? "The newly issued session was revoked." : "Remote revocation could not be confirmed; revoke the session in the product's session management before retrying."} ${localRestored ? phase === "login" ? "The previous local credentials were preserved." : "The unusable rotated local credentials were removed; sign in again." : "Local cleanup could not be confirmed. Inspect your own native credential backend before retrying."}`);
|
|
5683
|
+
this.remoteRevoked = remoteRevoked;
|
|
5684
|
+
this.localRestored = localRestored;
|
|
5685
|
+
this.phase = phase;
|
|
5686
|
+
this.name = "CliCredentialPersistenceError";
|
|
5687
|
+
}
|
|
5688
|
+
}
|
|
5689
|
+
var getCliStorageError = (error) => {
|
|
5690
|
+
if (error instanceof CliCredentialStoreUnavailableError)
|
|
5691
|
+
return {
|
|
5692
|
+
code: error.code,
|
|
5693
|
+
message: error.message,
|
|
5694
|
+
details: { reason: error.reason }
|
|
5695
|
+
};
|
|
5696
|
+
if (error instanceof CliCredentialPersistenceError)
|
|
5697
|
+
return {
|
|
5698
|
+
code: error.code,
|
|
5699
|
+
message: error.message,
|
|
5700
|
+
details: {
|
|
5701
|
+
remoteRevoked: error.remoteRevoked,
|
|
5702
|
+
localRestored: error.localRestored,
|
|
5703
|
+
phase: error.phase
|
|
5704
|
+
}
|
|
5705
|
+
};
|
|
5706
|
+
return;
|
|
5707
|
+
};
|
|
5708
|
+
|
|
5709
|
+
// ../../cli-auth/src/client/secret-backend.ts
|
|
5710
|
+
var timeoutMs = 1e4;
|
|
5711
|
+
var maxBuffer = 64 * 1024;
|
|
5712
|
+
var runBackend = (product, executable, args, input, failure) => new Promise((resolve, reject) => {
|
|
5713
|
+
const child = execFile(executable, args, {
|
|
5714
|
+
timeout: timeoutMs,
|
|
5715
|
+
killSignal: "SIGKILL",
|
|
5716
|
+
maxBuffer,
|
|
5717
|
+
windowsHide: true
|
|
5718
|
+
}, (error, stdout, stderr) => {
|
|
5719
|
+
if (error) {
|
|
5720
|
+
const reason = error.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" ? "invalid-response" : error.killed ? "timeout" : classifyCredentialStoreFailure(`${stdout}
|
|
5721
|
+
${stderr}`, failure);
|
|
5722
|
+
reject(new CliCredentialStoreUnavailableError(product, reason));
|
|
5723
|
+
return;
|
|
5724
|
+
}
|
|
5725
|
+
resolve(stdout);
|
|
5726
|
+
});
|
|
5727
|
+
child.stdin?.on("error", () => {});
|
|
5728
|
+
child.stdin?.end(input);
|
|
5729
|
+
});
|
|
5730
|
+
var nativeWorker = `
|
|
5731
|
+
try {
|
|
5732
|
+
const { operation, input } = JSON.parse(await Bun.stdin.text());
|
|
5733
|
+
const value = await Bun.secrets[operation](input);
|
|
5734
|
+
process.stdout.write(JSON.stringify({ value: value ?? null }));
|
|
5735
|
+
} catch (error) {
|
|
5736
|
+
const message = error instanceof Error ? error.message : "Credential backend failed";
|
|
5737
|
+
// libsecret's bus transport errors can omit the D-Bus context.
|
|
5738
|
+
const context = process.platform === "linux" && /^Could not connect:/i.test(message) ? "D-Bus: " : "";
|
|
5739
|
+
process.stderr.write(context + message);
|
|
5740
|
+
process.exitCode = 1;
|
|
5741
|
+
}
|
|
5742
|
+
`;
|
|
5743
|
+
var nativeResponse = object({ value: union([string2(), boolean2(), _null3()]) }).strict();
|
|
5744
|
+
var createIsolatedBunSecrets = (product) => {
|
|
5745
|
+
const run = async (operation, input) => {
|
|
5746
|
+
assertBunRuntime();
|
|
5747
|
+
const output = await runBackend(product, process.execPath, ["--no-env-file", "--eval", nativeWorker], JSON.stringify({ operation, input }), operation === "set" ? "write-failed" : operation === "delete" ? "delete-failed" : "read-failed");
|
|
5748
|
+
try {
|
|
5749
|
+
return nativeResponse.parse(JSON.parse(output)).value;
|
|
5750
|
+
} catch {
|
|
5751
|
+
throw new CliCredentialStoreUnavailableError(product, "invalid-response");
|
|
5752
|
+
}
|
|
5753
|
+
};
|
|
5754
|
+
return {
|
|
5755
|
+
get: async (input) => {
|
|
5756
|
+
const value = await run("get", input);
|
|
5757
|
+
if (value === null || typeof value === "string")
|
|
5758
|
+
return value;
|
|
5759
|
+
throw new CliCredentialStoreUnavailableError(product, "invalid-response");
|
|
5760
|
+
},
|
|
5761
|
+
set: async (input) => {
|
|
5762
|
+
await run("set", input);
|
|
5763
|
+
},
|
|
5764
|
+
delete: async (input) => await run("delete", input) === true
|
|
5765
|
+
};
|
|
5766
|
+
};
|
|
5767
|
+
|
|
5768
|
+
// ../../cli-auth/src/client/credential-store.ts
|
|
5769
|
+
var runSecretOperation = async (product, operation, fallback = "read-failed") => {
|
|
5566
5770
|
try {
|
|
5567
5771
|
return await operation();
|
|
5568
5772
|
} catch (cause) {
|
|
5569
5773
|
if (cause instanceof CliCredentialStoreUnavailableError)
|
|
5570
5774
|
throw cause;
|
|
5571
|
-
throw new CliCredentialStoreUnavailableError(product,
|
|
5775
|
+
throw new CliCredentialStoreUnavailableError(product, classifyCredentialStoreFailure(cause instanceof Error ? cause.message : "", fallback));
|
|
5572
5776
|
}
|
|
5573
5777
|
};
|
|
5574
|
-
var defaultSecrets = () => {
|
|
5575
|
-
assertBunRuntime();
|
|
5576
|
-
return Bun.secrets;
|
|
5577
|
-
};
|
|
5578
5778
|
var refreshLockTimeoutMs = 5 * 60 * 1000;
|
|
5579
5779
|
var refreshLockRetryMs = 50;
|
|
5580
5780
|
var getCredentialLockDirectoryPath = () => {
|
|
@@ -5646,18 +5846,38 @@ var acquireRefreshLock = async (path) => {
|
|
|
5646
5846
|
}
|
|
5647
5847
|
throw new Error("Timed out waiting for another CLI process to refresh credentials. Retry the command.");
|
|
5648
5848
|
};
|
|
5849
|
+
var resolveCliCredentialAccount = (environment = process.env) => environment.GO_LABS_CLI_CREDENTIAL_ACCOUNT?.trim() || "default";
|
|
5649
5850
|
var createBunCredentialStore = ({
|
|
5650
|
-
account =
|
|
5851
|
+
account = resolveCliCredentialAccount(),
|
|
5651
5852
|
product,
|
|
5652
|
-
secrets =
|
|
5853
|
+
secrets = createIsolatedBunSecrets(product)
|
|
5653
5854
|
}) => {
|
|
5654
5855
|
const service = `go-labs-cli:${product}`;
|
|
5655
5856
|
return {
|
|
5857
|
+
preflight: async () => {
|
|
5858
|
+
const input = { name: `probe-${randomUUID()}`, service };
|
|
5859
|
+
const value = `go-labs-non-secret-probe:${randomUUID()}`;
|
|
5860
|
+
try {
|
|
5861
|
+
await runSecretOperation(product, () => secrets.set({ ...input, value }), "write-failed");
|
|
5862
|
+
if (await runSecretOperation(product, () => secrets.get(input)) !== value) {
|
|
5863
|
+
throw new CliCredentialStoreUnavailableError(product, "write-failed");
|
|
5864
|
+
}
|
|
5865
|
+
} catch (error) {
|
|
5866
|
+
await secrets.delete(input).catch(() => {
|
|
5867
|
+
return;
|
|
5868
|
+
});
|
|
5869
|
+
throw error;
|
|
5870
|
+
}
|
|
5871
|
+
await runSecretOperation(product, () => secrets.delete(input), "delete-failed");
|
|
5872
|
+
if (await runSecretOperation(product, () => secrets.get(input)) !== null) {
|
|
5873
|
+
throw new CliCredentialStoreUnavailableError(product, "delete-failed");
|
|
5874
|
+
}
|
|
5875
|
+
},
|
|
5656
5876
|
delete: async () => {
|
|
5657
|
-
await
|
|
5877
|
+
await runSecretOperation(product, () => secrets.delete({ name: account, service }), "delete-failed");
|
|
5658
5878
|
},
|
|
5659
5879
|
get: async () => {
|
|
5660
|
-
const value = await
|
|
5880
|
+
const value = await runSecretOperation(product, () => secrets.get({ name: account, service }));
|
|
5661
5881
|
if (value === null)
|
|
5662
5882
|
return null;
|
|
5663
5883
|
try {
|
|
@@ -5668,11 +5888,11 @@ var createBunCredentialStore = ({
|
|
|
5668
5888
|
},
|
|
5669
5889
|
set: async (credentials) => {
|
|
5670
5890
|
const parsed = storedCliCredentialsSchema.parse(credentials);
|
|
5671
|
-
await
|
|
5891
|
+
await runSecretOperation(product, () => secrets.set({
|
|
5672
5892
|
name: account,
|
|
5673
5893
|
service,
|
|
5674
5894
|
value: JSON.stringify(parsed)
|
|
5675
|
-
}));
|
|
5895
|
+
}), "write-failed");
|
|
5676
5896
|
},
|
|
5677
5897
|
withCredentialLock: async (operation) => {
|
|
5678
5898
|
const refreshLockPath = await getCredentialLockPath(product, account);
|
|
@@ -5693,6 +5913,7 @@ var createBunCredentialStore = ({
|
|
|
5693
5913
|
};
|
|
5694
5914
|
// ../../cli-auth/src/client/login.ts
|
|
5695
5915
|
import { hostname } from "os";
|
|
5916
|
+
import { isDeepStrictEqual } from "util";
|
|
5696
5917
|
var normalizeCliAuthIssuer = (value) => {
|
|
5697
5918
|
const url = new URL(value);
|
|
5698
5919
|
url.hash = "";
|
|
@@ -5755,12 +5976,15 @@ var openSystemBrowser = async (url) => {
|
|
|
5755
5976
|
"-NoProfile",
|
|
5756
5977
|
"-NonInteractive",
|
|
5757
5978
|
"-Command",
|
|
5758
|
-
"Start-Process -FilePath $
|
|
5759
|
-
url
|
|
5979
|
+
"Start-Process -FilePath $env:GO_LABS_CLI_VERIFICATION_URI -ErrorAction Stop"
|
|
5760
5980
|
] : ["xdg-open", url];
|
|
5761
5981
|
try {
|
|
5762
|
-
const
|
|
5763
|
-
|
|
5982
|
+
const browserProcess = Bun.spawn(command, {
|
|
5983
|
+
env: process.platform === "win32" ? { ...process.env, GO_LABS_CLI_VERIFICATION_URI: url } : process.env,
|
|
5984
|
+
stderr: "ignore",
|
|
5985
|
+
stdout: "ignore"
|
|
5986
|
+
});
|
|
5987
|
+
return await browserProcess.exited === 0;
|
|
5764
5988
|
} catch {
|
|
5765
5989
|
return false;
|
|
5766
5990
|
}
|
|
@@ -5778,6 +6002,8 @@ var loginWithBrowser = async ({
|
|
|
5778
6002
|
store
|
|
5779
6003
|
}) => {
|
|
5780
6004
|
const baseUrl = normalizeCliAuthIssuer(apiBaseUrl);
|
|
6005
|
+
if (store.preflight)
|
|
6006
|
+
await withCredentialLock(store, () => store.preflight?.() ?? Promise.resolve());
|
|
5781
6007
|
const pkce = createPkcePair();
|
|
5782
6008
|
const beginResponse = await postJson(fetchImplementation, `${baseUrl}/api/cli-auth/device`, {
|
|
5783
6009
|
clientName,
|
|
@@ -5805,7 +6031,19 @@ var loginWithBrowser = async ({
|
|
|
5805
6031
|
if (response.ok) {
|
|
5806
6032
|
const issued = tokenResponseSchema.parse(payload);
|
|
5807
6033
|
const credentials = toStoredCredentials(issued, baseUrl, now());
|
|
5808
|
-
|
|
6034
|
+
try {
|
|
6035
|
+
await withCredentialLock(store, () => persistIssuedCredentials({
|
|
6036
|
+
store,
|
|
6037
|
+
credentials,
|
|
6038
|
+
apiBaseUrl: baseUrl,
|
|
6039
|
+
fetch: fetchImplementation,
|
|
6040
|
+
phase: "login"
|
|
6041
|
+
}));
|
|
6042
|
+
} catch (error) {
|
|
6043
|
+
if (error instanceof CliCredentialPersistenceError)
|
|
6044
|
+
throw error;
|
|
6045
|
+
throw new CliCredentialPersistenceError(await revokeIssuedCredentials(fetchImplementation, baseUrl, credentials), false, "login");
|
|
6046
|
+
}
|
|
5809
6047
|
return credentials;
|
|
5810
6048
|
}
|
|
5811
6049
|
const error = deviceTokenErrorSchema.safeParse(payload);
|
|
@@ -5825,6 +6063,55 @@ var loginWithBrowser = async ({
|
|
|
5825
6063
|
}
|
|
5826
6064
|
throw new Error("CLI authentication expired. Run auth login again.");
|
|
5827
6065
|
};
|
|
6066
|
+
var revokeIssuedCredentials = async (fetchImplementation, apiBaseUrl, credentials) => {
|
|
6067
|
+
try {
|
|
6068
|
+
const response = await postJson(fetchImplementation, `${normalizeCliAuthIssuer(apiBaseUrl)}/api/cli-auth/revoke`, { refreshToken: credentials.refreshToken }, { signal: AbortSignal.timeout(refreshRequestTimeoutMs) });
|
|
6069
|
+
return response.ok;
|
|
6070
|
+
} catch {
|
|
6071
|
+
return false;
|
|
6072
|
+
}
|
|
6073
|
+
};
|
|
6074
|
+
var persistIssuedCredentials = async ({
|
|
6075
|
+
store,
|
|
6076
|
+
credentials,
|
|
6077
|
+
previous,
|
|
6078
|
+
apiBaseUrl,
|
|
6079
|
+
fetch: fetchImplementation,
|
|
6080
|
+
phase
|
|
6081
|
+
}) => {
|
|
6082
|
+
let prior = previous;
|
|
6083
|
+
try {
|
|
6084
|
+
if (phase === "login")
|
|
6085
|
+
prior = await store.get();
|
|
6086
|
+
await store.set(credentials);
|
|
6087
|
+
const saved = await store.get();
|
|
6088
|
+
if (!isDeepStrictEqual(saved, credentials))
|
|
6089
|
+
throw new Error("Credential write could not be verified");
|
|
6090
|
+
return;
|
|
6091
|
+
} catch {
|
|
6092
|
+
const remoteRevoked = await revokeIssuedCredentials(fetchImplementation, apiBaseUrl, credentials);
|
|
6093
|
+
let localRestored = false;
|
|
6094
|
+
try {
|
|
6095
|
+
const current = await store.get();
|
|
6096
|
+
if (phase === "login" && prior !== undefined) {
|
|
6097
|
+
if (isDeepStrictEqual(current, prior))
|
|
6098
|
+
localRestored = true;
|
|
6099
|
+
else if (current === null || current.refreshToken === credentials.refreshToken) {
|
|
6100
|
+
if (prior)
|
|
6101
|
+
await store.set(prior);
|
|
6102
|
+
else
|
|
6103
|
+
await store.delete();
|
|
6104
|
+
localRestored = isDeepStrictEqual(await store.get(), prior);
|
|
6105
|
+
}
|
|
6106
|
+
} else if (phase === "refresh" && (!current || current.refreshToken === credentials.refreshToken || current.refreshToken === prior?.refreshToken)) {
|
|
6107
|
+
if (current)
|
|
6108
|
+
await store.delete();
|
|
6109
|
+
localRestored = await store.get() === null;
|
|
6110
|
+
}
|
|
6111
|
+
} catch {}
|
|
6112
|
+
throw new CliCredentialPersistenceError(remoteRevoked, localRestored, phase);
|
|
6113
|
+
}
|
|
6114
|
+
};
|
|
5828
6115
|
var refreshes = new WeakMap;
|
|
5829
6116
|
var refreshRequestTimeoutMs = 30000;
|
|
5830
6117
|
var isInvalidGrantResponse = (payload) => {
|
|
@@ -5879,7 +6166,14 @@ var refreshAccessToken = async ({
|
|
|
5879
6166
|
throw new Error("Unable to refresh the CLI session because the API returned invalid credentials. Credentials were kept; retry the command.");
|
|
5880
6167
|
}
|
|
5881
6168
|
const stored = toStoredCredentials(issued, normalizeCliAuthIssuer(apiBaseUrl), now());
|
|
5882
|
-
await
|
|
6169
|
+
await persistIssuedCredentials({
|
|
6170
|
+
store,
|
|
6171
|
+
credentials: stored,
|
|
6172
|
+
previous: credentials,
|
|
6173
|
+
apiBaseUrl,
|
|
6174
|
+
fetch: fetchImplementation,
|
|
6175
|
+
phase: "refresh"
|
|
6176
|
+
});
|
|
5883
6177
|
return stored.accessToken;
|
|
5884
6178
|
};
|
|
5885
6179
|
var refreshWithCurrentCredentials = async ({
|
|
@@ -6332,6 +6626,8 @@ Object.assign(commands, {
|
|
|
6332
6626
|
"admission identity-exception resolve": payloadCommand((api, input) => api.admission.resolveIdentityException.mutate(input), "Resolve an identity exception (--payload JSON).", ["sensitive-data", "state-change"]),
|
|
6333
6627
|
"audit list": payloadCommand((api, input) => api.audit.listEventActivity.query(input), "List event audit activity (--payload JSON).", ["sensitive-data"]),
|
|
6334
6628
|
"event check-in qr": payloadCommand((api, input) => api.event.checkInByQr.mutate(input), "Check in using a QR token (--payload JSON).", ["state-change"]),
|
|
6629
|
+
"event logo upload create": payloadCommand((api, input) => api.event.createLogoUpload.mutate(input), "Create an event-logo upload URL (--payload JSON).", ["external-write", "state-change"], "eventId"),
|
|
6630
|
+
"event logo update": payloadCommand((api, input) => api.event.updateLogo.mutate(input), "Set or clear the event logo (--payload JSON).", ["state-change"], "eventId"),
|
|
6335
6631
|
"event banner upload create": payloadCommand((api, input) => api.event.createBannerUpload.mutate(input), "Create a banner-image upload URL (--payload JSON).", ["external-write", "state-change"], "eventId"),
|
|
6336
6632
|
"event banner update": payloadCommand((api, input) => api.event.updateBanner.mutate(input), "Set or clear the event banner image (--payload JSON).", ["state-change"], "eventId"),
|
|
6337
6633
|
"event background upload create": payloadCommand((api, input) => api.event.createBackgroundUpload.mutate(input), "Create a background-image upload URL (--payload JSON).", ["external-write", "state-change"]),
|
|
@@ -6453,7 +6749,6 @@ var getCommandManifest = () => ({
|
|
|
6453
6749
|
// src/runtime/index.ts
|
|
6454
6750
|
import { stdin } from "process";
|
|
6455
6751
|
import { createInterface } from "readline/promises";
|
|
6456
|
-
|
|
6457
6752
|
class CliUsageError extends Error {
|
|
6458
6753
|
}
|
|
6459
6754
|
var output = (io, command, data, meta = {}) => {
|
|
@@ -6463,7 +6758,7 @@ var output = (io, command, data, meta = {}) => {
|
|
|
6463
6758
|
var errorOutput = (io, command, error) => {
|
|
6464
6759
|
const rawMessage = error instanceof Error ? error.message : String(error);
|
|
6465
6760
|
const message = rawMessage.replace(/\bBearer\s+\S+/giu, "Bearer [redacted]");
|
|
6466
|
-
io.writeStderr(`${JSON.stringify({ command, error: message, ok: false })}
|
|
6761
|
+
io.writeStderr(`${JSON.stringify({ command, error: message, ok: false, ...getCliStorageError(error) ? { storageError: getCliStorageError(error) } : {} })}
|
|
6467
6762
|
`);
|
|
6468
6763
|
};
|
|
6469
6764
|
var processIo = {
|
|
@@ -6793,6 +7088,8 @@ Waiting for approval\u2026
|
|
|
6793
7088
|
});
|
|
6794
7089
|
return 0;
|
|
6795
7090
|
} catch (cause) {
|
|
7091
|
+
if (getCliStorageError(cause))
|
|
7092
|
+
throw cause;
|
|
6796
7093
|
output(dependencies.io, command, {
|
|
6797
7094
|
authenticated: false,
|
|
6798
7095
|
accessTokenExpiresAt: credentials.accessTokenExpiresAt,
|