@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.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,7 +279,7 @@ 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,
|
|
274
283
|
provider_id: this.providerId
|
|
275
284
|
};
|
|
276
285
|
}
|
|
@@ -278,7 +287,7 @@ var TokenResponse = class _TokenResponse {
|
|
|
278
287
|
* Custom string representation — EXCLUDES access_token for security.
|
|
279
288
|
*/
|
|
280
289
|
toString() {
|
|
281
|
-
return `TokenResponse(
|
|
290
|
+
return `TokenResponse(grant_id=${this.grantId}, token_type=${this.tokenType}, scopes=[${this.scopes.join(", ")}])`;
|
|
282
291
|
}
|
|
283
292
|
/**
|
|
284
293
|
* Custom Node.js inspect output — EXCLUDES access_token for security.
|
|
@@ -287,8 +296,8 @@ var TokenResponse = class _TokenResponse {
|
|
|
287
296
|
return this.toString();
|
|
288
297
|
}
|
|
289
298
|
};
|
|
290
|
-
var
|
|
291
|
-
|
|
299
|
+
var GrantInfo = class {
|
|
300
|
+
grantId;
|
|
292
301
|
providerId;
|
|
293
302
|
scopes;
|
|
294
303
|
accountIdentifier;
|
|
@@ -299,7 +308,7 @@ var ConnectionInfo = class {
|
|
|
299
308
|
createdAt;
|
|
300
309
|
lastUsedAt;
|
|
301
310
|
constructor(data) {
|
|
302
|
-
this.
|
|
311
|
+
this.grantId = data.grant_id;
|
|
303
312
|
this.providerId = data.provider_id;
|
|
304
313
|
this.scopes = data.scopes ?? [];
|
|
305
314
|
this.accountIdentifier = data.account_identifier ?? null;
|
|
@@ -313,7 +322,7 @@ var ConnectionInfo = class {
|
|
|
313
322
|
}
|
|
314
323
|
toJSON() {
|
|
315
324
|
return {
|
|
316
|
-
|
|
325
|
+
grant_id: this.grantId,
|
|
317
326
|
provider_id: this.providerId,
|
|
318
327
|
scopes: this.scopes,
|
|
319
328
|
account_identifier: this.accountIdentifier,
|
|
@@ -326,7 +335,7 @@ var ConnectionInfo = class {
|
|
|
326
335
|
};
|
|
327
336
|
}
|
|
328
337
|
toString() {
|
|
329
|
-
return `
|
|
338
|
+
return `GrantInfo(grant_id=${this.grantId}, provider=${this.providerId}, status=${this.status})`;
|
|
330
339
|
}
|
|
331
340
|
};
|
|
332
341
|
var ConnectSession = class {
|
|
@@ -353,60 +362,91 @@ var ConnectSession = class {
|
|
|
353
362
|
return `ConnectSession(url=${this.connectUrl}, expires_in=${this.expiresIn})`;
|
|
354
363
|
}
|
|
355
364
|
};
|
|
356
|
-
var
|
|
357
|
-
|
|
365
|
+
var GrantListResult = class {
|
|
366
|
+
grants;
|
|
358
367
|
total;
|
|
359
368
|
limit;
|
|
360
369
|
offset;
|
|
361
370
|
hasMore;
|
|
362
371
|
constructor(data) {
|
|
363
|
-
this.
|
|
372
|
+
this.grants = data.grants;
|
|
364
373
|
this.total = data.total;
|
|
365
374
|
this.limit = data.limit;
|
|
366
375
|
this.offset = data.offset;
|
|
367
376
|
this.hasMore = data.has_more;
|
|
368
377
|
Object.freeze(this);
|
|
369
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
|
+
}
|
|
370
391
|
};
|
|
371
392
|
var ConnectResult = class {
|
|
372
|
-
|
|
393
|
+
grantId;
|
|
373
394
|
providerId;
|
|
374
395
|
accountIdentifier;
|
|
375
396
|
scopes;
|
|
376
|
-
|
|
397
|
+
grantPolicy;
|
|
377
398
|
constructor(data) {
|
|
378
|
-
this.
|
|
399
|
+
this.grantId = data.grant_id;
|
|
379
400
|
this.providerId = data.provider_id;
|
|
380
401
|
this.accountIdentifier = data.account_identifier ?? null;
|
|
381
402
|
this.scopes = data.scopes ?? [];
|
|
382
|
-
const rawPolicy = data.
|
|
403
|
+
const rawPolicy = data.grant_policy;
|
|
383
404
|
if (rawPolicy) {
|
|
384
405
|
const raw = rawPolicy;
|
|
385
|
-
this.
|
|
406
|
+
this.grantPolicy = Object.freeze({
|
|
386
407
|
expiresAt: raw["expires_at"] !== void 0 ? raw["expires_at"] : rawPolicy.expiresAt ?? null,
|
|
387
408
|
createdBy: raw["created_by"] !== void 0 ? raw["created_by"] : rawPolicy.createdBy ?? null,
|
|
388
409
|
createdAt: raw["created_at"] !== void 0 ? raw["created_at"] : rawPolicy.createdAt ?? null
|
|
389
410
|
});
|
|
390
411
|
} else {
|
|
391
|
-
this.
|
|
412
|
+
this.grantPolicy = null;
|
|
392
413
|
}
|
|
393
414
|
Object.freeze(this);
|
|
394
415
|
}
|
|
395
416
|
toJSON() {
|
|
396
417
|
return {
|
|
397
|
-
|
|
418
|
+
grant_id: this.grantId,
|
|
398
419
|
provider_id: this.providerId,
|
|
399
420
|
account_identifier: this.accountIdentifier,
|
|
400
421
|
scopes: this.scopes,
|
|
401
|
-
|
|
402
|
-
expires_at: this.
|
|
403
|
-
created_by: this.
|
|
404
|
-
created_at: this.
|
|
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
|
|
405
426
|
} : null
|
|
406
427
|
};
|
|
407
428
|
}
|
|
408
429
|
toString() {
|
|
409
|
-
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})`;
|
|
410
450
|
}
|
|
411
451
|
};
|
|
412
452
|
var SENSITIVE_HEADERS = /* @__PURE__ */ new Set([
|
|
@@ -420,7 +460,7 @@ var SENSITIVE_HEADERS = /* @__PURE__ */ new Set([
|
|
|
420
460
|
"x-amz-security-token"
|
|
421
461
|
]);
|
|
422
462
|
var APICallAuditLog = class {
|
|
423
|
-
|
|
463
|
+
grantId;
|
|
424
464
|
providerId;
|
|
425
465
|
method;
|
|
426
466
|
url;
|
|
@@ -439,22 +479,28 @@ var APICallAuditLog = class {
|
|
|
439
479
|
threadId;
|
|
440
480
|
/** Tool invocation ID for actor tracking */
|
|
441
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;
|
|
442
486
|
constructor(data) {
|
|
443
|
-
this.
|
|
444
|
-
this.providerId = data.
|
|
487
|
+
this.grantId = data.grant_id;
|
|
488
|
+
this.providerId = data.provider_id;
|
|
445
489
|
this.method = data.method;
|
|
446
490
|
this.url = data.url;
|
|
447
|
-
this.requestHeaders = data.
|
|
448
|
-
this.requestBody = data.
|
|
449
|
-
this.responseStatus = data.
|
|
450
|
-
this.responseHeaders = data.
|
|
451
|
-
this.responseBody = data.
|
|
452
|
-
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;
|
|
453
497
|
this.timestamp = /* @__PURE__ */ new Date();
|
|
454
498
|
this.reason = data.reason ?? null;
|
|
455
|
-
this.runId = data.
|
|
456
|
-
this.threadId = data.
|
|
457
|
-
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;
|
|
458
504
|
}
|
|
459
505
|
/**
|
|
460
506
|
* Sanitize sensitive data before sending.
|
|
@@ -463,7 +509,7 @@ var APICallAuditLog = class {
|
|
|
463
509
|
*/
|
|
464
510
|
sanitize() {
|
|
465
511
|
const sanitized = {
|
|
466
|
-
|
|
512
|
+
grant_id: this.grantId,
|
|
467
513
|
provider_id: this.providerId,
|
|
468
514
|
method: this.method,
|
|
469
515
|
url: this.url,
|
|
@@ -476,7 +522,9 @@ var APICallAuditLog = class {
|
|
|
476
522
|
reason: this.reason,
|
|
477
523
|
run_id: this.runId,
|
|
478
524
|
thread_id: this.threadId,
|
|
479
|
-
tool_call_id: this.toolCallId
|
|
525
|
+
tool_call_id: this.toolCallId,
|
|
526
|
+
tool_id: this.toolId,
|
|
527
|
+
tool_bundle_id: this.toolBundleId
|
|
480
528
|
};
|
|
481
529
|
return sanitized;
|
|
482
530
|
}
|
|
@@ -675,7 +723,7 @@ function _extractAdditionalCredentials(token) {
|
|
|
675
723
|
return _additionalCredsStore.get(token);
|
|
676
724
|
}
|
|
677
725
|
var _fetch;
|
|
678
|
-
var SDK_VERSION = "0.
|
|
726
|
+
var SDK_VERSION = "0.7.0";
|
|
679
727
|
var SDK_USER_AGENT = `alter-sdk-node/${SDK_VERSION}`;
|
|
680
728
|
var HTTP_FORBIDDEN = 403;
|
|
681
729
|
var HTTP_NOT_FOUND = 404;
|
|
@@ -787,6 +835,7 @@ var AlterVault = class _AlterVault {
|
|
|
787
835
|
#actorVersion;
|
|
788
836
|
#clientType;
|
|
789
837
|
#framework;
|
|
838
|
+
#toolBundleId;
|
|
790
839
|
constructor(options) {
|
|
791
840
|
_AlterVault.#validateInitParams(
|
|
792
841
|
options.apiKey,
|
|
@@ -798,13 +847,14 @@ var AlterVault = class _AlterVault {
|
|
|
798
847
|
["actorName", options.actorName],
|
|
799
848
|
["actorVersion", options.actorVersion],
|
|
800
849
|
["clientType", options.clientType],
|
|
801
|
-
["framework", options.framework]
|
|
850
|
+
["framework", options.framework],
|
|
851
|
+
["toolBundleId", options.toolBundleId]
|
|
802
852
|
];
|
|
803
853
|
for (const [name, value] of actorStrings) {
|
|
804
854
|
_AlterVault.#validateActorString(value, name);
|
|
805
855
|
}
|
|
806
856
|
this.#hmacKey = (0, import_node_crypto2.createHmac)("sha256", options.apiKey).update("alter-signing-v1").digest();
|
|
807
|
-
this.baseUrl = (process.env.ALTER_BASE_URL ?? "https://backend.
|
|
857
|
+
this.baseUrl = (process.env.ALTER_BASE_URL ?? "https://backend.alterauth.com").replace(/\/+$/, "");
|
|
808
858
|
const timeoutMs = options.timeout ?? 3e4;
|
|
809
859
|
this.#actorType = options.actorType;
|
|
810
860
|
this.#actorIdentifier = options.actorIdentifier;
|
|
@@ -813,6 +863,7 @@ var AlterVault = class _AlterVault {
|
|
|
813
863
|
this.#clientType = options.clientType;
|
|
814
864
|
this.#userTokenGetter = options.userTokenGetter ?? null;
|
|
815
865
|
this.#framework = options.framework;
|
|
866
|
+
this.#toolBundleId = options.toolBundleId;
|
|
816
867
|
if (!_fetch) {
|
|
817
868
|
_fetch = globalThis.fetch;
|
|
818
869
|
}
|
|
@@ -860,7 +911,7 @@ var AlterVault = class _AlterVault {
|
|
|
860
911
|
}
|
|
861
912
|
if (!actorType) {
|
|
862
913
|
throw new AlterSDKError(
|
|
863
|
-
"actor_type is required (use ActorType.AI_AGENT
|
|
914
|
+
"actor_type is required (use ActorType.AI_AGENT or ActorType.BACKEND_SERVICE)"
|
|
864
915
|
);
|
|
865
916
|
}
|
|
866
917
|
const validValues = Object.values(ActorType);
|
|
@@ -889,7 +940,8 @@ var AlterVault = class _AlterVault {
|
|
|
889
940
|
"X-Alter-Actor-Name": this.#actorName,
|
|
890
941
|
"X-Alter-Actor-Version": this.#actorVersion,
|
|
891
942
|
"X-Alter-Client-Type": this.#clientType,
|
|
892
|
-
"X-Alter-Framework": this.#framework
|
|
943
|
+
"X-Alter-Framework": this.#framework,
|
|
944
|
+
"X-Alter-Tool-Bundle-ID": this.#toolBundleId
|
|
893
945
|
};
|
|
894
946
|
for (const [key, value] of Object.entries(actorHeaders)) {
|
|
895
947
|
if (value) {
|
|
@@ -901,21 +953,24 @@ var AlterVault = class _AlterVault {
|
|
|
901
953
|
/**
|
|
902
954
|
* Compute HMAC-SHA256 signature headers for an Alter backend request.
|
|
903
955
|
*
|
|
904
|
-
* 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
|
|
905
957
|
*
|
|
906
958
|
* The path should include sorted query parameters if present (e.g. "/sdk/endpoint?a=1&b=2").
|
|
907
|
-
* Currently all SDK
|
|
959
|
+
* Currently all SDK->backend calls are POSTs without query params, so the path is clean.
|
|
908
960
|
*/
|
|
909
961
|
#computeHmacHeaders(method, path, body) {
|
|
910
962
|
const timestamp = String(Math.floor(Date.now() / 1e3));
|
|
963
|
+
const nonce = (0, import_node_crypto2.randomBytes)(16).toString("hex");
|
|
911
964
|
const contentHash = (0, import_node_crypto2.createHash)("sha256").update(body ?? "").digest("hex");
|
|
912
965
|
const stringToSign = `${method.toUpperCase()}
|
|
913
966
|
${path}
|
|
914
967
|
${timestamp}
|
|
968
|
+
${nonce}
|
|
915
969
|
${contentHash}`;
|
|
916
970
|
const signature = (0, import_node_crypto2.createHmac)("sha256", this.#hmacKey).update(stringToSign).digest("hex");
|
|
917
971
|
return {
|
|
918
972
|
"X-Alter-Timestamp": timestamp,
|
|
973
|
+
"X-Alter-Nonce": nonce,
|
|
919
974
|
"X-Alter-Content-SHA256": contentHash,
|
|
920
975
|
"X-Alter-Signature": signature
|
|
921
976
|
};
|
|
@@ -923,7 +978,7 @@ ${contentHash}`;
|
|
|
923
978
|
/**
|
|
924
979
|
* Build per-request actor headers for instance tracking.
|
|
925
980
|
*/
|
|
926
|
-
#getActorRequestHeaders(runId, threadId, toolCallId) {
|
|
981
|
+
#getActorRequestHeaders(runId, threadId, toolCallId, toolId, toolBundleId) {
|
|
927
982
|
const headers = {};
|
|
928
983
|
if (this.#cachedActorId) {
|
|
929
984
|
headers["X-Alter-Actor-ID"] = this.#cachedActorId;
|
|
@@ -953,6 +1008,24 @@ ${contentHash}`;
|
|
|
953
1008
|
);
|
|
954
1009
|
}
|
|
955
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
|
+
}
|
|
956
1029
|
return headers;
|
|
957
1030
|
}
|
|
958
1031
|
/**
|
|
@@ -1004,9 +1077,20 @@ ${contentHash}`;
|
|
|
1004
1077
|
}
|
|
1005
1078
|
return null;
|
|
1006
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
|
+
*/
|
|
1007
1087
|
static async #safeParseJson(response) {
|
|
1008
1088
|
try {
|
|
1009
|
-
|
|
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;
|
|
1010
1094
|
} catch {
|
|
1011
1095
|
try {
|
|
1012
1096
|
const text = await response.clone().text();
|
|
@@ -1025,38 +1109,69 @@ ${contentHash}`;
|
|
|
1025
1109
|
}
|
|
1026
1110
|
if (response.status === HTTP_FORBIDDEN) {
|
|
1027
1111
|
const errorData = await _AlterVault.#safeParseJson(response);
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1112
|
+
const errorCode = errorData.error;
|
|
1113
|
+
if (errorCode === "grant_expired") {
|
|
1114
|
+
throw new GrantExpiredError(
|
|
1115
|
+
errorData.message ?? "Grant expired per TTL policy",
|
|
1031
1116
|
errorData.details
|
|
1032
1117
|
);
|
|
1033
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
|
+
}
|
|
1034
1142
|
throw new PolicyViolationError(
|
|
1035
1143
|
errorData.message ?? "Access denied by policy",
|
|
1036
|
-
|
|
1144
|
+
errorCode,
|
|
1037
1145
|
errorData.details
|
|
1038
1146
|
);
|
|
1039
1147
|
}
|
|
1040
1148
|
if (response.status === HTTP_GONE) {
|
|
1041
1149
|
const errorData = await _AlterVault.#safeParseJson(response);
|
|
1042
|
-
throw new
|
|
1043
|
-
errorData.message ?? "
|
|
1150
|
+
throw new GrantDeletedError(
|
|
1151
|
+
errorData.message ?? "Grant has been deleted. A new grant_id will be issued on re-authorization.",
|
|
1044
1152
|
errorData
|
|
1045
1153
|
);
|
|
1046
1154
|
}
|
|
1047
1155
|
if (response.status === HTTP_NOT_FOUND) {
|
|
1048
1156
|
const errorData = await _AlterVault.#safeParseJson(response);
|
|
1049
|
-
throw new
|
|
1050
|
-
errorData.message ?? "OAuth
|
|
1157
|
+
throw new GrantNotFoundError(
|
|
1158
|
+
errorData.message ?? "OAuth grant not found for the given grant_id",
|
|
1051
1159
|
errorData
|
|
1052
1160
|
);
|
|
1053
1161
|
}
|
|
1054
1162
|
if (response.status === HTTP_BAD_REQUEST || response.status === HTTP_BAD_GATEWAY) {
|
|
1055
1163
|
const errorData = await _AlterVault.#safeParseJson(response);
|
|
1056
|
-
if (errorData.error === "
|
|
1057
|
-
throw new
|
|
1058
|
-
errorData.message ?? "
|
|
1059
|
-
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,
|
|
1060
1175
|
errorData
|
|
1061
1176
|
);
|
|
1062
1177
|
}
|
|
@@ -1106,11 +1221,13 @@ ${contentHash}`;
|
|
|
1106
1221
|
}
|
|
1107
1222
|
return token;
|
|
1108
1223
|
}
|
|
1109
|
-
async #getToken(
|
|
1224
|
+
async #getToken(grantId, reason, requestMetadata, runId, threadId, toolCallId, toolId, provider, account, toolBundleId) {
|
|
1110
1225
|
const actorHeaders = this.#getActorRequestHeaders(
|
|
1111
1226
|
runId,
|
|
1112
1227
|
threadId,
|
|
1113
|
-
toolCallId
|
|
1228
|
+
toolCallId,
|
|
1229
|
+
toolId,
|
|
1230
|
+
toolBundleId
|
|
1114
1231
|
);
|
|
1115
1232
|
let response;
|
|
1116
1233
|
let tokenBody;
|
|
@@ -1127,7 +1244,7 @@ ${contentHash}`;
|
|
|
1127
1244
|
}
|
|
1128
1245
|
} else {
|
|
1129
1246
|
tokenBody = {
|
|
1130
|
-
|
|
1247
|
+
grant_id: grantId,
|
|
1131
1248
|
reason: reason ?? null,
|
|
1132
1249
|
request: requestMetadata ?? null
|
|
1133
1250
|
};
|
|
@@ -1155,7 +1272,7 @@ ${contentHash}`;
|
|
|
1155
1272
|
}
|
|
1156
1273
|
throw new BackendError(
|
|
1157
1274
|
`Failed to retrieve token: ${error instanceof Error ? error.message : String(error)}`,
|
|
1158
|
-
{
|
|
1275
|
+
{ grant_id: grantId, error: String(error) }
|
|
1159
1276
|
);
|
|
1160
1277
|
}
|
|
1161
1278
|
this.#cacheActorIdFromResponse(response);
|
|
@@ -1167,13 +1284,13 @@ ${contentHash}`;
|
|
|
1167
1284
|
if (!/^[A-Za-z][A-Za-z0-9-]*$/.test(tokenResponse.injectionHeader)) {
|
|
1168
1285
|
throw new BackendError(
|
|
1169
1286
|
`Backend returned invalid injection_header: ${tokenResponse.injectionHeader}`,
|
|
1170
|
-
{
|
|
1287
|
+
{ grantId: String(grantId) }
|
|
1171
1288
|
);
|
|
1172
1289
|
}
|
|
1173
1290
|
if (/[\r\n\x00]/.test(tokenResponse.injectionFormat)) {
|
|
1174
1291
|
throw new BackendError(
|
|
1175
1292
|
`Backend returned invalid injection_format (contains control characters)`,
|
|
1176
|
-
{
|
|
1293
|
+
{ grantId: String(grantId) }
|
|
1177
1294
|
);
|
|
1178
1295
|
}
|
|
1179
1296
|
_storeAccessToken(tokenResponse, typedData.access_token);
|
|
@@ -1191,20 +1308,22 @@ ${contentHash}`;
|
|
|
1191
1308
|
async #logApiCall(params) {
|
|
1192
1309
|
try {
|
|
1193
1310
|
const auditLog = new APICallAuditLog({
|
|
1194
|
-
|
|
1195
|
-
|
|
1311
|
+
grant_id: params.grantId,
|
|
1312
|
+
provider_id: params.providerId,
|
|
1196
1313
|
method: params.method,
|
|
1197
1314
|
url: params.url,
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
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,
|
|
1204
1321
|
reason: params.reason,
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
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
|
|
1208
1327
|
});
|
|
1209
1328
|
const sanitized = auditLog.sanitize();
|
|
1210
1329
|
const actorHeaders = this.#getActorRequestHeaders(params.runId);
|
|
@@ -1280,7 +1399,7 @@ ${contentHash}`;
|
|
|
1280
1399
|
* 4. Logs the call for audit (fire-and-forget)
|
|
1281
1400
|
* 5. Returns the raw response
|
|
1282
1401
|
*/
|
|
1283
|
-
async request(
|
|
1402
|
+
async request(grantId, method, url, options) {
|
|
1284
1403
|
if (this.#closed) {
|
|
1285
1404
|
throw new AlterSDKError(
|
|
1286
1405
|
"SDK instance has been closed. Create a new AlterVault instance to make requests."
|
|
@@ -1288,13 +1407,13 @@ ${contentHash}`;
|
|
|
1288
1407
|
}
|
|
1289
1408
|
const provider = options?.provider;
|
|
1290
1409
|
const account = options?.account;
|
|
1291
|
-
if (!
|
|
1292
|
-
throw new AlterSDKError("Provide
|
|
1410
|
+
if (!grantId && !provider) {
|
|
1411
|
+
throw new AlterSDKError("Provide grantId or options.provider");
|
|
1293
1412
|
}
|
|
1294
|
-
if (
|
|
1295
|
-
throw new AlterSDKError("Cannot provide both
|
|
1413
|
+
if (grantId && provider) {
|
|
1414
|
+
throw new AlterSDKError("Cannot provide both grantId and options.provider");
|
|
1296
1415
|
}
|
|
1297
|
-
const
|
|
1416
|
+
const effectiveGrantId = grantId ?? null;
|
|
1298
1417
|
let currentUrl = url;
|
|
1299
1418
|
const runId = options?.runId ?? (0, import_node_crypto2.randomUUID)();
|
|
1300
1419
|
const methodStr = String(method).toUpperCase();
|
|
@@ -1335,14 +1454,16 @@ ${contentHash}`;
|
|
|
1335
1454
|
}
|
|
1336
1455
|
}
|
|
1337
1456
|
const { tokenResponse, scopeMismatch } = await this.#getToken(
|
|
1338
|
-
|
|
1457
|
+
effectiveGrantId,
|
|
1339
1458
|
options?.reason,
|
|
1340
1459
|
{ method: methodStr, url: currentUrl },
|
|
1341
1460
|
runId,
|
|
1342
1461
|
options?.threadId,
|
|
1343
1462
|
options?.toolCallId,
|
|
1463
|
+
options?.toolId,
|
|
1344
1464
|
provider,
|
|
1345
|
-
account
|
|
1465
|
+
account,
|
|
1466
|
+
options?.toolBundleId
|
|
1346
1467
|
);
|
|
1347
1468
|
const requestHeaders = options?.extraHeaders ? { ...options.extraHeaders } : {};
|
|
1348
1469
|
const accessToken = _extractAccessToken(tokenResponse);
|
|
@@ -1361,7 +1482,7 @@ ${contentHash}`;
|
|
|
1361
1482
|
if (!additionalCreds.secret_key) {
|
|
1362
1483
|
throw new BackendError(
|
|
1363
1484
|
"AWS SigV4 credential is missing secret_key in additional_credentials. Re-store the credential with both Access Key ID and Secret Access Key.",
|
|
1364
|
-
{
|
|
1485
|
+
{ grant_id: effectiveGrantId }
|
|
1365
1486
|
);
|
|
1366
1487
|
}
|
|
1367
1488
|
const awsHeaders = signAwsRequest({
|
|
@@ -1428,7 +1549,7 @@ ${contentHash}`;
|
|
|
1428
1549
|
throw new TimeoutError(
|
|
1429
1550
|
`Provider API request timed out: ${error instanceof Error ? error.message : String(error)}`,
|
|
1430
1551
|
{
|
|
1431
|
-
|
|
1552
|
+
grant_id: effectiveGrantId,
|
|
1432
1553
|
method: methodStr,
|
|
1433
1554
|
url: currentUrl
|
|
1434
1555
|
}
|
|
@@ -1437,7 +1558,7 @@ ${contentHash}`;
|
|
|
1437
1558
|
throw new NetworkError(
|
|
1438
1559
|
`Failed to call provider API: ${error instanceof Error ? error.message : String(error)}`,
|
|
1439
1560
|
{
|
|
1440
|
-
|
|
1561
|
+
grant_id: effectiveGrantId,
|
|
1441
1562
|
method: methodStr,
|
|
1442
1563
|
url: currentUrl,
|
|
1443
1564
|
error: String(error)
|
|
@@ -1466,8 +1587,8 @@ ${contentHash}`;
|
|
|
1466
1587
|
responseHeaders[key] = value;
|
|
1467
1588
|
});
|
|
1468
1589
|
this.#scheduleAuditLog({
|
|
1469
|
-
|
|
1470
|
-
providerId: tokenResponse.providerId ||
|
|
1590
|
+
grantId: tokenResponse.grantId,
|
|
1591
|
+
providerId: tokenResponse.providerId || effectiveGrantId || "",
|
|
1471
1592
|
method: methodStr,
|
|
1472
1593
|
url: auditUrl,
|
|
1473
1594
|
requestHeaders: auditHeaders,
|
|
@@ -1479,18 +1600,20 @@ ${contentHash}`;
|
|
|
1479
1600
|
reason: options?.reason ?? null,
|
|
1480
1601
|
runId,
|
|
1481
1602
|
threadId: options?.threadId ?? null,
|
|
1482
|
-
toolCallId: options?.toolCallId ?? null
|
|
1603
|
+
toolCallId: options?.toolCallId ?? null,
|
|
1604
|
+
toolId: options?.toolId ?? null,
|
|
1605
|
+
toolBundleId: options?.toolBundleId ?? null
|
|
1483
1606
|
});
|
|
1484
1607
|
if (response.status >= HTTP_CLIENT_ERROR_START) {
|
|
1485
1608
|
if (response.status === HTTP_FORBIDDEN && scopeMismatch) {
|
|
1486
1609
|
throw new ScopeReauthRequiredError(
|
|
1487
|
-
"Provider API returned 403 and the
|
|
1488
|
-
tokenResponse.
|
|
1489
|
-
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,
|
|
1490
1613
|
response.status,
|
|
1491
1614
|
responseBody,
|
|
1492
1615
|
{
|
|
1493
|
-
|
|
1616
|
+
grant_id: tokenResponse.grantId,
|
|
1494
1617
|
provider_id: tokenResponse.providerId,
|
|
1495
1618
|
method: methodStr,
|
|
1496
1619
|
url: currentUrl
|
|
@@ -1502,7 +1625,7 @@ ${contentHash}`;
|
|
|
1502
1625
|
response.status,
|
|
1503
1626
|
responseBody,
|
|
1504
1627
|
{
|
|
1505
|
-
|
|
1628
|
+
grant_id: effectiveGrantId,
|
|
1506
1629
|
method: methodStr,
|
|
1507
1630
|
url: currentUrl
|
|
1508
1631
|
}
|
|
@@ -1511,12 +1634,12 @@ ${contentHash}`;
|
|
|
1511
1634
|
return response;
|
|
1512
1635
|
}
|
|
1513
1636
|
/**
|
|
1514
|
-
* List OAuth
|
|
1637
|
+
* List OAuth grants for this app.
|
|
1515
1638
|
*
|
|
1516
|
-
* Returns paginated
|
|
1639
|
+
* Returns paginated grant metadata (no tokens).
|
|
1517
1640
|
* Useful for discovering which services a user has connected.
|
|
1518
1641
|
*/
|
|
1519
|
-
async
|
|
1642
|
+
async listGrants(options) {
|
|
1520
1643
|
if (this.#closed) {
|
|
1521
1644
|
throw new AlterSDKError(
|
|
1522
1645
|
"SDK instance has been closed. Create a new AlterVault instance to make requests."
|
|
@@ -1534,12 +1657,12 @@ ${contentHash}`;
|
|
|
1534
1657
|
listBody.user_token = await this.#getUserToken();
|
|
1535
1658
|
} catch (err) {
|
|
1536
1659
|
console.warn(
|
|
1537
|
-
"user_token_getter failed in
|
|
1660
|
+
"user_token_getter failed in listGrants, falling back to un-scoped listing:",
|
|
1538
1661
|
err instanceof Error ? err.message : String(err)
|
|
1539
1662
|
);
|
|
1540
1663
|
}
|
|
1541
1664
|
}
|
|
1542
|
-
const listPath = "/sdk/oauth/
|
|
1665
|
+
const listPath = "/sdk/oauth/grants/list";
|
|
1543
1666
|
const listBodyStr = JSON.stringify(listBody);
|
|
1544
1667
|
const listHmac = this.#computeHmacHeaders("POST", listPath, listBodyStr);
|
|
1545
1668
|
try {
|
|
@@ -1561,19 +1684,19 @@ ${contentHash}`;
|
|
|
1561
1684
|
);
|
|
1562
1685
|
}
|
|
1563
1686
|
throw new AlterSDKError(
|
|
1564
|
-
`Failed to list
|
|
1687
|
+
`Failed to list grants: ${error instanceof Error ? error.message : String(error)}`
|
|
1565
1688
|
);
|
|
1566
1689
|
}
|
|
1567
1690
|
this.#cacheActorIdFromResponse(response);
|
|
1568
1691
|
await this.#handleErrorResponse(response);
|
|
1569
1692
|
const data = await response.json();
|
|
1570
|
-
const
|
|
1571
|
-
(
|
|
1572
|
-
|
|
1693
|
+
const grants = data.grants.map(
|
|
1694
|
+
(g) => new GrantInfo(
|
|
1695
|
+
g
|
|
1573
1696
|
)
|
|
1574
1697
|
);
|
|
1575
|
-
return new
|
|
1576
|
-
|
|
1698
|
+
return new GrantListResult({
|
|
1699
|
+
grants,
|
|
1577
1700
|
total: data.total,
|
|
1578
1701
|
limit: data.limit,
|
|
1579
1702
|
offset: data.offset,
|
|
@@ -1599,10 +1722,10 @@ ${contentHash}`;
|
|
|
1599
1722
|
allowed_origin: options.allowedOrigin ?? null,
|
|
1600
1723
|
metadata: options.metadata ?? null
|
|
1601
1724
|
};
|
|
1602
|
-
if (options.
|
|
1603
|
-
sessionBody.
|
|
1604
|
-
max_ttl_seconds: options.
|
|
1605
|
-
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
|
|
1606
1729
|
};
|
|
1607
1730
|
}
|
|
1608
1731
|
if (this.#userTokenGetter) {
|
|
@@ -1669,7 +1792,7 @@ ${contentHash}`;
|
|
|
1669
1792
|
const openBrowser = options.openBrowser ?? true;
|
|
1670
1793
|
const session = await this.createConnectSession({
|
|
1671
1794
|
allowedProviders: options.providers,
|
|
1672
|
-
|
|
1795
|
+
grantPolicy: options.grantPolicy
|
|
1673
1796
|
});
|
|
1674
1797
|
if (openBrowser) {
|
|
1675
1798
|
try {
|
|
@@ -1700,14 +1823,14 @@ ${contentHash}`;
|
|
|
1700
1823
|
const pollResult = await this.#pollSession(session.sessionToken);
|
|
1701
1824
|
const pollStatus = pollResult.status;
|
|
1702
1825
|
if (pollStatus === "completed") {
|
|
1703
|
-
const
|
|
1704
|
-
return
|
|
1705
|
-
(
|
|
1706
|
-
|
|
1707
|
-
provider_id:
|
|
1708
|
-
account_identifier:
|
|
1709
|
-
scopes:
|
|
1710
|
-
|
|
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
|
|
1711
1834
|
})
|
|
1712
1835
|
);
|
|
1713
1836
|
}
|
|
@@ -1734,12 +1857,133 @@ ${contentHash}`;
|
|
|
1734
1857
|
"Connect session expired before OAuth was completed"
|
|
1735
1858
|
);
|
|
1736
1859
|
}
|
|
1860
|
+
if (pollStatus !== "pending") {
|
|
1861
|
+
throw new ConnectFlowError(
|
|
1862
|
+
`Unexpected poll status from server: ${pollStatus}`,
|
|
1863
|
+
{ status: pollStatus }
|
|
1864
|
+
);
|
|
1865
|
+
}
|
|
1737
1866
|
}
|
|
1738
1867
|
throw new ConnectTimeoutError(
|
|
1739
1868
|
`OAuth flow did not complete within ${timeout} seconds. The user may not have finished authorizing in the browser.`,
|
|
1740
1869
|
{ timeout }
|
|
1741
1870
|
);
|
|
1742
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
|
+
}
|
|
1743
1987
|
/**
|
|
1744
1988
|
* Poll the Connect session for completion status (INTERNAL).
|
|
1745
1989
|
*
|
|
@@ -1891,6 +2135,7 @@ var HttpMethod = /* @__PURE__ */ ((HttpMethod2) => {
|
|
|
1891
2135
|
ActorType,
|
|
1892
2136
|
AlterSDKError,
|
|
1893
2137
|
AlterVault,
|
|
2138
|
+
AuthResult,
|
|
1894
2139
|
BackendError,
|
|
1895
2140
|
ConnectConfigError,
|
|
1896
2141
|
ConnectDeniedError,
|
|
@@ -1898,12 +2143,13 @@ var HttpMethod = /* @__PURE__ */ ((HttpMethod2) => {
|
|
|
1898
2143
|
ConnectResult,
|
|
1899
2144
|
ConnectSession,
|
|
1900
2145
|
ConnectTimeoutError,
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
2146
|
+
CredentialRevokedError,
|
|
2147
|
+
GrantDeletedError,
|
|
2148
|
+
GrantExpiredError,
|
|
2149
|
+
GrantInfo,
|
|
2150
|
+
GrantListResult,
|
|
2151
|
+
GrantNotFoundError,
|
|
2152
|
+
GrantRevokedError,
|
|
1907
2153
|
HttpMethod,
|
|
1908
2154
|
NetworkError,
|
|
1909
2155
|
PolicyViolationError,
|