@alter-ai/cli 0.8.0 → 0.9.1
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/dist/cli.js +719 -126
- package/npm-shrinkwrap.json +848 -0
- package/package.json +10 -6
package/dist/cli.js
CHANGED
|
@@ -33,7 +33,7 @@ import { join } from "path";
|
|
|
33
33
|
// package.json
|
|
34
34
|
var package_default = {
|
|
35
35
|
name: "@alter-ai/cli",
|
|
36
|
-
version: "0.
|
|
36
|
+
version: "0.9.1",
|
|
37
37
|
description: "Command-line interface for the Alter Vault dev portal \u2014 scripted dashboard automation.",
|
|
38
38
|
type: "module",
|
|
39
39
|
bin: {
|
|
@@ -44,7 +44,8 @@ var package_default = {
|
|
|
44
44
|
"dist",
|
|
45
45
|
"README.md",
|
|
46
46
|
"LICENSE",
|
|
47
|
-
"THIRD_PARTY_NOTICES"
|
|
47
|
+
"THIRD_PARTY_NOTICES",
|
|
48
|
+
"npm-shrinkwrap.json"
|
|
48
49
|
],
|
|
49
50
|
scripts: {
|
|
50
51
|
build: "tsup",
|
|
@@ -69,14 +70,17 @@ var package_default = {
|
|
|
69
70
|
author: "Alter Labs, Inc.",
|
|
70
71
|
license: "MIT",
|
|
71
72
|
dependencies: {
|
|
72
|
-
commander: "
|
|
73
|
-
"posthog-node": "
|
|
73
|
+
commander: "12.1.0",
|
|
74
|
+
"posthog-node": "4.18.0"
|
|
74
75
|
},
|
|
75
76
|
optionalDependencies: {
|
|
76
|
-
keytar: "
|
|
77
|
+
keytar: "7.9.0"
|
|
78
|
+
},
|
|
79
|
+
overrides: {
|
|
80
|
+
esbuild: "0.28.1"
|
|
77
81
|
},
|
|
78
82
|
devDependencies: {
|
|
79
|
-
"@alter-ai/alter-sdk": "workspace:0.
|
|
83
|
+
"@alter-ai/alter-sdk": "workspace:0.24.2",
|
|
80
84
|
"@alter-vault/shared-types": "workspace:0.0.1",
|
|
81
85
|
"@alter-vault/shared-utils": "workspace:0.0.1",
|
|
82
86
|
"@eslint/js": "9.39.4",
|
|
@@ -4618,7 +4622,7 @@ var AlterSDKError = class extends Error {
|
|
|
4618
4622
|
constructor(message, details) {
|
|
4619
4623
|
super(message);
|
|
4620
4624
|
this.name = "AlterSDKError";
|
|
4621
|
-
this.details = details
|
|
4625
|
+
this.details = details !== null && typeof details === "object" && !Array.isArray(details) ? details : {};
|
|
4622
4626
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
4623
4627
|
}
|
|
4624
4628
|
toString() {
|
|
@@ -4836,9 +4840,9 @@ var InsufficientScopeError = class _InsufficientScopeError extends BackendError
|
|
|
4836
4840
|
constructor(message = "Insufficient scope", options = {}) {
|
|
4837
4841
|
super(message, options.details);
|
|
4838
4842
|
this.name = "InsufficientScopeError";
|
|
4839
|
-
this.required = options.required
|
|
4840
|
-
this.granted = options.granted
|
|
4841
|
-
this.missing = options.missing
|
|
4843
|
+
this.required = options.required ? [...options.required] : [];
|
|
4844
|
+
this.granted = options.granted ? [...options.granted] : [];
|
|
4845
|
+
this.missing = options.missing ? [...options.missing] : [];
|
|
4842
4846
|
this.scopeVersion = options.scopeVersion ?? null;
|
|
4843
4847
|
this.currentScopeVersion = options.currentScopeVersion ?? null;
|
|
4844
4848
|
this.scopeVersionMismatch = options.scopeVersionMismatch ?? false;
|
|
@@ -4871,7 +4875,9 @@ var InsufficientScopeError = class _InsufficientScopeError extends BackendError
|
|
|
4871
4875
|
*/
|
|
4872
4876
|
static fromErrorBody(errorData) {
|
|
4873
4877
|
return new _InsufficientScopeError(
|
|
4874
|
-
|
|
4878
|
+
// Non-empty string only (parity with `_messageOr` / Python
|
|
4879
|
+
// `from_error_body`): an empty wire message is as uninformative as none.
|
|
4880
|
+
typeof errorData.message === "string" && errorData.message ? errorData.message : "Insufficient scope",
|
|
4875
4881
|
{
|
|
4876
4882
|
// Element-level filtering: a non-array coerces to [], and non-string
|
|
4877
4883
|
// elements inside an array are dropped (a bare cast would smuggle
|
|
@@ -5056,7 +5062,7 @@ var AgentError = class extends BackendError {
|
|
|
5056
5062
|
hint;
|
|
5057
5063
|
constructor(opts = {}) {
|
|
5058
5064
|
const subclassCode = new.target.code;
|
|
5059
|
-
super(opts.message
|
|
5065
|
+
super(opts.message || subclassCode, opts.details);
|
|
5060
5066
|
this.name = "AgentError";
|
|
5061
5067
|
this.code = subclassCode;
|
|
5062
5068
|
this.hint = opts.hint;
|
|
@@ -6253,7 +6259,11 @@ var ConnectResult = class {
|
|
|
6253
6259
|
providerId;
|
|
6254
6260
|
accountIdentifier;
|
|
6255
6261
|
scopes;
|
|
6256
|
-
/**
|
|
6262
|
+
/**
|
|
6263
|
+
* "creation" when a new grant was minted; "reauth" when an existing
|
|
6264
|
+
* grant was returned with its id preserved (credential repair, or reuse
|
|
6265
|
+
* of an already-connected grant).
|
|
6266
|
+
*/
|
|
6257
6267
|
operation;
|
|
6258
6268
|
grantPolicy;
|
|
6259
6269
|
/**
|
|
@@ -6796,12 +6806,13 @@ var ApprovalResult = class {
|
|
|
6796
6806
|
headers: this.headers,
|
|
6797
6807
|
body_b64: this.bodyB64,
|
|
6798
6808
|
body_truncated: this.bodyTruncated,
|
|
6799
|
-
//
|
|
6800
|
-
//
|
|
6801
|
-
//
|
|
6802
|
-
//
|
|
6803
|
-
//
|
|
6804
|
-
//
|
|
6809
|
+
// Include duration_ms so JSON round-trips carry the timing metadata.
|
|
6810
|
+
// The Python twin's ApprovalResult is a plain Pydantic model whose
|
|
6811
|
+
// model_dump() includes EVERY public field — duration_ms and
|
|
6812
|
+
// credential_hint alike — so omitting either here would make a TS
|
|
6813
|
+
// JSON round-trip lossy where the Python one is not (wire-shape
|
|
6814
|
+
// parity: toJSON() must serialize the same field set as model_dump()).
|
|
6815
|
+
duration_ms: this.durationMs,
|
|
6805
6816
|
credential_hint: this.credentialHint
|
|
6806
6817
|
};
|
|
6807
6818
|
}
|
|
@@ -8112,7 +8123,7 @@ async function _raiseForStatus(response) {
|
|
|
8112
8123
|
const rawCode = body.error;
|
|
8113
8124
|
const code = typeof rawCode === "string" ? rawCode : "";
|
|
8114
8125
|
const rawMessage = body.message;
|
|
8115
|
-
const message = typeof rawMessage === "string" ? rawMessage : `Backend error ${response.status}`;
|
|
8126
|
+
const message = typeof rawMessage === "string" && rawMessage ? rawMessage : `Backend error ${response.status}`;
|
|
8116
8127
|
const rawHint = body.hint;
|
|
8117
8128
|
const hint = typeof rawHint === "string" ? rawHint : void 0;
|
|
8118
8129
|
if (code === "insufficient_scope") {
|
|
@@ -8120,7 +8131,7 @@ async function _raiseForStatus(response) {
|
|
|
8120
8131
|
}
|
|
8121
8132
|
if (code === "rate_limit_exceeded") {
|
|
8122
8133
|
throw new RateLimitError(
|
|
8123
|
-
typeof body.message === "string" ? body.message : "Rate limit exceeded \u2014 retry after the window resets.",
|
|
8134
|
+
typeof body.message === "string" && body.message ? body.message : "Rate limit exceeded \u2014 retry after the window resets.",
|
|
8124
8135
|
_parseRetryAfter(response.headers.get("Retry-After"), body.retry_after),
|
|
8125
8136
|
body,
|
|
8126
8137
|
typeof body.scope === "string" ? body.scope : void 0,
|
|
@@ -8140,7 +8151,7 @@ async function _raiseForStatus(response) {
|
|
|
8140
8151
|
if (code === AgentConcurrentUpdateError.code && response.status === 409) {
|
|
8141
8152
|
throw new AgentConcurrentUpdateError({ message, details: body, hint });
|
|
8142
8153
|
}
|
|
8143
|
-
const ExcCtor = ERROR_CODE_STATUS_MAP[`${code}:${response.status}`] ?? ERROR_CODE_MAP[code];
|
|
8154
|
+
const ExcCtor = ERROR_CODE_STATUS_MAP[`${code}:${response.status}`] ?? (Object.hasOwn(ERROR_CODE_MAP, code) ? ERROR_CODE_MAP[code] : void 0);
|
|
8144
8155
|
if (ExcCtor !== void 0) {
|
|
8145
8156
|
throw new ExcCtor({ message, details: body, hint });
|
|
8146
8157
|
}
|
|
@@ -8318,9 +8329,15 @@ var AgentsNamespace = class {
|
|
|
8318
8329
|
const limit = options.limit ?? 100;
|
|
8319
8330
|
const offset = options.offset ?? 0;
|
|
8320
8331
|
const includeRevoked = options.includeRevoked ?? false;
|
|
8332
|
+
if (!Number.isInteger(limit)) {
|
|
8333
|
+
throw new AlterValueError("limit must be an integer");
|
|
8334
|
+
}
|
|
8321
8335
|
if (limit < 1 || limit > LIST_LIMIT_MAX) {
|
|
8322
8336
|
throw new AlterValueError("limit must be between 1 and 1000");
|
|
8323
8337
|
}
|
|
8338
|
+
if (!Number.isInteger(offset)) {
|
|
8339
|
+
throw new AlterValueError("offset must be an integer");
|
|
8340
|
+
}
|
|
8324
8341
|
if (offset < 0) {
|
|
8325
8342
|
throw new AlterValueError("offset must be >= 0");
|
|
8326
8343
|
}
|
|
@@ -8766,7 +8783,7 @@ function _optionalStringArray(raw, key, context) {
|
|
|
8766
8783
|
}
|
|
8767
8784
|
function _normalizeKeyId(value, operation) {
|
|
8768
8785
|
if (typeof value === "string") {
|
|
8769
|
-
if (value.length === 0) {
|
|
8786
|
+
if (value.trim().length === 0) {
|
|
8770
8787
|
throw new AlterValueError(`${operation}() requires a non-empty keyId`);
|
|
8771
8788
|
}
|
|
8772
8789
|
return value;
|
|
@@ -9468,6 +9485,26 @@ var SpansNamespace = class {
|
|
|
9468
9485
|
`spans[${index}] must be a UserSpan with traceId, name, and startTime`
|
|
9469
9486
|
);
|
|
9470
9487
|
}
|
|
9488
|
+
if (span.traceId.length < 1 || span.traceId.length > 255) {
|
|
9489
|
+
throw new AlterValueError(
|
|
9490
|
+
`spans[${index}].traceId must be 1\u2013255 characters`
|
|
9491
|
+
);
|
|
9492
|
+
}
|
|
9493
|
+
if (span.name.length < 1 || span.name.length > 255) {
|
|
9494
|
+
throw new AlterValueError(
|
|
9495
|
+
`spans[${index}].name must be 1\u2013255 characters`
|
|
9496
|
+
);
|
|
9497
|
+
}
|
|
9498
|
+
if (span.spanId !== void 0 && (typeof span.spanId !== "string" || span.spanId.length < 1 || span.spanId.length > 32)) {
|
|
9499
|
+
throw new AlterValueError(
|
|
9500
|
+
`spans[${index}].spanId must be 1\u201332 characters when provided`
|
|
9501
|
+
);
|
|
9502
|
+
}
|
|
9503
|
+
if (span.parentSpanId !== void 0 && (typeof span.parentSpanId !== "string" || span.parentSpanId.length < 1 || span.parentSpanId.length > 32)) {
|
|
9504
|
+
throw new AlterValueError(
|
|
9505
|
+
`spans[${index}].parentSpanId must be 1\u201332 characters when provided`
|
|
9506
|
+
);
|
|
9507
|
+
}
|
|
9471
9508
|
});
|
|
9472
9509
|
const body = {
|
|
9473
9510
|
spans: spans.map((span) => {
|
|
@@ -9794,8 +9831,14 @@ var FORBIDDEN_INJECTION_HEADERS2 = /* @__PURE__ */ new Set([
|
|
|
9794
9831
|
function _coerceInt(value) {
|
|
9795
9832
|
return Number.isInteger(value) && value >= 0 ? value : void 0;
|
|
9796
9833
|
}
|
|
9834
|
+
var IDENTITY_SYNC_POLL_STATUS = {
|
|
9835
|
+
service_unavailable: 503,
|
|
9836
|
+
identity_provider_inactive: 409,
|
|
9837
|
+
identity_group_name_ambiguous: 409
|
|
9838
|
+
};
|
|
9797
9839
|
function _messageOr(errorData, fallback) {
|
|
9798
|
-
|
|
9840
|
+
const raw = errorData.message;
|
|
9841
|
+
return typeof raw === "string" && raw ? raw : fallback;
|
|
9799
9842
|
}
|
|
9800
9843
|
function _errorPayload(errorData) {
|
|
9801
9844
|
const nested = errorData.details;
|
|
@@ -9918,7 +9961,8 @@ function validateTokenInjectionMetadata(tokenData) {
|
|
|
9918
9961
|
let additionalCredentials = null;
|
|
9919
9962
|
if (additionalCredentialsRaw != null) {
|
|
9920
9963
|
if (typeof additionalCredentialsRaw !== "object" || Array.isArray(additionalCredentialsRaw) || Object.keys(additionalCredentialsRaw).length > MAX_ADDITIONAL_CREDENTIAL_KEYS || !Object.entries(additionalCredentialsRaw).every(
|
|
9921
|
-
([key, value]) => key.length <= MAX_ADDITIONAL_CREDENTIAL_KEY_LENGTH && typeof value === "string" &&
|
|
9964
|
+
([key, value]) => key.length <= MAX_ADDITIONAL_CREDENTIAL_KEY_LENGTH && typeof value === "string" && // pythonStrip: match the backend's Python str.strip() (see token_type).
|
|
9965
|
+
pythonStrip(value).length > 0 && value.length <= MAX_ADDITIONAL_CREDENTIAL_VALUE_LENGTH && // eslint-disable-next-line no-control-regex
|
|
9922
9966
|
!/[\r\n\x00]/.test(value)
|
|
9923
9967
|
)) {
|
|
9924
9968
|
throw new BackendError("Backend returned invalid additional_credentials");
|
|
@@ -10016,7 +10060,23 @@ function isValidOffsetDateTime(value) {
|
|
|
10016
10060
|
const hour = Number(hourText);
|
|
10017
10061
|
const minute = Number(minuteText);
|
|
10018
10062
|
const second = Number(secondText);
|
|
10019
|
-
|
|
10063
|
+
const isLeapYear = year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
|
|
10064
|
+
const daysInMonth = [
|
|
10065
|
+
31,
|
|
10066
|
+
isLeapYear ? 29 : 28,
|
|
10067
|
+
31,
|
|
10068
|
+
30,
|
|
10069
|
+
31,
|
|
10070
|
+
30,
|
|
10071
|
+
31,
|
|
10072
|
+
31,
|
|
10073
|
+
30,
|
|
10074
|
+
31,
|
|
10075
|
+
30,
|
|
10076
|
+
31
|
|
10077
|
+
];
|
|
10078
|
+
const maxDay = daysInMonth[month - 1] ?? 0;
|
|
10079
|
+
if (year < 1 || month < 1 || month > 12 || day < 1 || day > maxDay || hour > 23 || minute > 59 || second > 59 || offsetHour > 23 || offsetMinute > 59) {
|
|
10020
10080
|
return false;
|
|
10021
10081
|
}
|
|
10022
10082
|
return !Number.isNaN(Date.parse(value));
|
|
@@ -10036,7 +10096,10 @@ function validateTokenResponseContract(tokenData) {
|
|
|
10036
10096
|
throw new BackendError("Backend returned an invalid access_token");
|
|
10037
10097
|
}
|
|
10038
10098
|
const tokenType = tokenData.token_type === void 0 ? "Bearer" : tokenData.token_type;
|
|
10039
|
-
if (typeof tokenType !== "string" ||
|
|
10099
|
+
if (typeof tokenType !== "string" || // pythonStrip (not .trim()): the backend normalizes with Python str.strip(),
|
|
10100
|
+
// whose whitespace set differs from JS trim() at the edges (NEL/BOM/C0
|
|
10101
|
+
// separators). Match the backend so the two SDKs accept/reject identically.
|
|
10102
|
+
pythonStrip(tokenType).length === 0 || tokenType !== pythonStrip(tokenType) || tokenType.length > 255 || // eslint-disable-next-line no-control-regex
|
|
10040
10103
|
/[\x00-\x1f\x7f]/.test(tokenType)) {
|
|
10041
10104
|
throw new BackendError("Backend returned an invalid token_type");
|
|
10042
10105
|
}
|
|
@@ -10050,7 +10113,8 @@ function validateTokenResponseContract(tokenData) {
|
|
|
10050
10113
|
}
|
|
10051
10114
|
const scopes = tokenData.scopes === void 0 ? [] : tokenData.scopes;
|
|
10052
10115
|
if (!Array.isArray(scopes) || scopes.some(
|
|
10053
|
-
(scope) => typeof scope !== "string" ||
|
|
10116
|
+
(scope) => typeof scope !== "string" || // pythonStrip: match the backend's Python str.strip() (see token_type).
|
|
10117
|
+
pythonStrip(scope).length === 0 || scope !== pythonStrip(scope) || // eslint-disable-next-line no-control-regex
|
|
10054
10118
|
/[\x00-\x1f\x7f]/.test(scope)
|
|
10055
10119
|
)) {
|
|
10056
10120
|
throw new BackendError("Backend returned invalid scopes");
|
|
@@ -10060,7 +10124,8 @@ function validateTokenResponseContract(tokenData) {
|
|
|
10060
10124
|
throw new BackendError("Backend returned an invalid grant_id");
|
|
10061
10125
|
}
|
|
10062
10126
|
const providerId = tokenData.provider_id === void 0 ? "" : tokenData.provider_id;
|
|
10063
|
-
if (typeof providerId !== "string" ||
|
|
10127
|
+
if (typeof providerId !== "string" || // pythonStrip: match the backend's Python str.strip() (see token_type).
|
|
10128
|
+
providerId !== pythonStrip(providerId) || providerId.length > 255 || // eslint-disable-next-line no-control-regex
|
|
10064
10129
|
/[\x00-\x1f\x7f]/.test(providerId)) {
|
|
10065
10130
|
throw new BackendError("Backend returned an invalid provider_id");
|
|
10066
10131
|
}
|
|
@@ -10089,7 +10154,10 @@ function isRetryErrorInfoPayload(x) {
|
|
|
10089
10154
|
return false;
|
|
10090
10155
|
}
|
|
10091
10156
|
const e = x;
|
|
10092
|
-
return typeof e.attempt === "number" &&
|
|
10157
|
+
return typeof e.attempt === "number" && // Integer-only (not merely finite) — mirrors the backend Pydantic `int`
|
|
10158
|
+
// schema and the Python SDK's strict `RetryErrorInfo.attempt`, so a
|
|
10159
|
+
// fractional attempt (e.g. 1.5) is dropped identically in both SDKs.
|
|
10160
|
+
Number.isInteger(e.attempt) && e.attempt >= 1 && typeof e.error === "string" && e.error.length <= 500 && typeof e.error_type === "string" && typeof e.delay_s === "number" && Number.isFinite(e.delay_s) && e.delay_s >= 0 && (e.permanent === void 0 || typeof e.permanent === "boolean");
|
|
10093
10161
|
}
|
|
10094
10162
|
var _tokenStore = /* @__PURE__ */ new WeakMap();
|
|
10095
10163
|
var _additionalCredsStore = /* @__PURE__ */ new WeakMap();
|
|
@@ -10112,7 +10180,7 @@ function _extractAdditionalCredentials(token) {
|
|
|
10112
10180
|
return _additionalCredsStore.get(token);
|
|
10113
10181
|
}
|
|
10114
10182
|
var _fetch;
|
|
10115
|
-
var SDK_VERSION = "0.
|
|
10183
|
+
var SDK_VERSION = "0.24.2";
|
|
10116
10184
|
var SDK_USER_AGENT = `alter-sdk-node/${SDK_VERSION}`;
|
|
10117
10185
|
function pyUnquote(s) {
|
|
10118
10186
|
if (!s.includes("%")) return s;
|
|
@@ -10183,8 +10251,12 @@ function canonicalSigningQuery(rawQuery) {
|
|
|
10183
10251
|
pairs.sort((p, q) => byCodePoint(p[0], q[0]) || byCodePoint(p[1], q[1]));
|
|
10184
10252
|
return pairs.map(([k, v]) => `${pyQuotePlus(k)}=${pyQuotePlus(v)}`).join("&");
|
|
10185
10253
|
}
|
|
10254
|
+
var DEFAULT_TIMEOUT_MS = 95e3;
|
|
10255
|
+
var _ApprovalDeadlineElapsed = class extends Error {
|
|
10256
|
+
};
|
|
10186
10257
|
var AUTH_POLL_SERVER_WAIT_MS = 25e3;
|
|
10187
10258
|
var AUTH_POLL_HTTP_BUFFER_MS = 15e3;
|
|
10259
|
+
var APPROVAL_POLL_SERVER_WAIT_MS = 25e3;
|
|
10188
10260
|
var PERMANENT_POLL_STATUSES = /* @__PURE__ */ new Set([400, 401, 403, 404, 422]);
|
|
10189
10261
|
var HTTP_FORBIDDEN = 403;
|
|
10190
10262
|
var HTTP_NO_CONTENT2 = 204;
|
|
@@ -10381,7 +10453,14 @@ var PROXY_FORBIDDEN_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
|
10381
10453
|
"x-goog-authorization",
|
|
10382
10454
|
"x-goog-iam-authorization-token",
|
|
10383
10455
|
"x-stripe-account",
|
|
10384
|
-
"x-shopify-access-token"
|
|
10456
|
+
"x-shopify-access-token",
|
|
10457
|
+
// Method tunnelling: OData V2 (SAP S/4HANA) and several enterprise APIs
|
|
10458
|
+
// honour these as an override of the real HTTP verb. The policy gate
|
|
10459
|
+
// classifies on the wire method, so a tunnelled verb would split the
|
|
10460
|
+
// operation the gate names from the one the provider executes.
|
|
10461
|
+
"x-http-method",
|
|
10462
|
+
"x-http-method-override",
|
|
10463
|
+
"x-method-override"
|
|
10385
10464
|
]);
|
|
10386
10465
|
var MAX_CONTEXT_LENGTH = 4096;
|
|
10387
10466
|
var MAX_CONTEXT_KEYS = 20;
|
|
@@ -10418,9 +10497,10 @@ function validateAndSerializeContext(context) {
|
|
|
10418
10497
|
`context keys must be strings, got ${typeof k}`
|
|
10419
10498
|
);
|
|
10420
10499
|
}
|
|
10421
|
-
|
|
10500
|
+
const keyLength = [...k].length;
|
|
10501
|
+
if (keyLength > MAX_CONTEXT_KEY_LENGTH) {
|
|
10422
10502
|
throw new AlterValueError(
|
|
10423
|
-
`context key "${k.slice(0, 32)}" exceeds max length (${
|
|
10503
|
+
`context key "${k.slice(0, 32)}" exceeds max length (${keyLength} > ${MAX_CONTEXT_KEY_LENGTH})`
|
|
10424
10504
|
);
|
|
10425
10505
|
}
|
|
10426
10506
|
const v = context[k];
|
|
@@ -10429,9 +10509,10 @@ function validateAndSerializeContext(context) {
|
|
|
10429
10509
|
`context value for key "${k}" must be a string, got ${typeof v}`
|
|
10430
10510
|
);
|
|
10431
10511
|
}
|
|
10432
|
-
|
|
10512
|
+
const valueLength = [...v].length;
|
|
10513
|
+
if (valueLength > MAX_CONTEXT_VALUE_LENGTH) {
|
|
10433
10514
|
throw new AlterValueError(
|
|
10434
|
-
`context value for key "${k}" exceeds max length (${
|
|
10515
|
+
`context value for key "${k}" exceeds max length (${valueLength} > ${MAX_CONTEXT_VALUE_LENGTH})`
|
|
10435
10516
|
);
|
|
10436
10517
|
}
|
|
10437
10518
|
}
|
|
@@ -10451,6 +10532,23 @@ function validateAndSerializeContext(context) {
|
|
|
10451
10532
|
return encoded;
|
|
10452
10533
|
}
|
|
10453
10534
|
var MAX_BODY_SIZE_BYTES = 1e4;
|
|
10535
|
+
var AUDIT_REFUSAL_BODY_MAX_CHARS = 65536;
|
|
10536
|
+
var REFUSAL_MESSAGE_CONTROL_CHARS = /[\u0000-\u001f\u007f-\u009f]/g;
|
|
10537
|
+
var REFUSAL_MESSAGE_MAX_CHARS = 300;
|
|
10538
|
+
function sanitizeRefusalMessage(message) {
|
|
10539
|
+
const cleaned = message.replace(REFUSAL_MESSAGE_CONTROL_CHARS, " ");
|
|
10540
|
+
return cleaned.length > REFUSAL_MESSAGE_MAX_CHARS ? `${cleaned.slice(0, REFUSAL_MESSAGE_MAX_CHARS)}\u2026` : cleaned;
|
|
10541
|
+
}
|
|
10542
|
+
function _redactUrlForLog(url2) {
|
|
10543
|
+
try {
|
|
10544
|
+
const parsed = new URL(url2);
|
|
10545
|
+
if (parsed.protocol && parsed.hostname) {
|
|
10546
|
+
return `${parsed.protocol}//${parsed.hostname}`;
|
|
10547
|
+
}
|
|
10548
|
+
} catch {
|
|
10549
|
+
}
|
|
10550
|
+
return "<unparseable-url>";
|
|
10551
|
+
}
|
|
10454
10552
|
var HTTP_CLIENT_ERROR_START = 400;
|
|
10455
10553
|
var MAX_ACTOR_STRING_LENGTH = 255;
|
|
10456
10554
|
var SAFE_HEADER_PATTERN = /^[\x20-\x7E]+$/;
|
|
@@ -10531,11 +10629,28 @@ function connectMetadataToWire(metadata) {
|
|
|
10531
10629
|
}
|
|
10532
10630
|
return wire;
|
|
10533
10631
|
}
|
|
10534
|
-
var
|
|
10535
|
-
"expiresAt",
|
|
10536
|
-
"maxTtlSeconds",
|
|
10537
|
-
"defaultTtlSeconds"
|
|
10538
|
-
]
|
|
10632
|
+
var GRANT_POLICY_CAMEL_TO_WIRE = [
|
|
10633
|
+
["expiresAt", "expires_at"],
|
|
10634
|
+
["maxTtlSeconds", "max_ttl_seconds"],
|
|
10635
|
+
["defaultTtlSeconds", "default_ttl_seconds"]
|
|
10636
|
+
];
|
|
10637
|
+
var GRANT_POLICY_INPUT_KEYS = new Set(
|
|
10638
|
+
GRANT_POLICY_CAMEL_TO_WIRE.map(([camelKey]) => camelKey)
|
|
10639
|
+
);
|
|
10640
|
+
function assertParseableExpiresAt(value) {
|
|
10641
|
+
if (typeof value !== "string" || value.length === 0 || Number.isNaN(Date.parse(value))) {
|
|
10642
|
+
throw new AlterValueError(
|
|
10643
|
+
"grantPolicy.expiresAt must be a non-empty ISO 8601 string when provided"
|
|
10644
|
+
);
|
|
10645
|
+
}
|
|
10646
|
+
}
|
|
10647
|
+
function assertPositiveIntegerSeconds(camelKey, value) {
|
|
10648
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
10649
|
+
throw new AlterValueError(
|
|
10650
|
+
`grantPolicy.${camelKey} must be a positive integer (seconds) when provided`
|
|
10651
|
+
);
|
|
10652
|
+
}
|
|
10653
|
+
}
|
|
10539
10654
|
function grantPolicyInputToWire(policy) {
|
|
10540
10655
|
if (policy === null || typeof policy !== "object" || Array.isArray(policy)) {
|
|
10541
10656
|
throw new AlterValueError(
|
|
@@ -10545,32 +10660,57 @@ function grantPolicyInputToWire(policy) {
|
|
|
10545
10660
|
for (const key of Object.keys(policy)) {
|
|
10546
10661
|
if (!GRANT_POLICY_INPUT_KEYS.has(key)) {
|
|
10547
10662
|
throw new AlterValueError(
|
|
10548
|
-
`grantPolicy contains unknown key "${key}" (allowed:
|
|
10663
|
+
`grantPolicy contains unknown key "${key}" (allowed: ${[...GRANT_POLICY_INPUT_KEYS].join(", ")}). Note: keys are camelCase in the TypeScript SDK \u2014 snake_case keys like max_ttl_seconds are the Python SDK surface.`
|
|
10549
10664
|
);
|
|
10550
10665
|
}
|
|
10551
10666
|
}
|
|
10552
10667
|
const wire = {};
|
|
10553
|
-
|
|
10554
|
-
|
|
10668
|
+
for (const [camelKey, wireKey] of GRANT_POLICY_CAMEL_TO_WIRE) {
|
|
10669
|
+
const value = policy[camelKey];
|
|
10670
|
+
if (value === void 0 || value === null) {
|
|
10671
|
+
continue;
|
|
10672
|
+
}
|
|
10673
|
+
if (camelKey === "expiresAt") {
|
|
10674
|
+
assertParseableExpiresAt(value);
|
|
10675
|
+
} else {
|
|
10676
|
+
assertPositiveIntegerSeconds(camelKey, value);
|
|
10677
|
+
}
|
|
10678
|
+
wire[wireKey] = value;
|
|
10679
|
+
}
|
|
10680
|
+
return wire;
|
|
10681
|
+
}
|
|
10682
|
+
function _mintGrantPolicyToWire(policy) {
|
|
10683
|
+
for (const spelling of ["defaultTtlSeconds", "default_ttl_seconds"]) {
|
|
10684
|
+
if (Object.prototype.hasOwnProperty.call(policy, spelling)) {
|
|
10555
10685
|
throw new AlterValueError(
|
|
10556
|
-
|
|
10686
|
+
`grantPolicy.${spelling} is not supported on mint \u2014 a default TTL is accepted only by Connect sessions (createConnectSession / connect) and createManagedSecretGrant`
|
|
10557
10687
|
);
|
|
10558
10688
|
}
|
|
10559
|
-
wire.expires_at = policy.expiresAt;
|
|
10560
10689
|
}
|
|
10561
|
-
|
|
10562
|
-
|
|
10563
|
-
|
|
10564
|
-
|
|
10690
|
+
const wire = {};
|
|
10691
|
+
for (const [key, value] of Object.entries(policy)) {
|
|
10692
|
+
if (!GRANT_POLICY_INPUT_KEYS.has(key)) {
|
|
10693
|
+
wire[key] = value;
|
|
10694
|
+
}
|
|
10695
|
+
}
|
|
10696
|
+
for (const [camelKey, wireKey] of GRANT_POLICY_CAMEL_TO_WIRE) {
|
|
10565
10697
|
const value = policy[camelKey];
|
|
10566
|
-
if (value === void 0
|
|
10698
|
+
if (value === void 0) {
|
|
10567
10699
|
continue;
|
|
10568
10700
|
}
|
|
10569
|
-
if (
|
|
10701
|
+
if (Object.prototype.hasOwnProperty.call(policy, wireKey)) {
|
|
10570
10702
|
throw new AlterValueError(
|
|
10571
|
-
`grantPolicy
|
|
10703
|
+
`grantPolicy sets both "${camelKey}" and "${wireKey}" \u2014 supply exactly one spelling of the field`
|
|
10572
10704
|
);
|
|
10573
10705
|
}
|
|
10706
|
+
if (value === null) {
|
|
10707
|
+
continue;
|
|
10708
|
+
}
|
|
10709
|
+
if (camelKey === "expiresAt") {
|
|
10710
|
+
assertParseableExpiresAt(value);
|
|
10711
|
+
} else {
|
|
10712
|
+
assertPositiveIntegerSeconds(camelKey, value);
|
|
10713
|
+
}
|
|
10574
10714
|
wire[wireKey] = value;
|
|
10575
10715
|
}
|
|
10576
10716
|
return wire;
|
|
@@ -10697,6 +10837,16 @@ var HttpClient = class {
|
|
|
10697
10837
|
new DOMException("The operation timed out.", "TimeoutError")
|
|
10698
10838
|
);
|
|
10699
10839
|
}, effectiveTimeoutMs);
|
|
10840
|
+
const callerSignal = options?.signal;
|
|
10841
|
+
let onCallerAbort;
|
|
10842
|
+
if (callerSignal !== void 0) {
|
|
10843
|
+
if (callerSignal.aborted) {
|
|
10844
|
+
controller.abort(callerSignal.reason);
|
|
10845
|
+
} else {
|
|
10846
|
+
onCallerAbort = () => controller.abort(callerSignal.reason);
|
|
10847
|
+
callerSignal.addEventListener("abort", onCallerAbort);
|
|
10848
|
+
}
|
|
10849
|
+
}
|
|
10700
10850
|
const init = {
|
|
10701
10851
|
method,
|
|
10702
10852
|
headers: mergedHeaders,
|
|
@@ -10735,6 +10885,9 @@ var HttpClient = class {
|
|
|
10735
10885
|
return response;
|
|
10736
10886
|
} finally {
|
|
10737
10887
|
clearTimeout(timeoutId);
|
|
10888
|
+
if (callerSignal !== void 0 && onCallerAbort !== void 0) {
|
|
10889
|
+
callerSignal.removeEventListener("abort", onCallerAbort);
|
|
10890
|
+
}
|
|
10738
10891
|
}
|
|
10739
10892
|
}
|
|
10740
10893
|
/**
|
|
@@ -10847,7 +11000,7 @@ var _VaultClient = class __VaultClient {
|
|
|
10847
11000
|
unresolvedBaseUrl,
|
|
10848
11001
|
options.logger ?? console
|
|
10849
11002
|
);
|
|
10850
|
-
const timeoutMs = options.timeout ??
|
|
11003
|
+
const timeoutMs = options.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
10851
11004
|
this.#caller = options.caller;
|
|
10852
11005
|
const rawCallerType = options.callerType ?? "agent";
|
|
10853
11006
|
if (rawCallerType !== "agent" && rawCallerType !== "service") {
|
|
@@ -11428,8 +11581,13 @@ ${label}:${value}`;
|
|
|
11428
11581
|
};
|
|
11429
11582
|
throw new ScopeReauthRequiredError(
|
|
11430
11583
|
_messageOr(errorData, "Grant is missing required scopes"),
|
|
11431
|
-
|
|
11432
|
-
|
|
11584
|
+
// `?? undefined` alone only replaces null — a NON-string grant_id /
|
|
11585
|
+
// provider_id (number, object, bool) would be cast `as string` and
|
|
11586
|
+
// flow through as the wrong runtime value. typeof-guard instead, to
|
|
11587
|
+
// match every sibling branch in this method AND the Python SDK's
|
|
11588
|
+
// `_handle_forbidden` scope_mismatch branch (isinstance → None).
|
|
11589
|
+
typeof details.grant_id === "string" ? details.grant_id : void 0,
|
|
11590
|
+
typeof details.provider_id === "string" ? details.provider_id : void 0,
|
|
11433
11591
|
response.status,
|
|
11434
11592
|
// No provider response body on this path — the backend refused at
|
|
11435
11593
|
// /sdk/token before any provider call, so responseBody is left unset
|
|
@@ -11471,6 +11629,15 @@ ${label}:${value}`;
|
|
|
11471
11629
|
}
|
|
11472
11630
|
if (response.status === HTTP_CONFLICT) {
|
|
11473
11631
|
const errorData = await __VaultClient.#safeParseJson(response);
|
|
11632
|
+
if (errorData.error === "managed_oauth_scope_approval_required") {
|
|
11633
|
+
throw new ConnectConfigError(
|
|
11634
|
+
_messageOr(
|
|
11635
|
+
errorData,
|
|
11636
|
+
"The requested permissions require managed OAuth approval."
|
|
11637
|
+
),
|
|
11638
|
+
errorData
|
|
11639
|
+
);
|
|
11640
|
+
}
|
|
11474
11641
|
if (errorData.error === "token_refresh_in_progress") {
|
|
11475
11642
|
throw new TokenRefreshInProgressError(
|
|
11476
11643
|
_messageOr(
|
|
@@ -12038,9 +12205,34 @@ ${label}:${value}`;
|
|
|
12038
12205
|
});
|
|
12039
12206
|
this.#cacheActorIdFromResponse(response);
|
|
12040
12207
|
if (!response.ok) {
|
|
12208
|
+
let detail = "";
|
|
12209
|
+
try {
|
|
12210
|
+
const raw = await response.text();
|
|
12211
|
+
if (raw.length <= AUDIT_REFUSAL_BODY_MAX_CHARS) {
|
|
12212
|
+
const body = JSON.parse(raw);
|
|
12213
|
+
let message = body.message;
|
|
12214
|
+
if (typeof message !== "string" || message === "") {
|
|
12215
|
+
const inner = body.detail;
|
|
12216
|
+
if (typeof inner === "string") {
|
|
12217
|
+
message = inner;
|
|
12218
|
+
} else if (inner !== null && typeof inner === "object") {
|
|
12219
|
+
message = inner.message;
|
|
12220
|
+
}
|
|
12221
|
+
}
|
|
12222
|
+
if (typeof message === "string" && message !== "") {
|
|
12223
|
+
detail = `: ${sanitizeRefusalMessage(message)}`;
|
|
12224
|
+
}
|
|
12225
|
+
}
|
|
12226
|
+
} catch {
|
|
12227
|
+
}
|
|
12041
12228
|
this.#logger.warn(
|
|
12042
|
-
`Audit log failed with status ${response.status} (non-fatal)`
|
|
12229
|
+
`Audit log failed with status ${response.status} (non-fatal)${detail} \u2014 this call's api-call audit row was not recorded (grant_id=${params.grantId}, method=${params.method}, url=${_redactUrlForLog(params.url)})`
|
|
12043
12230
|
);
|
|
12231
|
+
} else {
|
|
12232
|
+
try {
|
|
12233
|
+
await response.arrayBuffer();
|
|
12234
|
+
} catch {
|
|
12235
|
+
}
|
|
12044
12236
|
}
|
|
12045
12237
|
} catch (error51) {
|
|
12046
12238
|
this.#logger.warn(
|
|
@@ -12551,7 +12743,7 @@ ${label}:${value}`;
|
|
|
12551
12743
|
headers: hmacHeaders,
|
|
12552
12744
|
// Cap the best-effort background report at 2s (parity with the Python
|
|
12553
12745
|
// SDK's asyncio.wait_for). Without an override it would inherit the
|
|
12554
|
-
// instance timeout (default
|
|
12746
|
+
// instance timeout (default 95s), so a hung backend could keep the
|
|
12555
12747
|
// fire-and-forget task — and close()'s drain of it — alive far longer
|
|
12556
12748
|
// than the Python SDK does. Next provider 403 re-reports (self-healing).
|
|
12557
12749
|
timeoutMs: 2e3
|
|
@@ -12928,8 +13120,11 @@ ${label}:${value}`;
|
|
|
12928
13120
|
* @param options - Optional revocation options (reason for audit log).
|
|
12929
13121
|
* @returns RevokeGrantResult confirming the revocation.
|
|
12930
13122
|
* @throws {GrantNotFoundError} If the grant does not exist, is not
|
|
12931
|
-
* active,
|
|
12932
|
-
* the calling user
|
|
13123
|
+
* active, (when a user token is configured) does not belong to
|
|
13124
|
+
* the calling user, or — when this client holds an AGENT key — is
|
|
13125
|
+
* not a grant the agent is the principal of (its own delegation or
|
|
13126
|
+
* an agent-owned grant). An allowlist entry for the grant's provider
|
|
13127
|
+
* is never enough.
|
|
12933
13128
|
* @throws {NetworkError} If connection to backend fails.
|
|
12934
13129
|
* @throws {TimeoutError} If request to backend times out.
|
|
12935
13130
|
*/
|
|
@@ -13350,6 +13545,16 @@ ${label}:${value}`;
|
|
|
13350
13545
|
* entry. Thrown before any wire traffic (parity with the Python SDK).
|
|
13351
13546
|
* @throws AlterSDKError if the SDK instance is closed
|
|
13352
13547
|
* @throws NetworkError if the connection to the backend fails
|
|
13548
|
+
* @throws ConnectConfigError if the session's requested scopes exceed what
|
|
13549
|
+
* the Alter-managed OAuth client is approved for (HTTP 409
|
|
13550
|
+
* `managed_oauth_scope_approval_required`). NOT a `BackendError` — a
|
|
13551
|
+
* caller that branches only on `BackendError` leaves this uncaught.
|
|
13552
|
+
* `details` carries the whole 409 body: `details.details.provider_id` and
|
|
13553
|
+
* `details.details.unapproved_scopes` name what needs approving,
|
|
13554
|
+
* `details.remediation.action` / `.description` carry the typed remedy,
|
|
13555
|
+
* and `details.retryable` is `false`. Retrying is futile: contact Alter to
|
|
13556
|
+
* approve the scopes, or narrow the app's configured scopes to the
|
|
13557
|
+
* approved set, and only then create a new Connect session.
|
|
13353
13558
|
* @throws BackendError if the backend rejects the request (e.g.
|
|
13354
13559
|
* `scope_not_allowed` when a requested scope is not in the app's Dev
|
|
13355
13560
|
* Portal allowlist)
|
|
@@ -13682,6 +13887,10 @@ ${label}:${value}`;
|
|
|
13682
13887
|
* authorized grant is revoked because the user's usage limits could not be
|
|
13683
13888
|
* applied. In the last case, `details.failed_grants` contains the typed
|
|
13684
13889
|
* failure data in snake_case wire form.
|
|
13890
|
+
* @throws BackendError if identity synchronization could not be completed —
|
|
13891
|
+
* `statusCode` 503 (transient, with `details.retry_after_seconds`) or 409
|
|
13892
|
+
* (an operator must repair the provider; no retry hint). Not a
|
|
13893
|
+
* `ConnectFlowError`: the flow itself was fine.
|
|
13685
13894
|
* @throws AlterSDKError if SDK is closed or session creation fails
|
|
13686
13895
|
*/
|
|
13687
13896
|
async connect(options) {
|
|
@@ -13726,12 +13935,17 @@ ${label}:${value}`;
|
|
|
13726
13935
|
* @returns One `ConnectResult` per provider the user completed
|
|
13727
13936
|
* within the session (multi-provider Connect sessions yield
|
|
13728
13937
|
* multiple results).
|
|
13729
|
-
* @throws AlterValueError if `sessionToken` is blank,
|
|
13730
|
-
* `pollIntervalMs` is non-finite or not greater than zero
|
|
13938
|
+
* @throws AlterValueError if `sessionToken` is blank, `timeoutMs` /
|
|
13939
|
+
* `pollIntervalMs` is non-finite or not greater than zero, or
|
|
13940
|
+
* `onEvent` is provided but not a function.
|
|
13731
13941
|
* @throws ConnectTimeoutError if the local `timeoutMs` elapses, or a
|
|
13732
13942
|
* session observed as pending expires before Alter receives a
|
|
13733
13943
|
* completion callback. `details.reason` distinguishes
|
|
13734
|
-
* `poll_deadline_elapsed`
|
|
13944
|
+
* `poll_deadline_elapsed`, `session_expired`, and `rate_limited` (the
|
|
13945
|
+
* whole budget was spent backing off a throttled poll —
|
|
13946
|
+
* `details.retryAfter` carries the last hint the server gave). When
|
|
13947
|
+
* denials were observed before the timeout,
|
|
13948
|
+
* `details.declined_providers` lists them on EVERY variant.
|
|
13735
13949
|
* @throws ConnectFlowError / ConnectDeniedError / ConnectConfigError
|
|
13736
13950
|
* for user denial, a first poll that finds an unavailable/expired
|
|
13737
13951
|
* session, unrecognized status, or a completed session whose every grant
|
|
@@ -13739,6 +13953,11 @@ ${label}:${value}`;
|
|
|
13739
13953
|
* total-failure error carries
|
|
13740
13954
|
* `details.failed_grants`; partial failures are surfaced on every
|
|
13741
13955
|
* returned result as `failedGrants`.
|
|
13956
|
+
* @throws BackendError if identity synchronization could not be completed.
|
|
13957
|
+
* `statusCode` says whether to come back: 503 is transient contention
|
|
13958
|
+
* and `details.retry_after_seconds` carries the backoff hint, while 409
|
|
13959
|
+
* is an operator-repairable provider fault (disabled provider, duplicate
|
|
13960
|
+
* group name) that no retry will clear and which carries no hint.
|
|
13742
13961
|
* @throws AlterSDKError if the SDK instance has been closed.
|
|
13743
13962
|
*/
|
|
13744
13963
|
async pollConnectSession(sessionToken, options) {
|
|
@@ -13746,11 +13965,48 @@ ${label}:${value}`;
|
|
|
13746
13965
|
const timeoutMs = options?.timeoutMs ?? 3e5;
|
|
13747
13966
|
const pollIntervalMs = options?.pollIntervalMs ?? 2e3;
|
|
13748
13967
|
validatePollingInputs(sessionToken, timeoutMs, pollIntervalMs);
|
|
13968
|
+
if (options?.onEvent !== void 0 && typeof options.onEvent !== "function") {
|
|
13969
|
+
throw new AlterValueError("onEvent must be a function when provided");
|
|
13970
|
+
}
|
|
13749
13971
|
const deadline = performance.now() + timeoutMs;
|
|
13750
13972
|
let sawPending = false;
|
|
13973
|
+
const seenDeclinedProviders = /* @__PURE__ */ new Set();
|
|
13974
|
+
const withDeclined = (base) => {
|
|
13975
|
+
if (seenDeclinedProviders.size > 0) {
|
|
13976
|
+
base.declined_providers = [...seenDeclinedProviders].sort();
|
|
13977
|
+
}
|
|
13978
|
+
return base;
|
|
13979
|
+
};
|
|
13751
13980
|
while (true) {
|
|
13752
13981
|
this.#assertNotClosed();
|
|
13753
|
-
|
|
13982
|
+
let pollResult;
|
|
13983
|
+
try {
|
|
13984
|
+
pollResult = await this.#pollSession(sessionToken);
|
|
13985
|
+
} catch (e) {
|
|
13986
|
+
if (!(e instanceof RateLimitError)) {
|
|
13987
|
+
throw e;
|
|
13988
|
+
}
|
|
13989
|
+
const remaining2 = deadline - performance.now();
|
|
13990
|
+
if (remaining2 <= 0) {
|
|
13991
|
+
throw new ConnectTimeoutError(
|
|
13992
|
+
`OAuth flow did not complete within ${Math.round(timeoutMs / 1e3)} seconds: polling was rate-limited for the remainder of the budget. The authorization may still have succeeded \u2014 retry the poll with a fresh timeout, or raise the API key's per-minute rate limit if Connect flows run concurrently.`,
|
|
13993
|
+
withDeclined({
|
|
13994
|
+
timeoutMs,
|
|
13995
|
+
reason: "rate_limited",
|
|
13996
|
+
retryAfter: e.retryAfter
|
|
13997
|
+
})
|
|
13998
|
+
);
|
|
13999
|
+
}
|
|
14000
|
+
let backoffMs = pollIntervalMs;
|
|
14001
|
+
if (typeof e.retryAfter === "number" && e.retryAfter > 0) {
|
|
14002
|
+
backoffMs = Math.max(e.retryAfter * 1e3, pollIntervalMs);
|
|
14003
|
+
}
|
|
14004
|
+
await new Promise(
|
|
14005
|
+
(resolve5) => setTimeout(resolve5, Math.min(backoffMs, remaining2))
|
|
14006
|
+
);
|
|
14007
|
+
this.#assertNotClosed();
|
|
14008
|
+
continue;
|
|
14009
|
+
}
|
|
13754
14010
|
const pollStatus = pollResult.status;
|
|
13755
14011
|
if (pollStatus === "completed") {
|
|
13756
14012
|
const rawGrants = pollResult.grants;
|
|
@@ -13848,7 +14104,15 @@ ${label}:${value}`;
|
|
|
13848
14104
|
}
|
|
13849
14105
|
);
|
|
13850
14106
|
}
|
|
13851
|
-
const errorDetails = {
|
|
14107
|
+
const errorDetails = {
|
|
14108
|
+
error_code: errorCode
|
|
14109
|
+
};
|
|
14110
|
+
const retryAfter = _coerceInt(
|
|
14111
|
+
err2.retry_after_seconds
|
|
14112
|
+
);
|
|
14113
|
+
if (retryAfter !== void 0) {
|
|
14114
|
+
errorDetails.retry_after_seconds = retryAfter;
|
|
14115
|
+
}
|
|
13852
14116
|
if (errorCode === "connect_denied") {
|
|
13853
14117
|
throw new ConnectDeniedError(errorMessage, errorDetails);
|
|
13854
14118
|
}
|
|
@@ -13858,17 +14122,29 @@ ${label}:${value}`;
|
|
|
13858
14122
|
"invalid_client",
|
|
13859
14123
|
"unauthorized_client",
|
|
13860
14124
|
"provider_configuration_unavailable",
|
|
13861
|
-
"shared_dev_credential_unavailable"
|
|
14125
|
+
"shared_dev_credential_unavailable",
|
|
14126
|
+
"managed_oauth_scope_approval_required"
|
|
13862
14127
|
].includes(errorCode)) {
|
|
13863
14128
|
throw new ConnectConfigError(errorMessage, errorDetails);
|
|
13864
14129
|
}
|
|
14130
|
+
const identitySyncStatus = Object.hasOwn(
|
|
14131
|
+
IDENTITY_SYNC_POLL_STATUS,
|
|
14132
|
+
errorCode
|
|
14133
|
+
) ? IDENTITY_SYNC_POLL_STATUS[errorCode] : void 0;
|
|
14134
|
+
if (identitySyncStatus !== void 0) {
|
|
14135
|
+
throw new BackendError(
|
|
14136
|
+
errorMessage,
|
|
14137
|
+
errorDetails,
|
|
14138
|
+
identitySyncStatus
|
|
14139
|
+
);
|
|
14140
|
+
}
|
|
13865
14141
|
throw new ConnectFlowError(errorMessage, errorDetails);
|
|
13866
14142
|
}
|
|
13867
14143
|
if (pollStatus === "expired") {
|
|
13868
14144
|
if (sawPending) {
|
|
13869
14145
|
throw new ConnectTimeoutError(
|
|
13870
14146
|
"Connect session expired before authorization returned to Alter. The provider may have shown an error without redirecting, the browser may have been closed, or the user may not have completed the flow.",
|
|
13871
|
-
{ timeoutMs, reason: "session_expired" }
|
|
14147
|
+
withDeclined({ timeoutMs, reason: "session_expired" })
|
|
13872
14148
|
);
|
|
13873
14149
|
}
|
|
13874
14150
|
throw new ConnectFlowError(
|
|
@@ -13881,6 +14157,46 @@ ${label}:${value}`;
|
|
|
13881
14157
|
{ status: pollStatus }
|
|
13882
14158
|
);
|
|
13883
14159
|
}
|
|
14160
|
+
const rawDeclined = pollResult.declined_providers;
|
|
14161
|
+
if (Array.isArray(rawDeclined)) {
|
|
14162
|
+
for (const entry of rawDeclined) {
|
|
14163
|
+
if (entry === null || typeof entry !== "object") continue;
|
|
14164
|
+
const declinedProvider = entry.provider_id;
|
|
14165
|
+
if (typeof declinedProvider !== "string" || declinedProvider === "") {
|
|
14166
|
+
continue;
|
|
14167
|
+
}
|
|
14168
|
+
const rawCode = entry.error_code;
|
|
14169
|
+
if (typeof rawCode !== "string" || rawCode === "") continue;
|
|
14170
|
+
if (seenDeclinedProviders.has(declinedProvider)) continue;
|
|
14171
|
+
seenDeclinedProviders.add(declinedProvider);
|
|
14172
|
+
if (!options?.onEvent) continue;
|
|
14173
|
+
const rawDeclinedAt = entry.declined_at;
|
|
14174
|
+
const logCallbackFailure = (error51) => {
|
|
14175
|
+
this.#logger.warn(
|
|
14176
|
+
"pollConnectSession onEvent callback threw (non-fatal)",
|
|
14177
|
+
{
|
|
14178
|
+
provider_id: declinedProvider,
|
|
14179
|
+
error_type: error51 instanceof Error ? error51.constructor.name : typeof error51
|
|
14180
|
+
}
|
|
14181
|
+
);
|
|
14182
|
+
};
|
|
14183
|
+
try {
|
|
14184
|
+
const result = options.onEvent({
|
|
14185
|
+
type: "provider_declined",
|
|
14186
|
+
provider_id: declinedProvider,
|
|
14187
|
+
error_code: rawCode,
|
|
14188
|
+
declined_at: typeof rawDeclinedAt === "string" ? rawDeclinedAt : null
|
|
14189
|
+
});
|
|
14190
|
+
if (result !== null && (typeof result === "object" || typeof result === "function") && typeof result.then === "function") {
|
|
14191
|
+
void Promise.resolve(result).catch(
|
|
14192
|
+
logCallbackFailure
|
|
14193
|
+
);
|
|
14194
|
+
}
|
|
14195
|
+
} catch (error51) {
|
|
14196
|
+
logCallbackFailure(error51);
|
|
14197
|
+
}
|
|
14198
|
+
}
|
|
14199
|
+
}
|
|
13884
14200
|
sawPending = true;
|
|
13885
14201
|
const remaining = deadline - performance.now();
|
|
13886
14202
|
if (remaining <= 0) break;
|
|
@@ -13890,7 +14206,7 @@ ${label}:${value}`;
|
|
|
13890
14206
|
}
|
|
13891
14207
|
throw new ConnectTimeoutError(
|
|
13892
14208
|
`OAuth flow did not complete within ${Math.round(timeoutMs / 1e3)} seconds. Alter did not receive a completion callback; the provider may have shown an error without redirecting, the browser may have been closed, or the user may not have finished authorizing.`,
|
|
13893
|
-
{ timeoutMs, reason: "poll_deadline_elapsed" }
|
|
14209
|
+
withDeclined({ timeoutMs, reason: "poll_deadline_elapsed" })
|
|
13894
14210
|
);
|
|
13895
14211
|
}
|
|
13896
14212
|
/**
|
|
@@ -13955,6 +14271,14 @@ ${label}:${value}`;
|
|
|
13955
14271
|
* shape (popup, mobile redirect, headless). The convenience
|
|
13956
14272
|
* method doesn't infer these from the error.
|
|
13957
14273
|
* @throws AlterValueError if `error.providerId` is `undefined`.
|
|
14274
|
+
* @throws ConnectConfigError if the recovery session could not be minted
|
|
14275
|
+
* because the Alter-managed OAuth client is not approved for the scopes
|
|
14276
|
+
* this app requests (HTTP 409 `managed_oauth_scope_approval_required`).
|
|
14277
|
+
* This helper forwards to `createConnectSession`, so it throws
|
|
14278
|
+
* everything that method throws — see its `@throws` list for the
|
|
14279
|
+
* `details` payload. Retrying is futile: contact Alter to approve the
|
|
14280
|
+
* scopes, or narrow the app's configured scopes to the approved set, and
|
|
14281
|
+
* only then mint a recovery session.
|
|
13958
14282
|
*/
|
|
13959
14283
|
async createConnectSessionForError(error51, options) {
|
|
13960
14284
|
const providerId = error51.providerId;
|
|
@@ -14096,6 +14420,11 @@ ${label}:${value}`;
|
|
|
14096
14420
|
* `pollIntervalMs` is non-finite or not greater than zero.
|
|
14097
14421
|
* @throws AlterSDKError if the SDK is closed or the IDP returned a terminal
|
|
14098
14422
|
* error / the session expired.
|
|
14423
|
+
* @throws BackendError if identity synchronization could not be completed.
|
|
14424
|
+
* Surfaced separately from the terminal `AlterSDKError` above precisely
|
|
14425
|
+
* because it is NOT a rejected login: `statusCode` 503 is transient
|
|
14426
|
+
* contention carrying `details.retry_after_seconds`, and 409 is an
|
|
14427
|
+
* operator-repairable provider fault that no retry will clear.
|
|
14099
14428
|
* @throws ConnectTimeoutError if the user did not complete login in time
|
|
14100
14429
|
* (`details.reason` is `"poll_deadline_elapsed"`).
|
|
14101
14430
|
*/
|
|
@@ -14127,8 +14456,22 @@ ${label}:${value}`;
|
|
|
14127
14456
|
});
|
|
14128
14457
|
}
|
|
14129
14458
|
if (pollData.status === "error") {
|
|
14130
|
-
const errorMessage = pollData.error_message
|
|
14131
|
-
|
|
14459
|
+
const errorMessage = typeof pollData.error_message === "string" && pollData.error_message ? pollData.error_message : "unknown error";
|
|
14460
|
+
const message = `Authentication failed: ${errorMessage}`;
|
|
14461
|
+
const syncCode = typeof pollData.error_code === "string" ? pollData.error_code : "";
|
|
14462
|
+
const identitySyncStatus = Object.hasOwn(
|
|
14463
|
+
IDENTITY_SYNC_POLL_STATUS,
|
|
14464
|
+
syncCode
|
|
14465
|
+
) ? IDENTITY_SYNC_POLL_STATUS[syncCode] : void 0;
|
|
14466
|
+
if (identitySyncStatus !== void 0) {
|
|
14467
|
+
const details = { error_code: syncCode };
|
|
14468
|
+
const retryAfter = _coerceInt(pollData.retry_after_seconds);
|
|
14469
|
+
if (retryAfter !== void 0) {
|
|
14470
|
+
details.retry_after_seconds = retryAfter;
|
|
14471
|
+
}
|
|
14472
|
+
throw new BackendError(message, details, identitySyncStatus);
|
|
14473
|
+
}
|
|
14474
|
+
throw new AlterSDKError(message);
|
|
14132
14475
|
}
|
|
14133
14476
|
if (pollData.status === "expired") {
|
|
14134
14477
|
throw new AlterSDKError("Authentication session expired");
|
|
@@ -14145,10 +14488,13 @@ ${label}:${value}`;
|
|
|
14145
14488
|
(resolve5) => setTimeout(resolve5, Math.min(pollIntervalMs, remaining))
|
|
14146
14489
|
);
|
|
14147
14490
|
}
|
|
14148
|
-
throw new ConnectTimeoutError(
|
|
14149
|
-
timeoutMs
|
|
14150
|
-
|
|
14151
|
-
|
|
14491
|
+
throw new ConnectTimeoutError(
|
|
14492
|
+
`Authentication timed out after ${timeoutMs}ms. The user may not have completed login in the browser.`,
|
|
14493
|
+
{
|
|
14494
|
+
timeoutMs,
|
|
14495
|
+
reason: "poll_deadline_elapsed"
|
|
14496
|
+
}
|
|
14497
|
+
);
|
|
14152
14498
|
}
|
|
14153
14499
|
/**
|
|
14154
14500
|
* Single sign-in poll attempt (INTERNAL).
|
|
@@ -14712,12 +15058,13 @@ ${label}:${value}`;
|
|
|
14712
15058
|
async #mapProxyErrorResponse(response) {
|
|
14713
15059
|
let detail = {};
|
|
14714
15060
|
try {
|
|
14715
|
-
const
|
|
14716
|
-
|
|
15061
|
+
const parsed = await response.clone().json();
|
|
15062
|
+
const payloadDict = isPlainRecord(parsed) ? parsed : {};
|
|
15063
|
+
detail = isPlainRecord(payloadDict.detail) ? payloadDict.detail : payloadDict;
|
|
14717
15064
|
} catch {
|
|
14718
15065
|
}
|
|
14719
15066
|
const errCode = detail.error || "";
|
|
14720
|
-
const msg = detail.message
|
|
15067
|
+
const msg = typeof detail.message === "string" && detail.message ? detail.message : `/sdk/proxy returned ${response.status}`;
|
|
14721
15068
|
if (errCode === "approval_denied")
|
|
14722
15069
|
throw new ApprovalDeniedError({ message: msg });
|
|
14723
15070
|
if (errCode === "approval_expired")
|
|
@@ -14740,10 +15087,34 @@ ${label}:${value}`;
|
|
|
14740
15087
|
error: errCode
|
|
14741
15088
|
});
|
|
14742
15089
|
}
|
|
14743
|
-
/**
|
|
14744
|
-
|
|
15090
|
+
/**
|
|
15091
|
+
* Single-shot poll of an approval.
|
|
15092
|
+
*
|
|
15093
|
+
* `wait` asks the server to hold the request open until the approval's
|
|
15094
|
+
* status changes, up to that many seconds (0 = return immediately, the
|
|
15095
|
+
* default and the historical behaviour). It is a pure latency optimisation:
|
|
15096
|
+
* the response shape is identical either way, and a backend that predates
|
|
15097
|
+
* the parameter simply ignores it and answers immediately. The server caps
|
|
15098
|
+
* `wait` at 25 seconds (`APPROVAL_POLL_SERVER_WAIT_MS`, mirroring the
|
|
15099
|
+
* backend's `MAX_LONG_POLL_SECONDS`) and REJECTS larger values with a
|
|
15100
|
+
* validation error (422) rather than clamping them.
|
|
15101
|
+
*
|
|
15102
|
+
* `signal` (internal — not exposed on App/Agent) lets `awaitApproval` abort
|
|
15103
|
+
* an in-flight held request its local deadline has abandoned.
|
|
15104
|
+
*
|
|
15105
|
+
* @throws AlterValueError if `wait` is not a finite number.
|
|
15106
|
+
*/
|
|
15107
|
+
async getApprovalStatus(approvalId, options = {}) {
|
|
14745
15108
|
this.#assertNotClosed();
|
|
14746
|
-
|
|
15109
|
+
if (options !== null && options !== void 0 && typeof options !== "object") {
|
|
15110
|
+
throw new AlterValueError("options must be an object");
|
|
15111
|
+
}
|
|
15112
|
+
const waitOption = options?.wait ?? 0;
|
|
15113
|
+
if (typeof waitOption !== "number" || !Number.isFinite(waitOption)) {
|
|
15114
|
+
throw new AlterValueError("wait must be a finite number");
|
|
15115
|
+
}
|
|
15116
|
+
const wait = Math.max(0, Math.floor(waitOption));
|
|
15117
|
+
const sdkPath = wait > 0 ? `/sdk/approvals/${approvalId}?wait=${wait}` : `/sdk/approvals/${approvalId}`;
|
|
14747
15118
|
const hmacHeaders = this.#computeHmacHeaders("GET", sdkPath, "");
|
|
14748
15119
|
const traceparent = await ambientTraceparent();
|
|
14749
15120
|
if (traceparent !== void 0) {
|
|
@@ -14752,7 +15123,18 @@ ${label}:${value}`;
|
|
|
14752
15123
|
let response;
|
|
14753
15124
|
try {
|
|
14754
15125
|
response = await this.#alterClient.request("GET", sdkPath, {
|
|
14755
|
-
headers: hmacHeaders
|
|
15126
|
+
headers: hmacHeaders,
|
|
15127
|
+
// A held request needs a wider per-request timeout than the client
|
|
15128
|
+
// default, or the client aborts its own long-poll (see
|
|
15129
|
+
// AUTH_POLL_HTTP_BUFFER_MS). Only applied when actually parking.
|
|
15130
|
+
// Capped at the server's own ceiling: `wait` is deliberately NOT
|
|
15131
|
+
// clamped on the wire (the backend owns that contract and answers an
|
|
15132
|
+
// over-cap value with its documented validation error), but an
|
|
15133
|
+
// unbounded `wait * 1000` overflows to `Infinity` for a huge finite
|
|
15134
|
+
// value, and Node coerces such a timer to fire almost immediately —
|
|
15135
|
+
// aborting the request before that very error can come back.
|
|
15136
|
+
timeoutMs: wait > 0 ? Math.min(wait * 1e3, APPROVAL_POLL_SERVER_WAIT_MS) + AUTH_POLL_HTTP_BUFFER_MS : void 0,
|
|
15137
|
+
signal: options?.signal
|
|
14756
15138
|
});
|
|
14757
15139
|
} catch (error51) {
|
|
14758
15140
|
if (error51 instanceof Error && error51.name === "AbortError") {
|
|
@@ -14801,11 +15183,21 @@ ${label}:${value}`;
|
|
|
14801
15183
|
* @throws ApprovalExpiredError on `expired`.
|
|
14802
15184
|
* @throws ApprovalExecutionFailedError on `failed`.
|
|
14803
15185
|
* @throws ApprovalTimeoutError if the local wait elapses before any decision.
|
|
15186
|
+
* @throws AlterValueError if `timeoutMs` is not a finite number, or
|
|
15187
|
+
* `pollIntervalMs` is non-finite or not greater than zero.
|
|
14804
15188
|
*/
|
|
14805
15189
|
async awaitApproval(approvalId, options = {}) {
|
|
14806
15190
|
this.#assertNotClosed();
|
|
14807
15191
|
const timeoutMs = options.timeoutMs ?? 3e5;
|
|
14808
15192
|
const pollIntervalMs = options.pollIntervalMs ?? 2e3;
|
|
15193
|
+
if (typeof timeoutMs !== "number" || !Number.isFinite(timeoutMs)) {
|
|
15194
|
+
throw new AlterValueError("timeoutMs must be a finite number");
|
|
15195
|
+
}
|
|
15196
|
+
if (typeof pollIntervalMs !== "number" || !Number.isFinite(pollIntervalMs) || pollIntervalMs <= 0) {
|
|
15197
|
+
throw new AlterValueError(
|
|
15198
|
+
"pollIntervalMs must be a finite number greater than 0"
|
|
15199
|
+
);
|
|
15200
|
+
}
|
|
14809
15201
|
const deadline = Date.now() + timeoutMs;
|
|
14810
15202
|
let lastTransient = null;
|
|
14811
15203
|
while (true) {
|
|
@@ -14813,9 +15205,39 @@ ${label}:${value}`;
|
|
|
14813
15205
|
throw this.#buildApprovalTimeout(approvalId, timeoutMs, lastTransient);
|
|
14814
15206
|
}
|
|
14815
15207
|
let status;
|
|
15208
|
+
const raceController = new AbortController();
|
|
15209
|
+
let deadlineTimer;
|
|
14816
15210
|
try {
|
|
14817
|
-
|
|
15211
|
+
const serverWaitSeconds = Math.max(
|
|
15212
|
+
0,
|
|
15213
|
+
Math.min(
|
|
15214
|
+
Math.floor(APPROVAL_POLL_SERVER_WAIT_MS / 1e3),
|
|
15215
|
+
Math.floor((deadline - Date.now()) / 1e3)
|
|
15216
|
+
)
|
|
15217
|
+
);
|
|
15218
|
+
const remainingMs = Math.max(0, deadline - Date.now());
|
|
15219
|
+
status = await Promise.race([
|
|
15220
|
+
this.getApprovalStatus(approvalId, {
|
|
15221
|
+
wait: serverWaitSeconds,
|
|
15222
|
+
signal: raceController.signal
|
|
15223
|
+
}),
|
|
15224
|
+
new Promise((_resolve, reject) => {
|
|
15225
|
+
deadlineTimer = setTimeout(
|
|
15226
|
+
() => reject(new _ApprovalDeadlineElapsed()),
|
|
15227
|
+
remainingMs
|
|
15228
|
+
);
|
|
15229
|
+
deadlineTimer.unref?.();
|
|
15230
|
+
})
|
|
15231
|
+
]);
|
|
14818
15232
|
} catch (err2) {
|
|
15233
|
+
if (err2 instanceof _ApprovalDeadlineElapsed) {
|
|
15234
|
+
raceController.abort();
|
|
15235
|
+
throw this.#buildApprovalTimeout(
|
|
15236
|
+
approvalId,
|
|
15237
|
+
timeoutMs,
|
|
15238
|
+
lastTransient
|
|
15239
|
+
);
|
|
15240
|
+
}
|
|
14819
15241
|
if (!__VaultClient.#isTransientPollError(err2)) {
|
|
14820
15242
|
throw err2;
|
|
14821
15243
|
}
|
|
@@ -14831,16 +15253,18 @@ ${label}:${value}`;
|
|
|
14831
15253
|
lastTransient
|
|
14832
15254
|
);
|
|
14833
15255
|
}
|
|
14834
|
-
const sleep2 = Math.
|
|
14835
|
-
|
|
14836
|
-
Math.
|
|
15256
|
+
const sleep2 = Math.min(
|
|
15257
|
+
pollIntervalMs,
|
|
15258
|
+
Math.max(0, deadline - Date.now())
|
|
14837
15259
|
);
|
|
14838
15260
|
await new Promise((res) => setTimeout(res, sleep2));
|
|
14839
15261
|
continue;
|
|
15262
|
+
} finally {
|
|
15263
|
+
if (deadlineTimer !== void 0) clearTimeout(deadlineTimer);
|
|
14840
15264
|
}
|
|
14841
15265
|
lastTransient = null;
|
|
14842
15266
|
if (status.status === "executed" && !status.hasResult) {
|
|
14843
|
-
const next = Math.max(
|
|
15267
|
+
const next = Math.max(0, deadline - Date.now());
|
|
14844
15268
|
await new Promise((r) => setTimeout(r, Math.min(pollIntervalMs, next)));
|
|
14845
15269
|
continue;
|
|
14846
15270
|
}
|
|
@@ -14867,9 +15291,9 @@ ${label}:${value}`;
|
|
|
14867
15291
|
lastTransient
|
|
14868
15292
|
);
|
|
14869
15293
|
}
|
|
14870
|
-
const sleep2 = Math.
|
|
14871
|
-
|
|
14872
|
-
Math.
|
|
15294
|
+
const sleep2 = Math.min(
|
|
15295
|
+
pollIntervalMs,
|
|
15296
|
+
Math.max(0, deadline - Date.now())
|
|
14873
15297
|
);
|
|
14874
15298
|
await new Promise((res) => setTimeout(res, sleep2));
|
|
14875
15299
|
continue;
|
|
@@ -14879,7 +15303,7 @@ ${label}:${value}`;
|
|
|
14879
15303
|
if (now >= deadline) {
|
|
14880
15304
|
throw this.#buildApprovalTimeout(approvalId, timeoutMs, lastTransient);
|
|
14881
15305
|
}
|
|
14882
|
-
const sleep = Math.
|
|
15306
|
+
const sleep = Math.min(pollIntervalMs, Math.max(0, deadline - now));
|
|
14883
15307
|
await new Promise((res) => setTimeout(res, sleep));
|
|
14884
15308
|
}
|
|
14885
15309
|
}
|
|
@@ -15652,8 +16076,8 @@ var Agent = class _Agent {
|
|
|
15652
16076
|
return this.#client.delegate(grantId, toAgentId, options);
|
|
15653
16077
|
}
|
|
15654
16078
|
// ── Approvals ──────────────────────────────────────────────────────────
|
|
15655
|
-
async getApprovalStatus(approvalId) {
|
|
15656
|
-
return this.#client.getApprovalStatus(approvalId);
|
|
16079
|
+
async getApprovalStatus(approvalId, options = {}) {
|
|
16080
|
+
return this.#client.getApprovalStatus(approvalId, options);
|
|
15657
16081
|
}
|
|
15658
16082
|
async awaitApproval(...args) {
|
|
15659
16083
|
return this.#client.awaitApproval(...args);
|
|
@@ -15841,11 +16265,18 @@ var App = class _App {
|
|
|
15841
16265
|
* @param options.label - Required sibling address (resolution key:
|
|
15842
16266
|
* provider + label). Must be unique among the credential's active
|
|
15843
16267
|
* sibling grants.
|
|
15844
|
-
* @param options.grantPolicy - Optional per-grant policy
|
|
15845
|
-
*
|
|
15846
|
-
*
|
|
15847
|
-
*
|
|
15848
|
-
* `
|
|
16268
|
+
* @param options.grantPolicy - Optional per-grant policy. `expiresAt`
|
|
16269
|
+
* and `maxTtlSeconds` are accepted in this SDK's camelCase
|
|
16270
|
+
* (validated locally and mapped to the snake_case wire keys,
|
|
16271
|
+
* exactly like `createConnectSession`); the wider policy grammar
|
|
16272
|
+
* (`restrictions`, `requires_approval`, …) uses the backend's
|
|
16273
|
+
* snake_case keys and is validated server-side — an invalid policy
|
|
16274
|
+
* throws `BackendError` from the 422 `invalid_grant_policy`
|
|
16275
|
+
* response. A default TTL is not part of the mint grammar:
|
|
16276
|
+
* `defaultTtlSeconds` / `default_ttl_seconds` throws
|
|
16277
|
+
* `AlterValueError` (it is accepted only by Connect sessions and
|
|
16278
|
+
* `createManagedSecretGrant`). Supplying both spellings of one
|
|
16279
|
+
* trio field also throws `AlterValueError`.
|
|
15849
16280
|
* @param options.grantTags - Optional list of tag strings stored on
|
|
15850
16281
|
* the sibling grant.
|
|
15851
16282
|
* @returns {@link GrantInfo} for the newly minted sibling grant
|
|
@@ -15881,7 +16312,7 @@ var App = class _App {
|
|
|
15881
16312
|
}
|
|
15882
16313
|
const wireBody = { label: options.label };
|
|
15883
16314
|
if (options.grantPolicy !== void 0) {
|
|
15884
|
-
wireBody.grant_policy = options.grantPolicy;
|
|
16315
|
+
wireBody.grant_policy = _mintGrantPolicyToWire(options.grantPolicy);
|
|
15885
16316
|
}
|
|
15886
16317
|
if (options.grantTags !== void 0) {
|
|
15887
16318
|
wireBody.grant_tags = options.grantTags;
|
|
@@ -16066,8 +16497,8 @@ var App = class _App {
|
|
|
16066
16497
|
});
|
|
16067
16498
|
}
|
|
16068
16499
|
// ── Approvals ──────────────────────────────────────────────────────────
|
|
16069
|
-
async getApprovalStatus(approvalId) {
|
|
16070
|
-
return this.#client.getApprovalStatus(approvalId);
|
|
16500
|
+
async getApprovalStatus(approvalId, options = {}) {
|
|
16501
|
+
return this.#client.getApprovalStatus(approvalId, options);
|
|
16071
16502
|
}
|
|
16072
16503
|
async awaitApproval(...args) {
|
|
16073
16504
|
return this.#client.awaitApproval(...args);
|
|
@@ -16323,8 +16754,8 @@ async function _listGrantsUnified(client, body) {
|
|
|
16323
16754
|
var DEFAULT_BASE_URL = "https://backend.alterauth.com";
|
|
16324
16755
|
var PAT_API_PREFIX = "/api/v1/dev-portal";
|
|
16325
16756
|
var HTTP_ERROR_THRESHOLD = 400;
|
|
16326
|
-
var
|
|
16327
|
-
var CLI_VERSION = "0.
|
|
16757
|
+
var DEFAULT_TIMEOUT_MS2 = 3e4;
|
|
16758
|
+
var CLI_VERSION = "0.9.1";
|
|
16328
16759
|
var USER_AGENT = buildUserAgent();
|
|
16329
16760
|
function buildUserAgent() {
|
|
16330
16761
|
let osTag = "";
|
|
@@ -16569,7 +17000,7 @@ var DashboardClient = class {
|
|
|
16569
17000
|
);
|
|
16570
17001
|
}
|
|
16571
17002
|
}
|
|
16572
|
-
this.#timeoutMs = options.timeoutMs ??
|
|
17003
|
+
this.#timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
16573
17004
|
this.pats = new PATsNamespace(this);
|
|
16574
17005
|
this.apps = new AppsNamespace(this);
|
|
16575
17006
|
this.keys = new KeysNamespace2(this);
|
|
@@ -17330,7 +17761,7 @@ function isProviderCatalogEntry(value) {
|
|
|
17330
17761
|
const v = value;
|
|
17331
17762
|
return typeof v.id === "string" && typeof v.name === "string" && typeof v.display_name === "string" && (v.category === null || typeof v.category === "string") && (v.supports_refresh === null || typeof v.supports_refresh === "boolean") && (v.supports_pkce === null || typeof v.supports_pkce === "boolean") && (v.available_environments === void 0 || Array.isArray(v.available_environments) && v.available_environments.every(
|
|
17332
17763
|
(environment) => environment === "production" || environment === "sandbox"
|
|
17333
|
-
)) && v.available_scopes !== null && typeof v.available_scopes === "object" && !Array.isArray(v.available_scopes) && Object.values(v.available_scopes).every(
|
|
17764
|
+
)) && (v.scopes_configured_on_provider === void 0 || typeof v.scopes_configured_on_provider === "boolean") && v.available_scopes !== null && typeof v.available_scopes === "object" && !Array.isArray(v.available_scopes) && Object.values(v.available_scopes).every(
|
|
17334
17765
|
isProviderScopeCatalogEntry
|
|
17335
17766
|
) && (v.managed_credentials_available === void 0 || typeof v.managed_credentials_available === "boolean") && (v.managed_approved_scopes === void 0 || v.managed_approved_scopes === null || Array.isArray(v.managed_approved_scopes) && v.managed_approved_scopes.every(
|
|
17336
17767
|
(scope) => typeof scope === "string"
|
|
@@ -18485,6 +18916,25 @@ var AuditNamespace = class {
|
|
|
18485
18916
|
});
|
|
18486
18917
|
return expectDict(body, "audit.list", 200);
|
|
18487
18918
|
}
|
|
18919
|
+
/** Provider facet values present in one app's audit events. Requires
|
|
18920
|
+
* `dashboard_audit:read`.
|
|
18921
|
+
*
|
|
18922
|
+
* Returns ``{providers}`` — the alphabetized distinct provider ids
|
|
18923
|
+
* (OAuth provider ids, managed-secret template ids — a custom secret's
|
|
18924
|
+
* name-derived slug — and IDP types) actually
|
|
18925
|
+
* present on the app's audit rows; the accepted vocabulary for the
|
|
18926
|
+
* ``--provider`` filters. Data-derived, never a hardcoded shortlist.
|
|
18927
|
+
*/
|
|
18928
|
+
async providers(appId) {
|
|
18929
|
+
const query = optionsToQuery({ app_id: appId });
|
|
18930
|
+
const body = await this.#client._call(
|
|
18931
|
+
"GET",
|
|
18932
|
+
"/audit-logs/unified/providers",
|
|
18933
|
+
"audit.providers",
|
|
18934
|
+
{ query }
|
|
18935
|
+
);
|
|
18936
|
+
return expectDict(body, "audit.providers", 200);
|
|
18937
|
+
}
|
|
18488
18938
|
/** List dashboard / CLI admin actions. Requires `dashboard_audit:read`.
|
|
18489
18939
|
*
|
|
18490
18940
|
* Returns the canonical envelope ``{items, total, limit, offset, has_more}``
|
|
@@ -19281,6 +19731,38 @@ function resolveAppId(flagValue, options = {}) {
|
|
|
19281
19731
|
return null;
|
|
19282
19732
|
}
|
|
19283
19733
|
|
|
19734
|
+
// ../shared-utils/src/dns-host.ts
|
|
19735
|
+
var LABEL_SEPARATOR = /[.。.。]/;
|
|
19736
|
+
var TRAILING_LABEL_SEPARATOR = /[.。.。]$/;
|
|
19737
|
+
var isInvalidLabel = (label) => label.length === 0 || label.includes("_") || label.startsWith("-") || label.endsWith("-") || label.length >= 4 && label[2] === "-" && label[3] === "-" && !label.startsWith("xn--");
|
|
19738
|
+
var IDNA2008_LABEL = /^(?:(?!\u0640|\u07FA|\u302E|\u302F|[\u3031-\u3035]|\u303B)[\p{L}\p{Mn}\p{Mc}\p{Nd}\-\u00DF\u03C2\u06FD\u06FE\u0F0B\u3007\u00B7\u0375\u05F3\u05F4\u30FB]|\u200C|\u200D)*$/u;
|
|
19739
|
+
var isOutsideIdna2008Repertoire = (label) => !IDNA2008_LABEL.test(label);
|
|
19740
|
+
var canonicalizeDnsHost = (host) => {
|
|
19741
|
+
let h = host;
|
|
19742
|
+
if (!/^[\x20-\x7e]*$/.test(h)) {
|
|
19743
|
+
if (/[/:@\s?#%\\]/.test(h)) return null;
|
|
19744
|
+
const uLabels = h.normalize("NFKC").replace(TRAILING_LABEL_SEPARATOR, "").split(LABEL_SEPARATOR);
|
|
19745
|
+
if (uLabels.some(isInvalidLabel)) return null;
|
|
19746
|
+
if (uLabels.some(isOutsideIdna2008Repertoire)) return null;
|
|
19747
|
+
let url2;
|
|
19748
|
+
try {
|
|
19749
|
+
url2 = new URL(`http://${h}`);
|
|
19750
|
+
} catch {
|
|
19751
|
+
return null;
|
|
19752
|
+
}
|
|
19753
|
+
if (url2.pathname !== "/" || url2.search !== "" || url2.hash !== "" || url2.port !== "" || url2.username !== "" || url2.password !== "") {
|
|
19754
|
+
return null;
|
|
19755
|
+
}
|
|
19756
|
+
h = url2.hostname;
|
|
19757
|
+
if (!/^[\x20-\x7e]*$/.test(h)) return null;
|
|
19758
|
+
h = h.replace(/\.$/, "");
|
|
19759
|
+
if (h.split(".").some(isInvalidLabel)) return null;
|
|
19760
|
+
}
|
|
19761
|
+
if (h.length > 253) return null;
|
|
19762
|
+
if (h.split(".").some((label) => label.length > 63)) return null;
|
|
19763
|
+
return h;
|
|
19764
|
+
};
|
|
19765
|
+
|
|
19284
19766
|
// src/commands/_helpers.ts
|
|
19285
19767
|
async function withClient(fn) {
|
|
19286
19768
|
const client = await createPortalClient();
|
|
@@ -19833,9 +20315,9 @@ function parseAllowedHostsOrExit(flag, entries) {
|
|
|
19833
20315
|
fail(`${JSON.stringify(entry)} must not contain control characters`);
|
|
19834
20316
|
}
|
|
19835
20317
|
}
|
|
19836
|
-
if (/[/:@\s]/.test(e)) {
|
|
20318
|
+
if (/[/:@\s?#%\\]/.test(e)) {
|
|
19837
20319
|
fail(
|
|
19838
|
-
`${JSON.stringify(entry)} must be a bare host \u2014 no scheme, port, path,
|
|
20320
|
+
`${JSON.stringify(entry)} must be a bare host \u2014 no scheme, port, path, "@", whitespace, or the URL delimiters ? # % \\`
|
|
19839
20321
|
);
|
|
19840
20322
|
}
|
|
19841
20323
|
let normalized;
|
|
@@ -19846,12 +20328,23 @@ function parseAllowedHostsOrExit(flag, entries) {
|
|
|
19846
20328
|
`${JSON.stringify(entry)} is a malformed wildcard \u2014 use "*.domain.tld"`
|
|
19847
20329
|
);
|
|
19848
20330
|
}
|
|
19849
|
-
|
|
20331
|
+
const canonicalRest = canonicalizeDnsHost(rest);
|
|
20332
|
+
if (canonicalRest === null) {
|
|
20333
|
+
fail(
|
|
20334
|
+
`${JSON.stringify(entry)} is not a valid DNS name (IDNA-encodable, labels up to 63 characters)`
|
|
20335
|
+
);
|
|
20336
|
+
}
|
|
20337
|
+
if (!canonicalRest.includes(".")) {
|
|
19850
20338
|
fail(
|
|
19851
20339
|
`${JSON.stringify(entry)}: wildcard must cover a domain, not a bare TLD (use "*.example.com", not "*.com")`
|
|
19852
20340
|
);
|
|
19853
20341
|
}
|
|
19854
|
-
|
|
20342
|
+
if (canonicalRest.length > 251) {
|
|
20343
|
+
fail(
|
|
20344
|
+
`${JSON.stringify(entry)} is too long for a wildcard \u2014 no host could ever match it`
|
|
20345
|
+
);
|
|
20346
|
+
}
|
|
20347
|
+
normalized = `*.${canonicalRest}`;
|
|
19855
20348
|
} else if (e.includes("*")) {
|
|
19856
20349
|
fail(
|
|
19857
20350
|
`${JSON.stringify(entry)}: "*" is only allowed as a leading subdomain wildcard ("*.domain.tld")`
|
|
@@ -19860,9 +20353,16 @@ function parseAllowedHostsOrExit(flag, entries) {
|
|
|
19860
20353
|
normalized = e.replace(/\.+$/, "");
|
|
19861
20354
|
if (!normalized) {
|
|
19862
20355
|
fail(
|
|
19863
|
-
`${JSON.stringify(entry)} must be a bare host \u2014 no scheme, port, path,
|
|
20356
|
+
`${JSON.stringify(entry)} must be a bare host \u2014 no scheme, port, path, "@", whitespace, or the URL delimiters ? # % \\`
|
|
19864
20357
|
);
|
|
19865
20358
|
}
|
|
20359
|
+
const canonical = canonicalizeDnsHost(normalized);
|
|
20360
|
+
if (canonical === null) {
|
|
20361
|
+
fail(
|
|
20362
|
+
`${JSON.stringify(entry)} is not a valid DNS name (IDNA-encodable, labels up to 63 characters)`
|
|
20363
|
+
);
|
|
20364
|
+
}
|
|
20365
|
+
normalized = canonical;
|
|
19866
20366
|
}
|
|
19867
20367
|
if (seen.has(normalized)) continue;
|
|
19868
20368
|
seen.add(normalized);
|
|
@@ -20118,6 +20618,20 @@ function surfaceCatalogWarnings(row) {
|
|
|
20118
20618
|
);
|
|
20119
20619
|
}
|
|
20120
20620
|
}
|
|
20621
|
+
function surfaceScopeWarnings(row) {
|
|
20622
|
+
if (typeof row !== "object" || row === null) return;
|
|
20623
|
+
const warnings = row.scope_warnings;
|
|
20624
|
+
if (!Array.isArray(warnings)) return;
|
|
20625
|
+
for (const warning of warnings) {
|
|
20626
|
+
if (typeof warning !== "string") continue;
|
|
20627
|
+
const sanitized = sanitizeStderrText(warning);
|
|
20628
|
+
if (sanitized.length === 0) continue;
|
|
20629
|
+
process.stderr.write(
|
|
20630
|
+
`alter: scope warning \u2014 ${sanitized} (save was allowed)
|
|
20631
|
+
`
|
|
20632
|
+
);
|
|
20633
|
+
}
|
|
20634
|
+
}
|
|
20121
20635
|
function writeNextPageHint(page, noun) {
|
|
20122
20636
|
if (typeof page !== "object" || page === null) return;
|
|
20123
20637
|
const { has_more: hasMore, offset, limit } = page;
|
|
@@ -20312,6 +20826,7 @@ function buildAgentsCommand() {
|
|
|
20312
20826
|
"alter: the api_key field above is shown ONCE and cannot be retrieved later.\n"
|
|
20313
20827
|
);
|
|
20314
20828
|
surfaceApproverWarnings(result);
|
|
20829
|
+
surfaceScopeWarnings(result);
|
|
20315
20830
|
});
|
|
20316
20831
|
return;
|
|
20317
20832
|
}
|
|
@@ -20367,6 +20882,7 @@ function buildAgentsCommand() {
|
|
|
20367
20882
|
"alter: the api_key field above is shown ONCE and cannot be retrieved later.\n"
|
|
20368
20883
|
);
|
|
20369
20884
|
surfaceApproverWarnings(result);
|
|
20885
|
+
surfaceScopeWarnings(result);
|
|
20370
20886
|
});
|
|
20371
20887
|
}
|
|
20372
20888
|
);
|
|
@@ -20586,6 +21102,7 @@ function buildAgentsCommand() {
|
|
|
20586
21102
|
}
|
|
20587
21103
|
);
|
|
20588
21104
|
emit2(format, row);
|
|
21105
|
+
surfaceScopeWarnings(row);
|
|
20589
21106
|
} catch (error51) {
|
|
20590
21107
|
if (error51 instanceof PortalBackendError && error51.statusCode === 409 && error51.code === "agent_concurrent_update") {
|
|
20591
21108
|
throw new PortalBackendError(
|
|
@@ -35607,6 +36124,9 @@ var CONTENT_MATCH_LIMITS = {
|
|
|
35607
36124
|
};
|
|
35608
36125
|
var MAX_RULE_NAME_LEN = 120;
|
|
35609
36126
|
var MAX_RULE_DESCRIPTION_LEN = 2e3;
|
|
36127
|
+
var MAX_RULES_PER_TARGET = 100;
|
|
36128
|
+
var MAX_DERIVED_RULES_PER_TARGET = 2;
|
|
36129
|
+
var MAX_POLICY_RULE_PAGE_SIZE = MAX_RULES_PER_TARGET + MAX_DERIVED_RULES_PER_TARGET;
|
|
35610
36130
|
var HITL_NOTIFICATION_CHANNELS = ["email"];
|
|
35611
36131
|
var HITL_LIMITS = {
|
|
35612
36132
|
maxApprovers: 10,
|
|
@@ -35651,7 +36171,7 @@ var RULE_TYPE_LABELS = /* @__PURE__ */ new Map([
|
|
|
35651
36171
|
["json_match", "Request condition"],
|
|
35652
36172
|
["ip_allowlist", "IP allowlist"],
|
|
35653
36173
|
["time_window", "Time window"],
|
|
35654
|
-
["require_approval", "
|
|
36174
|
+
["require_approval", "Human in the loop (HITL)"],
|
|
35655
36175
|
["restriction", "Method and endpoint allowlist"],
|
|
35656
36176
|
["quota", "Request quota"],
|
|
35657
36177
|
["content_match", "Operation and parameter policy"]
|
|
@@ -36077,10 +36597,13 @@ function buildPolicyRulePresentation(rule) {
|
|
|
36077
36597
|
});
|
|
36078
36598
|
}
|
|
36079
36599
|
if (rule.display?.inherited) {
|
|
36080
|
-
displayItems.push({
|
|
36600
|
+
displayItems.push({
|
|
36601
|
+
label: "Relationship",
|
|
36602
|
+
value: "Inherited runtime policy"
|
|
36603
|
+
});
|
|
36081
36604
|
}
|
|
36082
36605
|
if (displayItems.length > 0) {
|
|
36083
|
-
groups.unshift({ label: "
|
|
36606
|
+
groups.unshift({ label: "Runtime policy scope", items: displayItems });
|
|
36084
36607
|
}
|
|
36085
36608
|
return {
|
|
36086
36609
|
typeLabel: policyRuleTypeLabel(rule.rule_type),
|
|
@@ -36093,7 +36616,10 @@ function formatPolicyRuleAsText(rule) {
|
|
|
36093
36616
|
const lines = [
|
|
36094
36617
|
...rule.name ? [`Name: ${rule.name}`] : [],
|
|
36095
36618
|
...rule.id ? [`Policy ID: ${rule.id}`] : [],
|
|
36096
|
-
|
|
36619
|
+
// The friendly label matches the dashboard; the wire identifier is what
|
|
36620
|
+
// `alter policy rules create --type <rule_type>` accepts, so the text
|
|
36621
|
+
// view prints both for operators who script from it.
|
|
36622
|
+
`Type: ${presentation.typeLabel} [${rule.rule_type}]`,
|
|
36097
36623
|
`Effect: ${presentation.effectLabel}`,
|
|
36098
36624
|
`Status: ${rule.enabled === false ? "Disabled" : "Enabled"}`,
|
|
36099
36625
|
presentation.summary,
|
|
@@ -36608,7 +37134,7 @@ function buildAppsCommand() {
|
|
|
36608
37134
|
"table"
|
|
36609
37135
|
).option(
|
|
36610
37136
|
"--no-include-stats",
|
|
36611
|
-
"Skip per-app statistics (grant / key / API-call counts)"
|
|
37137
|
+
"Skip per-app statistics (grant / connection / secret / provider / key / API-call counts)"
|
|
36612
37138
|
).option(
|
|
36613
37139
|
"--include-archived",
|
|
36614
37140
|
"Include archived (soft-deleted) apps. Default is active-only \u2014 archive's whole point is to hide clutter from the default view, so opt in only when you want the full set (e.g. before unarchiving something)."
|
|
@@ -37302,6 +37828,33 @@ function buildAuditCommand() {
|
|
|
37302
37828
|
});
|
|
37303
37829
|
}
|
|
37304
37830
|
);
|
|
37831
|
+
audit.command("providers").description(
|
|
37832
|
+
"List the provider IDs present in an app's audit events \u2014 the values `--provider` accepts (OAuth provider ids, managed-secret template ids \u2014 a custom secret's name-derived slug \u2014 and IDP types). Derived from the audit data, so providers whose config was since deleted still appear."
|
|
37833
|
+
).option(
|
|
37834
|
+
APP_FLAG,
|
|
37835
|
+
"App whose audit trail to facet (ID or name; falls back to ALTER_APP_ID / the workspace pin)"
|
|
37836
|
+
).option(
|
|
37837
|
+
"--output <format>",
|
|
37838
|
+
"Output format: json|jsonl|table (default: table)",
|
|
37839
|
+
"table"
|
|
37840
|
+
).action(async (options) => {
|
|
37841
|
+
const format = coerceOutputFormat(options.output);
|
|
37842
|
+
const appId = await resolveAppOrExit(options.app);
|
|
37843
|
+
await withClient(async (client) => {
|
|
37844
|
+
const result = await client.audit.providers(appId);
|
|
37845
|
+
const providers = result["providers"];
|
|
37846
|
+
if (!Array.isArray(providers) || !providers.every((p) => typeof p === "string")) {
|
|
37847
|
+
err(
|
|
37848
|
+
'audit.providers: response is missing the "providers" string array. This is a wire-format regression; report it to the backend team.'
|
|
37849
|
+
);
|
|
37850
|
+
process.exit(EXIT_ERROR);
|
|
37851
|
+
}
|
|
37852
|
+
const rows = providers.map((provider_id) => ({ provider_id }));
|
|
37853
|
+
emit2(format, rows, [
|
|
37854
|
+
{ label: "PROVIDER", get: (r) => r.provider_id }
|
|
37855
|
+
]);
|
|
37856
|
+
});
|
|
37857
|
+
});
|
|
37305
37858
|
audit.command("show <trace-id>").description("Show every event for one trace").option(
|
|
37306
37859
|
"--output <format>",
|
|
37307
37860
|
"Output format: json|jsonl|table (default: json)",
|
|
@@ -40782,6 +41335,7 @@ async function runStatus() {
|
|
|
40782
41335
|
// src/commands/managed-secrets.ts
|
|
40783
41336
|
import { readFileSync as readFileSync9 } from "fs";
|
|
40784
41337
|
import { Command as Command16, Option } from "commander";
|
|
41338
|
+
var MAX_SEARCH_LENGTH = 255;
|
|
40785
41339
|
var PRINCIPAL_TYPES = ["user", "group", "system", "agent"];
|
|
40786
41340
|
var CREDENTIAL_TYPES = [
|
|
40787
41341
|
"bearer_token",
|
|
@@ -41186,6 +41740,13 @@ function surfaceManagedSecretResponse(row) {
|
|
|
41186
41740
|
}
|
|
41187
41741
|
surfaceApproverWarnings(row);
|
|
41188
41742
|
}
|
|
41743
|
+
function derivedAllowlistMessage(e) {
|
|
41744
|
+
if (!(e instanceof PortalBackendError) || e.statusCode !== 409 || e.code !== "managed_secret_allowed_hosts_derived") {
|
|
41745
|
+
return null;
|
|
41746
|
+
}
|
|
41747
|
+
const detail = e.body?.detail;
|
|
41748
|
+
return typeof detail?.message === "string" ? sanitizeStderrText(detail.message) : "This secret's allowed hosts are derived from its provider binding and cannot be edited directly.";
|
|
41749
|
+
}
|
|
41189
41750
|
function groupPrincipalGateMessage(e) {
|
|
41190
41751
|
if (!(e instanceof PortalBackendError) || e.statusCode !== 422 || e.code !== "group_principal_unsupported_idp") {
|
|
41191
41752
|
return null;
|
|
@@ -41522,7 +42083,11 @@ var TEMPLATE_COLUMNS = [
|
|
|
41522
42083
|
get: (t) => t.form_schema?.fields.map(
|
|
41523
42084
|
(field) => `${credentialFieldFlag(field)}${field.required ? "" : " (optional)"}`
|
|
41524
42085
|
).join(" ") || "\u2014"
|
|
41525
|
-
}
|
|
42086
|
+
},
|
|
42087
|
+
// The field a bound template derives its allowlist from — the operator
|
|
42088
|
+
// needs it to know which --credential-field sets (and later retargets,
|
|
42089
|
+
// via rotate) the secret's allowed hosts.
|
|
42090
|
+
{ label: "BINDING", get: (t) => t.allowed_hosts_from_field ?? "\u2014" }
|
|
41526
42091
|
];
|
|
41527
42092
|
var SECRET_COLUMNS = [
|
|
41528
42093
|
{ label: "ID", get: (s) => s.id, maxWidth: 36 },
|
|
@@ -41991,7 +42556,7 @@ function buildGroupsSubcommand() {
|
|
|
41991
42556
|
);
|
|
41992
42557
|
groups.command("list").description("List app-user-groups (Group-tab picker source)").option(APP_FLAG, APP_TARGET_DESC).option("--idp <uuid>", "Filter by identity provider id").option(
|
|
41993
42558
|
"--search <substring>",
|
|
41994
|
-
"Substring search on group name / external_group_id"
|
|
42559
|
+
"Substring search on group name / external_group_id (max 255 chars)"
|
|
41995
42560
|
).option(
|
|
41996
42561
|
"--limit <n>",
|
|
41997
42562
|
"Page size (default: 50)",
|
|
@@ -42008,6 +42573,12 @@ function buildGroupsSubcommand() {
|
|
|
42008
42573
|
async (options) => {
|
|
42009
42574
|
const format = coerceOutputFormat(options.output);
|
|
42010
42575
|
if (options.idp !== void 0) validateUuidOrExit("--idp", options.idp);
|
|
42576
|
+
if (options.search !== void 0)
|
|
42577
|
+
validateMaxLengthOrExit(
|
|
42578
|
+
"--search",
|
|
42579
|
+
options.search,
|
|
42580
|
+
MAX_SEARCH_LENGTH
|
|
42581
|
+
);
|
|
42011
42582
|
const resolvedAppId = await resolveAppOrExit(options.app);
|
|
42012
42583
|
await withClient(async (client) => {
|
|
42013
42584
|
const response = await client.managedSecrets.listGroups(
|
|
@@ -42120,7 +42691,8 @@ function buildManagedSecretsCommand() {
|
|
|
42120
42691
|
credential_type: isString,
|
|
42121
42692
|
category: isOptionalString,
|
|
42122
42693
|
popular: (v) => typeof v === "boolean",
|
|
42123
|
-
form_schema: isTemplateFormSchema
|
|
42694
|
+
form_schema: isTemplateFormSchema,
|
|
42695
|
+
allowed_hosts_from_field: isAbsentOrOptionalString
|
|
42124
42696
|
},
|
|
42125
42697
|
"managed-secrets.templates"
|
|
42126
42698
|
);
|
|
@@ -42504,11 +43076,25 @@ function buildManagedSecretsCommand() {
|
|
|
42504
43076
|
}
|
|
42505
43077
|
const hostList = options.clear ? null : parseAllowedHostsOrExit("--host", options.host);
|
|
42506
43078
|
await withClient(async (client) => {
|
|
42507
|
-
|
|
42508
|
-
|
|
42509
|
-
|
|
42510
|
-
|
|
42511
|
-
|
|
43079
|
+
let row;
|
|
43080
|
+
try {
|
|
43081
|
+
row = await client.managedSecrets.setAllowedHosts(
|
|
43082
|
+
appId,
|
|
43083
|
+
secretId,
|
|
43084
|
+
hostList
|
|
43085
|
+
);
|
|
43086
|
+
} catch (e) {
|
|
43087
|
+
const derived = derivedAllowlistMessage(e);
|
|
43088
|
+
if (derived !== null) {
|
|
43089
|
+
process.stderr.write(`alter: ${derived}
|
|
43090
|
+
`);
|
|
43091
|
+
process.stderr.write(
|
|
43092
|
+
"alter: rotate the credential (`alter managed-secrets rotate`) to change its binding\n"
|
|
43093
|
+
);
|
|
43094
|
+
process.exit(EXIT_CONFLICT);
|
|
43095
|
+
}
|
|
43096
|
+
throw e;
|
|
43097
|
+
}
|
|
42512
43098
|
emit2(format, row);
|
|
42513
43099
|
if (options.clear) {
|
|
42514
43100
|
process.stderr.write(
|
|
@@ -42794,7 +43380,7 @@ function buildManagedSecretsCommand() {
|
|
|
42794
43380
|
);
|
|
42795
43381
|
root.command("users").description("List app-users (User-tab picker source for grants create)").option(APP_FLAG, APP_TARGET_DESC).option("--idp <uuid>", "Filter by identity provider id").option(
|
|
42796
43382
|
"--search <substring>",
|
|
42797
|
-
"Substring search on email / display_name / external_subject_id"
|
|
43383
|
+
"Substring search on email / display_name / external_subject_id (max 255 chars)"
|
|
42798
43384
|
).option(
|
|
42799
43385
|
"--limit <n>",
|
|
42800
43386
|
"Page size (default: 50)",
|
|
@@ -42811,6 +43397,12 @@ function buildManagedSecretsCommand() {
|
|
|
42811
43397
|
async (options) => {
|
|
42812
43398
|
const format = coerceOutputFormat(options.output);
|
|
42813
43399
|
if (options.idp !== void 0) validateUuidOrExit("--idp", options.idp);
|
|
43400
|
+
if (options.search !== void 0)
|
|
43401
|
+
validateMaxLengthOrExit(
|
|
43402
|
+
"--search",
|
|
43403
|
+
options.search,
|
|
43404
|
+
MAX_SEARCH_LENGTH
|
|
43405
|
+
);
|
|
42814
43406
|
const resolvedAppId = await resolveAppOrExit(options.app);
|
|
42815
43407
|
await withClient(async (client) => {
|
|
42816
43408
|
const response = await client.managedSecrets.listUsers(
|
|
@@ -44326,8 +44918,8 @@ function buildRulesSubcommand() {
|
|
|
44326
44918
|
)
|
|
44327
44919
|
).option(
|
|
44328
44920
|
"--limit <n>",
|
|
44329
|
-
|
|
44330
|
-
parseBoundedInt("--limit", 1,
|
|
44921
|
+
`Page size (1-${MAX_POLICY_RULE_PAGE_SIZE}; when omitted the backend default of ${MAX_POLICY_RULE_PAGE_SIZE} applies)`,
|
|
44922
|
+
parseBoundedInt("--limit", 1, MAX_POLICY_RULE_PAGE_SIZE)
|
|
44331
44923
|
).option(
|
|
44332
44924
|
"--offset <n>",
|
|
44333
44925
|
"Page offset (default 0)",
|
|
@@ -44761,7 +45353,8 @@ async function providerCatalogError(client, providerId, environment, credentialS
|
|
|
44761
45353
|
const availableScopes = provider.available_scopes ?? {};
|
|
44762
45354
|
if (Object.keys(availableScopes).length === 0) {
|
|
44763
45355
|
if (scopes !== void 0 && scopes.length > 0) {
|
|
44764
|
-
|
|
45356
|
+
const where = provider.scopes_configured_on_provider === true ? `; its permissions are configured on the ${provider.display_name} app itself` : "";
|
|
45357
|
+
return `alter: provider '${providerId}' publishes no selectable scopes \u2014 omit --scopes (Alter does not send a scope parameter to it${where})
|
|
44765
45358
|
`;
|
|
44766
45359
|
}
|
|
44767
45360
|
return null;
|