@trillioncore/cli 1.0.0-next.4 → 1.0.0-next.5
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 +6 -2
- package/dist/index.js +187 -118
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The `tc` CLI signs you into your own Trillioncore organization and reads the integration accounts you authorize. No repository checkout, provider secrets, `.env` file, or manually entered organization ID is required.
|
|
4
4
|
|
|
5
|
-
**Requirements:** Node.js 22+, a browser
|
|
5
|
+
**Requirements:** Node.js 22+, a browser on any device, and a Trillioncore deployment with CLI device login enabled. This is a fresh v1 preview, not v0 command parity; v0 configuration is not imported.
|
|
6
6
|
|
|
7
7
|
## Get started
|
|
8
8
|
|
|
@@ -18,6 +18,8 @@ npm exec --yes --package=@trillioncore/cli@next -- tc sql '<PostgreSQL account I
|
|
|
18
18
|
npm exec --yes --package=@trillioncore/cli@next -- tc logout
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
`tc login` registers a five-minute request and prints a browser URL plus a separate eight-character code. Open the URL on any device and enter the code shown in your terminal. Keep the code private and approve only a login you started. The CLI polls without a callback listener, tunnel, or copied token. After approval it verifies your identity, saves the private session, and acknowledges delivery.
|
|
22
|
+
|
|
21
23
|
Browser sign-in lets you choose an organization and explicitly select its enabled accounts. An organization administrator must approve access. For a brand-new account, finish organization setup and connect your integrations in the app, then rerun `tc login` if prompted. No provider reconnection is required just to authorize the CLI.
|
|
22
24
|
|
|
23
25
|
Prefer `npm exec` during evaluation. `npm install -g @trillioncore/cli@next` replaces your local `tc` executable, even though publishing `next` does not change the npm `latest` tag.
|
|
@@ -66,7 +68,9 @@ Sessions refresh automatically. File-based credentials live in `~/.trillioncore/
|
|
|
66
68
|
One profile is active at a time. A new successful login replaces it and attempts to revoke the prior connection. Change account scopes or revoke access from the app's **Tokens** page. Membership changes and server revocation are enforced on subsequent requests.
|
|
67
69
|
|
|
68
70
|
- Default API: `https://api.trillioncore.com`. A saved session remains pinned to its issuing server. Custom origins must use HTTPS, except literal loopback development endpoints.
|
|
69
|
-
- `--no-browser` prints the sign-in URL
|
|
71
|
+
- `--no-browser` prints the device sign-in URL and separate code without launching a browser. Use it over SSH or on a headless machine. Default login uses the same remote-capable flow; there is no loopback fallback flag.
|
|
72
|
+
- Pending and transient polling failures retry within a fixed five-minute deadline. Rate-limit retries respect `Retry-After`. Ctrl-C or timeout makes one best-effort cancellation attempt; unreachable requests expire on the server. If registration itself is lost, rerun login and let the unknown request expire.
|
|
73
|
+
- A lost completed poll can be retrieved again before expiry. Acknowledgement occurs only after private persistence. If acknowledgement fails, the saved session remains active and the temporary delivery expires automatically.
|
|
70
74
|
- A disabled/not-yet-deployed CLI login returns a clear deployment-unavailable error. Signing in repeatedly cannot enable the server.
|
|
71
75
|
- Advanced/manual use: `TRILLIONCORE_TOKEN` and `TRILLIONCORE_ORG_ID` must be supplied together and override the saved session. Remove both to use browser login. API overrides prefer `TRILLIONCORE_CLI_API_URL`, then `TRILLIONCORE_API_URL`; a mismatch with a saved session is refused, not silently redirected. Application API configuration is never rewritten.
|
|
72
76
|
- `TRILLIONCORE_CONFIG_DIR` selects a private CLI-only profile directory (also useful for isolated testing). Legacy v0 configuration is not migrated.
|
package/dist/index.js
CHANGED
|
@@ -3051,8 +3051,8 @@ import { fileURLToPath } from "node:url";
|
|
|
3051
3051
|
import { realpathSync } from "node:fs";
|
|
3052
3052
|
|
|
3053
3053
|
// src/auth.ts
|
|
3054
|
-
import {
|
|
3055
|
-
import { createHash, randomBytes as randomBytes2 } from "node:crypto";
|
|
3054
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
3055
|
+
import { createHash, randomBytes as randomBytes2, randomInt } from "node:crypto";
|
|
3056
3056
|
import { spawn } from "node:child_process";
|
|
3057
3057
|
|
|
3058
3058
|
// src/session-store.ts
|
|
@@ -3243,6 +3243,14 @@ function sessionStore(environment = process.env) {
|
|
|
3243
3243
|
|
|
3244
3244
|
// src/auth.ts
|
|
3245
3245
|
var DEFAULT_API = "https://api.trillioncore.com";
|
|
3246
|
+
var deviceSecret = /^[A-Za-z0-9_-]{43}$/;
|
|
3247
|
+
var RetryableAuthError = class extends Error {
|
|
3248
|
+
constructor(message, retryAfterMs = 0) {
|
|
3249
|
+
super(message);
|
|
3250
|
+
this.retryAfterMs = retryAfterMs;
|
|
3251
|
+
}
|
|
3252
|
+
retryAfterMs;
|
|
3253
|
+
};
|
|
3246
3254
|
async function jsonRequest(issuer, path, init = {}) {
|
|
3247
3255
|
let response;
|
|
3248
3256
|
try {
|
|
@@ -3253,14 +3261,27 @@ async function jsonRequest(issuer, path, init = {}) {
|
|
|
3253
3261
|
});
|
|
3254
3262
|
} catch {
|
|
3255
3263
|
if (init.signal?.aborted) throw init.signal.reason;
|
|
3256
|
-
throw new
|
|
3264
|
+
throw new RetryableAuthError(
|
|
3257
3265
|
"Cannot reach the CLI authentication service. Check connectivity and retry."
|
|
3258
3266
|
);
|
|
3259
3267
|
}
|
|
3260
|
-
if (
|
|
3268
|
+
if (response.status === 429 || response.status >= 500) {
|
|
3269
|
+
const header = response.headers.get("retry-after")?.trim();
|
|
3270
|
+
const seconds = header && /^\d+$/.test(header) ? Number(header) : NaN;
|
|
3271
|
+
const until = header ? Date.parse(header) : NaN;
|
|
3272
|
+
const retryAfterMs = Number.isFinite(seconds) ? seconds * 1e3 : Number.isFinite(until) ? Math.max(0, until - Date.now()) : 0;
|
|
3273
|
+
await response.body?.cancel();
|
|
3274
|
+
throw new RetryableAuthError(
|
|
3275
|
+
"CLI authentication is temporarily unavailable.",
|
|
3276
|
+
retryAfterMs
|
|
3277
|
+
);
|
|
3278
|
+
}
|
|
3279
|
+
if (!response.ok) {
|
|
3280
|
+
await response.body?.cancel();
|
|
3261
3281
|
throw new Error(
|
|
3262
3282
|
response.status === 404 ? "CLI login is not enabled on this deployment yet." : "CLI authentication was refused or expired. Run tc login again."
|
|
3263
3283
|
);
|
|
3284
|
+
}
|
|
3264
3285
|
if (!response.body)
|
|
3265
3286
|
throw new Error(
|
|
3266
3287
|
"The CLI authentication service returned an invalid response."
|
|
@@ -3270,7 +3291,11 @@ async function jsonRequest(issuer, path, init = {}) {
|
|
|
3270
3291
|
const parts = [];
|
|
3271
3292
|
try {
|
|
3272
3293
|
while (true) {
|
|
3273
|
-
const part = await reader.read()
|
|
3294
|
+
const part = await reader.read().catch(() => {
|
|
3295
|
+
throw new RetryableAuthError(
|
|
3296
|
+
"CLI authentication response was interrupted."
|
|
3297
|
+
);
|
|
3298
|
+
});
|
|
3274
3299
|
if (part.done) break;
|
|
3275
3300
|
size += part.value.length;
|
|
3276
3301
|
if (size > 32768) {
|
|
@@ -3283,7 +3308,9 @@ async function jsonRequest(issuer, path, init = {}) {
|
|
|
3283
3308
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
3284
3309
|
throw new Error();
|
|
3285
3310
|
return value;
|
|
3286
|
-
} catch {
|
|
3311
|
+
} catch (error) {
|
|
3312
|
+
if (init.signal?.aborted) throw init.signal.reason;
|
|
3313
|
+
if (error instanceof RetryableAuthError) throw error;
|
|
3287
3314
|
throw new Error(
|
|
3288
3315
|
"The CLI authentication service returned an invalid response."
|
|
3289
3316
|
);
|
|
@@ -3298,13 +3325,20 @@ function form(values) {
|
|
|
3298
3325
|
body: new URLSearchParams(values)
|
|
3299
3326
|
};
|
|
3300
3327
|
}
|
|
3301
|
-
function tokens(value) {
|
|
3328
|
+
function tokens(value, device = false) {
|
|
3302
3329
|
if (typeof value.access_token !== "string" || !/^tc_cli_[A-Za-z0-9_-]{43}$/.test(value.access_token) || typeof value.refresh_token !== "string" || !/^tc_refresh_[A-Za-z0-9_-]{43}$/.test(value.refresh_token) || value.token_type !== "Bearer" || typeof value.expires_in !== "number" || !Number.isFinite(value.expires_in) || value.expires_in <= 0 || value.expires_in > 86400)
|
|
3303
3330
|
throw new Error("The service did not return a valid CLI session.");
|
|
3331
|
+
const now = Date.now();
|
|
3332
|
+
let expiresAt = now + value.expires_in * 1e3;
|
|
3333
|
+
if (device) {
|
|
3334
|
+
if (typeof value.access_expires_at !== "number" || !Number.isFinite(value.access_expires_at) || value.access_expires_at <= now || value.access_expires_at > now + 86400 * 1e3)
|
|
3335
|
+
throw new Error("The service did not return a valid CLI session expiry.");
|
|
3336
|
+
expiresAt = value.access_expires_at;
|
|
3337
|
+
}
|
|
3304
3338
|
return {
|
|
3305
3339
|
accessToken: value.access_token,
|
|
3306
3340
|
refreshToken: value.refresh_token,
|
|
3307
|
-
expiresAt
|
|
3341
|
+
expiresAt
|
|
3308
3342
|
};
|
|
3309
3343
|
}
|
|
3310
3344
|
async function identity(issuer, accessToken, signal) {
|
|
@@ -3346,125 +3380,147 @@ async function revoke(session) {
|
|
|
3346
3380
|
"Could not revoke this connection. Retry logout or revoke it on the Tokens page."
|
|
3347
3381
|
);
|
|
3348
3382
|
}
|
|
3383
|
+
function loginOrigin(value) {
|
|
3384
|
+
const origin = apiOrigin(value);
|
|
3385
|
+
if (origin.startsWith("http:") && !/^http:\/\/(?:localhost|127\.0\.0\.1|\[::1\])(?::\d+)?\/?$/i.test(value))
|
|
3386
|
+
throw new Error(
|
|
3387
|
+
"API URL must be an HTTPS origin or literal loopback development origin."
|
|
3388
|
+
);
|
|
3389
|
+
return origin;
|
|
3390
|
+
}
|
|
3391
|
+
function verificationUrl(value, requestId) {
|
|
3392
|
+
try {
|
|
3393
|
+
if (typeof value !== "string") throw new Error();
|
|
3394
|
+
const url = new URL(value);
|
|
3395
|
+
loginOrigin(value.slice(0, value.indexOf("/connect/cli")));
|
|
3396
|
+
if (url.username || url.password || url.hash || url.pathname !== "/connect/cli" || url.search !== `?request=${requestId}`)
|
|
3397
|
+
throw new Error();
|
|
3398
|
+
return url.toString();
|
|
3399
|
+
} catch {
|
|
3400
|
+
throw new Error(
|
|
3401
|
+
"The service returned an invalid browser verification URL."
|
|
3402
|
+
);
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3349
3405
|
async function login(options = {}, environment = process.env) {
|
|
3350
|
-
const issuer =
|
|
3406
|
+
const issuer = loginOrigin(
|
|
3351
3407
|
options.apiUrl ?? environment.TRILLIONCORE_CLI_API_URL ?? environment.TRILLIONCORE_API_URL ?? DEFAULT_API
|
|
3352
3408
|
);
|
|
3353
3409
|
const abort = new AbortController();
|
|
3354
|
-
const state = randomBytes2(32).toString("base64url");
|
|
3355
3410
|
const verifier = randomBytes2(32).toString("base64url");
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3411
|
+
const alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
|
|
3412
|
+
const userCode = Array.from(
|
|
3413
|
+
{ length: 8 },
|
|
3414
|
+
() => alphabet[randomInt(alphabet.length)]
|
|
3415
|
+
).join("");
|
|
3416
|
+
const started = Date.now();
|
|
3417
|
+
const timeout = options.timeoutMs ?? 3e5;
|
|
3418
|
+
if (!Number.isFinite(timeout) || timeout <= 0)
|
|
3419
|
+
throw new Error("Invalid sign-in timeout.");
|
|
3420
|
+
let deadline = started + Math.min(timeout, 3e5);
|
|
3421
|
+
let requestId;
|
|
3422
|
+
let persisted = false;
|
|
3423
|
+
const stop = (message) => abort.abort(new Error(message));
|
|
3424
|
+
const cancel = () => {
|
|
3425
|
+
if (!persisted) stop("Sign-in cancelled.");
|
|
3426
|
+
};
|
|
3427
|
+
process.on("SIGINT", cancel);
|
|
3428
|
+
let timer = setTimeout(
|
|
3429
|
+
() => stop("Sign-in timed out. Run tc login again."),
|
|
3430
|
+
deadline - Date.now()
|
|
3431
|
+
);
|
|
3432
|
+
const check = () => {
|
|
3433
|
+
if (Date.now() >= deadline) stop("Sign-in timed out. Run tc login again.");
|
|
3434
|
+
abort.signal.throwIfAborted();
|
|
3435
|
+
};
|
|
3436
|
+
const wait = async (milliseconds) => {
|
|
3437
|
+
check();
|
|
3369
3438
|
try {
|
|
3370
|
-
|
|
3439
|
+
await delay(Math.min(milliseconds, deadline - Date.now()), void 0, {
|
|
3440
|
+
signal: abort.signal
|
|
3441
|
+
});
|
|
3371
3442
|
} catch {
|
|
3372
|
-
|
|
3373
|
-
return;
|
|
3443
|
+
abort.signal.throwIfAborted();
|
|
3374
3444
|
}
|
|
3375
|
-
|
|
3376
|
-
response.writeHead(404).end();
|
|
3377
|
-
return;
|
|
3378
|
-
}
|
|
3379
|
-
if ([...url.searchParams.keys()].some(
|
|
3380
|
-
(key) => url.searchParams.getAll(key).length !== 1
|
|
3381
|
-
) || url.searchParams.get("state") !== state || url.searchParams.get("iss") !== issuer) {
|
|
3382
|
-
response.writeHead(400).end("Invalid sign-in response.");
|
|
3383
|
-
return;
|
|
3384
|
-
}
|
|
3385
|
-
if (url.searchParams.has("error")) {
|
|
3386
|
-
response.end("Sign-in cancelled. Return to your terminal.");
|
|
3387
|
-
rejectCallback(new Error("Sign-in was not approved."));
|
|
3388
|
-
return;
|
|
3389
|
-
}
|
|
3390
|
-
if (!/^[A-Za-z0-9_-]{43}$/.test(url.searchParams.get("code") ?? "")) {
|
|
3391
|
-
response.writeHead(400).end("Invalid sign-in response.");
|
|
3392
|
-
return;
|
|
3393
|
-
}
|
|
3394
|
-
response.end(
|
|
3395
|
-
"Sign-in response received. Return to your terminal to confirm completion."
|
|
3396
|
-
);
|
|
3397
|
-
receive(url);
|
|
3398
|
-
});
|
|
3399
|
-
const stop = (message) => {
|
|
3400
|
-
const error = new Error(message);
|
|
3401
|
-
abort.abort(error);
|
|
3402
|
-
rejectCallback(error);
|
|
3445
|
+
check();
|
|
3403
3446
|
};
|
|
3404
|
-
const
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
);
|
|
3447
|
+
const post = (operation, body, signal) => jsonRequest(issuer, `/cli/oauth/device/${operation}`, {
|
|
3448
|
+
method: "POST",
|
|
3449
|
+
headers: { "content-type": "application/json" },
|
|
3450
|
+
body: JSON.stringify(body),
|
|
3451
|
+
...signal ? { signal } : {}
|
|
3452
|
+
});
|
|
3410
3453
|
try {
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
redirect_uri: redirectUri,
|
|
3436
|
-
response_type: "code",
|
|
3437
|
-
code_challenge_method: "S256",
|
|
3438
|
-
code_challenge: createHash("sha256").update(verifier).digest("base64url"),
|
|
3439
|
-
resource: `${issuer}/cli`,
|
|
3440
|
-
scope: "cli:access offline_access",
|
|
3441
|
-
state
|
|
3442
|
-
}).toString();
|
|
3454
|
+
const registration = await post(
|
|
3455
|
+
"register",
|
|
3456
|
+
{
|
|
3457
|
+
verifier_digest: createHash("sha256").update(verifier).digest("hex"),
|
|
3458
|
+
user_code_digest: createHash("sha256").update(userCode).digest("hex")
|
|
3459
|
+
},
|
|
3460
|
+
abort.signal
|
|
3461
|
+
);
|
|
3462
|
+
if (typeof registration.request_id !== "string" || !deviceSecret.test(registration.request_id))
|
|
3463
|
+
throw new Error("The service returned an invalid device request.");
|
|
3464
|
+
requestId = registration.request_id;
|
|
3465
|
+
if (registration.client_id !== "cli_device" || typeof registration.expires_in !== "number" || !Number.isInteger(registration.expires_in) || registration.expires_in <= 0 || registration.expires_in > 300 || typeof registration.interval !== "number" || !Number.isInteger(registration.interval) || registration.interval <= 0 || registration.interval > 30)
|
|
3466
|
+
throw new Error("The service returned an invalid device request.");
|
|
3467
|
+
deadline = Math.min(deadline, started + registration.expires_in * 1e3);
|
|
3468
|
+
clearTimeout(timer);
|
|
3469
|
+
check();
|
|
3470
|
+
timer = setTimeout(
|
|
3471
|
+
() => stop("Sign-in timed out. Run tc login again."),
|
|
3472
|
+
deadline - Date.now()
|
|
3473
|
+
);
|
|
3474
|
+
const authorization = verificationUrl(
|
|
3475
|
+
registration.verification_uri,
|
|
3476
|
+
requestId
|
|
3477
|
+
);
|
|
3443
3478
|
if (options.onAuthorizationUrl)
|
|
3444
|
-
await options.onAuthorizationUrl(authorization
|
|
3479
|
+
await options.onAuthorizationUrl(authorization, userCode);
|
|
3445
3480
|
else {
|
|
3446
3481
|
console.log(
|
|
3447
|
-
`
|
|
3482
|
+
`Open this URL in a browser on any device:
|
|
3448
3483
|
${authorization}
|
|
3449
|
-
|
|
3484
|
+
Enter this separate code: ${userCode}
|
|
3485
|
+
Only approve a login you started. Keep the code private.`
|
|
3450
3486
|
);
|
|
3451
|
-
if (!options.noBrowser) openBrowser(authorization
|
|
3487
|
+
if (!options.noBrowser) openBrowser(authorization);
|
|
3452
3488
|
}
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3489
|
+
let approved;
|
|
3490
|
+
let attempts = 0;
|
|
3491
|
+
const interval = registration.interval * 1e3;
|
|
3492
|
+
while (true) {
|
|
3493
|
+
check();
|
|
3494
|
+
try {
|
|
3495
|
+
approved = await post(
|
|
3496
|
+
"poll",
|
|
3497
|
+
{ request_id: requestId, verifier },
|
|
3498
|
+
abort.signal
|
|
3499
|
+
);
|
|
3500
|
+
check();
|
|
3501
|
+
if (approved.status !== "pending") break;
|
|
3502
|
+
attempts = 0;
|
|
3503
|
+
await wait(interval);
|
|
3504
|
+
} catch (error) {
|
|
3505
|
+
check();
|
|
3506
|
+
if (!(error instanceof RetryableAuthError)) throw error;
|
|
3507
|
+
const backoff = Math.min(
|
|
3508
|
+
1e4,
|
|
3509
|
+
interval * 2 ** Math.min(attempts++, 4)
|
|
3510
|
+
);
|
|
3511
|
+
await wait(
|
|
3512
|
+
Math.max(
|
|
3513
|
+
interval,
|
|
3514
|
+
backoff * (1 + Math.random() * 0.2),
|
|
3515
|
+
error.retryAfterMs
|
|
3516
|
+
)
|
|
3517
|
+
);
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
if (approved.client_id !== "cli_device" || typeof approved.delivery_receipt !== "string" || !deviceSecret.test(approved.delivery_receipt))
|
|
3521
|
+
throw new Error("The service returned an invalid device delivery.");
|
|
3522
|
+
const fresh = tokens(approved, true);
|
|
3523
|
+
const credentials = { issuer, clientId: "cli_device", ...fresh };
|
|
3468
3524
|
try {
|
|
3469
3525
|
const who = await identity(issuer, fresh.accessToken, abort.signal);
|
|
3470
3526
|
const value = {
|
|
@@ -3476,8 +3532,10 @@ Do not share this sign-in link. The browser must reach this computer's loopback
|
|
|
3476
3532
|
const store = sessionStore(environment);
|
|
3477
3533
|
await store.withLock(async () => {
|
|
3478
3534
|
const previous = await store.read().catch(() => null);
|
|
3479
|
-
|
|
3535
|
+
check();
|
|
3480
3536
|
await store.write(value);
|
|
3537
|
+
persisted = true;
|
|
3538
|
+
clearTimeout(timer);
|
|
3481
3539
|
if (previous)
|
|
3482
3540
|
await revoke(previous).catch(
|
|
3483
3541
|
() => console.error(
|
|
@@ -3485,20 +3543,31 @@ Do not share this sign-in link. The browser must reach this computer's loopback
|
|
|
3485
3543
|
)
|
|
3486
3544
|
);
|
|
3487
3545
|
});
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3546
|
+
await post("acknowledge", {
|
|
3547
|
+
request_id: requestId,
|
|
3548
|
+
verifier,
|
|
3549
|
+
delivery_receipt: approved.delivery_receipt
|
|
3550
|
+
}).catch(
|
|
3491
3551
|
() => console.error(
|
|
3492
|
-
"
|
|
3552
|
+
"Signed in, but delivery acknowledgement failed. The temporary delivery expires automatically."
|
|
3493
3553
|
)
|
|
3494
3554
|
);
|
|
3555
|
+
return { issuer, ...who };
|
|
3556
|
+
} catch (error) {
|
|
3557
|
+
if (!persisted)
|
|
3558
|
+
await revoke(credentials).catch(
|
|
3559
|
+
() => console.error(
|
|
3560
|
+
"Incomplete sign-in could not be revoked. Revoke the unused connection on the Tokens page."
|
|
3561
|
+
)
|
|
3562
|
+
);
|
|
3495
3563
|
throw error;
|
|
3496
3564
|
}
|
|
3497
3565
|
} finally {
|
|
3498
3566
|
clearTimeout(timer);
|
|
3567
|
+
if (requestId && !persisted)
|
|
3568
|
+
await post("cancel", { request_id: requestId, verifier }).catch(() => {
|
|
3569
|
+
});
|
|
3499
3570
|
process.removeListener("SIGINT", cancel);
|
|
3500
|
-
server.closeAllConnections();
|
|
3501
|
-
await new Promise((resolve) => server.close(() => resolve()));
|
|
3502
3571
|
}
|
|
3503
3572
|
}
|
|
3504
3573
|
async function authenticatedEnvironment(environment = process.env) {
|