@alter-ai/alter-sdk 0.6.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 +116 -73
- package/dist/index.cjs +394 -148
- package/dist/index.d.cts +142 -77
- package/dist/index.d.ts +142 -77
- package/dist/index.js +387 -143
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/client.ts
|
|
2
|
-
import { createHash as createHash2, createHmac as createHmac2, randomUUID } from "crypto";
|
|
2
|
+
import { createHash as createHash2, createHmac as createHmac2, randomBytes, randomUUID } from "crypto";
|
|
3
3
|
|
|
4
4
|
// src/exceptions.ts
|
|
5
5
|
var AlterSDKError = class extends Error {
|
|
@@ -29,30 +29,38 @@ var ReAuthRequiredError = class extends BackendError {
|
|
|
29
29
|
this.name = "ReAuthRequiredError";
|
|
30
30
|
}
|
|
31
31
|
};
|
|
32
|
-
var
|
|
32
|
+
var GrantExpiredError = class extends ReAuthRequiredError {
|
|
33
33
|
constructor(message, details) {
|
|
34
34
|
super(message, details);
|
|
35
|
-
this.name = "
|
|
35
|
+
this.name = "GrantExpiredError";
|
|
36
36
|
}
|
|
37
37
|
};
|
|
38
|
-
var
|
|
39
|
-
|
|
40
|
-
constructor(message,
|
|
38
|
+
var GrantRevokedError = class extends ReAuthRequiredError {
|
|
39
|
+
grantId;
|
|
40
|
+
constructor(message, grantId, details) {
|
|
41
41
|
super(message, details);
|
|
42
|
-
this.name = "
|
|
43
|
-
this.
|
|
42
|
+
this.name = "GrantRevokedError";
|
|
43
|
+
this.grantId = grantId;
|
|
44
44
|
}
|
|
45
45
|
};
|
|
46
|
-
var
|
|
46
|
+
var CredentialRevokedError = class extends ReAuthRequiredError {
|
|
47
|
+
grantId;
|
|
48
|
+
constructor(message, grantId, details) {
|
|
49
|
+
super(message, details);
|
|
50
|
+
this.name = "CredentialRevokedError";
|
|
51
|
+
this.grantId = grantId;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var GrantDeletedError = class extends ReAuthRequiredError {
|
|
47
55
|
constructor(message, details) {
|
|
48
56
|
super(message, details);
|
|
49
|
-
this.name = "
|
|
57
|
+
this.name = "GrantDeletedError";
|
|
50
58
|
}
|
|
51
59
|
};
|
|
52
|
-
var
|
|
60
|
+
var GrantNotFoundError = class extends BackendError {
|
|
53
61
|
constructor(message, details) {
|
|
54
62
|
super(message, details);
|
|
55
|
-
this.name = "
|
|
63
|
+
this.name = "GrantNotFoundError";
|
|
56
64
|
}
|
|
57
65
|
};
|
|
58
66
|
var PolicyViolationError = class extends BackendError {
|
|
@@ -98,12 +106,12 @@ var ProviderAPIError = class extends AlterSDKError {
|
|
|
98
106
|
}
|
|
99
107
|
};
|
|
100
108
|
var ScopeReauthRequiredError = class extends ProviderAPIError {
|
|
101
|
-
|
|
109
|
+
grantId;
|
|
102
110
|
providerId;
|
|
103
|
-
constructor(message,
|
|
111
|
+
constructor(message, grantId, providerId, statusCode, responseBody, details) {
|
|
104
112
|
super(message, statusCode, responseBody, details);
|
|
105
113
|
this.name = "ScopeReauthRequiredError";
|
|
106
|
-
this.
|
|
114
|
+
this.grantId = grantId;
|
|
107
115
|
this.providerId = providerId;
|
|
108
116
|
}
|
|
109
117
|
};
|
|
@@ -124,7 +132,6 @@ var TimeoutError = class extends NetworkError {
|
|
|
124
132
|
var ActorType = /* @__PURE__ */ ((ActorType2) => {
|
|
125
133
|
ActorType2["BACKEND_SERVICE"] = "backend_service";
|
|
126
134
|
ActorType2["AI_AGENT"] = "ai_agent";
|
|
127
|
-
ActorType2["MCP_SERVER"] = "mcp_server";
|
|
128
135
|
return ActorType2;
|
|
129
136
|
})(ActorType || {});
|
|
130
137
|
var TokenResponse = class _TokenResponse {
|
|
@@ -136,8 +143,8 @@ var TokenResponse = class _TokenResponse {
|
|
|
136
143
|
expiresAt;
|
|
137
144
|
/** OAuth scopes granted */
|
|
138
145
|
scopes;
|
|
139
|
-
/**
|
|
140
|
-
|
|
146
|
+
/** Grant ID that provided this token */
|
|
147
|
+
grantId;
|
|
141
148
|
/** Provider ID (google, github, etc.) */
|
|
142
149
|
providerId;
|
|
143
150
|
/** HTTP header name for credential injection (e.g., "Authorization", "X-API-Key") */
|
|
@@ -153,8 +160,8 @@ var TokenResponse = class _TokenResponse {
|
|
|
153
160
|
this.expiresIn = data.expires_in ?? null;
|
|
154
161
|
this.expiresAt = data.expires_at ? _TokenResponse.parseExpiresAt(data.expires_at) : null;
|
|
155
162
|
this.scopes = data.scopes ?? [];
|
|
156
|
-
this.
|
|
157
|
-
this.providerId = data.provider_id ??
|
|
163
|
+
this.grantId = data.grant_id;
|
|
164
|
+
this.providerId = data.provider_id ?? null;
|
|
158
165
|
this.injectionHeader = data.injection_header ?? "Authorization";
|
|
159
166
|
this.injectionFormat = data.injection_format ?? "Bearer {token}";
|
|
160
167
|
if (data.additional_credentials) {
|
|
@@ -209,7 +216,7 @@ var TokenResponse = class _TokenResponse {
|
|
|
209
216
|
expires_in: this.expiresIn,
|
|
210
217
|
expires_at: this.expiresAt?.toISOString() ?? null,
|
|
211
218
|
scopes: this.scopes,
|
|
212
|
-
|
|
219
|
+
grant_id: this.grantId,
|
|
213
220
|
provider_id: this.providerId
|
|
214
221
|
};
|
|
215
222
|
}
|
|
@@ -217,7 +224,7 @@ var TokenResponse = class _TokenResponse {
|
|
|
217
224
|
* Custom string representation — EXCLUDES access_token for security.
|
|
218
225
|
*/
|
|
219
226
|
toString() {
|
|
220
|
-
return `TokenResponse(
|
|
227
|
+
return `TokenResponse(grant_id=${this.grantId}, token_type=${this.tokenType}, scopes=[${this.scopes.join(", ")}])`;
|
|
221
228
|
}
|
|
222
229
|
/**
|
|
223
230
|
* Custom Node.js inspect output — EXCLUDES access_token for security.
|
|
@@ -226,8 +233,8 @@ var TokenResponse = class _TokenResponse {
|
|
|
226
233
|
return this.toString();
|
|
227
234
|
}
|
|
228
235
|
};
|
|
229
|
-
var
|
|
230
|
-
|
|
236
|
+
var GrantInfo = class {
|
|
237
|
+
grantId;
|
|
231
238
|
providerId;
|
|
232
239
|
scopes;
|
|
233
240
|
accountIdentifier;
|
|
@@ -238,7 +245,7 @@ var ConnectionInfo = class {
|
|
|
238
245
|
createdAt;
|
|
239
246
|
lastUsedAt;
|
|
240
247
|
constructor(data) {
|
|
241
|
-
this.
|
|
248
|
+
this.grantId = data.grant_id;
|
|
242
249
|
this.providerId = data.provider_id;
|
|
243
250
|
this.scopes = data.scopes ?? [];
|
|
244
251
|
this.accountIdentifier = data.account_identifier ?? null;
|
|
@@ -252,7 +259,7 @@ var ConnectionInfo = class {
|
|
|
252
259
|
}
|
|
253
260
|
toJSON() {
|
|
254
261
|
return {
|
|
255
|
-
|
|
262
|
+
grant_id: this.grantId,
|
|
256
263
|
provider_id: this.providerId,
|
|
257
264
|
scopes: this.scopes,
|
|
258
265
|
account_identifier: this.accountIdentifier,
|
|
@@ -265,7 +272,7 @@ var ConnectionInfo = class {
|
|
|
265
272
|
};
|
|
266
273
|
}
|
|
267
274
|
toString() {
|
|
268
|
-
return `
|
|
275
|
+
return `GrantInfo(grant_id=${this.grantId}, provider=${this.providerId}, status=${this.status})`;
|
|
269
276
|
}
|
|
270
277
|
};
|
|
271
278
|
var ConnectSession = class {
|
|
@@ -292,60 +299,91 @@ var ConnectSession = class {
|
|
|
292
299
|
return `ConnectSession(url=${this.connectUrl}, expires_in=${this.expiresIn})`;
|
|
293
300
|
}
|
|
294
301
|
};
|
|
295
|
-
var
|
|
296
|
-
|
|
302
|
+
var GrantListResult = class {
|
|
303
|
+
grants;
|
|
297
304
|
total;
|
|
298
305
|
limit;
|
|
299
306
|
offset;
|
|
300
307
|
hasMore;
|
|
301
308
|
constructor(data) {
|
|
302
|
-
this.
|
|
309
|
+
this.grants = data.grants;
|
|
303
310
|
this.total = data.total;
|
|
304
311
|
this.limit = data.limit;
|
|
305
312
|
this.offset = data.offset;
|
|
306
313
|
this.hasMore = data.has_more;
|
|
307
314
|
Object.freeze(this);
|
|
308
315
|
}
|
|
316
|
+
/**
|
|
317
|
+
* Custom JSON serialization — snake_case wire format.
|
|
318
|
+
*/
|
|
319
|
+
toJSON() {
|
|
320
|
+
return {
|
|
321
|
+
grants: this.grants.map((g) => g.toJSON()),
|
|
322
|
+
total: this.total,
|
|
323
|
+
limit: this.limit,
|
|
324
|
+
offset: this.offset,
|
|
325
|
+
has_more: this.hasMore
|
|
326
|
+
};
|
|
327
|
+
}
|
|
309
328
|
};
|
|
310
329
|
var ConnectResult = class {
|
|
311
|
-
|
|
330
|
+
grantId;
|
|
312
331
|
providerId;
|
|
313
332
|
accountIdentifier;
|
|
314
333
|
scopes;
|
|
315
|
-
|
|
334
|
+
grantPolicy;
|
|
316
335
|
constructor(data) {
|
|
317
|
-
this.
|
|
336
|
+
this.grantId = data.grant_id;
|
|
318
337
|
this.providerId = data.provider_id;
|
|
319
338
|
this.accountIdentifier = data.account_identifier ?? null;
|
|
320
339
|
this.scopes = data.scopes ?? [];
|
|
321
|
-
const rawPolicy = data.
|
|
340
|
+
const rawPolicy = data.grant_policy;
|
|
322
341
|
if (rawPolicy) {
|
|
323
342
|
const raw = rawPolicy;
|
|
324
|
-
this.
|
|
343
|
+
this.grantPolicy = Object.freeze({
|
|
325
344
|
expiresAt: raw["expires_at"] !== void 0 ? raw["expires_at"] : rawPolicy.expiresAt ?? null,
|
|
326
345
|
createdBy: raw["created_by"] !== void 0 ? raw["created_by"] : rawPolicy.createdBy ?? null,
|
|
327
346
|
createdAt: raw["created_at"] !== void 0 ? raw["created_at"] : rawPolicy.createdAt ?? null
|
|
328
347
|
});
|
|
329
348
|
} else {
|
|
330
|
-
this.
|
|
349
|
+
this.grantPolicy = null;
|
|
331
350
|
}
|
|
332
351
|
Object.freeze(this);
|
|
333
352
|
}
|
|
334
353
|
toJSON() {
|
|
335
354
|
return {
|
|
336
|
-
|
|
355
|
+
grant_id: this.grantId,
|
|
337
356
|
provider_id: this.providerId,
|
|
338
357
|
account_identifier: this.accountIdentifier,
|
|
339
358
|
scopes: this.scopes,
|
|
340
|
-
|
|
341
|
-
expires_at: this.
|
|
342
|
-
created_by: this.
|
|
343
|
-
created_at: this.
|
|
359
|
+
grant_policy: this.grantPolicy ? {
|
|
360
|
+
expires_at: this.grantPolicy.expiresAt ?? null,
|
|
361
|
+
created_by: this.grantPolicy.createdBy ?? null,
|
|
362
|
+
created_at: this.grantPolicy.createdAt ?? null
|
|
344
363
|
} : null
|
|
345
364
|
};
|
|
346
365
|
}
|
|
347
366
|
toString() {
|
|
348
|
-
return `ConnectResult(
|
|
367
|
+
return `ConnectResult(grant_id=${this.grantId}, provider=${this.providerId})`;
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
var AuthResult = class {
|
|
371
|
+
userToken;
|
|
372
|
+
userInfo;
|
|
373
|
+
constructor(data) {
|
|
374
|
+
this.userToken = data.user_token;
|
|
375
|
+
this.userInfo = data.user_info ?? {};
|
|
376
|
+
Object.freeze(this);
|
|
377
|
+
}
|
|
378
|
+
toJSON() {
|
|
379
|
+
return {
|
|
380
|
+
user_token: this.userToken,
|
|
381
|
+
user_info: this.userInfo
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
toString() {
|
|
385
|
+
const sub = this.userInfo?.sub ?? "unknown";
|
|
386
|
+
return `AuthResult(sub=${sub})`;
|
|
349
387
|
}
|
|
350
388
|
};
|
|
351
389
|
var SENSITIVE_HEADERS = /* @__PURE__ */ new Set([
|
|
@@ -359,7 +397,7 @@ var SENSITIVE_HEADERS = /* @__PURE__ */ new Set([
|
|
|
359
397
|
"x-amz-security-token"
|
|
360
398
|
]);
|
|
361
399
|
var APICallAuditLog = class {
|
|
362
|
-
|
|
400
|
+
grantId;
|
|
363
401
|
providerId;
|
|
364
402
|
method;
|
|
365
403
|
url;
|
|
@@ -378,22 +416,28 @@ var APICallAuditLog = class {
|
|
|
378
416
|
threadId;
|
|
379
417
|
/** Tool invocation ID for actor tracking */
|
|
380
418
|
toolCallId;
|
|
419
|
+
/** Specific tool being invoked (e.g., "read_calendar") */
|
|
420
|
+
toolId;
|
|
421
|
+
/** Tool bundle identifier (e.g., "google-calendar-mcp-server") */
|
|
422
|
+
toolBundleId;
|
|
381
423
|
constructor(data) {
|
|
382
|
-
this.
|
|
383
|
-
this.providerId = data.
|
|
424
|
+
this.grantId = data.grant_id;
|
|
425
|
+
this.providerId = data.provider_id;
|
|
384
426
|
this.method = data.method;
|
|
385
427
|
this.url = data.url;
|
|
386
|
-
this.requestHeaders = data.
|
|
387
|
-
this.requestBody = data.
|
|
388
|
-
this.responseStatus = data.
|
|
389
|
-
this.responseHeaders = data.
|
|
390
|
-
this.responseBody = data.
|
|
391
|
-
this.latencyMs = data.
|
|
428
|
+
this.requestHeaders = data.request_headers ?? null;
|
|
429
|
+
this.requestBody = data.request_body ?? null;
|
|
430
|
+
this.responseStatus = data.response_status;
|
|
431
|
+
this.responseHeaders = data.response_headers ?? null;
|
|
432
|
+
this.responseBody = data.response_body ?? null;
|
|
433
|
+
this.latencyMs = data.latency_ms;
|
|
392
434
|
this.timestamp = /* @__PURE__ */ new Date();
|
|
393
435
|
this.reason = data.reason ?? null;
|
|
394
|
-
this.runId = data.
|
|
395
|
-
this.threadId = data.
|
|
396
|
-
this.toolCallId = data.
|
|
436
|
+
this.runId = data.run_id ?? null;
|
|
437
|
+
this.threadId = data.thread_id ?? null;
|
|
438
|
+
this.toolCallId = data.tool_call_id ?? null;
|
|
439
|
+
this.toolId = data.tool_id ?? null;
|
|
440
|
+
this.toolBundleId = data.tool_bundle_id ?? null;
|
|
397
441
|
}
|
|
398
442
|
/**
|
|
399
443
|
* Sanitize sensitive data before sending.
|
|
@@ -402,7 +446,7 @@ var APICallAuditLog = class {
|
|
|
402
446
|
*/
|
|
403
447
|
sanitize() {
|
|
404
448
|
const sanitized = {
|
|
405
|
-
|
|
449
|
+
grant_id: this.grantId,
|
|
406
450
|
provider_id: this.providerId,
|
|
407
451
|
method: this.method,
|
|
408
452
|
url: this.url,
|
|
@@ -415,7 +459,9 @@ var APICallAuditLog = class {
|
|
|
415
459
|
reason: this.reason,
|
|
416
460
|
run_id: this.runId,
|
|
417
461
|
thread_id: this.threadId,
|
|
418
|
-
tool_call_id: this.toolCallId
|
|
462
|
+
tool_call_id: this.toolCallId,
|
|
463
|
+
tool_id: this.toolId,
|
|
464
|
+
tool_bundle_id: this.toolBundleId
|
|
419
465
|
};
|
|
420
466
|
return sanitized;
|
|
421
467
|
}
|
|
@@ -614,7 +660,7 @@ function _extractAdditionalCredentials(token) {
|
|
|
614
660
|
return _additionalCredsStore.get(token);
|
|
615
661
|
}
|
|
616
662
|
var _fetch;
|
|
617
|
-
var SDK_VERSION = "0.
|
|
663
|
+
var SDK_VERSION = "0.7.0";
|
|
618
664
|
var SDK_USER_AGENT = `alter-sdk-node/${SDK_VERSION}`;
|
|
619
665
|
var HTTP_FORBIDDEN = 403;
|
|
620
666
|
var HTTP_NOT_FOUND = 404;
|
|
@@ -726,6 +772,7 @@ var AlterVault = class _AlterVault {
|
|
|
726
772
|
#actorVersion;
|
|
727
773
|
#clientType;
|
|
728
774
|
#framework;
|
|
775
|
+
#toolBundleId;
|
|
729
776
|
constructor(options) {
|
|
730
777
|
_AlterVault.#validateInitParams(
|
|
731
778
|
options.apiKey,
|
|
@@ -737,13 +784,14 @@ var AlterVault = class _AlterVault {
|
|
|
737
784
|
["actorName", options.actorName],
|
|
738
785
|
["actorVersion", options.actorVersion],
|
|
739
786
|
["clientType", options.clientType],
|
|
740
|
-
["framework", options.framework]
|
|
787
|
+
["framework", options.framework],
|
|
788
|
+
["toolBundleId", options.toolBundleId]
|
|
741
789
|
];
|
|
742
790
|
for (const [name, value] of actorStrings) {
|
|
743
791
|
_AlterVault.#validateActorString(value, name);
|
|
744
792
|
}
|
|
745
793
|
this.#hmacKey = createHmac2("sha256", options.apiKey).update("alter-signing-v1").digest();
|
|
746
|
-
this.baseUrl = (process.env.ALTER_BASE_URL ?? "https://backend.
|
|
794
|
+
this.baseUrl = (process.env.ALTER_BASE_URL ?? "https://backend.alterauth.com").replace(/\/+$/, "");
|
|
747
795
|
const timeoutMs = options.timeout ?? 3e4;
|
|
748
796
|
this.#actorType = options.actorType;
|
|
749
797
|
this.#actorIdentifier = options.actorIdentifier;
|
|
@@ -752,6 +800,7 @@ var AlterVault = class _AlterVault {
|
|
|
752
800
|
this.#clientType = options.clientType;
|
|
753
801
|
this.#userTokenGetter = options.userTokenGetter ?? null;
|
|
754
802
|
this.#framework = options.framework;
|
|
803
|
+
this.#toolBundleId = options.toolBundleId;
|
|
755
804
|
if (!_fetch) {
|
|
756
805
|
_fetch = globalThis.fetch;
|
|
757
806
|
}
|
|
@@ -799,7 +848,7 @@ var AlterVault = class _AlterVault {
|
|
|
799
848
|
}
|
|
800
849
|
if (!actorType) {
|
|
801
850
|
throw new AlterSDKError(
|
|
802
|
-
"actor_type is required (use ActorType.AI_AGENT
|
|
851
|
+
"actor_type is required (use ActorType.AI_AGENT or ActorType.BACKEND_SERVICE)"
|
|
803
852
|
);
|
|
804
853
|
}
|
|
805
854
|
const validValues = Object.values(ActorType);
|
|
@@ -828,7 +877,8 @@ var AlterVault = class _AlterVault {
|
|
|
828
877
|
"X-Alter-Actor-Name": this.#actorName,
|
|
829
878
|
"X-Alter-Actor-Version": this.#actorVersion,
|
|
830
879
|
"X-Alter-Client-Type": this.#clientType,
|
|
831
|
-
"X-Alter-Framework": this.#framework
|
|
880
|
+
"X-Alter-Framework": this.#framework,
|
|
881
|
+
"X-Alter-Tool-Bundle-ID": this.#toolBundleId
|
|
832
882
|
};
|
|
833
883
|
for (const [key, value] of Object.entries(actorHeaders)) {
|
|
834
884
|
if (value) {
|
|
@@ -840,21 +890,24 @@ var AlterVault = class _AlterVault {
|
|
|
840
890
|
/**
|
|
841
891
|
* Compute HMAC-SHA256 signature headers for an Alter backend request.
|
|
842
892
|
*
|
|
843
|
-
* String-to-sign format: METHOD\nPATH_WITH_SORTED_QUERY\nTIMESTAMP\nCONTENT_HASH
|
|
893
|
+
* String-to-sign format: METHOD\nPATH_WITH_SORTED_QUERY\nTIMESTAMP\nNONCE\nCONTENT_HASH
|
|
844
894
|
*
|
|
845
895
|
* The path should include sorted query parameters if present (e.g. "/sdk/endpoint?a=1&b=2").
|
|
846
|
-
* Currently all SDK
|
|
896
|
+
* Currently all SDK->backend calls are POSTs without query params, so the path is clean.
|
|
847
897
|
*/
|
|
848
898
|
#computeHmacHeaders(method, path, body) {
|
|
849
899
|
const timestamp = String(Math.floor(Date.now() / 1e3));
|
|
900
|
+
const nonce = randomBytes(16).toString("hex");
|
|
850
901
|
const contentHash = createHash2("sha256").update(body ?? "").digest("hex");
|
|
851
902
|
const stringToSign = `${method.toUpperCase()}
|
|
852
903
|
${path}
|
|
853
904
|
${timestamp}
|
|
905
|
+
${nonce}
|
|
854
906
|
${contentHash}`;
|
|
855
907
|
const signature = createHmac2("sha256", this.#hmacKey).update(stringToSign).digest("hex");
|
|
856
908
|
return {
|
|
857
909
|
"X-Alter-Timestamp": timestamp,
|
|
910
|
+
"X-Alter-Nonce": nonce,
|
|
858
911
|
"X-Alter-Content-SHA256": contentHash,
|
|
859
912
|
"X-Alter-Signature": signature
|
|
860
913
|
};
|
|
@@ -862,7 +915,7 @@ ${contentHash}`;
|
|
|
862
915
|
/**
|
|
863
916
|
* Build per-request actor headers for instance tracking.
|
|
864
917
|
*/
|
|
865
|
-
#getActorRequestHeaders(runId, threadId, toolCallId) {
|
|
918
|
+
#getActorRequestHeaders(runId, threadId, toolCallId, toolId, toolBundleId) {
|
|
866
919
|
const headers = {};
|
|
867
920
|
if (this.#cachedActorId) {
|
|
868
921
|
headers["X-Alter-Actor-ID"] = this.#cachedActorId;
|
|
@@ -892,6 +945,24 @@ ${contentHash}`;
|
|
|
892
945
|
);
|
|
893
946
|
}
|
|
894
947
|
}
|
|
948
|
+
if (toolId) {
|
|
949
|
+
if (toolId.length <= MAX_TRACKING_ID_LENGTH && SAFE_HEADER_PATTERN.test(toolId)) {
|
|
950
|
+
headers["X-Alter-Tool-ID"] = toolId;
|
|
951
|
+
} else {
|
|
952
|
+
console.warn(
|
|
953
|
+
"Invalid tool_id (too long or invalid chars), skipping"
|
|
954
|
+
);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
if (toolBundleId) {
|
|
958
|
+
if (toolBundleId.length <= MAX_TRACKING_ID_LENGTH && SAFE_HEADER_PATTERN.test(toolBundleId)) {
|
|
959
|
+
headers["X-Alter-Tool-Bundle-ID"] = toolBundleId;
|
|
960
|
+
} else {
|
|
961
|
+
console.warn(
|
|
962
|
+
"Invalid tool_bundle_id (too long or invalid chars), skipping"
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
895
966
|
return headers;
|
|
896
967
|
}
|
|
897
968
|
/**
|
|
@@ -943,9 +1014,20 @@ ${contentHash}`;
|
|
|
943
1014
|
}
|
|
944
1015
|
return null;
|
|
945
1016
|
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Parse JSON from response, unwrapping FastAPI's HTTPException envelope.
|
|
1019
|
+
*
|
|
1020
|
+
* FastAPI wraps `HTTPException.detail` in `{"detail": ...}`. When the
|
|
1021
|
+
* inner detail is an object we unwrap it so callers can access error
|
|
1022
|
+
* fields (`error`, `message`, `details`) directly.
|
|
1023
|
+
*/
|
|
946
1024
|
static async #safeParseJson(response) {
|
|
947
1025
|
try {
|
|
948
|
-
|
|
1026
|
+
const data = await response.clone().json();
|
|
1027
|
+
if ("detail" in data && typeof data.detail === "object" && data.detail !== null && !Array.isArray(data.detail)) {
|
|
1028
|
+
return data.detail;
|
|
1029
|
+
}
|
|
1030
|
+
return data;
|
|
949
1031
|
} catch {
|
|
950
1032
|
try {
|
|
951
1033
|
const text = await response.clone().text();
|
|
@@ -964,38 +1046,69 @@ ${contentHash}`;
|
|
|
964
1046
|
}
|
|
965
1047
|
if (response.status === HTTP_FORBIDDEN) {
|
|
966
1048
|
const errorData = await _AlterVault.#safeParseJson(response);
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
1049
|
+
const errorCode = errorData.error;
|
|
1050
|
+
if (errorCode === "grant_expired") {
|
|
1051
|
+
throw new GrantExpiredError(
|
|
1052
|
+
errorData.message ?? "Grant expired per TTL policy",
|
|
970
1053
|
errorData.details
|
|
971
1054
|
);
|
|
972
1055
|
}
|
|
1056
|
+
if (errorCode === "grant_revoked") {
|
|
1057
|
+
throw new GrantRevokedError(
|
|
1058
|
+
errorData.message ?? "Grant has been revoked. User must re-authorize.",
|
|
1059
|
+
errorData.grant_id
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
1062
|
+
if (errorCode === "credential_revoked") {
|
|
1063
|
+
throw new CredentialRevokedError(
|
|
1064
|
+
errorData.message ?? "Credential has been revoked. User must re-authorize.",
|
|
1065
|
+
errorData.grant_id
|
|
1066
|
+
);
|
|
1067
|
+
}
|
|
1068
|
+
if (errorData.error === "scope_mismatch") {
|
|
1069
|
+
const details = errorData.details ?? {};
|
|
1070
|
+
throw new ScopeReauthRequiredError(
|
|
1071
|
+
errorData.message ?? "Grant is missing required scopes",
|
|
1072
|
+
details.grant_id ?? void 0,
|
|
1073
|
+
details.provider_id ?? void 0,
|
|
1074
|
+
response.status,
|
|
1075
|
+
JSON.stringify(errorData),
|
|
1076
|
+
details
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
973
1079
|
throw new PolicyViolationError(
|
|
974
1080
|
errorData.message ?? "Access denied by policy",
|
|
975
|
-
|
|
1081
|
+
errorCode,
|
|
976
1082
|
errorData.details
|
|
977
1083
|
);
|
|
978
1084
|
}
|
|
979
1085
|
if (response.status === HTTP_GONE) {
|
|
980
1086
|
const errorData = await _AlterVault.#safeParseJson(response);
|
|
981
|
-
throw new
|
|
982
|
-
errorData.message ?? "
|
|
1087
|
+
throw new GrantDeletedError(
|
|
1088
|
+
errorData.message ?? "Grant has been deleted. A new grant_id will be issued on re-authorization.",
|
|
983
1089
|
errorData
|
|
984
1090
|
);
|
|
985
1091
|
}
|
|
986
1092
|
if (response.status === HTTP_NOT_FOUND) {
|
|
987
1093
|
const errorData = await _AlterVault.#safeParseJson(response);
|
|
988
|
-
throw new
|
|
989
|
-
errorData.message ?? "OAuth
|
|
1094
|
+
throw new GrantNotFoundError(
|
|
1095
|
+
errorData.message ?? "OAuth grant not found for the given grant_id",
|
|
990
1096
|
errorData
|
|
991
1097
|
);
|
|
992
1098
|
}
|
|
993
1099
|
if (response.status === HTTP_BAD_REQUEST || response.status === HTTP_BAD_GATEWAY) {
|
|
994
1100
|
const errorData = await _AlterVault.#safeParseJson(response);
|
|
995
|
-
if (errorData.error === "
|
|
996
|
-
throw new
|
|
997
|
-
errorData.message ?? "
|
|
998
|
-
errorData.
|
|
1101
|
+
if (errorData.error === "grant_revoked") {
|
|
1102
|
+
throw new GrantRevokedError(
|
|
1103
|
+
errorData.message ?? "Grant has been revoked. User must re-authorize.",
|
|
1104
|
+
errorData.grant_id,
|
|
1105
|
+
errorData
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
if (errorData.error === "credential_revoked") {
|
|
1109
|
+
throw new CredentialRevokedError(
|
|
1110
|
+
errorData.message ?? "Underlying credential has been revoked. User must re-authorize.",
|
|
1111
|
+
errorData.grant_id,
|
|
999
1112
|
errorData
|
|
1000
1113
|
);
|
|
1001
1114
|
}
|
|
@@ -1045,11 +1158,13 @@ ${contentHash}`;
|
|
|
1045
1158
|
}
|
|
1046
1159
|
return token;
|
|
1047
1160
|
}
|
|
1048
|
-
async #getToken(
|
|
1161
|
+
async #getToken(grantId, reason, requestMetadata, runId, threadId, toolCallId, toolId, provider, account, toolBundleId) {
|
|
1049
1162
|
const actorHeaders = this.#getActorRequestHeaders(
|
|
1050
1163
|
runId,
|
|
1051
1164
|
threadId,
|
|
1052
|
-
toolCallId
|
|
1165
|
+
toolCallId,
|
|
1166
|
+
toolId,
|
|
1167
|
+
toolBundleId
|
|
1053
1168
|
);
|
|
1054
1169
|
let response;
|
|
1055
1170
|
let tokenBody;
|
|
@@ -1066,7 +1181,7 @@ ${contentHash}`;
|
|
|
1066
1181
|
}
|
|
1067
1182
|
} else {
|
|
1068
1183
|
tokenBody = {
|
|
1069
|
-
|
|
1184
|
+
grant_id: grantId,
|
|
1070
1185
|
reason: reason ?? null,
|
|
1071
1186
|
request: requestMetadata ?? null
|
|
1072
1187
|
};
|
|
@@ -1094,7 +1209,7 @@ ${contentHash}`;
|
|
|
1094
1209
|
}
|
|
1095
1210
|
throw new BackendError(
|
|
1096
1211
|
`Failed to retrieve token: ${error instanceof Error ? error.message : String(error)}`,
|
|
1097
|
-
{
|
|
1212
|
+
{ grant_id: grantId, error: String(error) }
|
|
1098
1213
|
);
|
|
1099
1214
|
}
|
|
1100
1215
|
this.#cacheActorIdFromResponse(response);
|
|
@@ -1106,13 +1221,13 @@ ${contentHash}`;
|
|
|
1106
1221
|
if (!/^[A-Za-z][A-Za-z0-9-]*$/.test(tokenResponse.injectionHeader)) {
|
|
1107
1222
|
throw new BackendError(
|
|
1108
1223
|
`Backend returned invalid injection_header: ${tokenResponse.injectionHeader}`,
|
|
1109
|
-
{
|
|
1224
|
+
{ grantId: String(grantId) }
|
|
1110
1225
|
);
|
|
1111
1226
|
}
|
|
1112
1227
|
if (/[\r\n\x00]/.test(tokenResponse.injectionFormat)) {
|
|
1113
1228
|
throw new BackendError(
|
|
1114
1229
|
`Backend returned invalid injection_format (contains control characters)`,
|
|
1115
|
-
{
|
|
1230
|
+
{ grantId: String(grantId) }
|
|
1116
1231
|
);
|
|
1117
1232
|
}
|
|
1118
1233
|
_storeAccessToken(tokenResponse, typedData.access_token);
|
|
@@ -1130,20 +1245,22 @@ ${contentHash}`;
|
|
|
1130
1245
|
async #logApiCall(params) {
|
|
1131
1246
|
try {
|
|
1132
1247
|
const auditLog = new APICallAuditLog({
|
|
1133
|
-
|
|
1134
|
-
|
|
1248
|
+
grant_id: params.grantId,
|
|
1249
|
+
provider_id: params.providerId,
|
|
1135
1250
|
method: params.method,
|
|
1136
1251
|
url: params.url,
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1252
|
+
request_headers: params.requestHeaders,
|
|
1253
|
+
request_body: params.requestBody,
|
|
1254
|
+
response_status: params.responseStatus,
|
|
1255
|
+
response_headers: params.responseHeaders,
|
|
1256
|
+
response_body: params.responseBody,
|
|
1257
|
+
latency_ms: params.latencyMs,
|
|
1143
1258
|
reason: params.reason,
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1259
|
+
run_id: params.runId,
|
|
1260
|
+
thread_id: params.threadId,
|
|
1261
|
+
tool_call_id: params.toolCallId,
|
|
1262
|
+
tool_id: params.toolId,
|
|
1263
|
+
tool_bundle_id: params.toolBundleId
|
|
1147
1264
|
});
|
|
1148
1265
|
const sanitized = auditLog.sanitize();
|
|
1149
1266
|
const actorHeaders = this.#getActorRequestHeaders(params.runId);
|
|
@@ -1219,7 +1336,7 @@ ${contentHash}`;
|
|
|
1219
1336
|
* 4. Logs the call for audit (fire-and-forget)
|
|
1220
1337
|
* 5. Returns the raw response
|
|
1221
1338
|
*/
|
|
1222
|
-
async request(
|
|
1339
|
+
async request(grantId, method, url, options) {
|
|
1223
1340
|
if (this.#closed) {
|
|
1224
1341
|
throw new AlterSDKError(
|
|
1225
1342
|
"SDK instance has been closed. Create a new AlterVault instance to make requests."
|
|
@@ -1227,13 +1344,13 @@ ${contentHash}`;
|
|
|
1227
1344
|
}
|
|
1228
1345
|
const provider = options?.provider;
|
|
1229
1346
|
const account = options?.account;
|
|
1230
|
-
if (!
|
|
1231
|
-
throw new AlterSDKError("Provide
|
|
1347
|
+
if (!grantId && !provider) {
|
|
1348
|
+
throw new AlterSDKError("Provide grantId or options.provider");
|
|
1232
1349
|
}
|
|
1233
|
-
if (
|
|
1234
|
-
throw new AlterSDKError("Cannot provide both
|
|
1350
|
+
if (grantId && provider) {
|
|
1351
|
+
throw new AlterSDKError("Cannot provide both grantId and options.provider");
|
|
1235
1352
|
}
|
|
1236
|
-
const
|
|
1353
|
+
const effectiveGrantId = grantId ?? null;
|
|
1237
1354
|
let currentUrl = url;
|
|
1238
1355
|
const runId = options?.runId ?? randomUUID();
|
|
1239
1356
|
const methodStr = String(method).toUpperCase();
|
|
@@ -1274,14 +1391,16 @@ ${contentHash}`;
|
|
|
1274
1391
|
}
|
|
1275
1392
|
}
|
|
1276
1393
|
const { tokenResponse, scopeMismatch } = await this.#getToken(
|
|
1277
|
-
|
|
1394
|
+
effectiveGrantId,
|
|
1278
1395
|
options?.reason,
|
|
1279
1396
|
{ method: methodStr, url: currentUrl },
|
|
1280
1397
|
runId,
|
|
1281
1398
|
options?.threadId,
|
|
1282
1399
|
options?.toolCallId,
|
|
1400
|
+
options?.toolId,
|
|
1283
1401
|
provider,
|
|
1284
|
-
account
|
|
1402
|
+
account,
|
|
1403
|
+
options?.toolBundleId
|
|
1285
1404
|
);
|
|
1286
1405
|
const requestHeaders = options?.extraHeaders ? { ...options.extraHeaders } : {};
|
|
1287
1406
|
const accessToken = _extractAccessToken(tokenResponse);
|
|
@@ -1300,7 +1419,7 @@ ${contentHash}`;
|
|
|
1300
1419
|
if (!additionalCreds.secret_key) {
|
|
1301
1420
|
throw new BackendError(
|
|
1302
1421
|
"AWS SigV4 credential is missing secret_key in additional_credentials. Re-store the credential with both Access Key ID and Secret Access Key.",
|
|
1303
|
-
{
|
|
1422
|
+
{ grant_id: effectiveGrantId }
|
|
1304
1423
|
);
|
|
1305
1424
|
}
|
|
1306
1425
|
const awsHeaders = signAwsRequest({
|
|
@@ -1367,7 +1486,7 @@ ${contentHash}`;
|
|
|
1367
1486
|
throw new TimeoutError(
|
|
1368
1487
|
`Provider API request timed out: ${error instanceof Error ? error.message : String(error)}`,
|
|
1369
1488
|
{
|
|
1370
|
-
|
|
1489
|
+
grant_id: effectiveGrantId,
|
|
1371
1490
|
method: methodStr,
|
|
1372
1491
|
url: currentUrl
|
|
1373
1492
|
}
|
|
@@ -1376,7 +1495,7 @@ ${contentHash}`;
|
|
|
1376
1495
|
throw new NetworkError(
|
|
1377
1496
|
`Failed to call provider API: ${error instanceof Error ? error.message : String(error)}`,
|
|
1378
1497
|
{
|
|
1379
|
-
|
|
1498
|
+
grant_id: effectiveGrantId,
|
|
1380
1499
|
method: methodStr,
|
|
1381
1500
|
url: currentUrl,
|
|
1382
1501
|
error: String(error)
|
|
@@ -1405,8 +1524,8 @@ ${contentHash}`;
|
|
|
1405
1524
|
responseHeaders[key] = value;
|
|
1406
1525
|
});
|
|
1407
1526
|
this.#scheduleAuditLog({
|
|
1408
|
-
|
|
1409
|
-
providerId: tokenResponse.providerId ||
|
|
1527
|
+
grantId: tokenResponse.grantId,
|
|
1528
|
+
providerId: tokenResponse.providerId || effectiveGrantId || "",
|
|
1410
1529
|
method: methodStr,
|
|
1411
1530
|
url: auditUrl,
|
|
1412
1531
|
requestHeaders: auditHeaders,
|
|
@@ -1418,18 +1537,20 @@ ${contentHash}`;
|
|
|
1418
1537
|
reason: options?.reason ?? null,
|
|
1419
1538
|
runId,
|
|
1420
1539
|
threadId: options?.threadId ?? null,
|
|
1421
|
-
toolCallId: options?.toolCallId ?? null
|
|
1540
|
+
toolCallId: options?.toolCallId ?? null,
|
|
1541
|
+
toolId: options?.toolId ?? null,
|
|
1542
|
+
toolBundleId: options?.toolBundleId ?? null
|
|
1422
1543
|
});
|
|
1423
1544
|
if (response.status >= HTTP_CLIENT_ERROR_START) {
|
|
1424
1545
|
if (response.status === HTTP_FORBIDDEN && scopeMismatch) {
|
|
1425
1546
|
throw new ScopeReauthRequiredError(
|
|
1426
|
-
"Provider API returned 403 and the
|
|
1427
|
-
tokenResponse.
|
|
1428
|
-
tokenResponse.providerId,
|
|
1547
|
+
"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.",
|
|
1548
|
+
tokenResponse.grantId,
|
|
1549
|
+
tokenResponse.providerId ?? void 0,
|
|
1429
1550
|
response.status,
|
|
1430
1551
|
responseBody,
|
|
1431
1552
|
{
|
|
1432
|
-
|
|
1553
|
+
grant_id: tokenResponse.grantId,
|
|
1433
1554
|
provider_id: tokenResponse.providerId,
|
|
1434
1555
|
method: methodStr,
|
|
1435
1556
|
url: currentUrl
|
|
@@ -1441,7 +1562,7 @@ ${contentHash}`;
|
|
|
1441
1562
|
response.status,
|
|
1442
1563
|
responseBody,
|
|
1443
1564
|
{
|
|
1444
|
-
|
|
1565
|
+
grant_id: effectiveGrantId,
|
|
1445
1566
|
method: methodStr,
|
|
1446
1567
|
url: currentUrl
|
|
1447
1568
|
}
|
|
@@ -1450,12 +1571,12 @@ ${contentHash}`;
|
|
|
1450
1571
|
return response;
|
|
1451
1572
|
}
|
|
1452
1573
|
/**
|
|
1453
|
-
* List OAuth
|
|
1574
|
+
* List OAuth grants for this app.
|
|
1454
1575
|
*
|
|
1455
|
-
* Returns paginated
|
|
1576
|
+
* Returns paginated grant metadata (no tokens).
|
|
1456
1577
|
* Useful for discovering which services a user has connected.
|
|
1457
1578
|
*/
|
|
1458
|
-
async
|
|
1579
|
+
async listGrants(options) {
|
|
1459
1580
|
if (this.#closed) {
|
|
1460
1581
|
throw new AlterSDKError(
|
|
1461
1582
|
"SDK instance has been closed. Create a new AlterVault instance to make requests."
|
|
@@ -1473,12 +1594,12 @@ ${contentHash}`;
|
|
|
1473
1594
|
listBody.user_token = await this.#getUserToken();
|
|
1474
1595
|
} catch (err) {
|
|
1475
1596
|
console.warn(
|
|
1476
|
-
"user_token_getter failed in
|
|
1597
|
+
"user_token_getter failed in listGrants, falling back to un-scoped listing:",
|
|
1477
1598
|
err instanceof Error ? err.message : String(err)
|
|
1478
1599
|
);
|
|
1479
1600
|
}
|
|
1480
1601
|
}
|
|
1481
|
-
const listPath = "/sdk/oauth/
|
|
1602
|
+
const listPath = "/sdk/oauth/grants/list";
|
|
1482
1603
|
const listBodyStr = JSON.stringify(listBody);
|
|
1483
1604
|
const listHmac = this.#computeHmacHeaders("POST", listPath, listBodyStr);
|
|
1484
1605
|
try {
|
|
@@ -1500,19 +1621,19 @@ ${contentHash}`;
|
|
|
1500
1621
|
);
|
|
1501
1622
|
}
|
|
1502
1623
|
throw new AlterSDKError(
|
|
1503
|
-
`Failed to list
|
|
1624
|
+
`Failed to list grants: ${error instanceof Error ? error.message : String(error)}`
|
|
1504
1625
|
);
|
|
1505
1626
|
}
|
|
1506
1627
|
this.#cacheActorIdFromResponse(response);
|
|
1507
1628
|
await this.#handleErrorResponse(response);
|
|
1508
1629
|
const data = await response.json();
|
|
1509
|
-
const
|
|
1510
|
-
(
|
|
1511
|
-
|
|
1630
|
+
const grants = data.grants.map(
|
|
1631
|
+
(g) => new GrantInfo(
|
|
1632
|
+
g
|
|
1512
1633
|
)
|
|
1513
1634
|
);
|
|
1514
|
-
return new
|
|
1515
|
-
|
|
1635
|
+
return new GrantListResult({
|
|
1636
|
+
grants,
|
|
1516
1637
|
total: data.total,
|
|
1517
1638
|
limit: data.limit,
|
|
1518
1639
|
offset: data.offset,
|
|
@@ -1538,10 +1659,10 @@ ${contentHash}`;
|
|
|
1538
1659
|
allowed_origin: options.allowedOrigin ?? null,
|
|
1539
1660
|
metadata: options.metadata ?? null
|
|
1540
1661
|
};
|
|
1541
|
-
if (options.
|
|
1542
|
-
sessionBody.
|
|
1543
|
-
max_ttl_seconds: options.
|
|
1544
|
-
default_ttl_seconds: options.
|
|
1662
|
+
if (options.grantPolicy) {
|
|
1663
|
+
sessionBody.grant_policy = {
|
|
1664
|
+
max_ttl_seconds: options.grantPolicy.maxTtlSeconds ?? null,
|
|
1665
|
+
default_ttl_seconds: options.grantPolicy.defaultTtlSeconds ?? null
|
|
1545
1666
|
};
|
|
1546
1667
|
}
|
|
1547
1668
|
if (this.#userTokenGetter) {
|
|
@@ -1608,7 +1729,7 @@ ${contentHash}`;
|
|
|
1608
1729
|
const openBrowser = options.openBrowser ?? true;
|
|
1609
1730
|
const session = await this.createConnectSession({
|
|
1610
1731
|
allowedProviders: options.providers,
|
|
1611
|
-
|
|
1732
|
+
grantPolicy: options.grantPolicy
|
|
1612
1733
|
});
|
|
1613
1734
|
if (openBrowser) {
|
|
1614
1735
|
try {
|
|
@@ -1639,14 +1760,14 @@ ${contentHash}`;
|
|
|
1639
1760
|
const pollResult = await this.#pollSession(session.sessionToken);
|
|
1640
1761
|
const pollStatus = pollResult.status;
|
|
1641
1762
|
if (pollStatus === "completed") {
|
|
1642
|
-
const
|
|
1643
|
-
return
|
|
1644
|
-
(
|
|
1645
|
-
|
|
1646
|
-
provider_id:
|
|
1647
|
-
account_identifier:
|
|
1648
|
-
scopes:
|
|
1649
|
-
|
|
1763
|
+
const grantsData = pollResult.grants ?? [];
|
|
1764
|
+
return grantsData.map(
|
|
1765
|
+
(grant) => new ConnectResult({
|
|
1766
|
+
grant_id: grant.grant_id ?? "",
|
|
1767
|
+
provider_id: grant.provider_id ?? "",
|
|
1768
|
+
account_identifier: grant.account_identifier ?? null,
|
|
1769
|
+
scopes: grant.scopes ?? [],
|
|
1770
|
+
grant_policy: grant.grant_policy ?? null
|
|
1650
1771
|
})
|
|
1651
1772
|
);
|
|
1652
1773
|
}
|
|
@@ -1673,12 +1794,133 @@ ${contentHash}`;
|
|
|
1673
1794
|
"Connect session expired before OAuth was completed"
|
|
1674
1795
|
);
|
|
1675
1796
|
}
|
|
1797
|
+
if (pollStatus !== "pending") {
|
|
1798
|
+
throw new ConnectFlowError(
|
|
1799
|
+
`Unexpected poll status from server: ${pollStatus}`,
|
|
1800
|
+
{ status: pollStatus }
|
|
1801
|
+
);
|
|
1802
|
+
}
|
|
1676
1803
|
}
|
|
1677
1804
|
throw new ConnectTimeoutError(
|
|
1678
1805
|
`OAuth flow did not complete within ${timeout} seconds. The user may not have finished authorizing in the browser.`,
|
|
1679
1806
|
{ timeout }
|
|
1680
1807
|
);
|
|
1681
1808
|
}
|
|
1809
|
+
/**
|
|
1810
|
+
* Trigger IDP login for end user via browser.
|
|
1811
|
+
*
|
|
1812
|
+
* Opens the app's configured IDP login page in the user's default browser.
|
|
1813
|
+
* Polls for completion and returns the user's IDP JWT token.
|
|
1814
|
+
*
|
|
1815
|
+
* After authenticate() completes, the returned userToken is automatically
|
|
1816
|
+
* used for subsequent request() calls that use identity resolution.
|
|
1817
|
+
*
|
|
1818
|
+
* @param options - Optional configuration (timeout in milliseconds, default 300000 = 5 min)
|
|
1819
|
+
* @returns AuthResult with userToken and userInfo
|
|
1820
|
+
* @throws ConnectTimeoutError if authentication times out
|
|
1821
|
+
* @throws AlterSDKError if session creation or authentication fails
|
|
1822
|
+
*/
|
|
1823
|
+
async authenticate(options) {
|
|
1824
|
+
if (this.#closed) {
|
|
1825
|
+
throw new AlterSDKError(
|
|
1826
|
+
"SDK instance has been closed. Create a new AlterVault instance to make requests."
|
|
1827
|
+
);
|
|
1828
|
+
}
|
|
1829
|
+
const timeoutMs = options?.timeout ?? 3e5;
|
|
1830
|
+
const actorHeaders = this.#getActorRequestHeaders();
|
|
1831
|
+
const sessionPath = "/sdk/auth/session";
|
|
1832
|
+
const sessionBodyStr = JSON.stringify({});
|
|
1833
|
+
const sessionHmac = this.#computeHmacHeaders("POST", sessionPath, sessionBodyStr);
|
|
1834
|
+
let sessionResp;
|
|
1835
|
+
try {
|
|
1836
|
+
sessionResp = await this.#alterClient.post(sessionPath, {
|
|
1837
|
+
body: sessionBodyStr,
|
|
1838
|
+
headers: { ...actorHeaders, ...sessionHmac, "Content-Type": "application/json" }
|
|
1839
|
+
});
|
|
1840
|
+
} catch (error) {
|
|
1841
|
+
if (_AlterVault.#isTimeoutOrAbortError(error)) {
|
|
1842
|
+
throw new TimeoutError(
|
|
1843
|
+
`Request to Alter Vault backend timed out: ${error instanceof Error ? error.message : String(error)}`,
|
|
1844
|
+
{ base_url: this.baseUrl }
|
|
1845
|
+
);
|
|
1846
|
+
}
|
|
1847
|
+
if (error instanceof TypeError) {
|
|
1848
|
+
throw new NetworkError(
|
|
1849
|
+
`Failed to connect to Alter Vault backend: ${error.message}`,
|
|
1850
|
+
{ base_url: this.baseUrl }
|
|
1851
|
+
);
|
|
1852
|
+
}
|
|
1853
|
+
throw new AlterSDKError(
|
|
1854
|
+
`Failed to create auth session: ${error instanceof Error ? error.message : String(error)}`
|
|
1855
|
+
);
|
|
1856
|
+
}
|
|
1857
|
+
this.#cacheActorIdFromResponse(sessionResp);
|
|
1858
|
+
await this.#handleErrorResponse(sessionResp);
|
|
1859
|
+
const sessionData = await sessionResp.json();
|
|
1860
|
+
try {
|
|
1861
|
+
const openModule = await import("open");
|
|
1862
|
+
const openFn = openModule.default;
|
|
1863
|
+
if (openFn) {
|
|
1864
|
+
await openFn(sessionData.auth_url);
|
|
1865
|
+
} else {
|
|
1866
|
+
console.log(
|
|
1867
|
+
`Open this URL to authenticate: ${sessionData.auth_url}`
|
|
1868
|
+
);
|
|
1869
|
+
}
|
|
1870
|
+
} catch {
|
|
1871
|
+
console.log(
|
|
1872
|
+
`Open this URL to authenticate: ${sessionData.auth_url}`
|
|
1873
|
+
);
|
|
1874
|
+
}
|
|
1875
|
+
const startTime = Date.now();
|
|
1876
|
+
while (true) {
|
|
1877
|
+
if (Date.now() - startTime > timeoutMs) {
|
|
1878
|
+
throw new ConnectTimeoutError(
|
|
1879
|
+
"Authentication timed out",
|
|
1880
|
+
{ timeout: timeoutMs }
|
|
1881
|
+
);
|
|
1882
|
+
}
|
|
1883
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
1884
|
+
const pollPath = "/sdk/auth/poll";
|
|
1885
|
+
const pollBodyStr = JSON.stringify({ session_token: sessionData.session_token });
|
|
1886
|
+
const pollHmac = this.#computeHmacHeaders("POST", pollPath, pollBodyStr);
|
|
1887
|
+
let pollResp;
|
|
1888
|
+
try {
|
|
1889
|
+
pollResp = await this.#alterClient.post(pollPath, {
|
|
1890
|
+
body: pollBodyStr,
|
|
1891
|
+
headers: { ...actorHeaders, ...pollHmac, "Content-Type": "application/json" }
|
|
1892
|
+
});
|
|
1893
|
+
} catch {
|
|
1894
|
+
continue;
|
|
1895
|
+
}
|
|
1896
|
+
this.#cacheActorIdFromResponse(pollResp);
|
|
1897
|
+
if (!pollResp.ok) {
|
|
1898
|
+
continue;
|
|
1899
|
+
}
|
|
1900
|
+
const pollData = await pollResp.json();
|
|
1901
|
+
if (pollData.status === "completed") {
|
|
1902
|
+
const userToken = pollData.user_token;
|
|
1903
|
+
if (!userToken) {
|
|
1904
|
+
throw new AlterSDKError(
|
|
1905
|
+
"Authentication completed but no user token was returned by the IDP"
|
|
1906
|
+
);
|
|
1907
|
+
}
|
|
1908
|
+
this.#userTokenGetter = () => userToken;
|
|
1909
|
+
return new AuthResult({
|
|
1910
|
+
user_token: userToken,
|
|
1911
|
+
user_info: pollData.user_info
|
|
1912
|
+
});
|
|
1913
|
+
}
|
|
1914
|
+
if (pollData.status === "error") {
|
|
1915
|
+
throw new AlterSDKError(
|
|
1916
|
+
pollData.error_message ?? "Authentication failed"
|
|
1917
|
+
);
|
|
1918
|
+
}
|
|
1919
|
+
if (pollData.status === "expired") {
|
|
1920
|
+
throw new AlterSDKError("Authentication session expired");
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1682
1924
|
/**
|
|
1683
1925
|
* Poll the Connect session for completion status (INTERNAL).
|
|
1684
1926
|
*
|
|
@@ -1829,6 +2071,7 @@ export {
|
|
|
1829
2071
|
ActorType,
|
|
1830
2072
|
AlterSDKError,
|
|
1831
2073
|
AlterVault,
|
|
2074
|
+
AuthResult,
|
|
1832
2075
|
BackendError,
|
|
1833
2076
|
ConnectConfigError,
|
|
1834
2077
|
ConnectDeniedError,
|
|
@@ -1836,12 +2079,13 @@ export {
|
|
|
1836
2079
|
ConnectResult,
|
|
1837
2080
|
ConnectSession,
|
|
1838
2081
|
ConnectTimeoutError,
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
2082
|
+
CredentialRevokedError,
|
|
2083
|
+
GrantDeletedError,
|
|
2084
|
+
GrantExpiredError,
|
|
2085
|
+
GrantInfo,
|
|
2086
|
+
GrantListResult,
|
|
2087
|
+
GrantNotFoundError,
|
|
2088
|
+
GrantRevokedError,
|
|
1845
2089
|
HttpMethod,
|
|
1846
2090
|
NetworkError,
|
|
1847
2091
|
PolicyViolationError,
|