@indigoai-us/hq-cli 5.103.23 → 5.103.24
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/CHANGELOG.md
CHANGED
package/dist/main.js
CHANGED
|
@@ -72,6 +72,7 @@ import { isExpectedUserError } from "./utils/expected-cli-error.js";
|
|
|
72
72
|
import { isEpipe } from "./utils/epipe.js";
|
|
73
73
|
import { isInterceptedProcessExit } from "./utils/intercepted-process-exit.js";
|
|
74
74
|
import { isAuthError } from "./utils/auth-error.js";
|
|
75
|
+
import { browserLoginAbandonedMessage } from "./utils/browser-login-abandoned.js";
|
|
75
76
|
import { isCompanySelectionError } from "./utils/company-selection-error.js";
|
|
76
77
|
import { canOfferTeamUpgrade, formatPlanGateError, isPlanGateError, offerTeamUpgrade, } from "./utils/plan-gate-error.js";
|
|
77
78
|
import { upgradeToTeam } from "./utils/team-upgrade.js";
|
|
@@ -394,6 +395,21 @@ export async function handleTopLevelError(err, deps = defaultTopLevelErrorDepend
|
|
|
394
395
|
deps.stderr.write(`hq: ${err.message}\n`);
|
|
395
396
|
deps.setExitCode(1);
|
|
396
397
|
}
|
|
398
|
+
else if (browserLoginAbandonedMessage(err)) {
|
|
399
|
+
// HQ-CLI-V: the caller triggered the implicit browser sign-in fallback
|
|
400
|
+
// (an expiring/absent HQ session) and then never completed it —
|
|
401
|
+
// @indigoai-us/hq-cloud's browserLogin rejected either at its 15-minute
|
|
402
|
+
// deadline or because the user declined at the IdP (access_denied). A
|
|
403
|
+
// human walking away from an OAuth prompt is expected auth state the user
|
|
404
|
+
// fixes with `hq login`, not an hq-cli defect. Print the fixed actionable
|
|
405
|
+
// remedy and skip Sentry so one abandoned login doesn't file a permanent
|
|
406
|
+
// high-priority "crash". Placed with the auth carve-out (both mean "your
|
|
407
|
+
// HQ session isn't usable; run hq login"); every genuine browser-login or
|
|
408
|
+
// token-exchange fault stays outside the closed allowlist and still
|
|
409
|
+
// captures below.
|
|
410
|
+
deps.stderr.write(`hq: ${browserLoginAbandonedMessage(err)}\n`);
|
|
411
|
+
deps.setExitCode(1);
|
|
412
|
+
}
|
|
397
413
|
else if (isPlanGateError(err)) {
|
|
398
414
|
// hq-pro's plan denials are expected product limits, never a CLI crash.
|
|
399
415
|
// The shared vault client has already decoded and typed the small safe
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* If `err` is an abandoned implicit browser sign-in, return a short, fixed
|
|
3
|
+
* user-facing remedy; otherwise return `null`.
|
|
4
|
+
*
|
|
5
|
+
* A non-null result means the caller should print the message and SKIP Sentry
|
|
6
|
+
* capture — a user who walked away from an OAuth prompt is not an hq-cli defect.
|
|
7
|
+
* A null result means "handle as usual (capture to Sentry)". CognitoRefreshError
|
|
8
|
+
* is a refresh fault, never an abandonment, so it is excluded structurally even
|
|
9
|
+
* though it extends CognitoAuthError.
|
|
10
|
+
*/
|
|
11
|
+
export declare function browserLoginAbandonedMessage(err: unknown): string | null;
|
|
12
|
+
//# sourceMappingURL=browser-login-abandoned.d.ts.map
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// src/utils/browser-login-abandoned.ts
|
|
2
|
+
//
|
|
3
|
+
// Classify an ABANDONED implicit browser sign-in — the caller triggered the
|
|
4
|
+
// fallback OAuth flow (an expiring/absent HQ session on any of the ~100
|
|
5
|
+
// ensureCognitoToken/ensureCognitoIdToken call sites) and then never completed
|
|
6
|
+
// it. @indigoai-us/hq-cloud's browserLogin arms a 15-minute deadline and also
|
|
7
|
+
// rejects when the identity provider reports the user declined; both mint a
|
|
8
|
+
// bare CognitoAuthError. A human walking away from an OAuth prompt is expected
|
|
9
|
+
// auth state the user fixes with `hq login`, NOT an hq-cli defect — so the
|
|
10
|
+
// top-level handler prints an actionable line and exits non-zero but SKIPS
|
|
11
|
+
// Sentry capture, mirroring the auth (HQ-CLI-8), company-selection (HQ-CLI-7),
|
|
12
|
+
// expected-user-error (HQ-CLI-6), and environmental-FS (HQ-CLI-2) carve-outs.
|
|
13
|
+
//
|
|
14
|
+
// HQ-CLI-V (Sentry indigo-d0/hq-cli 7685055900): `hq integrations list
|
|
15
|
+
// --company <slug> --json` tried to refresh an expiring session, the refresh
|
|
16
|
+
// 400'd (invalid_grant), and it fell back to browserLogin(). Nobody finished
|
|
17
|
+
// the sign-in, so exactly 15 minutes later browserLogin rejected with
|
|
18
|
+
// `CognitoAuthError("Login timed out after 15 minutes")`. That error carries no
|
|
19
|
+
// `code`, no `expected` flag, and is not an AuthError/CompanySelectionError/…,
|
|
20
|
+
// so every predicate in handleTopLevelError returned false and the abandoned
|
|
21
|
+
// login was captured as a high-priority, unfixable "crash".
|
|
22
|
+
//
|
|
23
|
+
// The allowlist is CLOSED at the two upstream-minted human-abandonment
|
|
24
|
+
// signatures and defaults to null (capture). Every other CognitoAuthError — a
|
|
25
|
+
// state-parameter mismatch (possible CSRF), a missing callback code, a
|
|
26
|
+
// non-access_denied IdP error, a token-exchange failure — and every
|
|
27
|
+
// CognitoRefreshError (a refresh fault, not an abandonment) still reaches
|
|
28
|
+
// Sentry.
|
|
29
|
+
import { CognitoAuthError, CognitoRefreshError } from "@indigoai-us/hq-cloud";
|
|
30
|
+
/**
|
|
31
|
+
* Fixed, actionable remedy. Deliberately interpolates NO upstream error text,
|
|
32
|
+
* so this carve-out can never widen disclosure and needs no redaction pass.
|
|
33
|
+
*/
|
|
34
|
+
const BROWSER_LOGIN_ABANDONED_MESSAGE = "Browser sign-in was not completed. Run `hq login`, finish the sign-in in " +
|
|
35
|
+
"your browser, then re-run your command.";
|
|
36
|
+
/**
|
|
37
|
+
* The 15-minute login-deadline signature (@indigoai-us/hq-cloud cognito-auth
|
|
38
|
+
* `waitForAuthCode`). The minute count is tolerated (`\d+`) so an upstream
|
|
39
|
+
* retune of the deadline turns the contract test red rather than silently
|
|
40
|
+
* restoring the Sentry noise.
|
|
41
|
+
*/
|
|
42
|
+
const LOGIN_TIMED_OUT = /^Login timed out after \d+ minutes?$/;
|
|
43
|
+
/**
|
|
44
|
+
* The user declined at the identity provider. Matched EXACTLY — other
|
|
45
|
+
* `Cognito returned error: <x>` values (server_error, …) are not abandonment
|
|
46
|
+
* and stay reportable.
|
|
47
|
+
*/
|
|
48
|
+
const ACCESS_DENIED = "Cognito returned error: access_denied";
|
|
49
|
+
/**
|
|
50
|
+
* If `err` is an abandoned implicit browser sign-in, return a short, fixed
|
|
51
|
+
* user-facing remedy; otherwise return `null`.
|
|
52
|
+
*
|
|
53
|
+
* A non-null result means the caller should print the message and SKIP Sentry
|
|
54
|
+
* capture — a user who walked away from an OAuth prompt is not an hq-cli defect.
|
|
55
|
+
* A null result means "handle as usual (capture to Sentry)". CognitoRefreshError
|
|
56
|
+
* is a refresh fault, never an abandonment, so it is excluded structurally even
|
|
57
|
+
* though it extends CognitoAuthError.
|
|
58
|
+
*/
|
|
59
|
+
export function browserLoginAbandonedMessage(err) {
|
|
60
|
+
if (!(err instanceof CognitoAuthError))
|
|
61
|
+
return null;
|
|
62
|
+
if (err instanceof CognitoRefreshError)
|
|
63
|
+
return null;
|
|
64
|
+
const { message } = err;
|
|
65
|
+
if (LOGIN_TIMED_OUT.test(message) || message === ACCESS_DENIED) {
|
|
66
|
+
return BROWSER_LOGIN_ABANDONED_MESSAGE;
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=browser-login-abandoned.js.map
|