@atlasent/sdk 2.10.0 → 2.13.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 +9 -0
- package/dist/behavior.cjs +2 -1
- package/dist/behavior.cjs.map +1 -1
- package/dist/behavior.d.cts +32 -5
- package/dist/behavior.d.ts +32 -5
- package/dist/behavior.js +2 -1
- package/dist/behavior.js.map +1 -1
- package/dist/hono.cjs +800 -23
- package/dist/hono.cjs.map +1 -1
- package/dist/hono.d.cts +2 -2
- package/dist/hono.d.ts +2 -2
- package/dist/hono.js +799 -23
- package/dist/hono.js.map +1 -1
- package/dist/index.cjs +4765 -2051
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2375 -565
- package/dist/index.d.ts +2375 -565
- package/dist/index.js +4686 -2039
- package/dist/index.js.map +1 -1
- package/dist/{protect-C0t0fP1y.d.cts → protect-Bk9q12ia.d.cts} +588 -281
- package/dist/{protect-C0t0fP1y.d.ts → protect-Bk9q12ia.d.ts} +588 -281
- package/package.json +1 -1
package/dist/hono.js
CHANGED
|
@@ -47,7 +47,8 @@ var KNOWN_PERMIT_OUTCOMES = /* @__PURE__ */ new Set([
|
|
|
47
47
|
"permit_consumed",
|
|
48
48
|
"permit_expired",
|
|
49
49
|
"permit_revoked",
|
|
50
|
-
"permit_not_found"
|
|
50
|
+
"permit_not_found",
|
|
51
|
+
"permit_signing_key_revoked"
|
|
51
52
|
]);
|
|
52
53
|
function normalizePermitOutcome(raw) {
|
|
53
54
|
if (raw !== void 0 && KNOWN_PERMIT_OUTCOMES.has(raw)) {
|
|
@@ -108,7 +109,197 @@ var AtlaSentDeniedError = class extends AtlaSentError {
|
|
|
108
109
|
get isNotFound() {
|
|
109
110
|
return this.outcome === "permit_not_found";
|
|
110
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* `true` when the permit's signing key KID appears in the
|
|
114
|
+
* trust-root revocation list (ADR-005 D3 R2/R3 key rotation).
|
|
115
|
+
*/
|
|
116
|
+
get isSigningKeyRevoked() {
|
|
117
|
+
return this.outcome === "permit_signing_key_revoked";
|
|
118
|
+
}
|
|
111
119
|
};
|
|
120
|
+
var BundleVerificationError = class extends AtlaSentError {
|
|
121
|
+
name = "BundleVerificationError";
|
|
122
|
+
reason;
|
|
123
|
+
snapshotValidUntil;
|
|
124
|
+
snapshotFetchedAt;
|
|
125
|
+
snapshotSource;
|
|
126
|
+
kid;
|
|
127
|
+
constructor(init) {
|
|
128
|
+
super(`AtlaSent audit bundle verification failed: ${init.reason}`);
|
|
129
|
+
this.reason = init.reason;
|
|
130
|
+
this.snapshotValidUntil = init.snapshotValidUntil;
|
|
131
|
+
this.snapshotFetchedAt = init.snapshotFetchedAt;
|
|
132
|
+
this.snapshotSource = init.snapshotSource;
|
|
133
|
+
this.kid = init.kid;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// src/trustRoot.ts
|
|
138
|
+
import { readFileSync } from "fs";
|
|
139
|
+
import { fileURLToPath } from "url";
|
|
140
|
+
import { resolve, dirname } from "path";
|
|
141
|
+
var REFRESH_INTERVAL_MS_DEFAULT = 4 * 60 * 60 * 1e3;
|
|
142
|
+
var REFRESH_INTERVAL_MS_FLOOR = 5 * 60 * 1e3;
|
|
143
|
+
var KEYS_BASE_URL = "https://keys.atlasent.io/.well-known";
|
|
144
|
+
var _halfLifeWarningEmitted = false;
|
|
145
|
+
var _expiredWarningEmitted = false;
|
|
146
|
+
var TrustRootManager = class {
|
|
147
|
+
_snapshot;
|
|
148
|
+
_refreshTimer = null;
|
|
149
|
+
_opts;
|
|
150
|
+
constructor(initialSnapshot, opts = {}) {
|
|
151
|
+
this._snapshot = initialSnapshot;
|
|
152
|
+
const intervalMs = Math.max(
|
|
153
|
+
opts.refreshIntervalMs ?? REFRESH_INTERVAL_MS_DEFAULT,
|
|
154
|
+
REFRESH_INTERVAL_MS_FLOOR
|
|
155
|
+
);
|
|
156
|
+
this._opts = {
|
|
157
|
+
refreshBaseUrl: opts.refreshBaseUrl ?? KEYS_BASE_URL,
|
|
158
|
+
refreshIntervalMs: intervalMs,
|
|
159
|
+
disableRefresh: opts.disableRefresh ?? false,
|
|
160
|
+
fetch: opts.fetch ?? (typeof globalThis !== "undefined" && globalThis.fetch ? globalThis.fetch.bind(globalThis) : ((_url) => Promise.reject(new Error("fetch not available"))))
|
|
161
|
+
};
|
|
162
|
+
if (!this._opts.disableRefresh) {
|
|
163
|
+
this._scheduleRefresh();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
getSnapshot() {
|
|
167
|
+
return this._snapshot;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Check whether the snapshot is expired, emit one-time warnings at
|
|
171
|
+
* half-life and expiry. Returns "ok" | "half_life" | "expired".
|
|
172
|
+
*
|
|
173
|
+
* Emits console.warn once per process at half-life (ADR-005 D3).
|
|
174
|
+
* Emits console.warn once per process on expiry.
|
|
175
|
+
*/
|
|
176
|
+
checkExpiry() {
|
|
177
|
+
const snap = this._snapshot;
|
|
178
|
+
const now = Date.now();
|
|
179
|
+
const issuedAt = new Date(snap.issued_at).getTime();
|
|
180
|
+
const validUntil = new Date(snap.valid_until).getTime();
|
|
181
|
+
if (now > validUntil) {
|
|
182
|
+
if (!_expiredWarningEmitted) {
|
|
183
|
+
_expiredWarningEmitted = true;
|
|
184
|
+
const daysAgo = Math.floor((now - validUntil) / (24 * 60 * 60 * 1e3));
|
|
185
|
+
console.warn(
|
|
186
|
+
`[atlasent] Trust snapshot expired ${daysAgo} day(s) ago (valid_until: ${snap.valid_until}). Update to a newer SDK build or enable allowExpiredSnapshot.`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
return "expired";
|
|
190
|
+
}
|
|
191
|
+
const window = validUntil - issuedAt;
|
|
192
|
+
const halfLife = issuedAt + window / 2;
|
|
193
|
+
if (now > halfLife) {
|
|
194
|
+
if (!_halfLifeWarningEmitted) {
|
|
195
|
+
_halfLifeWarningEmitted = true;
|
|
196
|
+
const daysLeft = Math.floor((validUntil - now) / (24 * 60 * 60 * 1e3));
|
|
197
|
+
console.warn(
|
|
198
|
+
`[atlasent] Trust snapshot at half-life: expires in ${daysLeft} day(s) (valid_until: ${snap.valid_until}). Plan an SDK update.`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
return "half_life";
|
|
202
|
+
}
|
|
203
|
+
return "ok";
|
|
204
|
+
}
|
|
205
|
+
/** Look up a key entry by kid. Returns undefined if not found. */
|
|
206
|
+
lookupKey(kid) {
|
|
207
|
+
return this._snapshot.keys.find((k) => k.kid === kid);
|
|
208
|
+
}
|
|
209
|
+
/** Returns true if the kid appears in revoked_keys. */
|
|
210
|
+
isRevoked(kid) {
|
|
211
|
+
return this._snapshot.revoked_keys.some((r) => r.kid === kid);
|
|
212
|
+
}
|
|
213
|
+
/** Replace the snapshot (e.g. after a successful refresh). */
|
|
214
|
+
replaceSnapshot(next) {
|
|
215
|
+
this._snapshot = next;
|
|
216
|
+
}
|
|
217
|
+
stopRefresh() {
|
|
218
|
+
if (this._refreshTimer !== null) {
|
|
219
|
+
clearInterval(this._refreshTimer);
|
|
220
|
+
this._refreshTimer = null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
_scheduleRefresh() {
|
|
224
|
+
this._refreshTimer = setInterval(() => {
|
|
225
|
+
void this._doRefresh();
|
|
226
|
+
}, this._opts.refreshIntervalMs);
|
|
227
|
+
if (this._refreshTimer && typeof this._refreshTimer === "object" && "unref" in this._refreshTimer) {
|
|
228
|
+
this._refreshTimer.unref();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
async _doRefresh() {
|
|
232
|
+
try {
|
|
233
|
+
const base = this._opts.refreshBaseUrl.replace(/\/$/, "");
|
|
234
|
+
const [keysRes, revocRes] = await Promise.all([
|
|
235
|
+
this._opts.fetch(`${base}/atlasent-verifier-keys.json`),
|
|
236
|
+
this._opts.fetch(`${base}/atlasent-revocations.json`)
|
|
237
|
+
]);
|
|
238
|
+
const indexRes = await this._opts.fetch(`${base}/atlasent-trust-root.json`);
|
|
239
|
+
if (!keysRes.ok || !revocRes.ok || !indexRes.ok) return;
|
|
240
|
+
const [keys, revoc, index] = await Promise.all([
|
|
241
|
+
keysRes.json(),
|
|
242
|
+
revocRes.json(),
|
|
243
|
+
indexRes.json()
|
|
244
|
+
]);
|
|
245
|
+
if (!index.valid_until || !Array.isArray(keys.keys)) return;
|
|
246
|
+
this._snapshot = {
|
|
247
|
+
valid_until: index.valid_until,
|
|
248
|
+
issued_at: index.issued_at ?? this._snapshot.issued_at,
|
|
249
|
+
keys: keys.keys,
|
|
250
|
+
revoked_keys: revoc.revoked_keys ?? [],
|
|
251
|
+
revoked_identities: revoc.revoked_identities ?? []
|
|
252
|
+
};
|
|
253
|
+
} catch {
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
function _loadVendorSnapshot() {
|
|
258
|
+
try {
|
|
259
|
+
let packageRoot;
|
|
260
|
+
try {
|
|
261
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
262
|
+
packageRoot = resolve(dirname(thisFile), "..", "..");
|
|
263
|
+
} catch {
|
|
264
|
+
packageRoot = resolve(__dirname, "..", "..");
|
|
265
|
+
}
|
|
266
|
+
const vendorDir = resolve(packageRoot, "vendor", "trust-root");
|
|
267
|
+
const index = JSON.parse(
|
|
268
|
+
readFileSync(resolve(vendorDir, "atlasent-trust-root.json"), "utf8")
|
|
269
|
+
);
|
|
270
|
+
const verifierKeys = JSON.parse(
|
|
271
|
+
readFileSync(resolve(vendorDir, "atlasent-verifier-keys.json"), "utf8")
|
|
272
|
+
);
|
|
273
|
+
const revocations = JSON.parse(
|
|
274
|
+
readFileSync(resolve(vendorDir, "atlasent-revocations.json"), "utf8")
|
|
275
|
+
);
|
|
276
|
+
return {
|
|
277
|
+
valid_until: index.valid_until,
|
|
278
|
+
issued_at: index.issued_at,
|
|
279
|
+
keys: verifierKeys.keys ?? [],
|
|
280
|
+
revoked_keys: revocations.revoked_keys ?? [],
|
|
281
|
+
revoked_identities: revocations.revoked_identities ?? []
|
|
282
|
+
};
|
|
283
|
+
} catch {
|
|
284
|
+
return {
|
|
285
|
+
valid_until: "2099-01-01T00:00:00Z",
|
|
286
|
+
issued_at: "2026-05-26T00:00:00Z",
|
|
287
|
+
keys: [],
|
|
288
|
+
revoked_keys: [],
|
|
289
|
+
revoked_identities: []
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
var _globalManager = null;
|
|
294
|
+
function getGlobalTrustRootManager(opts) {
|
|
295
|
+
if (!_globalManager) {
|
|
296
|
+
_globalManager = new TrustRootManager(
|
|
297
|
+
_loadVendorSnapshot(),
|
|
298
|
+
opts ?? { disableRefresh: false }
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
return _globalManager;
|
|
302
|
+
}
|
|
112
303
|
|
|
113
304
|
// src/types.ts
|
|
114
305
|
var PRODUCTION_DEPLOY_ACTION = "production.deploy";
|
|
@@ -135,7 +326,13 @@ function normalizeEvaluateRequest(input) {
|
|
|
135
326
|
actor_id: legacy.agent
|
|
136
327
|
};
|
|
137
328
|
if (legacy.context !== void 0) normalized.context = legacy.context;
|
|
138
|
-
|
|
329
|
+
const l = legacy;
|
|
330
|
+
if (l.explain !== void 0) normalized.explain = l.explain;
|
|
331
|
+
if (l.environment !== void 0) normalized.environment = l.environment;
|
|
332
|
+
if (l.resource !== void 0) normalized.resource = l.resource;
|
|
333
|
+
if (l.current_state !== void 0) normalized.current_state = l.current_state;
|
|
334
|
+
if (l.proposed_state !== void 0) normalized.proposed_state = l.proposed_state;
|
|
335
|
+
if (l.execution_binding !== void 0) normalized.execution_binding = l.execution_binding;
|
|
139
336
|
return normalized;
|
|
140
337
|
}
|
|
141
338
|
return input;
|
|
@@ -143,9 +340,9 @@ function normalizeEvaluateRequest(input) {
|
|
|
143
340
|
|
|
144
341
|
// src/retry.ts
|
|
145
342
|
var DEFAULT_RETRY_POLICY = {
|
|
146
|
-
maxAttempts:
|
|
147
|
-
baseDelayMs:
|
|
148
|
-
maxDelayMs:
|
|
343
|
+
maxAttempts: 3,
|
|
344
|
+
baseDelayMs: 250,
|
|
345
|
+
maxDelayMs: 1e4
|
|
149
346
|
};
|
|
150
347
|
var RETRYABLE_CODES = /* @__PURE__ */ new Set([
|
|
151
348
|
"network",
|
|
@@ -194,10 +391,371 @@ function clampUnit(n) {
|
|
|
194
391
|
return n;
|
|
195
392
|
}
|
|
196
393
|
|
|
394
|
+
// src/scim.ts
|
|
395
|
+
var SCIM_USER_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:User";
|
|
396
|
+
var SCIM_GROUP_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:Group";
|
|
397
|
+
function scimUsersPath(orgId) {
|
|
398
|
+
return `/scim/v2/${encodeURIComponent(orgId)}/Users`;
|
|
399
|
+
}
|
|
400
|
+
function scimGroupsPath(orgId) {
|
|
401
|
+
return `/scim/v2/${encodeURIComponent(orgId)}/Groups`;
|
|
402
|
+
}
|
|
403
|
+
function buildScimQuery(filter, startIndex, count) {
|
|
404
|
+
const params = new URLSearchParams();
|
|
405
|
+
if (filter !== void 0) params.set("filter", filter);
|
|
406
|
+
if (startIndex !== void 0) params.set("startIndex", String(startIndex));
|
|
407
|
+
if (count !== void 0) params.set("count", String(count));
|
|
408
|
+
return params.size > 0 ? params : void 0;
|
|
409
|
+
}
|
|
410
|
+
function makeScimClient(postFn, getFn, putFn, deleteFn) {
|
|
411
|
+
const users = {
|
|
412
|
+
async list(params) {
|
|
413
|
+
const qs = buildScimQuery(
|
|
414
|
+
params.filter,
|
|
415
|
+
params.startIndex,
|
|
416
|
+
params.count
|
|
417
|
+
);
|
|
418
|
+
const { body } = await getFn(
|
|
419
|
+
scimUsersPath(params.orgId),
|
|
420
|
+
qs
|
|
421
|
+
);
|
|
422
|
+
return body;
|
|
423
|
+
},
|
|
424
|
+
async create(orgId, user) {
|
|
425
|
+
const payload = user.schemas ? user : { ...user, schemas: [SCIM_USER_SCHEMA] };
|
|
426
|
+
const { body } = await postFn(scimUsersPath(orgId), payload);
|
|
427
|
+
return body;
|
|
428
|
+
},
|
|
429
|
+
async update(orgId, id, user) {
|
|
430
|
+
const payload = user.schemas ? user : { ...user, schemas: [SCIM_USER_SCHEMA] };
|
|
431
|
+
const { body } = await putFn(
|
|
432
|
+
`${scimUsersPath(orgId)}/${encodeURIComponent(id)}`,
|
|
433
|
+
payload
|
|
434
|
+
);
|
|
435
|
+
return body;
|
|
436
|
+
},
|
|
437
|
+
async delete(orgId, id) {
|
|
438
|
+
return deleteFn(
|
|
439
|
+
`${scimUsersPath(orgId)}/${encodeURIComponent(id)}`
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
const groups = {
|
|
444
|
+
async list(params) {
|
|
445
|
+
const qs = buildScimQuery(
|
|
446
|
+
params.filter,
|
|
447
|
+
params.startIndex,
|
|
448
|
+
params.count
|
|
449
|
+
);
|
|
450
|
+
const { body } = await getFn(
|
|
451
|
+
scimGroupsPath(params.orgId),
|
|
452
|
+
qs
|
|
453
|
+
);
|
|
454
|
+
return body;
|
|
455
|
+
},
|
|
456
|
+
async create(orgId, group) {
|
|
457
|
+
const payload = group["schemas"] ? group : { ...group, schemas: [SCIM_GROUP_SCHEMA] };
|
|
458
|
+
const { body } = await postFn(
|
|
459
|
+
scimGroupsPath(orgId),
|
|
460
|
+
payload
|
|
461
|
+
);
|
|
462
|
+
return body;
|
|
463
|
+
},
|
|
464
|
+
async delete(orgId, id) {
|
|
465
|
+
return deleteFn(
|
|
466
|
+
`${scimGroupsPath(orgId)}/${encodeURIComponent(id)}`
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
return { users, groups };
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// src/evidence-bundle.ts
|
|
474
|
+
function wireToBundle(w) {
|
|
475
|
+
return {
|
|
476
|
+
bundleId: w.bundle_id,
|
|
477
|
+
orgId: w.org_id,
|
|
478
|
+
incidentId: w.incident_id,
|
|
479
|
+
status: w.status,
|
|
480
|
+
includedPermits: w.included_permits ?? [],
|
|
481
|
+
includeOverrides: w.include_overrides ?? false,
|
|
482
|
+
format: w.format,
|
|
483
|
+
createdAt: w.created_at,
|
|
484
|
+
expiresAt: w.expires_at,
|
|
485
|
+
...w.download_url !== void 0 ? { downloadUrl: w.download_url } : {},
|
|
486
|
+
...w.metadata !== void 0 ? { metadata: w.metadata } : {}
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
function makeEvidenceBundleClient(postFn, getFn, getRawFn) {
|
|
490
|
+
return {
|
|
491
|
+
async list(params = {}) {
|
|
492
|
+
const qs = new URLSearchParams();
|
|
493
|
+
if (params.executionId !== void 0) qs.set("execution_id", params.executionId);
|
|
494
|
+
if (params.limit !== void 0) qs.set("limit", String(params.limit));
|
|
495
|
+
if (params.cursor !== void 0) qs.set("cursor", params.cursor);
|
|
496
|
+
const { body } = await getFn("/v1/evidence-bundles", qs.size > 0 ? qs : void 0);
|
|
497
|
+
return {
|
|
498
|
+
bundles: (body.bundles ?? []).map(wireToBundle),
|
|
499
|
+
nextCursor: body.next_cursor ?? null
|
|
500
|
+
};
|
|
501
|
+
},
|
|
502
|
+
async create(params) {
|
|
503
|
+
const payload = {
|
|
504
|
+
incident_id: params.incidentId
|
|
505
|
+
};
|
|
506
|
+
if (params.includedPermits !== void 0) {
|
|
507
|
+
payload["included_permits"] = params.includedPermits;
|
|
508
|
+
}
|
|
509
|
+
if (params.includeOverrides !== void 0) {
|
|
510
|
+
payload["include_overrides"] = params.includeOverrides;
|
|
511
|
+
}
|
|
512
|
+
const { body } = await postFn(
|
|
513
|
+
"/v1/evidence-bundles",
|
|
514
|
+
payload
|
|
515
|
+
);
|
|
516
|
+
return wireToBundle(body);
|
|
517
|
+
},
|
|
518
|
+
async get(bundleId) {
|
|
519
|
+
const { body } = await getFn(
|
|
520
|
+
`/v1/evidence-bundles/${encodeURIComponent(bundleId)}`
|
|
521
|
+
);
|
|
522
|
+
return wireToBundle(body);
|
|
523
|
+
},
|
|
524
|
+
async download(bundleId, format = "json") {
|
|
525
|
+
const qs = new URLSearchParams({ format });
|
|
526
|
+
const raw = await getRawFn(
|
|
527
|
+
`/v1/evidence-bundles/${encodeURIComponent(bundleId)}/download?${qs}`
|
|
528
|
+
);
|
|
529
|
+
return Buffer.from(raw);
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// src/auth.ts
|
|
535
|
+
function wireToTokenResponse(w) {
|
|
536
|
+
return {
|
|
537
|
+
accessToken: w.access_token,
|
|
538
|
+
refreshToken: w.refresh_token,
|
|
539
|
+
tokenType: w.token_type,
|
|
540
|
+
expiresIn: w.expires_in,
|
|
541
|
+
...w.scope !== void 0 ? { scope: w.scope } : {},
|
|
542
|
+
...w.idp_id !== void 0 ? { idpId: w.idp_id } : {}
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
function wireToIdpConnection(w) {
|
|
546
|
+
return {
|
|
547
|
+
id: w.id,
|
|
548
|
+
name: w.name,
|
|
549
|
+
provider: w.provider,
|
|
550
|
+
enabled: w.enabled,
|
|
551
|
+
isDefault: w.default,
|
|
552
|
+
...w.domains !== void 0 ? { domains: w.domains } : {},
|
|
553
|
+
createdAt: w.created_at
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
function makeAuthClient(postFn, getFn) {
|
|
557
|
+
return {
|
|
558
|
+
async refresh(refreshToken) {
|
|
559
|
+
const { body } = await postFn(
|
|
560
|
+
"/v1/auth/token/refresh",
|
|
561
|
+
{ refresh_token: refreshToken, grant_type: "refresh_token" }
|
|
562
|
+
);
|
|
563
|
+
return wireToTokenResponse(body);
|
|
564
|
+
},
|
|
565
|
+
async refreshWithIdp(idpId, refreshToken) {
|
|
566
|
+
const path = `/v1/auth/idp/${encodeURIComponent(idpId)}/token/refresh`;
|
|
567
|
+
const { body } = await postFn(path, {
|
|
568
|
+
refresh_token: refreshToken,
|
|
569
|
+
grant_type: "refresh_token",
|
|
570
|
+
idp_id: idpId
|
|
571
|
+
});
|
|
572
|
+
return wireToTokenResponse(body);
|
|
573
|
+
},
|
|
574
|
+
async listIdpConnections() {
|
|
575
|
+
const { body } = await getFn(
|
|
576
|
+
"/v1/auth/idp-connections"
|
|
577
|
+
);
|
|
578
|
+
return (body.connections ?? []).map(wireToIdpConnection);
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// src/sso.ts
|
|
584
|
+
function wireToSsoConnection(w) {
|
|
585
|
+
return {
|
|
586
|
+
id: w.id,
|
|
587
|
+
organizationId: w.organization_id,
|
|
588
|
+
name: w.name,
|
|
589
|
+
protocol: w.protocol,
|
|
590
|
+
idpEntityId: w.idp_entity_id,
|
|
591
|
+
metadataUrl: w.metadata_url,
|
|
592
|
+
metadataXml: w.metadata_xml,
|
|
593
|
+
emailDomain: w.email_domain,
|
|
594
|
+
enforceForDomain: w.enforce_for_domain,
|
|
595
|
+
isActive: w.is_active,
|
|
596
|
+
supabaseProviderId: w.supabase_provider_id,
|
|
597
|
+
createdBy: w.created_by,
|
|
598
|
+
createdAt: w.created_at,
|
|
599
|
+
updatedAt: w.updated_at
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
function wireToSsoJitRule(w) {
|
|
603
|
+
return {
|
|
604
|
+
id: w.id,
|
|
605
|
+
connectionId: w.connection_id,
|
|
606
|
+
organizationId: w.organization_id,
|
|
607
|
+
claimAttribute: w.claim_attribute,
|
|
608
|
+
claimValue: w.claim_value,
|
|
609
|
+
grantedRole: w.granted_role,
|
|
610
|
+
precedence: w.precedence,
|
|
611
|
+
isActive: w.is_active,
|
|
612
|
+
createdAt: w.created_at,
|
|
613
|
+
updatedAt: w.updated_at
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
function wireToSsoReadiness(w) {
|
|
617
|
+
return {
|
|
618
|
+
connectionConfigured: w.connection_configured,
|
|
619
|
+
connectionTested: w.connection_tested,
|
|
620
|
+
breakGlassSet: w.break_glass_set,
|
|
621
|
+
serviceApiKeysReviewed: w.service_api_keys_reviewed
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
function ssoConnectionInputToWire(input) {
|
|
625
|
+
const w = {};
|
|
626
|
+
if (input.name !== void 0) w["name"] = input.name;
|
|
627
|
+
if (input.protocol !== void 0) w["protocol"] = input.protocol;
|
|
628
|
+
if (input.idpEntityId !== void 0) w["idp_entity_id"] = input.idpEntityId;
|
|
629
|
+
if (input.metadataUrl !== void 0) w["metadata_url"] = input.metadataUrl;
|
|
630
|
+
if (input.metadataXml !== void 0) w["metadata_xml"] = input.metadataXml;
|
|
631
|
+
if (input.emailDomain !== void 0) w["email_domain"] = input.emailDomain;
|
|
632
|
+
if (input.enforceForDomain !== void 0) w["enforce_for_domain"] = input.enforceForDomain;
|
|
633
|
+
return w;
|
|
634
|
+
}
|
|
635
|
+
function makeSsoClient(getFn, postFn, patchFn, deleteFn) {
|
|
636
|
+
return {
|
|
637
|
+
async listConnections() {
|
|
638
|
+
const { body } = await getFn("/v1/sso/connections");
|
|
639
|
+
return { connections: (body.connections ?? []).map(wireToSsoConnection) };
|
|
640
|
+
},
|
|
641
|
+
async getConnection(id) {
|
|
642
|
+
const { body } = await getFn(`/v1/sso/connections/${encodeURIComponent(id)}`);
|
|
643
|
+
return wireToSsoConnection(body);
|
|
644
|
+
},
|
|
645
|
+
async createConnection(input) {
|
|
646
|
+
const { body } = await postFn("/v1/sso/connections", ssoConnectionInputToWire(input));
|
|
647
|
+
return wireToSsoConnection(body);
|
|
648
|
+
},
|
|
649
|
+
async updateConnection(id, input) {
|
|
650
|
+
const { body } = await patchFn(
|
|
651
|
+
`/v1/sso/connections/${encodeURIComponent(id)}`,
|
|
652
|
+
ssoConnectionInputToWire(input)
|
|
653
|
+
);
|
|
654
|
+
return wireToSsoConnection(body);
|
|
655
|
+
},
|
|
656
|
+
async deleteConnection(id) {
|
|
657
|
+
await deleteFn(`/v1/sso/connections/${encodeURIComponent(id)}`);
|
|
658
|
+
},
|
|
659
|
+
async activateConnection(id) {
|
|
660
|
+
const { body } = await postFn(
|
|
661
|
+
`/v1/sso/connections/${encodeURIComponent(id)}/activate`,
|
|
662
|
+
{}
|
|
663
|
+
);
|
|
664
|
+
return { ok: body.ok, supabaseProviderId: body.supabase_provider_id };
|
|
665
|
+
},
|
|
666
|
+
async enforce(action) {
|
|
667
|
+
const { body } = await postFn("/v1/sso/enforce", { action });
|
|
668
|
+
return {
|
|
669
|
+
ok: body.ok,
|
|
670
|
+
action: body.action,
|
|
671
|
+
enforceSso: body.enforce_sso,
|
|
672
|
+
enforceSsoAt: body.enforce_sso_at
|
|
673
|
+
};
|
|
674
|
+
},
|
|
675
|
+
async getStatus() {
|
|
676
|
+
const { body } = await getFn("/v1/sso/status");
|
|
677
|
+
return wireToSsoReadiness(body.readiness);
|
|
678
|
+
},
|
|
679
|
+
async listJitRules(connectionId) {
|
|
680
|
+
const qs = connectionId ? new URLSearchParams({ connection_id: connectionId }) : void 0;
|
|
681
|
+
const { body } = await getFn("/v1/sso/jit-rules", qs);
|
|
682
|
+
return { rules: (body.rules ?? []).map(wireToSsoJitRule) };
|
|
683
|
+
},
|
|
684
|
+
async createJitRule(input) {
|
|
685
|
+
const payload = {
|
|
686
|
+
connection_id: input.connectionId,
|
|
687
|
+
claim_attribute: input.claimAttribute,
|
|
688
|
+
claim_value: input.claimValue,
|
|
689
|
+
granted_role: input.grantedRole
|
|
690
|
+
};
|
|
691
|
+
if (input.precedence !== void 0) payload["precedence"] = input.precedence;
|
|
692
|
+
const { body } = await postFn("/v1/sso/jit-rules", payload);
|
|
693
|
+
return wireToSsoJitRule(body);
|
|
694
|
+
},
|
|
695
|
+
async patchJitRule(id, patch) {
|
|
696
|
+
const payload = {};
|
|
697
|
+
if (patch.claimAttribute !== void 0) payload["claim_attribute"] = patch.claimAttribute;
|
|
698
|
+
if (patch.claimValue !== void 0) payload["claim_value"] = patch.claimValue;
|
|
699
|
+
if (patch.grantedRole !== void 0) payload["granted_role"] = patch.grantedRole;
|
|
700
|
+
if (patch.precedence !== void 0) payload["precedence"] = patch.precedence;
|
|
701
|
+
if (patch.isActive !== void 0) payload["is_active"] = patch.isActive;
|
|
702
|
+
const { body } = await patchFn(
|
|
703
|
+
`/v1/sso/jit-rules/${encodeURIComponent(id)}`,
|
|
704
|
+
payload
|
|
705
|
+
);
|
|
706
|
+
return wireToSsoJitRule(body);
|
|
707
|
+
},
|
|
708
|
+
async deleteJitRule(id) {
|
|
709
|
+
await deleteFn(`/v1/sso/jit-rules/${encodeURIComponent(id)}`);
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// src/access-governance-log.ts
|
|
715
|
+
function wireToEvent(w) {
|
|
716
|
+
return {
|
|
717
|
+
id: w.id,
|
|
718
|
+
eventType: w.event_type,
|
|
719
|
+
orgId: w.org_id,
|
|
720
|
+
actorId: w.actor_id,
|
|
721
|
+
actorEmail: w.actor_email,
|
|
722
|
+
ipAddress: w.ip_address,
|
|
723
|
+
metadata: w.metadata ?? {},
|
|
724
|
+
createdAt: w.created_at
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
function makeAccessGovernanceLogClient(getFn) {
|
|
728
|
+
return {
|
|
729
|
+
async list(query = {}) {
|
|
730
|
+
const qs = new URLSearchParams();
|
|
731
|
+
if (query.limit !== void 0) qs.set("limit", String(query.limit));
|
|
732
|
+
if (query.cursor) qs.set("cursor", query.cursor);
|
|
733
|
+
if (query.eventType) qs.set("event_type", query.eventType);
|
|
734
|
+
if (query.actorId) qs.set("actor_id", query.actorId);
|
|
735
|
+
if (query.from) qs.set("from", query.from);
|
|
736
|
+
if (query.to) qs.set("to", query.to);
|
|
737
|
+
const { body } = await getFn(
|
|
738
|
+
"/v1/access-governance-log",
|
|
739
|
+
qs.size > 0 ? qs : void 0
|
|
740
|
+
);
|
|
741
|
+
return {
|
|
742
|
+
events: (body.events ?? []).map(wireToEvent),
|
|
743
|
+
nextCursor: body.next_cursor,
|
|
744
|
+
totalCount: body.total_count ?? 0
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
|
|
197
750
|
// src/client.ts
|
|
198
751
|
var DEFAULT_BASE_URL = "https://api.atlasent.io";
|
|
199
752
|
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
200
|
-
var SDK_VERSION = "2.
|
|
753
|
+
var SDK_VERSION = "2.10.0";
|
|
754
|
+
var warnedBrowser = false;
|
|
755
|
+
var V1_EVALUATE_BATCH_PATH = "/v1/evaluate/batch";
|
|
756
|
+
var V1_EVALUATE_BATCH_LEGACY_PATH = "/v1-evaluate-batch";
|
|
757
|
+
var V1_EVALUATE_STREAM_PATH = "/v1/evaluate/stream";
|
|
758
|
+
var V1_EVALUATE_STREAM_LEGACY_PATH = "/v1-evaluate-stream";
|
|
201
759
|
function _buildUserAgent() {
|
|
202
760
|
const isNode2 = typeof process !== "undefined" && typeof process?.versions?.node === "string";
|
|
203
761
|
return isNode2 ? `@atlasent/sdk/${SDK_VERSION} node/${process.version}` : `@atlasent/sdk/${SDK_VERSION} browser`;
|
|
@@ -261,6 +819,18 @@ var AtlaSentClient = class {
|
|
|
261
819
|
fetchImpl;
|
|
262
820
|
userAgent;
|
|
263
821
|
retryPolicy;
|
|
822
|
+
/** SCIM 2.0 provisioning sub-client. Access as `client.scim`. */
|
|
823
|
+
scim;
|
|
824
|
+
/** Evidence bundle sub-client. Access as `client.evidenceBundles`. */
|
|
825
|
+
evidenceBundles;
|
|
826
|
+
/** Auth / token management sub-client. Access as `client.auth`. */
|
|
827
|
+
auth;
|
|
828
|
+
/** SSO administration sub-client. Access as `client.sso`. */
|
|
829
|
+
sso;
|
|
830
|
+
/** Access governance log sub-client. Access as `client.accessGovernanceLog`. */
|
|
831
|
+
accessGovernanceLog;
|
|
832
|
+
/** Trust-root snapshot manager for this client instance. */
|
|
833
|
+
trustRoot;
|
|
264
834
|
constructor(options) {
|
|
265
835
|
if (!options.apiKey || typeof options.apiKey !== "string") {
|
|
266
836
|
throw new AtlaSentError("apiKey is required", {
|
|
@@ -273,6 +843,12 @@ var AtlaSentClient = class {
|
|
|
273
843
|
{ code: "network" }
|
|
274
844
|
);
|
|
275
845
|
}
|
|
846
|
+
if (!warnedBrowser && typeof globalThis["window"] !== "undefined" && typeof process === "undefined") {
|
|
847
|
+
warnedBrowser = true;
|
|
848
|
+
console.warn(
|
|
849
|
+
"[@atlasent/sdk] Running in a browser environment. API keys should not be exposed in client-side bundles. Use a server-side proxy instead."
|
|
850
|
+
);
|
|
851
|
+
}
|
|
276
852
|
this.apiKey = _validateApiKey(options.apiKey);
|
|
277
853
|
this.baseUrl = _enforceTls(options.baseUrl ?? DEFAULT_BASE_URL).replace(
|
|
278
854
|
/\/+$/,
|
|
@@ -282,6 +858,44 @@ var AtlaSentClient = class {
|
|
|
282
858
|
this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
283
859
|
this.userAgent = _buildUserAgent();
|
|
284
860
|
this.retryPolicy = mergePolicy(options.retryPolicy ?? {});
|
|
861
|
+
this.scim = makeScimClient(
|
|
862
|
+
(path, body, query) => this._post(path, body, query),
|
|
863
|
+
(path, query) => this._get(path, query),
|
|
864
|
+
(path, body) => this._put(path, body),
|
|
865
|
+
(path) => this._delete(path)
|
|
866
|
+
);
|
|
867
|
+
this.evidenceBundles = makeEvidenceBundleClient(
|
|
868
|
+
(path, body) => this._post(path, body),
|
|
869
|
+
(path, query) => this._get(path, query),
|
|
870
|
+
(path) => this._getRaw(path)
|
|
871
|
+
);
|
|
872
|
+
this.auth = makeAuthClient(
|
|
873
|
+
(path, body) => this._post(path, body),
|
|
874
|
+
(path) => this._get(path)
|
|
875
|
+
);
|
|
876
|
+
this.sso = makeSsoClient(
|
|
877
|
+
(path, query) => this._get(path, query),
|
|
878
|
+
(path, body) => this._post(path, body),
|
|
879
|
+
(path, body) => this._patch(path, body),
|
|
880
|
+
(path) => this._delete(path)
|
|
881
|
+
);
|
|
882
|
+
this.accessGovernanceLog = makeAccessGovernanceLogClient(
|
|
883
|
+
(path, query) => this._get(path, query)
|
|
884
|
+
);
|
|
885
|
+
if (options.trustRootUrl !== void 0 || options.trustSnapshotRefreshMs !== void 0) {
|
|
886
|
+
const globalSnap = getGlobalTrustRootManager({ disableRefresh: true }).getSnapshot();
|
|
887
|
+
this.trustRoot = new TrustRootManager(globalSnap, {
|
|
888
|
+
...options.trustRootUrl !== void 0 && { refreshBaseUrl: options.trustRootUrl },
|
|
889
|
+
...options.trustSnapshotRefreshMs !== void 0 && { refreshIntervalMs: options.trustSnapshotRefreshMs }
|
|
890
|
+
});
|
|
891
|
+
} else {
|
|
892
|
+
this.trustRoot = getGlobalTrustRootManager();
|
|
893
|
+
}
|
|
894
|
+
this.trustRoot.checkExpiry();
|
|
895
|
+
}
|
|
896
|
+
/** Return the current trust-root snapshot (pinned or last successful refresh). */
|
|
897
|
+
getTrustSnapshot() {
|
|
898
|
+
return this.trustRoot.getSnapshot();
|
|
285
899
|
}
|
|
286
900
|
/**
|
|
287
901
|
* Ask the policy engine whether an agent action is permitted.
|
|
@@ -308,6 +922,11 @@ var AtlaSentClient = class {
|
|
|
308
922
|
context: normalized.context ?? {}
|
|
309
923
|
};
|
|
310
924
|
if (normalized.explain !== void 0) body.explain = normalized.explain;
|
|
925
|
+
if (normalized.environment !== void 0) body.environment = normalized.environment;
|
|
926
|
+
if (normalized.resource !== void 0) body.resource = normalized.resource;
|
|
927
|
+
if (normalized.current_state !== void 0) body.current_state = normalized.current_state;
|
|
928
|
+
if (normalized.proposed_state !== void 0) body.proposed_state = normalized.proposed_state;
|
|
929
|
+
if (normalized.execution_binding !== void 0) body.execution_binding = normalized.execution_binding;
|
|
311
930
|
const { body: wire, rateLimit } = await this.post(
|
|
312
931
|
"/v1-evaluate",
|
|
313
932
|
body
|
|
@@ -354,13 +973,25 @@ var AtlaSentClient = class {
|
|
|
354
973
|
hardBlocks: wire.risk_envelope.hard_blocks ?? [],
|
|
355
974
|
...wire.risk_envelope.factors && { factors: wire.risk_envelope.factors }
|
|
356
975
|
}
|
|
357
|
-
}
|
|
976
|
+
},
|
|
977
|
+
...wire.risk_class !== void 0 && { riskClass: wire.risk_class },
|
|
978
|
+
...wire.authority_basis && {
|
|
979
|
+
authorityBasis: {
|
|
980
|
+
kind: wire.authority_basis.kind,
|
|
981
|
+
...wire.authority_basis.reference !== void 0 && { reference: wire.authority_basis.reference },
|
|
982
|
+
...wire.authority_basis.granted_by !== void 0 && { grantedBy: wire.authority_basis.granted_by },
|
|
983
|
+
...wire.authority_basis.rationale !== void 0 && { rationale: wire.authority_basis.rationale },
|
|
984
|
+
...wire.authority_basis.expires_at !== void 0 && { expiresAt: wire.authority_basis.expires_at }
|
|
985
|
+
}
|
|
986
|
+
},
|
|
987
|
+
...wire.escalation_id !== void 0 && { escalationId: wire.escalation_id }
|
|
358
988
|
};
|
|
359
989
|
}
|
|
360
990
|
/**
|
|
361
991
|
* Batch evaluate — send up to 100 decisions in a single round-trip.
|
|
362
992
|
*
|
|
363
|
-
|
|
993
|
+
* Wraps `POST /v1/evaluate/batch` (with fallback to
|
|
994
|
+
* `POST /v1-evaluate-batch` on older runtimes). The server evaluates each item
|
|
364
995
|
* against the active policy bundle and returns results in the same
|
|
365
996
|
* order as the input. One rate-limit token is consumed for the
|
|
366
997
|
* whole batch, and one audit-chain entry lists every included
|
|
@@ -398,8 +1029,9 @@ var AtlaSentClient = class {
|
|
|
398
1029
|
}));
|
|
399
1030
|
const wireBody = { items: wireItems };
|
|
400
1031
|
if (batchId) wireBody.batch_id = batchId;
|
|
401
|
-
const { body: wire, rateLimit } = await this.
|
|
402
|
-
|
|
1032
|
+
const { body: wire, rateLimit } = await this.postWithPathFallback(
|
|
1033
|
+
V1_EVALUATE_BATCH_PATH,
|
|
1034
|
+
V1_EVALUATE_BATCH_LEGACY_PATH,
|
|
403
1035
|
wireBody
|
|
404
1036
|
);
|
|
405
1037
|
const items = (wire.items ?? []).map(
|
|
@@ -692,6 +1324,7 @@ var AtlaSentClient = class {
|
|
|
692
1324
|
const agent = input.agent ?? "ci-deploy-bot";
|
|
693
1325
|
const action = input.action ?? PRODUCTION_DEPLOY_ACTION;
|
|
694
1326
|
const context = input.context ?? {};
|
|
1327
|
+
const environment = typeof context.environment === "string" ? context.environment : typeof context.environment_name === "string" ? context.environment_name : void 0;
|
|
695
1328
|
const evaluation = await this.evaluate({ agent, action, context });
|
|
696
1329
|
if (evaluation.decision !== "allow") {
|
|
697
1330
|
return {
|
|
@@ -708,7 +1341,8 @@ var AtlaSentClient = class {
|
|
|
708
1341
|
permitId: evaluation.permitId,
|
|
709
1342
|
agent,
|
|
710
1343
|
action,
|
|
711
|
-
context
|
|
1344
|
+
context,
|
|
1345
|
+
...environment !== void 0 ? { environment } : {}
|
|
712
1346
|
});
|
|
713
1347
|
if (!verification.verified) {
|
|
714
1348
|
return {
|
|
@@ -922,15 +1556,15 @@ var AtlaSentClient = class {
|
|
|
922
1556
|
*/
|
|
923
1557
|
async keySelf() {
|
|
924
1558
|
const { body: wire, rateLimit } = await this.get("/v1-api-key-self");
|
|
925
|
-
if (typeof wire.key_id !== "string" || typeof wire.
|
|
1559
|
+
if (typeof wire.key_id !== "string" || typeof wire.org_id !== "string") {
|
|
926
1560
|
throw new AtlaSentError(
|
|
927
|
-
"Malformed response from /v1-api-key-self: missing `key_id` or `
|
|
1561
|
+
"Malformed response from /v1-api-key-self: missing `key_id` or `org_id`",
|
|
928
1562
|
{ code: "bad_response" }
|
|
929
1563
|
);
|
|
930
1564
|
}
|
|
931
1565
|
return {
|
|
932
1566
|
keyId: wire.key_id,
|
|
933
|
-
|
|
1567
|
+
orgId: wire.org_id,
|
|
934
1568
|
environment: wire.environment,
|
|
935
1569
|
scopes: wire.scopes ?? [],
|
|
936
1570
|
allowedCidrs: wire.allowed_cidrs ?? null,
|
|
@@ -1142,7 +1776,8 @@ var AtlaSentClient = class {
|
|
|
1142
1776
|
return response;
|
|
1143
1777
|
}
|
|
1144
1778
|
/**
|
|
1145
|
-
|
|
1779
|
+
* Open a streaming evaluation session against `POST /v1/evaluate/stream`
|
|
1780
|
+
* (with fallback to `POST /v1-evaluate-stream` on older runtimes).
|
|
1146
1781
|
*
|
|
1147
1782
|
* Yields {@link StreamDecisionEvent} and {@link StreamProgressEvent} objects
|
|
1148
1783
|
* as the server emits them. The iterator ends cleanly when the server sends
|
|
@@ -1180,7 +1815,7 @@ var AtlaSentClient = class {
|
|
|
1180
1815
|
api_key: this.apiKey
|
|
1181
1816
|
};
|
|
1182
1817
|
const requestId = globalThis.crypto.randomUUID();
|
|
1183
|
-
|
|
1818
|
+
let streamPath = V1_EVALUATE_STREAM_PATH;
|
|
1184
1819
|
let lastEventId;
|
|
1185
1820
|
let retryCount = 0;
|
|
1186
1821
|
while (true) {
|
|
@@ -1200,7 +1835,7 @@ var AtlaSentClient = class {
|
|
|
1200
1835
|
const signal = opts.signal ? AbortSignal.any([connectionTimeoutSignal, opts.signal]) : connectionTimeoutSignal;
|
|
1201
1836
|
let response;
|
|
1202
1837
|
try {
|
|
1203
|
-
response = await this.fetchImpl(
|
|
1838
|
+
response = await this.fetchImpl(`${this.baseUrl}${streamPath}`, {
|
|
1204
1839
|
method: "POST",
|
|
1205
1840
|
headers,
|
|
1206
1841
|
body: JSON.stringify(body),
|
|
@@ -1216,6 +1851,10 @@ var AtlaSentClient = class {
|
|
|
1216
1851
|
throw mapped;
|
|
1217
1852
|
}
|
|
1218
1853
|
if (!response.ok) {
|
|
1854
|
+
if (streamPath === V1_EVALUATE_STREAM_PATH && (response.status === 404 || response.status === 405)) {
|
|
1855
|
+
streamPath = V1_EVALUATE_STREAM_LEGACY_PATH;
|
|
1856
|
+
continue;
|
|
1857
|
+
}
|
|
1219
1858
|
throw await buildHttpError(response, requestId);
|
|
1220
1859
|
}
|
|
1221
1860
|
if (!response.body) {
|
|
@@ -1264,9 +1903,61 @@ var AtlaSentClient = class {
|
|
|
1264
1903
|
break;
|
|
1265
1904
|
}
|
|
1266
1905
|
}
|
|
1906
|
+
// ── License verification (self-hosted / air-gapped) ──────────────────────
|
|
1907
|
+
/**
|
|
1908
|
+
* Retrieve the license status of this self-hosted or air-gapped deployment.
|
|
1909
|
+
*
|
|
1910
|
+
* Calls `GET /v1/license`. Returns the current validity state, expiry,
|
|
1911
|
+
* enabled feature flags, and optional capacity limits for the installed
|
|
1912
|
+
* license key.
|
|
1913
|
+
*
|
|
1914
|
+
* Callers should check `result.status === "active"` before proceeding.
|
|
1915
|
+
* A `"grace"` status means the license has lapsed but a grace window
|
|
1916
|
+
* (`grace_until`) is still open — the deployment continues to function
|
|
1917
|
+
* but the license should be renewed immediately.
|
|
1918
|
+
*
|
|
1919
|
+
* Throws {@link AtlaSentError} on transport / auth failures.
|
|
1920
|
+
*/
|
|
1921
|
+
async getLicense() {
|
|
1922
|
+
const { body, rateLimit } = await this.get("/v1/license");
|
|
1923
|
+
return { ...body, rateLimit };
|
|
1924
|
+
}
|
|
1925
|
+
/**
|
|
1926
|
+
* Validate a signed license blob against this deployment's installed
|
|
1927
|
+
* public key.
|
|
1928
|
+
*
|
|
1929
|
+
* Calls `POST /v1/license/verify`. Use this when onboarding a new license
|
|
1930
|
+
* key or rotating an expiring one — submit the blob received from AtlaSent
|
|
1931
|
+
* and check `result.valid` before applying the new license.
|
|
1932
|
+
*
|
|
1933
|
+
* A `valid: false` response is **not** thrown — inspect the returned
|
|
1934
|
+
* object. Only transport / server errors throw {@link AtlaSentError}.
|
|
1935
|
+
*
|
|
1936
|
+
* @param blob — The signed license blob string provided by AtlaSent.
|
|
1937
|
+
*/
|
|
1938
|
+
async verifyLicense(blob) {
|
|
1939
|
+
if (!blob || typeof blob !== "string") {
|
|
1940
|
+
throw new AtlaSentError("blob is required", { code: "bad_request" });
|
|
1941
|
+
}
|
|
1942
|
+
const { body, rateLimit } = await this.post(
|
|
1943
|
+
"/v1/license/verify",
|
|
1944
|
+
{ blob }
|
|
1945
|
+
);
|
|
1946
|
+
return { ...body, rateLimit };
|
|
1947
|
+
}
|
|
1267
1948
|
async post(path, body, query) {
|
|
1268
1949
|
return this.request(path, "POST", body, query);
|
|
1269
1950
|
}
|
|
1951
|
+
async postWithPathFallback(primaryPath, fallbackPath, body, query) {
|
|
1952
|
+
try {
|
|
1953
|
+
return await this.post(primaryPath, body, query);
|
|
1954
|
+
} catch (err) {
|
|
1955
|
+
if (err instanceof AtlaSentError && (err.status === 404 || err.status === 405)) {
|
|
1956
|
+
return this.post(fallbackPath, body, query);
|
|
1957
|
+
}
|
|
1958
|
+
throw err;
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1270
1961
|
async get(path, query) {
|
|
1271
1962
|
return this.request(path, "GET", void 0, query);
|
|
1272
1963
|
}
|
|
@@ -1959,6 +2650,82 @@ var AtlaSentClient = class {
|
|
|
1959
2650
|
);
|
|
1960
2651
|
return [...body.evaluations ?? []];
|
|
1961
2652
|
}
|
|
2653
|
+
// ── Private adapters for sub-client factories ──────────────────────────────
|
|
2654
|
+
// Thin wrappers that expose the private request infrastructure to sub-client
|
|
2655
|
+
// factories (scim, evidenceBundles, auth) without widening the public API.
|
|
2656
|
+
async _post(path, body, query) {
|
|
2657
|
+
const { body: b } = await this.post(path, body, query);
|
|
2658
|
+
return { body: b };
|
|
2659
|
+
}
|
|
2660
|
+
async _get(path, query) {
|
|
2661
|
+
const { body: b } = await this.get(path, query);
|
|
2662
|
+
return { body: b };
|
|
2663
|
+
}
|
|
2664
|
+
async _put(path, body) {
|
|
2665
|
+
return this._requestRaw(path, "PUT", body, void 0);
|
|
2666
|
+
}
|
|
2667
|
+
async _patch(path, body) {
|
|
2668
|
+
return this._requestRaw(path, "PATCH", body, void 0);
|
|
2669
|
+
}
|
|
2670
|
+
async _delete(path) {
|
|
2671
|
+
await this._requestRaw(path, "DELETE", void 0, void 0);
|
|
2672
|
+
}
|
|
2673
|
+
async _getRaw(path) {
|
|
2674
|
+
const url = `${this.baseUrl}${path}`;
|
|
2675
|
+
const requestId = globalThis.crypto.randomUUID();
|
|
2676
|
+
const headers = {
|
|
2677
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
2678
|
+
"User-Agent": this.userAgent,
|
|
2679
|
+
"X-Request-ID": requestId,
|
|
2680
|
+
"X-AtlaSent-Protocol-Version": "1"
|
|
2681
|
+
};
|
|
2682
|
+
const response = await this.fetchImpl(url, {
|
|
2683
|
+
method: "GET",
|
|
2684
|
+
headers,
|
|
2685
|
+
signal: AbortSignal.timeout(this.timeoutMs)
|
|
2686
|
+
});
|
|
2687
|
+
if (!response.ok) {
|
|
2688
|
+
const text = await response.text().catch(() => "");
|
|
2689
|
+
throw new AtlaSentError(`GET ${path} returned ${response.status}`, {
|
|
2690
|
+
code: response.status >= 500 ? "server_error" : "bad_request",
|
|
2691
|
+
status: response.status,
|
|
2692
|
+
requestId
|
|
2693
|
+
});
|
|
2694
|
+
}
|
|
2695
|
+
return response.arrayBuffer();
|
|
2696
|
+
}
|
|
2697
|
+
async _requestRaw(path, method, body, query) {
|
|
2698
|
+
const qs = query && Array.from(query).length > 0 ? `?${query.toString()}` : "";
|
|
2699
|
+
const url = `${this.baseUrl}${path}${qs}`;
|
|
2700
|
+
const requestId = globalThis.crypto.randomUUID();
|
|
2701
|
+
const headers = {
|
|
2702
|
+
Accept: "application/json",
|
|
2703
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
2704
|
+
"User-Agent": this.userAgent,
|
|
2705
|
+
"X-Request-ID": requestId,
|
|
2706
|
+
"X-AtlaSent-Protocol-Version": "1"
|
|
2707
|
+
};
|
|
2708
|
+
if (method === "PUT" && body !== void 0) {
|
|
2709
|
+
headers["Content-Type"] = "application/json";
|
|
2710
|
+
}
|
|
2711
|
+
const init = { method, headers, signal: AbortSignal.timeout(this.timeoutMs) };
|
|
2712
|
+
if (method === "PUT" && body !== void 0) {
|
|
2713
|
+
init.body = JSON.stringify(body);
|
|
2714
|
+
}
|
|
2715
|
+
const response = await this.fetchImpl(url, init);
|
|
2716
|
+
if (!response.ok) {
|
|
2717
|
+
const text = await response.text().catch(() => "");
|
|
2718
|
+
throw new AtlaSentError(`${method} ${path} returned ${response.status}`, {
|
|
2719
|
+
code: response.status >= 500 ? "server_error" : "bad_request",
|
|
2720
|
+
status: response.status,
|
|
2721
|
+
requestId
|
|
2722
|
+
});
|
|
2723
|
+
}
|
|
2724
|
+
if (method === "DELETE") {
|
|
2725
|
+
return { body: {} };
|
|
2726
|
+
}
|
|
2727
|
+
return { body: await response.json() };
|
|
2728
|
+
}
|
|
1962
2729
|
};
|
|
1963
2730
|
function parseRateLimitHeaders(headers) {
|
|
1964
2731
|
const rawLimit = headers.get("x-ratelimit-limit");
|
|
@@ -2104,7 +2871,7 @@ function buildAuditEventsQuery(query) {
|
|
|
2104
2871
|
return params;
|
|
2105
2872
|
}
|
|
2106
2873
|
function sleep(ms) {
|
|
2107
|
-
return new Promise((
|
|
2874
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
2108
2875
|
}
|
|
2109
2876
|
function parseRetryAfter(raw) {
|
|
2110
2877
|
if (!raw) return void 0;
|
|
@@ -2125,14 +2892,14 @@ async function* parseSseStream(body, requestId, timeoutMs, onEventId) {
|
|
|
2125
2892
|
if (timeoutMs <= 0) {
|
|
2126
2893
|
return reader.read();
|
|
2127
2894
|
}
|
|
2128
|
-
return new Promise((
|
|
2895
|
+
return new Promise((resolve3, reject) => {
|
|
2129
2896
|
const timer = setTimeout(() => {
|
|
2130
2897
|
reject(new StreamTimeoutError(timeoutMs));
|
|
2131
2898
|
}, timeoutMs);
|
|
2132
2899
|
reader.read().then(
|
|
2133
2900
|
(result) => {
|
|
2134
2901
|
clearTimeout(timer);
|
|
2135
|
-
|
|
2902
|
+
resolve3(result);
|
|
2136
2903
|
},
|
|
2137
2904
|
(err) => {
|
|
2138
2905
|
clearTimeout(timer);
|
|
@@ -2305,6 +3072,15 @@ async function protect(request) {
|
|
|
2305
3072
|
{ code: "bad_request" }
|
|
2306
3073
|
);
|
|
2307
3074
|
}
|
|
3075
|
+
const trustMgr = getGlobalTrustRootManager({ disableRefresh: false });
|
|
3076
|
+
if (trustMgr.checkExpiry() === "expired") {
|
|
3077
|
+
const snap = trustMgr.getSnapshot();
|
|
3078
|
+
throw new BundleVerificationError({
|
|
3079
|
+
reason: "trust_snapshot_expired",
|
|
3080
|
+
snapshotValidUntil: snap.valid_until,
|
|
3081
|
+
snapshotFetchedAt: snap.issued_at
|
|
3082
|
+
});
|
|
3083
|
+
}
|
|
2308
3084
|
const client = getClient();
|
|
2309
3085
|
const evaluation = await client.evaluate(request);
|
|
2310
3086
|
if (evaluation.decision !== "allow") {
|
|
@@ -2359,15 +3135,15 @@ async function protect(request) {
|
|
|
2359
3135
|
|
|
2360
3136
|
// src/hono.ts
|
|
2361
3137
|
var DEFAULT_CONTEXT_KEY = "atlasent";
|
|
2362
|
-
async function
|
|
3138
|
+
async function resolve2(value, c) {
|
|
2363
3139
|
return typeof value === "function" ? await value(c) : value;
|
|
2364
3140
|
}
|
|
2365
3141
|
function atlaSentGuard(options) {
|
|
2366
3142
|
const contextKey = options.key ?? DEFAULT_CONTEXT_KEY;
|
|
2367
3143
|
return async (c, next) => {
|
|
2368
3144
|
const [agent, action, ctx] = await Promise.all([
|
|
2369
|
-
|
|
2370
|
-
|
|
3145
|
+
resolve2(options.agent, c),
|
|
3146
|
+
resolve2(options.action, c),
|
|
2371
3147
|
options.context ? options.context(c) : Promise.resolve(void 0)
|
|
2372
3148
|
]);
|
|
2373
3149
|
const request = { agent, action };
|