@alter-ai/alter-sdk 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +126 -81
- package/dist/index.cjs +423 -155
- package/dist/index.d.cts +145 -80
- package/dist/index.d.ts +145 -80
- package/dist/index.js +416 -150
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -34,6 +34,7 @@ __export(index_exports, {
|
|
|
34
34
|
ActorType: () => ActorType,
|
|
35
35
|
AlterSDKError: () => AlterSDKError,
|
|
36
36
|
AlterVault: () => AlterVault,
|
|
37
|
+
AuthResult: () => AuthResult,
|
|
37
38
|
BackendError: () => BackendError,
|
|
38
39
|
ConnectConfigError: () => ConnectConfigError,
|
|
39
40
|
ConnectDeniedError: () => ConnectDeniedError,
|
|
@@ -41,12 +42,13 @@ __export(index_exports, {
|
|
|
41
42
|
ConnectResult: () => ConnectResult,
|
|
42
43
|
ConnectSession: () => ConnectSession,
|
|
43
44
|
ConnectTimeoutError: () => ConnectTimeoutError,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
CredentialRevokedError: () => CredentialRevokedError,
|
|
46
|
+
GrantDeletedError: () => GrantDeletedError,
|
|
47
|
+
GrantExpiredError: () => GrantExpiredError,
|
|
48
|
+
GrantInfo: () => GrantInfo,
|
|
49
|
+
GrantListResult: () => GrantListResult,
|
|
50
|
+
GrantNotFoundError: () => GrantNotFoundError,
|
|
51
|
+
GrantRevokedError: () => GrantRevokedError,
|
|
50
52
|
HttpMethod: () => HttpMethod,
|
|
51
53
|
NetworkError: () => NetworkError,
|
|
52
54
|
PolicyViolationError: () => PolicyViolationError,
|
|
@@ -90,30 +92,38 @@ var ReAuthRequiredError = class extends BackendError {
|
|
|
90
92
|
this.name = "ReAuthRequiredError";
|
|
91
93
|
}
|
|
92
94
|
};
|
|
93
|
-
var
|
|
95
|
+
var GrantExpiredError = class extends ReAuthRequiredError {
|
|
94
96
|
constructor(message, details) {
|
|
95
97
|
super(message, details);
|
|
96
|
-
this.name = "
|
|
98
|
+
this.name = "GrantExpiredError";
|
|
97
99
|
}
|
|
98
100
|
};
|
|
99
|
-
var
|
|
100
|
-
|
|
101
|
-
constructor(message,
|
|
101
|
+
var GrantRevokedError = class extends ReAuthRequiredError {
|
|
102
|
+
grantId;
|
|
103
|
+
constructor(message, grantId, details) {
|
|
102
104
|
super(message, details);
|
|
103
|
-
this.name = "
|
|
104
|
-
this.
|
|
105
|
+
this.name = "GrantRevokedError";
|
|
106
|
+
this.grantId = grantId;
|
|
105
107
|
}
|
|
106
108
|
};
|
|
107
|
-
var
|
|
109
|
+
var CredentialRevokedError = class extends ReAuthRequiredError {
|
|
110
|
+
grantId;
|
|
111
|
+
constructor(message, grantId, details) {
|
|
112
|
+
super(message, details);
|
|
113
|
+
this.name = "CredentialRevokedError";
|
|
114
|
+
this.grantId = grantId;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
var GrantDeletedError = class extends ReAuthRequiredError {
|
|
108
118
|
constructor(message, details) {
|
|
109
119
|
super(message, details);
|
|
110
|
-
this.name = "
|
|
120
|
+
this.name = "GrantDeletedError";
|
|
111
121
|
}
|
|
112
122
|
};
|
|
113
|
-
var
|
|
123
|
+
var GrantNotFoundError = class extends BackendError {
|
|
114
124
|
constructor(message, details) {
|
|
115
125
|
super(message, details);
|
|
116
|
-
this.name = "
|
|
126
|
+
this.name = "GrantNotFoundError";
|
|
117
127
|
}
|
|
118
128
|
};
|
|
119
129
|
var PolicyViolationError = class extends BackendError {
|
|
@@ -159,12 +169,12 @@ var ProviderAPIError = class extends AlterSDKError {
|
|
|
159
169
|
}
|
|
160
170
|
};
|
|
161
171
|
var ScopeReauthRequiredError = class extends ProviderAPIError {
|
|
162
|
-
|
|
172
|
+
grantId;
|
|
163
173
|
providerId;
|
|
164
|
-
constructor(message,
|
|
174
|
+
constructor(message, grantId, providerId, statusCode, responseBody, details) {
|
|
165
175
|
super(message, statusCode, responseBody, details);
|
|
166
176
|
this.name = "ScopeReauthRequiredError";
|
|
167
|
-
this.
|
|
177
|
+
this.grantId = grantId;
|
|
168
178
|
this.providerId = providerId;
|
|
169
179
|
}
|
|
170
180
|
};
|
|
@@ -185,7 +195,6 @@ var TimeoutError = class extends NetworkError {
|
|
|
185
195
|
var ActorType = /* @__PURE__ */ ((ActorType2) => {
|
|
186
196
|
ActorType2["BACKEND_SERVICE"] = "backend_service";
|
|
187
197
|
ActorType2["AI_AGENT"] = "ai_agent";
|
|
188
|
-
ActorType2["MCP_SERVER"] = "mcp_server";
|
|
189
198
|
return ActorType2;
|
|
190
199
|
})(ActorType || {});
|
|
191
200
|
var TokenResponse = class _TokenResponse {
|
|
@@ -197,8 +206,8 @@ var TokenResponse = class _TokenResponse {
|
|
|
197
206
|
expiresAt;
|
|
198
207
|
/** OAuth scopes granted */
|
|
199
208
|
scopes;
|
|
200
|
-
/**
|
|
201
|
-
|
|
209
|
+
/** Grant ID that provided this token */
|
|
210
|
+
grantId;
|
|
202
211
|
/** Provider ID (google, github, etc.) */
|
|
203
212
|
providerId;
|
|
204
213
|
/** HTTP header name for credential injection (e.g., "Authorization", "X-API-Key") */
|
|
@@ -214,8 +223,8 @@ var TokenResponse = class _TokenResponse {
|
|
|
214
223
|
this.expiresIn = data.expires_in ?? null;
|
|
215
224
|
this.expiresAt = data.expires_at ? _TokenResponse.parseExpiresAt(data.expires_at) : null;
|
|
216
225
|
this.scopes = data.scopes ?? [];
|
|
217
|
-
this.
|
|
218
|
-
this.providerId = data.provider_id ??
|
|
226
|
+
this.grantId = data.grant_id;
|
|
227
|
+
this.providerId = data.provider_id ?? null;
|
|
219
228
|
this.injectionHeader = data.injection_header ?? "Authorization";
|
|
220
229
|
this.injectionFormat = data.injection_format ?? "Bearer {token}";
|
|
221
230
|
if (data.additional_credentials) {
|
|
@@ -270,14 +279,15 @@ var TokenResponse = class _TokenResponse {
|
|
|
270
279
|
expires_in: this.expiresIn,
|
|
271
280
|
expires_at: this.expiresAt?.toISOString() ?? null,
|
|
272
281
|
scopes: this.scopes,
|
|
273
|
-
|
|
282
|
+
grant_id: this.grantId,
|
|
283
|
+
provider_id: this.providerId
|
|
274
284
|
};
|
|
275
285
|
}
|
|
276
286
|
/**
|
|
277
287
|
* Custom string representation — EXCLUDES access_token for security.
|
|
278
288
|
*/
|
|
279
289
|
toString() {
|
|
280
|
-
return `TokenResponse(
|
|
290
|
+
return `TokenResponse(grant_id=${this.grantId}, token_type=${this.tokenType}, scopes=[${this.scopes.join(", ")}])`;
|
|
281
291
|
}
|
|
282
292
|
/**
|
|
283
293
|
* Custom Node.js inspect output — EXCLUDES access_token for security.
|
|
@@ -286,8 +296,8 @@ var TokenResponse = class _TokenResponse {
|
|
|
286
296
|
return this.toString();
|
|
287
297
|
}
|
|
288
298
|
};
|
|
289
|
-
var
|
|
290
|
-
|
|
299
|
+
var GrantInfo = class {
|
|
300
|
+
grantId;
|
|
291
301
|
providerId;
|
|
292
302
|
scopes;
|
|
293
303
|
accountIdentifier;
|
|
@@ -298,7 +308,7 @@ var ConnectionInfo = class {
|
|
|
298
308
|
createdAt;
|
|
299
309
|
lastUsedAt;
|
|
300
310
|
constructor(data) {
|
|
301
|
-
this.
|
|
311
|
+
this.grantId = data.grant_id;
|
|
302
312
|
this.providerId = data.provider_id;
|
|
303
313
|
this.scopes = data.scopes ?? [];
|
|
304
314
|
this.accountIdentifier = data.account_identifier ?? null;
|
|
@@ -312,7 +322,7 @@ var ConnectionInfo = class {
|
|
|
312
322
|
}
|
|
313
323
|
toJSON() {
|
|
314
324
|
return {
|
|
315
|
-
|
|
325
|
+
grant_id: this.grantId,
|
|
316
326
|
provider_id: this.providerId,
|
|
317
327
|
scopes: this.scopes,
|
|
318
328
|
account_identifier: this.accountIdentifier,
|
|
@@ -325,7 +335,7 @@ var ConnectionInfo = class {
|
|
|
325
335
|
};
|
|
326
336
|
}
|
|
327
337
|
toString() {
|
|
328
|
-
return `
|
|
338
|
+
return `GrantInfo(grant_id=${this.grantId}, provider=${this.providerId}, status=${this.status})`;
|
|
329
339
|
}
|
|
330
340
|
};
|
|
331
341
|
var ConnectSession = class {
|
|
@@ -352,46 +362,91 @@ var ConnectSession = class {
|
|
|
352
362
|
return `ConnectSession(url=${this.connectUrl}, expires_in=${this.expiresIn})`;
|
|
353
363
|
}
|
|
354
364
|
};
|
|
355
|
-
var
|
|
356
|
-
|
|
365
|
+
var GrantListResult = class {
|
|
366
|
+
grants;
|
|
357
367
|
total;
|
|
358
368
|
limit;
|
|
359
369
|
offset;
|
|
360
370
|
hasMore;
|
|
361
371
|
constructor(data) {
|
|
362
|
-
this.
|
|
372
|
+
this.grants = data.grants;
|
|
363
373
|
this.total = data.total;
|
|
364
374
|
this.limit = data.limit;
|
|
365
375
|
this.offset = data.offset;
|
|
366
376
|
this.hasMore = data.has_more;
|
|
367
377
|
Object.freeze(this);
|
|
368
378
|
}
|
|
379
|
+
/**
|
|
380
|
+
* Custom JSON serialization — snake_case wire format.
|
|
381
|
+
*/
|
|
382
|
+
toJSON() {
|
|
383
|
+
return {
|
|
384
|
+
grants: this.grants.map((g) => g.toJSON()),
|
|
385
|
+
total: this.total,
|
|
386
|
+
limit: this.limit,
|
|
387
|
+
offset: this.offset,
|
|
388
|
+
has_more: this.hasMore
|
|
389
|
+
};
|
|
390
|
+
}
|
|
369
391
|
};
|
|
370
392
|
var ConnectResult = class {
|
|
371
|
-
|
|
393
|
+
grantId;
|
|
372
394
|
providerId;
|
|
373
395
|
accountIdentifier;
|
|
374
396
|
scopes;
|
|
375
|
-
|
|
397
|
+
grantPolicy;
|
|
376
398
|
constructor(data) {
|
|
377
|
-
this.
|
|
399
|
+
this.grantId = data.grant_id;
|
|
378
400
|
this.providerId = data.provider_id;
|
|
379
401
|
this.accountIdentifier = data.account_identifier ?? null;
|
|
380
402
|
this.scopes = data.scopes ?? [];
|
|
381
|
-
|
|
403
|
+
const rawPolicy = data.grant_policy;
|
|
404
|
+
if (rawPolicy) {
|
|
405
|
+
const raw = rawPolicy;
|
|
406
|
+
this.grantPolicy = Object.freeze({
|
|
407
|
+
expiresAt: raw["expires_at"] !== void 0 ? raw["expires_at"] : rawPolicy.expiresAt ?? null,
|
|
408
|
+
createdBy: raw["created_by"] !== void 0 ? raw["created_by"] : rawPolicy.createdBy ?? null,
|
|
409
|
+
createdAt: raw["created_at"] !== void 0 ? raw["created_at"] : rawPolicy.createdAt ?? null
|
|
410
|
+
});
|
|
411
|
+
} else {
|
|
412
|
+
this.grantPolicy = null;
|
|
413
|
+
}
|
|
382
414
|
Object.freeze(this);
|
|
383
415
|
}
|
|
384
416
|
toJSON() {
|
|
385
417
|
return {
|
|
386
|
-
|
|
418
|
+
grant_id: this.grantId,
|
|
387
419
|
provider_id: this.providerId,
|
|
388
420
|
account_identifier: this.accountIdentifier,
|
|
389
421
|
scopes: this.scopes,
|
|
390
|
-
|
|
422
|
+
grant_policy: this.grantPolicy ? {
|
|
423
|
+
expires_at: this.grantPolicy.expiresAt ?? null,
|
|
424
|
+
created_by: this.grantPolicy.createdBy ?? null,
|
|
425
|
+
created_at: this.grantPolicy.createdAt ?? null
|
|
426
|
+
} : null
|
|
391
427
|
};
|
|
392
428
|
}
|
|
393
429
|
toString() {
|
|
394
|
-
return `ConnectResult(
|
|
430
|
+
return `ConnectResult(grant_id=${this.grantId}, provider=${this.providerId})`;
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
var AuthResult = class {
|
|
434
|
+
userToken;
|
|
435
|
+
userInfo;
|
|
436
|
+
constructor(data) {
|
|
437
|
+
this.userToken = data.user_token;
|
|
438
|
+
this.userInfo = data.user_info ?? {};
|
|
439
|
+
Object.freeze(this);
|
|
440
|
+
}
|
|
441
|
+
toJSON() {
|
|
442
|
+
return {
|
|
443
|
+
user_token: this.userToken,
|
|
444
|
+
user_info: this.userInfo
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
toString() {
|
|
448
|
+
const sub = this.userInfo?.sub ?? "unknown";
|
|
449
|
+
return `AuthResult(sub=${sub})`;
|
|
395
450
|
}
|
|
396
451
|
};
|
|
397
452
|
var SENSITIVE_HEADERS = /* @__PURE__ */ new Set([
|
|
@@ -401,10 +456,11 @@ var SENSITIVE_HEADERS = /* @__PURE__ */ new Set([
|
|
|
401
456
|
"x-api-key",
|
|
402
457
|
"x-auth-token",
|
|
403
458
|
"x-amz-date",
|
|
404
|
-
"x-amz-content-sha256"
|
|
459
|
+
"x-amz-content-sha256",
|
|
460
|
+
"x-amz-security-token"
|
|
405
461
|
]);
|
|
406
462
|
var APICallAuditLog = class {
|
|
407
|
-
|
|
463
|
+
grantId;
|
|
408
464
|
providerId;
|
|
409
465
|
method;
|
|
410
466
|
url;
|
|
@@ -423,22 +479,28 @@ var APICallAuditLog = class {
|
|
|
423
479
|
threadId;
|
|
424
480
|
/** Tool invocation ID for actor tracking */
|
|
425
481
|
toolCallId;
|
|
482
|
+
/** Specific tool being invoked (e.g., "read_calendar") */
|
|
483
|
+
toolId;
|
|
484
|
+
/** Tool bundle identifier (e.g., "google-calendar-mcp-server") */
|
|
485
|
+
toolBundleId;
|
|
426
486
|
constructor(data) {
|
|
427
|
-
this.
|
|
428
|
-
this.providerId = data.
|
|
487
|
+
this.grantId = data.grant_id;
|
|
488
|
+
this.providerId = data.provider_id;
|
|
429
489
|
this.method = data.method;
|
|
430
490
|
this.url = data.url;
|
|
431
|
-
this.requestHeaders = data.
|
|
432
|
-
this.requestBody = data.
|
|
433
|
-
this.responseStatus = data.
|
|
434
|
-
this.responseHeaders = data.
|
|
435
|
-
this.responseBody = data.
|
|
436
|
-
this.latencyMs = data.
|
|
491
|
+
this.requestHeaders = data.request_headers ?? null;
|
|
492
|
+
this.requestBody = data.request_body ?? null;
|
|
493
|
+
this.responseStatus = data.response_status;
|
|
494
|
+
this.responseHeaders = data.response_headers ?? null;
|
|
495
|
+
this.responseBody = data.response_body ?? null;
|
|
496
|
+
this.latencyMs = data.latency_ms;
|
|
437
497
|
this.timestamp = /* @__PURE__ */ new Date();
|
|
438
498
|
this.reason = data.reason ?? null;
|
|
439
|
-
this.runId = data.
|
|
440
|
-
this.threadId = data.
|
|
441
|
-
this.toolCallId = data.
|
|
499
|
+
this.runId = data.run_id ?? null;
|
|
500
|
+
this.threadId = data.thread_id ?? null;
|
|
501
|
+
this.toolCallId = data.tool_call_id ?? null;
|
|
502
|
+
this.toolId = data.tool_id ?? null;
|
|
503
|
+
this.toolBundleId = data.tool_bundle_id ?? null;
|
|
442
504
|
}
|
|
443
505
|
/**
|
|
444
506
|
* Sanitize sensitive data before sending.
|
|
@@ -447,7 +509,7 @@ var APICallAuditLog = class {
|
|
|
447
509
|
*/
|
|
448
510
|
sanitize() {
|
|
449
511
|
const sanitized = {
|
|
450
|
-
|
|
512
|
+
grant_id: this.grantId,
|
|
451
513
|
provider_id: this.providerId,
|
|
452
514
|
method: this.method,
|
|
453
515
|
url: this.url,
|
|
@@ -460,7 +522,9 @@ var APICallAuditLog = class {
|
|
|
460
522
|
reason: this.reason,
|
|
461
523
|
run_id: this.runId,
|
|
462
524
|
thread_id: this.threadId,
|
|
463
|
-
tool_call_id: this.toolCallId
|
|
525
|
+
tool_call_id: this.toolCallId,
|
|
526
|
+
tool_id: this.toolId,
|
|
527
|
+
tool_bundle_id: this.toolBundleId
|
|
464
528
|
};
|
|
465
529
|
return sanitized;
|
|
466
530
|
}
|
|
@@ -480,6 +544,7 @@ var import_node_crypto = require("crypto");
|
|
|
480
544
|
var ALGORITHM = "AWS4-HMAC-SHA256";
|
|
481
545
|
var AWS_HOST_RE = /^(?<service>[a-z0-9-]+)\.(?<region>[a-z]{2}(?:-[a-z0-9]+)+-\d+)\.amazonaws\.com$/;
|
|
482
546
|
var S3_VIRTUAL_HOST_RE = /^[^.]+\.s3\.(?<region>[a-z]{2}(?:-[a-z0-9]+)+-\d+)\.amazonaws\.com$/;
|
|
547
|
+
var VPCE_HOST_RE = /^vpce-[a-z0-9-]+\.(?<service>[a-z0-9-]+)\.(?<region>[a-z]{2}(?:-[a-z0-9]+)+-\d+)\.vpce\.amazonaws\.com$/;
|
|
483
548
|
function hmacSha256(key, message) {
|
|
484
549
|
return (0, import_node_crypto.createHmac)("sha256", key).update(message, "utf8").digest();
|
|
485
550
|
}
|
|
@@ -502,6 +567,10 @@ function detectServiceAndRegion(hostname) {
|
|
|
502
567
|
if (s3m?.groups) {
|
|
503
568
|
return { service: "s3", region: s3m.groups.region };
|
|
504
569
|
}
|
|
570
|
+
const vpcem = VPCE_HOST_RE.exec(lower);
|
|
571
|
+
if (vpcem?.groups) {
|
|
572
|
+
return { service: vpcem.groups.service, region: vpcem.groups.region };
|
|
573
|
+
}
|
|
505
574
|
return { service: null, region: null };
|
|
506
575
|
}
|
|
507
576
|
function deriveSigningKey(secretKey, dateStamp, region, service) {
|
|
@@ -536,18 +605,21 @@ function canonicalQueryString(query) {
|
|
|
536
605
|
]);
|
|
537
606
|
}
|
|
538
607
|
}
|
|
539
|
-
|
|
608
|
+
const sigv4Encode = (s) => encodeURIComponent(s).replace(
|
|
609
|
+
/[!'()*]/g,
|
|
610
|
+
(c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`
|
|
611
|
+
);
|
|
612
|
+
const encoded = sorted.map(
|
|
613
|
+
([k, v]) => [sigv4Encode(k), sigv4Encode(v)]
|
|
614
|
+
);
|
|
615
|
+
encoded.sort((a, b) => {
|
|
540
616
|
if (a[0] < b[0]) return -1;
|
|
541
617
|
if (a[0] > b[0]) return 1;
|
|
542
618
|
if (a[1] < b[1]) return -1;
|
|
543
619
|
if (a[1] > b[1]) return 1;
|
|
544
620
|
return 0;
|
|
545
621
|
});
|
|
546
|
-
|
|
547
|
-
/[!'()*]/g,
|
|
548
|
-
(c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`
|
|
549
|
-
);
|
|
550
|
-
return sorted.map(([k, v]) => `${sigv4Encode(k)}=${sigv4Encode(v)}`).join("&");
|
|
622
|
+
return encoded.map(([k, v]) => `${k}=${v}`).join("&");
|
|
551
623
|
}
|
|
552
624
|
function canonicalHeadersAndSigned(headers) {
|
|
553
625
|
const canonical = {};
|
|
@@ -566,11 +638,9 @@ function signAwsRequest(opts) {
|
|
|
566
638
|
const parsed = new URL(opts.url);
|
|
567
639
|
const hostname = parsed.hostname;
|
|
568
640
|
let { region, service } = opts;
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
if (service == null) service = detected.service;
|
|
573
|
-
}
|
|
641
|
+
const detected = detectServiceAndRegion(hostname);
|
|
642
|
+
if (detected.region != null) region = detected.region;
|
|
643
|
+
if (detected.service != null) service = detected.service;
|
|
574
644
|
if (!region) {
|
|
575
645
|
throw new Error(
|
|
576
646
|
`Cannot determine AWS region from URL '${opts.url}'. Pass region explicitly via additional_credentials.`
|
|
@@ -653,7 +723,7 @@ function _extractAdditionalCredentials(token) {
|
|
|
653
723
|
return _additionalCredsStore.get(token);
|
|
654
724
|
}
|
|
655
725
|
var _fetch;
|
|
656
|
-
var SDK_VERSION = "0.
|
|
726
|
+
var SDK_VERSION = "0.7.0";
|
|
657
727
|
var SDK_USER_AGENT = `alter-sdk-node/${SDK_VERSION}`;
|
|
658
728
|
var HTTP_FORBIDDEN = 403;
|
|
659
729
|
var HTTP_NOT_FOUND = 404;
|
|
@@ -765,6 +835,7 @@ var AlterVault = class _AlterVault {
|
|
|
765
835
|
#actorVersion;
|
|
766
836
|
#clientType;
|
|
767
837
|
#framework;
|
|
838
|
+
#toolBundleId;
|
|
768
839
|
constructor(options) {
|
|
769
840
|
_AlterVault.#validateInitParams(
|
|
770
841
|
options.apiKey,
|
|
@@ -776,13 +847,14 @@ var AlterVault = class _AlterVault {
|
|
|
776
847
|
["actorName", options.actorName],
|
|
777
848
|
["actorVersion", options.actorVersion],
|
|
778
849
|
["clientType", options.clientType],
|
|
779
|
-
["framework", options.framework]
|
|
850
|
+
["framework", options.framework],
|
|
851
|
+
["toolBundleId", options.toolBundleId]
|
|
780
852
|
];
|
|
781
853
|
for (const [name, value] of actorStrings) {
|
|
782
854
|
_AlterVault.#validateActorString(value, name);
|
|
783
855
|
}
|
|
784
856
|
this.#hmacKey = (0, import_node_crypto2.createHmac)("sha256", options.apiKey).update("alter-signing-v1").digest();
|
|
785
|
-
this.baseUrl = (process.env.ALTER_BASE_URL ?? "https://backend.
|
|
857
|
+
this.baseUrl = (process.env.ALTER_BASE_URL ?? "https://backend.alterauth.com").replace(/\/+$/, "");
|
|
786
858
|
const timeoutMs = options.timeout ?? 3e4;
|
|
787
859
|
this.#actorType = options.actorType;
|
|
788
860
|
this.#actorIdentifier = options.actorIdentifier;
|
|
@@ -791,6 +863,7 @@ var AlterVault = class _AlterVault {
|
|
|
791
863
|
this.#clientType = options.clientType;
|
|
792
864
|
this.#userTokenGetter = options.userTokenGetter ?? null;
|
|
793
865
|
this.#framework = options.framework;
|
|
866
|
+
this.#toolBundleId = options.toolBundleId;
|
|
794
867
|
if (!_fetch) {
|
|
795
868
|
_fetch = globalThis.fetch;
|
|
796
869
|
}
|
|
@@ -838,7 +911,7 @@ var AlterVault = class _AlterVault {
|
|
|
838
911
|
}
|
|
839
912
|
if (!actorType) {
|
|
840
913
|
throw new AlterSDKError(
|
|
841
|
-
"actor_type is required (use ActorType.AI_AGENT
|
|
914
|
+
"actor_type is required (use ActorType.AI_AGENT or ActorType.BACKEND_SERVICE)"
|
|
842
915
|
);
|
|
843
916
|
}
|
|
844
917
|
const validValues = Object.values(ActorType);
|
|
@@ -867,7 +940,8 @@ var AlterVault = class _AlterVault {
|
|
|
867
940
|
"X-Alter-Actor-Name": this.#actorName,
|
|
868
941
|
"X-Alter-Actor-Version": this.#actorVersion,
|
|
869
942
|
"X-Alter-Client-Type": this.#clientType,
|
|
870
|
-
"X-Alter-Framework": this.#framework
|
|
943
|
+
"X-Alter-Framework": this.#framework,
|
|
944
|
+
"X-Alter-Tool-Bundle-ID": this.#toolBundleId
|
|
871
945
|
};
|
|
872
946
|
for (const [key, value] of Object.entries(actorHeaders)) {
|
|
873
947
|
if (value) {
|
|
@@ -879,21 +953,24 @@ var AlterVault = class _AlterVault {
|
|
|
879
953
|
/**
|
|
880
954
|
* Compute HMAC-SHA256 signature headers for an Alter backend request.
|
|
881
955
|
*
|
|
882
|
-
* String-to-sign format: METHOD\nPATH_WITH_SORTED_QUERY\nTIMESTAMP\nCONTENT_HASH
|
|
956
|
+
* String-to-sign format: METHOD\nPATH_WITH_SORTED_QUERY\nTIMESTAMP\nNONCE\nCONTENT_HASH
|
|
883
957
|
*
|
|
884
958
|
* The path should include sorted query parameters if present (e.g. "/sdk/endpoint?a=1&b=2").
|
|
885
|
-
* Currently all SDK
|
|
959
|
+
* Currently all SDK->backend calls are POSTs without query params, so the path is clean.
|
|
886
960
|
*/
|
|
887
961
|
#computeHmacHeaders(method, path, body) {
|
|
888
962
|
const timestamp = String(Math.floor(Date.now() / 1e3));
|
|
963
|
+
const nonce = (0, import_node_crypto2.randomBytes)(16).toString("hex");
|
|
889
964
|
const contentHash = (0, import_node_crypto2.createHash)("sha256").update(body ?? "").digest("hex");
|
|
890
965
|
const stringToSign = `${method.toUpperCase()}
|
|
891
966
|
${path}
|
|
892
967
|
${timestamp}
|
|
968
|
+
${nonce}
|
|
893
969
|
${contentHash}`;
|
|
894
970
|
const signature = (0, import_node_crypto2.createHmac)("sha256", this.#hmacKey).update(stringToSign).digest("hex");
|
|
895
971
|
return {
|
|
896
972
|
"X-Alter-Timestamp": timestamp,
|
|
973
|
+
"X-Alter-Nonce": nonce,
|
|
897
974
|
"X-Alter-Content-SHA256": contentHash,
|
|
898
975
|
"X-Alter-Signature": signature
|
|
899
976
|
};
|
|
@@ -901,7 +978,7 @@ ${contentHash}`;
|
|
|
901
978
|
/**
|
|
902
979
|
* Build per-request actor headers for instance tracking.
|
|
903
980
|
*/
|
|
904
|
-
#getActorRequestHeaders(runId, threadId, toolCallId) {
|
|
981
|
+
#getActorRequestHeaders(runId, threadId, toolCallId, toolId, toolBundleId) {
|
|
905
982
|
const headers = {};
|
|
906
983
|
if (this.#cachedActorId) {
|
|
907
984
|
headers["X-Alter-Actor-ID"] = this.#cachedActorId;
|
|
@@ -931,6 +1008,24 @@ ${contentHash}`;
|
|
|
931
1008
|
);
|
|
932
1009
|
}
|
|
933
1010
|
}
|
|
1011
|
+
if (toolId) {
|
|
1012
|
+
if (toolId.length <= MAX_TRACKING_ID_LENGTH && SAFE_HEADER_PATTERN.test(toolId)) {
|
|
1013
|
+
headers["X-Alter-Tool-ID"] = toolId;
|
|
1014
|
+
} else {
|
|
1015
|
+
console.warn(
|
|
1016
|
+
"Invalid tool_id (too long or invalid chars), skipping"
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
if (toolBundleId) {
|
|
1021
|
+
if (toolBundleId.length <= MAX_TRACKING_ID_LENGTH && SAFE_HEADER_PATTERN.test(toolBundleId)) {
|
|
1022
|
+
headers["X-Alter-Tool-Bundle-ID"] = toolBundleId;
|
|
1023
|
+
} else {
|
|
1024
|
+
console.warn(
|
|
1025
|
+
"Invalid tool_bundle_id (too long or invalid chars), skipping"
|
|
1026
|
+
);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
934
1029
|
return headers;
|
|
935
1030
|
}
|
|
936
1031
|
/**
|
|
@@ -982,9 +1077,20 @@ ${contentHash}`;
|
|
|
982
1077
|
}
|
|
983
1078
|
return null;
|
|
984
1079
|
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Parse JSON from response, unwrapping FastAPI's HTTPException envelope.
|
|
1082
|
+
*
|
|
1083
|
+
* FastAPI wraps `HTTPException.detail` in `{"detail": ...}`. When the
|
|
1084
|
+
* inner detail is an object we unwrap it so callers can access error
|
|
1085
|
+
* fields (`error`, `message`, `details`) directly.
|
|
1086
|
+
*/
|
|
985
1087
|
static async #safeParseJson(response) {
|
|
986
1088
|
try {
|
|
987
|
-
|
|
1089
|
+
const data = await response.clone().json();
|
|
1090
|
+
if ("detail" in data && typeof data.detail === "object" && data.detail !== null && !Array.isArray(data.detail)) {
|
|
1091
|
+
return data.detail;
|
|
1092
|
+
}
|
|
1093
|
+
return data;
|
|
988
1094
|
} catch {
|
|
989
1095
|
try {
|
|
990
1096
|
const text = await response.clone().text();
|
|
@@ -1003,38 +1109,69 @@ ${contentHash}`;
|
|
|
1003
1109
|
}
|
|
1004
1110
|
if (response.status === HTTP_FORBIDDEN) {
|
|
1005
1111
|
const errorData = await _AlterVault.#safeParseJson(response);
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1112
|
+
const errorCode = errorData.error;
|
|
1113
|
+
if (errorCode === "grant_expired") {
|
|
1114
|
+
throw new GrantExpiredError(
|
|
1115
|
+
errorData.message ?? "Grant expired per TTL policy",
|
|
1009
1116
|
errorData.details
|
|
1010
1117
|
);
|
|
1011
1118
|
}
|
|
1119
|
+
if (errorCode === "grant_revoked") {
|
|
1120
|
+
throw new GrantRevokedError(
|
|
1121
|
+
errorData.message ?? "Grant has been revoked. User must re-authorize.",
|
|
1122
|
+
errorData.grant_id
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
if (errorCode === "credential_revoked") {
|
|
1126
|
+
throw new CredentialRevokedError(
|
|
1127
|
+
errorData.message ?? "Credential has been revoked. User must re-authorize.",
|
|
1128
|
+
errorData.grant_id
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
if (errorData.error === "scope_mismatch") {
|
|
1132
|
+
const details = errorData.details ?? {};
|
|
1133
|
+
throw new ScopeReauthRequiredError(
|
|
1134
|
+
errorData.message ?? "Grant is missing required scopes",
|
|
1135
|
+
details.grant_id ?? void 0,
|
|
1136
|
+
details.provider_id ?? void 0,
|
|
1137
|
+
response.status,
|
|
1138
|
+
JSON.stringify(errorData),
|
|
1139
|
+
details
|
|
1140
|
+
);
|
|
1141
|
+
}
|
|
1012
1142
|
throw new PolicyViolationError(
|
|
1013
1143
|
errorData.message ?? "Access denied by policy",
|
|
1014
|
-
|
|
1144
|
+
errorCode,
|
|
1015
1145
|
errorData.details
|
|
1016
1146
|
);
|
|
1017
1147
|
}
|
|
1018
1148
|
if (response.status === HTTP_GONE) {
|
|
1019
1149
|
const errorData = await _AlterVault.#safeParseJson(response);
|
|
1020
|
-
throw new
|
|
1021
|
-
errorData.message ?? "
|
|
1150
|
+
throw new GrantDeletedError(
|
|
1151
|
+
errorData.message ?? "Grant has been deleted. A new grant_id will be issued on re-authorization.",
|
|
1022
1152
|
errorData
|
|
1023
1153
|
);
|
|
1024
1154
|
}
|
|
1025
1155
|
if (response.status === HTTP_NOT_FOUND) {
|
|
1026
1156
|
const errorData = await _AlterVault.#safeParseJson(response);
|
|
1027
|
-
throw new
|
|
1028
|
-
errorData.message ?? "OAuth
|
|
1157
|
+
throw new GrantNotFoundError(
|
|
1158
|
+
errorData.message ?? "OAuth grant not found for the given grant_id",
|
|
1029
1159
|
errorData
|
|
1030
1160
|
);
|
|
1031
1161
|
}
|
|
1032
1162
|
if (response.status === HTTP_BAD_REQUEST || response.status === HTTP_BAD_GATEWAY) {
|
|
1033
1163
|
const errorData = await _AlterVault.#safeParseJson(response);
|
|
1034
|
-
if (errorData.error === "
|
|
1035
|
-
throw new
|
|
1036
|
-
errorData.message ?? "
|
|
1037
|
-
errorData.
|
|
1164
|
+
if (errorData.error === "grant_revoked") {
|
|
1165
|
+
throw new GrantRevokedError(
|
|
1166
|
+
errorData.message ?? "Grant has been revoked. User must re-authorize.",
|
|
1167
|
+
errorData.grant_id,
|
|
1168
|
+
errorData
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
1171
|
+
if (errorData.error === "credential_revoked") {
|
|
1172
|
+
throw new CredentialRevokedError(
|
|
1173
|
+
errorData.message ?? "Underlying credential has been revoked. User must re-authorize.",
|
|
1174
|
+
errorData.grant_id,
|
|
1038
1175
|
errorData
|
|
1039
1176
|
);
|
|
1040
1177
|
}
|
|
@@ -1084,11 +1221,13 @@ ${contentHash}`;
|
|
|
1084
1221
|
}
|
|
1085
1222
|
return token;
|
|
1086
1223
|
}
|
|
1087
|
-
async #getToken(
|
|
1224
|
+
async #getToken(grantId, reason, requestMetadata, runId, threadId, toolCallId, toolId, provider, account, toolBundleId) {
|
|
1088
1225
|
const actorHeaders = this.#getActorRequestHeaders(
|
|
1089
1226
|
runId,
|
|
1090
1227
|
threadId,
|
|
1091
|
-
toolCallId
|
|
1228
|
+
toolCallId,
|
|
1229
|
+
toolId,
|
|
1230
|
+
toolBundleId
|
|
1092
1231
|
);
|
|
1093
1232
|
let response;
|
|
1094
1233
|
let tokenBody;
|
|
@@ -1105,7 +1244,7 @@ ${contentHash}`;
|
|
|
1105
1244
|
}
|
|
1106
1245
|
} else {
|
|
1107
1246
|
tokenBody = {
|
|
1108
|
-
|
|
1247
|
+
grant_id: grantId,
|
|
1109
1248
|
reason: reason ?? null,
|
|
1110
1249
|
request: requestMetadata ?? null
|
|
1111
1250
|
};
|
|
@@ -1133,7 +1272,7 @@ ${contentHash}`;
|
|
|
1133
1272
|
}
|
|
1134
1273
|
throw new BackendError(
|
|
1135
1274
|
`Failed to retrieve token: ${error instanceof Error ? error.message : String(error)}`,
|
|
1136
|
-
{
|
|
1275
|
+
{ grant_id: grantId, error: String(error) }
|
|
1137
1276
|
);
|
|
1138
1277
|
}
|
|
1139
1278
|
this.#cacheActorIdFromResponse(response);
|
|
@@ -1145,13 +1284,13 @@ ${contentHash}`;
|
|
|
1145
1284
|
if (!/^[A-Za-z][A-Za-z0-9-]*$/.test(tokenResponse.injectionHeader)) {
|
|
1146
1285
|
throw new BackendError(
|
|
1147
1286
|
`Backend returned invalid injection_header: ${tokenResponse.injectionHeader}`,
|
|
1148
|
-
{
|
|
1287
|
+
{ grantId: String(grantId) }
|
|
1149
1288
|
);
|
|
1150
1289
|
}
|
|
1151
1290
|
if (/[\r\n\x00]/.test(tokenResponse.injectionFormat)) {
|
|
1152
1291
|
throw new BackendError(
|
|
1153
1292
|
`Backend returned invalid injection_format (contains control characters)`,
|
|
1154
|
-
{
|
|
1293
|
+
{ grantId: String(grantId) }
|
|
1155
1294
|
);
|
|
1156
1295
|
}
|
|
1157
1296
|
_storeAccessToken(tokenResponse, typedData.access_token);
|
|
@@ -1169,20 +1308,22 @@ ${contentHash}`;
|
|
|
1169
1308
|
async #logApiCall(params) {
|
|
1170
1309
|
try {
|
|
1171
1310
|
const auditLog = new APICallAuditLog({
|
|
1172
|
-
|
|
1173
|
-
|
|
1311
|
+
grant_id: params.grantId,
|
|
1312
|
+
provider_id: params.providerId,
|
|
1174
1313
|
method: params.method,
|
|
1175
1314
|
url: params.url,
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1315
|
+
request_headers: params.requestHeaders,
|
|
1316
|
+
request_body: params.requestBody,
|
|
1317
|
+
response_status: params.responseStatus,
|
|
1318
|
+
response_headers: params.responseHeaders,
|
|
1319
|
+
response_body: params.responseBody,
|
|
1320
|
+
latency_ms: params.latencyMs,
|
|
1182
1321
|
reason: params.reason,
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1322
|
+
run_id: params.runId,
|
|
1323
|
+
thread_id: params.threadId,
|
|
1324
|
+
tool_call_id: params.toolCallId,
|
|
1325
|
+
tool_id: params.toolId,
|
|
1326
|
+
tool_bundle_id: params.toolBundleId
|
|
1186
1327
|
});
|
|
1187
1328
|
const sanitized = auditLog.sanitize();
|
|
1188
1329
|
const actorHeaders = this.#getActorRequestHeaders(params.runId);
|
|
@@ -1258,7 +1399,7 @@ ${contentHash}`;
|
|
|
1258
1399
|
* 4. Logs the call for audit (fire-and-forget)
|
|
1259
1400
|
* 5. Returns the raw response
|
|
1260
1401
|
*/
|
|
1261
|
-
async request(
|
|
1402
|
+
async request(grantId, method, url, options) {
|
|
1262
1403
|
if (this.#closed) {
|
|
1263
1404
|
throw new AlterSDKError(
|
|
1264
1405
|
"SDK instance has been closed. Create a new AlterVault instance to make requests."
|
|
@@ -1266,13 +1407,13 @@ ${contentHash}`;
|
|
|
1266
1407
|
}
|
|
1267
1408
|
const provider = options?.provider;
|
|
1268
1409
|
const account = options?.account;
|
|
1269
|
-
if (!
|
|
1270
|
-
throw new AlterSDKError("Provide
|
|
1410
|
+
if (!grantId && !provider) {
|
|
1411
|
+
throw new AlterSDKError("Provide grantId or options.provider");
|
|
1271
1412
|
}
|
|
1272
|
-
if (
|
|
1273
|
-
throw new AlterSDKError("Cannot provide both
|
|
1413
|
+
if (grantId && provider) {
|
|
1414
|
+
throw new AlterSDKError("Cannot provide both grantId and options.provider");
|
|
1274
1415
|
}
|
|
1275
|
-
const
|
|
1416
|
+
const effectiveGrantId = grantId ?? null;
|
|
1276
1417
|
let currentUrl = url;
|
|
1277
1418
|
const runId = options?.runId ?? (0, import_node_crypto2.randomUUID)();
|
|
1278
1419
|
const methodStr = String(method).toUpperCase();
|
|
@@ -1313,14 +1454,16 @@ ${contentHash}`;
|
|
|
1313
1454
|
}
|
|
1314
1455
|
}
|
|
1315
1456
|
const { tokenResponse, scopeMismatch } = await this.#getToken(
|
|
1316
|
-
|
|
1457
|
+
effectiveGrantId,
|
|
1317
1458
|
options?.reason,
|
|
1318
1459
|
{ method: methodStr, url: currentUrl },
|
|
1319
1460
|
runId,
|
|
1320
1461
|
options?.threadId,
|
|
1321
1462
|
options?.toolCallId,
|
|
1463
|
+
options?.toolId,
|
|
1322
1464
|
provider,
|
|
1323
|
-
account
|
|
1465
|
+
account,
|
|
1466
|
+
options?.toolBundleId
|
|
1324
1467
|
);
|
|
1325
1468
|
const requestHeaders = options?.extraHeaders ? { ...options.extraHeaders } : {};
|
|
1326
1469
|
const accessToken = _extractAccessToken(tokenResponse);
|
|
@@ -1339,7 +1482,7 @@ ${contentHash}`;
|
|
|
1339
1482
|
if (!additionalCreds.secret_key) {
|
|
1340
1483
|
throw new BackendError(
|
|
1341
1484
|
"AWS SigV4 credential is missing secret_key in additional_credentials. Re-store the credential with both Access Key ID and Secret Access Key.",
|
|
1342
|
-
{
|
|
1485
|
+
{ grant_id: effectiveGrantId }
|
|
1343
1486
|
);
|
|
1344
1487
|
}
|
|
1345
1488
|
const awsHeaders = signAwsRequest({
|
|
@@ -1406,7 +1549,7 @@ ${contentHash}`;
|
|
|
1406
1549
|
throw new TimeoutError(
|
|
1407
1550
|
`Provider API request timed out: ${error instanceof Error ? error.message : String(error)}`,
|
|
1408
1551
|
{
|
|
1409
|
-
|
|
1552
|
+
grant_id: effectiveGrantId,
|
|
1410
1553
|
method: methodStr,
|
|
1411
1554
|
url: currentUrl
|
|
1412
1555
|
}
|
|
@@ -1415,7 +1558,7 @@ ${contentHash}`;
|
|
|
1415
1558
|
throw new NetworkError(
|
|
1416
1559
|
`Failed to call provider API: ${error instanceof Error ? error.message : String(error)}`,
|
|
1417
1560
|
{
|
|
1418
|
-
|
|
1561
|
+
grant_id: effectiveGrantId,
|
|
1419
1562
|
method: methodStr,
|
|
1420
1563
|
url: currentUrl,
|
|
1421
1564
|
error: String(error)
|
|
@@ -1444,8 +1587,8 @@ ${contentHash}`;
|
|
|
1444
1587
|
responseHeaders[key] = value;
|
|
1445
1588
|
});
|
|
1446
1589
|
this.#scheduleAuditLog({
|
|
1447
|
-
|
|
1448
|
-
providerId: tokenResponse.providerId ||
|
|
1590
|
+
grantId: tokenResponse.grantId,
|
|
1591
|
+
providerId: tokenResponse.providerId || effectiveGrantId || "",
|
|
1449
1592
|
method: methodStr,
|
|
1450
1593
|
url: auditUrl,
|
|
1451
1594
|
requestHeaders: auditHeaders,
|
|
@@ -1457,18 +1600,20 @@ ${contentHash}`;
|
|
|
1457
1600
|
reason: options?.reason ?? null,
|
|
1458
1601
|
runId,
|
|
1459
1602
|
threadId: options?.threadId ?? null,
|
|
1460
|
-
toolCallId: options?.toolCallId ?? null
|
|
1603
|
+
toolCallId: options?.toolCallId ?? null,
|
|
1604
|
+
toolId: options?.toolId ?? null,
|
|
1605
|
+
toolBundleId: options?.toolBundleId ?? null
|
|
1461
1606
|
});
|
|
1462
1607
|
if (response.status >= HTTP_CLIENT_ERROR_START) {
|
|
1463
1608
|
if (response.status === HTTP_FORBIDDEN && scopeMismatch) {
|
|
1464
1609
|
throw new ScopeReauthRequiredError(
|
|
1465
|
-
"Provider API returned 403 and the
|
|
1466
|
-
tokenResponse.
|
|
1467
|
-
tokenResponse.providerId,
|
|
1610
|
+
"Provider API returned 403 and the grant's scopes don't match the provider config. The user needs to re-authorize to grant updated permissions.",
|
|
1611
|
+
tokenResponse.grantId,
|
|
1612
|
+
tokenResponse.providerId ?? void 0,
|
|
1468
1613
|
response.status,
|
|
1469
1614
|
responseBody,
|
|
1470
1615
|
{
|
|
1471
|
-
|
|
1616
|
+
grant_id: tokenResponse.grantId,
|
|
1472
1617
|
provider_id: tokenResponse.providerId,
|
|
1473
1618
|
method: methodStr,
|
|
1474
1619
|
url: currentUrl
|
|
@@ -1480,7 +1625,7 @@ ${contentHash}`;
|
|
|
1480
1625
|
response.status,
|
|
1481
1626
|
responseBody,
|
|
1482
1627
|
{
|
|
1483
|
-
|
|
1628
|
+
grant_id: effectiveGrantId,
|
|
1484
1629
|
method: methodStr,
|
|
1485
1630
|
url: currentUrl
|
|
1486
1631
|
}
|
|
@@ -1489,12 +1634,12 @@ ${contentHash}`;
|
|
|
1489
1634
|
return response;
|
|
1490
1635
|
}
|
|
1491
1636
|
/**
|
|
1492
|
-
* List OAuth
|
|
1637
|
+
* List OAuth grants for this app.
|
|
1493
1638
|
*
|
|
1494
|
-
* Returns paginated
|
|
1639
|
+
* Returns paginated grant metadata (no tokens).
|
|
1495
1640
|
* Useful for discovering which services a user has connected.
|
|
1496
1641
|
*/
|
|
1497
|
-
async
|
|
1642
|
+
async listGrants(options) {
|
|
1498
1643
|
if (this.#closed) {
|
|
1499
1644
|
throw new AlterSDKError(
|
|
1500
1645
|
"SDK instance has been closed. Create a new AlterVault instance to make requests."
|
|
@@ -1512,12 +1657,12 @@ ${contentHash}`;
|
|
|
1512
1657
|
listBody.user_token = await this.#getUserToken();
|
|
1513
1658
|
} catch (err) {
|
|
1514
1659
|
console.warn(
|
|
1515
|
-
"user_token_getter failed in
|
|
1660
|
+
"user_token_getter failed in listGrants, falling back to un-scoped listing:",
|
|
1516
1661
|
err instanceof Error ? err.message : String(err)
|
|
1517
1662
|
);
|
|
1518
1663
|
}
|
|
1519
1664
|
}
|
|
1520
|
-
const listPath = "/sdk/oauth/
|
|
1665
|
+
const listPath = "/sdk/oauth/grants/list";
|
|
1521
1666
|
const listBodyStr = JSON.stringify(listBody);
|
|
1522
1667
|
const listHmac = this.#computeHmacHeaders("POST", listPath, listBodyStr);
|
|
1523
1668
|
try {
|
|
@@ -1539,19 +1684,19 @@ ${contentHash}`;
|
|
|
1539
1684
|
);
|
|
1540
1685
|
}
|
|
1541
1686
|
throw new AlterSDKError(
|
|
1542
|
-
`Failed to list
|
|
1687
|
+
`Failed to list grants: ${error instanceof Error ? error.message : String(error)}`
|
|
1543
1688
|
);
|
|
1544
1689
|
}
|
|
1545
1690
|
this.#cacheActorIdFromResponse(response);
|
|
1546
1691
|
await this.#handleErrorResponse(response);
|
|
1547
1692
|
const data = await response.json();
|
|
1548
|
-
const
|
|
1549
|
-
(
|
|
1550
|
-
|
|
1693
|
+
const grants = data.grants.map(
|
|
1694
|
+
(g) => new GrantInfo(
|
|
1695
|
+
g
|
|
1551
1696
|
)
|
|
1552
1697
|
);
|
|
1553
|
-
return new
|
|
1554
|
-
|
|
1698
|
+
return new GrantListResult({
|
|
1699
|
+
grants,
|
|
1555
1700
|
total: data.total,
|
|
1556
1701
|
limit: data.limit,
|
|
1557
1702
|
offset: data.offset,
|
|
@@ -1577,10 +1722,10 @@ ${contentHash}`;
|
|
|
1577
1722
|
allowed_origin: options.allowedOrigin ?? null,
|
|
1578
1723
|
metadata: options.metadata ?? null
|
|
1579
1724
|
};
|
|
1580
|
-
if (options.
|
|
1581
|
-
sessionBody.
|
|
1582
|
-
max_ttl_seconds: options.
|
|
1583
|
-
default_ttl_seconds: options.
|
|
1725
|
+
if (options.grantPolicy) {
|
|
1726
|
+
sessionBody.grant_policy = {
|
|
1727
|
+
max_ttl_seconds: options.grantPolicy.maxTtlSeconds ?? null,
|
|
1728
|
+
default_ttl_seconds: options.grantPolicy.defaultTtlSeconds ?? null
|
|
1584
1729
|
};
|
|
1585
1730
|
}
|
|
1586
1731
|
if (this.#userTokenGetter) {
|
|
@@ -1647,7 +1792,7 @@ ${contentHash}`;
|
|
|
1647
1792
|
const openBrowser = options.openBrowser ?? true;
|
|
1648
1793
|
const session = await this.createConnectSession({
|
|
1649
1794
|
allowedProviders: options.providers,
|
|
1650
|
-
|
|
1795
|
+
grantPolicy: options.grantPolicy
|
|
1651
1796
|
});
|
|
1652
1797
|
if (openBrowser) {
|
|
1653
1798
|
try {
|
|
@@ -1678,14 +1823,14 @@ ${contentHash}`;
|
|
|
1678
1823
|
const pollResult = await this.#pollSession(session.sessionToken);
|
|
1679
1824
|
const pollStatus = pollResult.status;
|
|
1680
1825
|
if (pollStatus === "completed") {
|
|
1681
|
-
const
|
|
1682
|
-
return
|
|
1683
|
-
(
|
|
1684
|
-
|
|
1685
|
-
provider_id:
|
|
1686
|
-
account_identifier:
|
|
1687
|
-
scopes:
|
|
1688
|
-
|
|
1826
|
+
const grantsData = pollResult.grants ?? [];
|
|
1827
|
+
return grantsData.map(
|
|
1828
|
+
(grant) => new ConnectResult({
|
|
1829
|
+
grant_id: grant.grant_id ?? "",
|
|
1830
|
+
provider_id: grant.provider_id ?? "",
|
|
1831
|
+
account_identifier: grant.account_identifier ?? null,
|
|
1832
|
+
scopes: grant.scopes ?? [],
|
|
1833
|
+
grant_policy: grant.grant_policy ?? null
|
|
1689
1834
|
})
|
|
1690
1835
|
);
|
|
1691
1836
|
}
|
|
@@ -1712,12 +1857,133 @@ ${contentHash}`;
|
|
|
1712
1857
|
"Connect session expired before OAuth was completed"
|
|
1713
1858
|
);
|
|
1714
1859
|
}
|
|
1860
|
+
if (pollStatus !== "pending") {
|
|
1861
|
+
throw new ConnectFlowError(
|
|
1862
|
+
`Unexpected poll status from server: ${pollStatus}`,
|
|
1863
|
+
{ status: pollStatus }
|
|
1864
|
+
);
|
|
1865
|
+
}
|
|
1715
1866
|
}
|
|
1716
1867
|
throw new ConnectTimeoutError(
|
|
1717
1868
|
`OAuth flow did not complete within ${timeout} seconds. The user may not have finished authorizing in the browser.`,
|
|
1718
1869
|
{ timeout }
|
|
1719
1870
|
);
|
|
1720
1871
|
}
|
|
1872
|
+
/**
|
|
1873
|
+
* Trigger IDP login for end user via browser.
|
|
1874
|
+
*
|
|
1875
|
+
* Opens the app's configured IDP login page in the user's default browser.
|
|
1876
|
+
* Polls for completion and returns the user's IDP JWT token.
|
|
1877
|
+
*
|
|
1878
|
+
* After authenticate() completes, the returned userToken is automatically
|
|
1879
|
+
* used for subsequent request() calls that use identity resolution.
|
|
1880
|
+
*
|
|
1881
|
+
* @param options - Optional configuration (timeout in milliseconds, default 300000 = 5 min)
|
|
1882
|
+
* @returns AuthResult with userToken and userInfo
|
|
1883
|
+
* @throws ConnectTimeoutError if authentication times out
|
|
1884
|
+
* @throws AlterSDKError if session creation or authentication fails
|
|
1885
|
+
*/
|
|
1886
|
+
async authenticate(options) {
|
|
1887
|
+
if (this.#closed) {
|
|
1888
|
+
throw new AlterSDKError(
|
|
1889
|
+
"SDK instance has been closed. Create a new AlterVault instance to make requests."
|
|
1890
|
+
);
|
|
1891
|
+
}
|
|
1892
|
+
const timeoutMs = options?.timeout ?? 3e5;
|
|
1893
|
+
const actorHeaders = this.#getActorRequestHeaders();
|
|
1894
|
+
const sessionPath = "/sdk/auth/session";
|
|
1895
|
+
const sessionBodyStr = JSON.stringify({});
|
|
1896
|
+
const sessionHmac = this.#computeHmacHeaders("POST", sessionPath, sessionBodyStr);
|
|
1897
|
+
let sessionResp;
|
|
1898
|
+
try {
|
|
1899
|
+
sessionResp = await this.#alterClient.post(sessionPath, {
|
|
1900
|
+
body: sessionBodyStr,
|
|
1901
|
+
headers: { ...actorHeaders, ...sessionHmac, "Content-Type": "application/json" }
|
|
1902
|
+
});
|
|
1903
|
+
} catch (error) {
|
|
1904
|
+
if (_AlterVault.#isTimeoutOrAbortError(error)) {
|
|
1905
|
+
throw new TimeoutError(
|
|
1906
|
+
`Request to Alter Vault backend timed out: ${error instanceof Error ? error.message : String(error)}`,
|
|
1907
|
+
{ base_url: this.baseUrl }
|
|
1908
|
+
);
|
|
1909
|
+
}
|
|
1910
|
+
if (error instanceof TypeError) {
|
|
1911
|
+
throw new NetworkError(
|
|
1912
|
+
`Failed to connect to Alter Vault backend: ${error.message}`,
|
|
1913
|
+
{ base_url: this.baseUrl }
|
|
1914
|
+
);
|
|
1915
|
+
}
|
|
1916
|
+
throw new AlterSDKError(
|
|
1917
|
+
`Failed to create auth session: ${error instanceof Error ? error.message : String(error)}`
|
|
1918
|
+
);
|
|
1919
|
+
}
|
|
1920
|
+
this.#cacheActorIdFromResponse(sessionResp);
|
|
1921
|
+
await this.#handleErrorResponse(sessionResp);
|
|
1922
|
+
const sessionData = await sessionResp.json();
|
|
1923
|
+
try {
|
|
1924
|
+
const openModule = await import("open");
|
|
1925
|
+
const openFn = openModule.default;
|
|
1926
|
+
if (openFn) {
|
|
1927
|
+
await openFn(sessionData.auth_url);
|
|
1928
|
+
} else {
|
|
1929
|
+
console.log(
|
|
1930
|
+
`Open this URL to authenticate: ${sessionData.auth_url}`
|
|
1931
|
+
);
|
|
1932
|
+
}
|
|
1933
|
+
} catch {
|
|
1934
|
+
console.log(
|
|
1935
|
+
`Open this URL to authenticate: ${sessionData.auth_url}`
|
|
1936
|
+
);
|
|
1937
|
+
}
|
|
1938
|
+
const startTime = Date.now();
|
|
1939
|
+
while (true) {
|
|
1940
|
+
if (Date.now() - startTime > timeoutMs) {
|
|
1941
|
+
throw new ConnectTimeoutError(
|
|
1942
|
+
"Authentication timed out",
|
|
1943
|
+
{ timeout: timeoutMs }
|
|
1944
|
+
);
|
|
1945
|
+
}
|
|
1946
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
1947
|
+
const pollPath = "/sdk/auth/poll";
|
|
1948
|
+
const pollBodyStr = JSON.stringify({ session_token: sessionData.session_token });
|
|
1949
|
+
const pollHmac = this.#computeHmacHeaders("POST", pollPath, pollBodyStr);
|
|
1950
|
+
let pollResp;
|
|
1951
|
+
try {
|
|
1952
|
+
pollResp = await this.#alterClient.post(pollPath, {
|
|
1953
|
+
body: pollBodyStr,
|
|
1954
|
+
headers: { ...actorHeaders, ...pollHmac, "Content-Type": "application/json" }
|
|
1955
|
+
});
|
|
1956
|
+
} catch {
|
|
1957
|
+
continue;
|
|
1958
|
+
}
|
|
1959
|
+
this.#cacheActorIdFromResponse(pollResp);
|
|
1960
|
+
if (!pollResp.ok) {
|
|
1961
|
+
continue;
|
|
1962
|
+
}
|
|
1963
|
+
const pollData = await pollResp.json();
|
|
1964
|
+
if (pollData.status === "completed") {
|
|
1965
|
+
const userToken = pollData.user_token;
|
|
1966
|
+
if (!userToken) {
|
|
1967
|
+
throw new AlterSDKError(
|
|
1968
|
+
"Authentication completed but no user token was returned by the IDP"
|
|
1969
|
+
);
|
|
1970
|
+
}
|
|
1971
|
+
this.#userTokenGetter = () => userToken;
|
|
1972
|
+
return new AuthResult({
|
|
1973
|
+
user_token: userToken,
|
|
1974
|
+
user_info: pollData.user_info
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
1977
|
+
if (pollData.status === "error") {
|
|
1978
|
+
throw new AlterSDKError(
|
|
1979
|
+
pollData.error_message ?? "Authentication failed"
|
|
1980
|
+
);
|
|
1981
|
+
}
|
|
1982
|
+
if (pollData.status === "expired") {
|
|
1983
|
+
throw new AlterSDKError("Authentication session expired");
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1721
1987
|
/**
|
|
1722
1988
|
* Poll the Connect session for completion status (INTERNAL).
|
|
1723
1989
|
*
|
|
@@ -1869,6 +2135,7 @@ var HttpMethod = /* @__PURE__ */ ((HttpMethod2) => {
|
|
|
1869
2135
|
ActorType,
|
|
1870
2136
|
AlterSDKError,
|
|
1871
2137
|
AlterVault,
|
|
2138
|
+
AuthResult,
|
|
1872
2139
|
BackendError,
|
|
1873
2140
|
ConnectConfigError,
|
|
1874
2141
|
ConnectDeniedError,
|
|
@@ -1876,12 +2143,13 @@ var HttpMethod = /* @__PURE__ */ ((HttpMethod2) => {
|
|
|
1876
2143
|
ConnectResult,
|
|
1877
2144
|
ConnectSession,
|
|
1878
2145
|
ConnectTimeoutError,
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
2146
|
+
CredentialRevokedError,
|
|
2147
|
+
GrantDeletedError,
|
|
2148
|
+
GrantExpiredError,
|
|
2149
|
+
GrantInfo,
|
|
2150
|
+
GrantListResult,
|
|
2151
|
+
GrantNotFoundError,
|
|
2152
|
+
GrantRevokedError,
|
|
1885
2153
|
HttpMethod,
|
|
1886
2154
|
NetworkError,
|
|
1887
2155
|
PolicyViolationError,
|