@apoa/core 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,1258 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ APOAError: () => APOAError,
34
+ AttenuationViolationError: () => AttenuationViolationError,
35
+ ChainVerificationError: () => ChainVerificationError,
36
+ DefinitionValidationError: () => DefinitionValidationError,
37
+ MemoryAuditStore: () => MemoryAuditStore,
38
+ MemoryRevocationStore: () => MemoryRevocationStore,
39
+ MetadataValidationError: () => MetadataValidationError,
40
+ RevocationError: () => RevocationError,
41
+ RuleEnforcementError: () => RuleEnforcementError,
42
+ ScopeViolationError: () => ScopeViolationError,
43
+ TokenExpiredError: () => TokenExpiredError,
44
+ authorize: () => authorize,
45
+ cascadeRevoke: () => cascadeRevoke,
46
+ checkConstraint: () => checkConstraint,
47
+ checkScope: () => checkScope,
48
+ createClient: () => createClient,
49
+ createToken: () => createToken,
50
+ decodeHeader: () => decodeHeader,
51
+ delegate: () => delegate,
52
+ generateKeyPair: () => generateKeyPair2,
53
+ getAuditTrail: () => getAuditTrail,
54
+ getAuditTrailByService: () => getAuditTrailByService,
55
+ isBeforeNotBefore: () => isBeforeNotBefore,
56
+ isExpired: () => isExpired,
57
+ isRevoked: () => isRevoked,
58
+ logAction: () => logAction,
59
+ matchScope: () => matchScope,
60
+ parseDefinition: () => parseDefinition,
61
+ parseScope: () => parseScope,
62
+ revoke: () => revoke,
63
+ sign: () => sign,
64
+ signToken: () => signToken,
65
+ validateToken: () => validateToken,
66
+ verify: () => verify,
67
+ verifyAttenuation: () => verifyAttenuation,
68
+ verifyChain: () => verifyChain,
69
+ verifySignature: () => verifySignature
70
+ });
71
+ module.exports = __toCommonJS(index_exports);
72
+
73
+ // src/utils/errors.ts
74
+ var APOAError = class extends Error {
75
+ code;
76
+ constructor(message, code) {
77
+ super(message);
78
+ this.code = code;
79
+ this.name = "APOAError";
80
+ }
81
+ };
82
+ var TokenExpiredError = class extends APOAError {
83
+ expiredAt;
84
+ constructor(message, expiredAt) {
85
+ super(message, "TOKEN_EXPIRED");
86
+ this.expiredAt = expiredAt;
87
+ this.name = "TokenExpiredError";
88
+ }
89
+ };
90
+ var ScopeViolationError = class extends APOAError {
91
+ requestedScope;
92
+ availableScopes;
93
+ constructor(message, requestedScope, availableScopes) {
94
+ super(message, "SCOPE_VIOLATION");
95
+ this.requestedScope = requestedScope;
96
+ this.availableScopes = availableScopes;
97
+ this.name = "ScopeViolationError";
98
+ }
99
+ };
100
+ var AttenuationViolationError = class extends APOAError {
101
+ parentScope;
102
+ requestedScope;
103
+ constructor(message, parentScope, requestedScope) {
104
+ super(message, "ATTENUATION_VIOLATION");
105
+ this.parentScope = parentScope;
106
+ this.requestedScope = requestedScope;
107
+ this.name = "AttenuationViolationError";
108
+ }
109
+ };
110
+ var RevocationError = class extends APOAError {
111
+ revokedAt;
112
+ revokedBy;
113
+ constructor(message, revokedAt, revokedBy) {
114
+ super(message, "TOKEN_REVOKED");
115
+ this.revokedAt = revokedAt;
116
+ this.revokedBy = revokedBy;
117
+ this.name = "RevocationError";
118
+ }
119
+ };
120
+ var ChainVerificationError = class extends APOAError {
121
+ failedAt;
122
+ reason;
123
+ constructor(message, failedAt, reason) {
124
+ super(message, "CHAIN_INVALID");
125
+ this.failedAt = failedAt;
126
+ this.reason = reason;
127
+ this.name = "ChainVerificationError";
128
+ }
129
+ };
130
+ var MetadataValidationError = class extends APOAError {
131
+ field;
132
+ constructor(message, field) {
133
+ super(message, "METADATA_INVALID");
134
+ this.field = field;
135
+ this.name = "MetadataValidationError";
136
+ }
137
+ };
138
+ var RuleEnforcementError = class extends APOAError {
139
+ ruleId;
140
+ enforcement = "hard";
141
+ constructor(message, ruleId) {
142
+ super(message, "RULE_VIOLATED");
143
+ this.ruleId = ruleId;
144
+ this.name = "RuleEnforcementError";
145
+ }
146
+ };
147
+ var DefinitionValidationError = class extends APOAError {
148
+ errors;
149
+ constructor(message, errors) {
150
+ super(message, "DEFINITION_INVALID");
151
+ this.errors = errors;
152
+ this.name = "DefinitionValidationError";
153
+ }
154
+ };
155
+
156
+ // src/utils/crypto.ts
157
+ var jose = __toESM(require("jose"), 1);
158
+ async function generateKeyPair2(algorithm = "EdDSA") {
159
+ const alg = algorithm === "EdDSA" ? "EdDSA" : "ES256";
160
+ const { publicKey, privateKey } = await jose.generateKeyPair(alg, {
161
+ extractable: true
162
+ });
163
+ return { publicKey, privateKey };
164
+ }
165
+ async function sign(payload, options) {
166
+ const alg = options.algorithm === "ES256" ? "ES256" : "EdDSA";
167
+ const header = { alg };
168
+ if (options.kid) {
169
+ header.kid = options.kid;
170
+ }
171
+ const jws = await new jose.CompactSign(
172
+ new TextEncoder().encode(JSON.stringify(payload))
173
+ ).setProtectedHeader(header).sign(options.privateKey);
174
+ return jws;
175
+ }
176
+ async function verify(token, key) {
177
+ const { payload } = await jose.compactVerify(token, key);
178
+ return JSON.parse(new TextDecoder().decode(payload));
179
+ }
180
+
181
+ // src/utils/time.ts
182
+ var DEFAULT_CLOCK_SKEW = 30;
183
+ var MAX_CLOCK_SKEW = 300;
184
+ function normalizeSkew(clockSkew) {
185
+ if (clockSkew === void 0) return DEFAULT_CLOCK_SKEW;
186
+ if (clockSkew < 0) return 0;
187
+ return Math.min(clockSkew, MAX_CLOCK_SKEW);
188
+ }
189
+ function toDate(value) {
190
+ return value instanceof Date ? value : new Date(value);
191
+ }
192
+ function isExpired(expires, clockSkew, now) {
193
+ const expiresDate = toDate(expires);
194
+ const skew = normalizeSkew(clockSkew);
195
+ const currentTime = now ?? /* @__PURE__ */ new Date();
196
+ return currentTime.getTime() > expiresDate.getTime() + skew * 1e3;
197
+ }
198
+ function isBeforeNotBefore(notBefore, clockSkew, now) {
199
+ const notBeforeDate = toDate(notBefore);
200
+ const skew = normalizeSkew(clockSkew);
201
+ const currentTime = now ?? /* @__PURE__ */ new Date();
202
+ return currentTime.getTime() < notBeforeDate.getTime() - skew * 1e3;
203
+ }
204
+
205
+ // src/scope/patterns.ts
206
+ function parseScope(scope) {
207
+ if (!scope) return [];
208
+ return scope.split(":");
209
+ }
210
+ function matchScope(pattern, requested) {
211
+ if (pattern === "*") return true;
212
+ const patternParts = parseScope(pattern);
213
+ const requestedParts = parseScope(requested);
214
+ if (patternParts.length !== requestedParts.length) return false;
215
+ for (let i = 0; i < patternParts.length; i++) {
216
+ if (patternParts[i] === "*") continue;
217
+ if (patternParts[i] !== requestedParts[i]) return false;
218
+ }
219
+ return true;
220
+ }
221
+
222
+ // src/revocation/store.ts
223
+ var MemoryRevocationStore = class {
224
+ records = /* @__PURE__ */ new Map();
225
+ async add(record) {
226
+ this.records.set(record.tokenId, record);
227
+ }
228
+ async check(tokenId) {
229
+ return this.records.get(tokenId) ?? null;
230
+ }
231
+ async list(principalId) {
232
+ const results = [];
233
+ for (const record of this.records.values()) {
234
+ if (record.revokedBy === principalId) {
235
+ results.push(record);
236
+ }
237
+ }
238
+ return results;
239
+ }
240
+ };
241
+
242
+ // src/audit/store.ts
243
+ var MemoryAuditStore = class {
244
+ entries = [];
245
+ async append(entry) {
246
+ this.entries.push(entry);
247
+ }
248
+ async query(tokenId, options) {
249
+ const results = this.entries.filter((e) => e.tokenId === tokenId);
250
+ return applyFilters(results, options);
251
+ }
252
+ async queryByService(service, options) {
253
+ const results = this.entries.filter((e) => e.service === service);
254
+ return applyFilters(results, options);
255
+ }
256
+ };
257
+ function applyFilters(entries, options) {
258
+ let results = entries;
259
+ if (options?.from) {
260
+ const from = options.from.getTime();
261
+ results = results.filter((e) => e.timestamp.getTime() >= from);
262
+ }
263
+ if (options?.to) {
264
+ const to = options.to.getTime();
265
+ results = results.filter((e) => e.timestamp.getTime() <= to);
266
+ }
267
+ if (options?.action) {
268
+ results = results.filter((e) => e.action === options.action);
269
+ }
270
+ if (options?.service) {
271
+ results = results.filter((e) => e.service === options.service);
272
+ }
273
+ if (options?.result) {
274
+ results = results.filter((e) => e.result === options.result);
275
+ }
276
+ const offset = options?.offset ?? 0;
277
+ const limit = options?.limit ?? 100;
278
+ results = results.slice(offset, offset + limit);
279
+ return results;
280
+ }
281
+
282
+ // src/scope/check.ts
283
+ function checkScope(token, service, action) {
284
+ const serviceAuth = token.definition.services.find(
285
+ (s) => s.service === service
286
+ );
287
+ if (!serviceAuth) {
288
+ return {
289
+ allowed: false,
290
+ reason: `service '${service}' not found in token`
291
+ };
292
+ }
293
+ for (const scope of serviceAuth.scopes) {
294
+ if (matchScope(scope, action)) {
295
+ return {
296
+ allowed: true,
297
+ reason: `matched scope '${scope}'`,
298
+ matchedScope: scope
299
+ };
300
+ }
301
+ }
302
+ return {
303
+ allowed: false,
304
+ reason: `scope '${action}' not in authorized scopes`
305
+ };
306
+ }
307
+ function checkConstraint(token, service, constraint) {
308
+ const serviceAuth = token.definition.services.find(
309
+ (s) => s.service === service
310
+ );
311
+ if (!serviceAuth) {
312
+ return {
313
+ allowed: false,
314
+ reason: `service '${service}' not found in token`
315
+ };
316
+ }
317
+ if (!serviceAuth.constraints) {
318
+ return {
319
+ allowed: true,
320
+ reason: `no constraints defined for service '${service}'`
321
+ };
322
+ }
323
+ const value = serviceAuth.constraints[constraint];
324
+ if (value === void 0) {
325
+ return {
326
+ allowed: true,
327
+ reason: `constraint '${constraint}' not defined`
328
+ };
329
+ }
330
+ if (value === false) {
331
+ return {
332
+ allowed: false,
333
+ reason: `constraint '${constraint}' is set to false`,
334
+ constraint
335
+ };
336
+ }
337
+ return {
338
+ allowed: true,
339
+ reason: `constraint '${constraint}' is set to ${JSON.stringify(value)}`
340
+ };
341
+ }
342
+ async function authorize(token, service, action, options) {
343
+ if (options?.revocationStore) {
344
+ const record = await options.revocationStore.check(token.jti);
345
+ if (record) {
346
+ return {
347
+ authorized: false,
348
+ reason: "token has been revoked",
349
+ checks: { revoked: true }
350
+ };
351
+ }
352
+ }
353
+ const scopeResult = checkScope(token, service, action);
354
+ if (!scopeResult.allowed) {
355
+ return {
356
+ authorized: false,
357
+ reason: scopeResult.reason,
358
+ checks: { revoked: false, scopeAllowed: false }
359
+ };
360
+ }
361
+ const serviceAuth = token.definition.services.find(
362
+ (s) => s.service === service
363
+ );
364
+ if (serviceAuth?.constraints) {
365
+ const actionSegments = action.split(":");
366
+ for (const [key, value] of Object.entries(serviceAuth.constraints)) {
367
+ if (value === false && actionSegments.includes(key)) {
368
+ return {
369
+ authorized: false,
370
+ reason: `constraint '${key}' is set to false`,
371
+ checks: { revoked: false, scopeAllowed: true, constraintsPassed: false }
372
+ };
373
+ }
374
+ }
375
+ }
376
+ const rules = token.definition.rules;
377
+ const violations = [];
378
+ if (rules && rules.length > 0) {
379
+ for (const rule of rules) {
380
+ if (rule.enforcement === "hard") {
381
+ const ruleKey = rule.id.startsWith("no-") ? rule.id.slice(3) : rule.id;
382
+ const actionLower = action.toLowerCase();
383
+ if (actionLower.includes(ruleKey.toLowerCase())) {
384
+ return {
385
+ authorized: false,
386
+ reason: `hard rule '${rule.id}' violated`,
387
+ checks: {
388
+ revoked: false,
389
+ scopeAllowed: true,
390
+ constraintsPassed: true,
391
+ rulesPassed: false
392
+ }
393
+ };
394
+ }
395
+ }
396
+ }
397
+ for (const rule of rules) {
398
+ if (rule.enforcement === "soft") {
399
+ const violation = {
400
+ ruleId: rule.id,
401
+ tokenId: token.jti,
402
+ action,
403
+ service,
404
+ timestamp: /* @__PURE__ */ new Date(),
405
+ details: rule.description
406
+ };
407
+ violations.push(violation);
408
+ if (options?.auditStore) {
409
+ await options.auditStore.append({
410
+ tokenId: token.jti,
411
+ timestamp: violation.timestamp,
412
+ action,
413
+ service,
414
+ result: "escalated",
415
+ details: { ruleId: rule.id, ruleDescription: rule.description }
416
+ });
417
+ }
418
+ if (rule.onViolation) {
419
+ await rule.onViolation(violation);
420
+ }
421
+ }
422
+ }
423
+ }
424
+ return {
425
+ authorized: true,
426
+ checks: {
427
+ revoked: false,
428
+ scopeAllowed: true,
429
+ constraintsPassed: true,
430
+ rulesPassed: true
431
+ },
432
+ violations: violations.length > 0 ? violations : void 0
433
+ };
434
+ }
435
+
436
+ // src/token/parse.ts
437
+ function parseDefinition(input, format) {
438
+ const detectedFormat = format ?? (input.trimStart().startsWith("{") ? "json" : "yaml");
439
+ let raw;
440
+ try {
441
+ if (detectedFormat === "json") {
442
+ raw = JSON.parse(input);
443
+ } else {
444
+ throw new Error('YAML parsing requires the "yaml" package. Use JSON format or install "yaml".');
445
+ }
446
+ } catch (err) {
447
+ if (err instanceof SyntaxError) {
448
+ throw new DefinitionValidationError("Failed to parse definition", [
449
+ `Invalid ${detectedFormat}: ${err.message}`
450
+ ]);
451
+ }
452
+ throw err;
453
+ }
454
+ return validateDefinition(raw);
455
+ }
456
+ function validateDefinition(raw) {
457
+ const errors = [];
458
+ const warnings = [];
459
+ if (!raw || typeof raw !== "object") {
460
+ throw new DefinitionValidationError("Definition must be an object", [
461
+ "Definition must be an object"
462
+ ]);
463
+ }
464
+ const obj = raw;
465
+ if (!obj.principal || typeof obj.principal !== "object") {
466
+ errors.push("missing required field 'principal'");
467
+ } else {
468
+ const p = obj.principal;
469
+ if (!p.id || typeof p.id !== "string") {
470
+ errors.push("'principal.id' must be a non-empty string");
471
+ }
472
+ }
473
+ if (!obj.agent || typeof obj.agent !== "object") {
474
+ errors.push("missing required field 'agent'");
475
+ } else {
476
+ const a = obj.agent;
477
+ if (!a.id || typeof a.id !== "string") {
478
+ errors.push("'agent.id' must be a non-empty string");
479
+ }
480
+ }
481
+ if (!obj.services || !Array.isArray(obj.services) || obj.services.length === 0) {
482
+ errors.push("'services' must be a non-empty array");
483
+ } else {
484
+ for (let i = 0; i < obj.services.length; i++) {
485
+ const svc = obj.services[i];
486
+ if (!svc.service || typeof svc.service !== "string") {
487
+ errors.push(`services[${i}].service must be a non-empty string`);
488
+ }
489
+ if (!svc.scopes || !Array.isArray(svc.scopes) || svc.scopes.length === 0) {
490
+ errors.push(`services[${i}].scopes must be a non-empty array`);
491
+ }
492
+ validateServiceAccessMode(svc, i, errors, warnings);
493
+ }
494
+ }
495
+ if (obj.expires === void 0 || obj.expires === null) {
496
+ errors.push("missing required field 'expires'");
497
+ }
498
+ if (obj.metadata !== void 0) {
499
+ validateMetadata(obj.metadata, errors);
500
+ }
501
+ if (obj.agentProvider !== void 0) {
502
+ validateAgentProvider(obj.agentProvider, errors);
503
+ }
504
+ if (obj.legal !== void 0) {
505
+ validateLegalFramework(obj.legal, errors);
506
+ }
507
+ if (errors.length > 0) {
508
+ const err = new DefinitionValidationError(
509
+ `Invalid definition: ${errors.length} problem(s) found`,
510
+ errors
511
+ );
512
+ err.warnings = warnings;
513
+ throw err;
514
+ }
515
+ const def = obj;
516
+ if (typeof def.expires === "string") {
517
+ def.expires = def.expires;
518
+ }
519
+ if (typeof def.notBefore === "string") {
520
+ def.notBefore = def.notBefore;
521
+ }
522
+ return def;
523
+ }
524
+ function validateMetadata(metadata, errors) {
525
+ if (typeof metadata !== "object" || metadata === null || Array.isArray(metadata)) {
526
+ errors.push("'metadata' must be a plain object");
527
+ return;
528
+ }
529
+ const keys = Object.keys(metadata);
530
+ if (keys.length > 20) {
531
+ errors.push(`metadata has ${keys.length} keys (max 20)`);
532
+ }
533
+ const serialized = JSON.stringify(metadata);
534
+ if (serialized.length > 1024) {
535
+ errors.push(
536
+ `metadata serialized size is ${serialized.length} bytes (max 1024)`
537
+ );
538
+ }
539
+ const record = metadata;
540
+ for (const key of keys) {
541
+ const value = record[key];
542
+ if (value !== null && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
543
+ errors.push(
544
+ `metadata['${key}'] has invalid type '${typeof value}' (must be string | number | boolean | null)`
545
+ );
546
+ }
547
+ }
548
+ }
549
+ function validateServiceAccessMode(svc, index, errors, warnings) {
550
+ const accessMode = svc.accessMode;
551
+ if (accessMode === "browser") {
552
+ if (!svc.browserConfig || typeof svc.browserConfig !== "object") {
553
+ errors.push(
554
+ `services[${index}] has accessMode 'browser' but no browserConfig. Browser-based access requires explicit URL restrictions and a credential vault reference.`
555
+ );
556
+ return;
557
+ }
558
+ const bc = svc.browserConfig;
559
+ if (!bc.allowedUrls || !Array.isArray(bc.allowedUrls) || bc.allowedUrls.length === 0) {
560
+ errors.push(
561
+ `services[${index}].browserConfig.allowedUrls must be a non-empty array`
562
+ );
563
+ }
564
+ if (!bc.credentialVaultRef || typeof bc.credentialVaultRef !== "string") {
565
+ errors.push(
566
+ `services[${index}].browserConfig.credentialVaultRef must be a non-empty string`
567
+ );
568
+ }
569
+ if (bc.maxSessionDuration !== void 0) {
570
+ if (typeof bc.maxSessionDuration !== "number" || bc.maxSessionDuration > 86400) {
571
+ errors.push(
572
+ `services[${index}].browserConfig.maxSessionDuration must be <= 86400 seconds`
573
+ );
574
+ }
575
+ }
576
+ }
577
+ if (accessMode === "api" && svc.browserConfig) {
578
+ warnings.push(
579
+ `services[${index}] has accessMode 'api' but browserConfig is present (will be ignored)`
580
+ );
581
+ }
582
+ }
583
+ function validateAgentProvider(provider, errors) {
584
+ if (typeof provider !== "object" || provider === null) {
585
+ errors.push("'agentProvider' must be an object");
586
+ return;
587
+ }
588
+ const p = provider;
589
+ if (!p.name || typeof p.name !== "string") {
590
+ errors.push("'agentProvider.name' is required and must be a non-empty string");
591
+ }
592
+ }
593
+ function validateLegalFramework(legal, errors) {
594
+ if (typeof legal !== "object" || legal === null) {
595
+ errors.push("'legal' must be an object");
596
+ return;
597
+ }
598
+ const l = legal;
599
+ if (l.model !== "provider-as-agent") {
600
+ errors.push("'legal.model' must be 'provider-as-agent'");
601
+ }
602
+ if (l.jurisdiction !== void 0) {
603
+ if (typeof l.jurisdiction !== "string") {
604
+ errors.push("'legal.jurisdiction' must be a string");
605
+ } else {
606
+ const iso3166Pattern = /^[A-Z]{2}(-[A-Z0-9]{1,3})?$/;
607
+ if (!iso3166Pattern.test(l.jurisdiction)) {
608
+ errors.push(
609
+ `'legal.jurisdiction' must be ISO 3166 format (e.g., "US", "US-CA", "GB"), got '${l.jurisdiction}'`
610
+ );
611
+ }
612
+ }
613
+ }
614
+ }
615
+
616
+ // src/revocation/revoke.ts
617
+ var defaultStore = new MemoryRevocationStore();
618
+ async function revoke(tokenId, options, store) {
619
+ const s = store ?? defaultStore;
620
+ const record = {
621
+ tokenId,
622
+ revokedAt: /* @__PURE__ */ new Date(),
623
+ revokedBy: options.revokedBy,
624
+ reason: options.reason,
625
+ cascaded: []
626
+ };
627
+ await s.add(record);
628
+ return record;
629
+ }
630
+ async function isRevoked(tokenId, store) {
631
+ const s = store ?? defaultStore;
632
+ const record = await s.check(tokenId);
633
+ return record !== null;
634
+ }
635
+
636
+ // src/audit/log.ts
637
+ var defaultStore2 = new MemoryAuditStore();
638
+ async function logAction(tokenId, entry, store) {
639
+ const s = store ?? defaultStore2;
640
+ const fullEntry = {
641
+ ...entry,
642
+ tokenId,
643
+ timestamp: /* @__PURE__ */ new Date()
644
+ };
645
+ await s.append(fullEntry);
646
+ }
647
+
648
+ // src/audit/trail.ts
649
+ var defaultStore3 = new MemoryAuditStore();
650
+ async function getAuditTrail(tokenId, options, store) {
651
+ const s = store ?? defaultStore3;
652
+ return s.query(tokenId, options);
653
+ }
654
+ async function getAuditTrailByService(service, options, store) {
655
+ const s = store ?? defaultStore3;
656
+ return s.queryByService(service, options);
657
+ }
658
+
659
+ // src/token/sign.ts
660
+ var jose2 = __toESM(require("jose"), 1);
661
+ async function signToken(payload, options) {
662
+ const alg = options.algorithm === "ES256" ? "ES256" : "EdDSA";
663
+ const header = { alg };
664
+ if (options.kid) {
665
+ header.kid = options.kid;
666
+ }
667
+ const jws = await new jose2.CompactSign(
668
+ new TextEncoder().encode(JSON.stringify(payload))
669
+ ).setProtectedHeader(header).sign(options.privateKey);
670
+ return jws;
671
+ }
672
+ function decodeHeader(token) {
673
+ const [headerB64] = token.split(".");
674
+ if (!headerB64) throw new Error("Invalid JWS: missing header");
675
+ return JSON.parse(
676
+ new TextDecoder().decode(jose2.base64url.decode(headerB64))
677
+ );
678
+ }
679
+ async function verifySignature(token, key) {
680
+ const { payload } = await jose2.compactVerify(token, key);
681
+ return JSON.parse(new TextDecoder().decode(payload));
682
+ }
683
+
684
+ // src/token/create.ts
685
+ async function createToken(definition, options) {
686
+ if (definition.metadata) {
687
+ validateMetadata2(definition.metadata);
688
+ }
689
+ const jti = crypto.randomUUID();
690
+ const issuedAt = /* @__PURE__ */ new Date();
691
+ const issuer = definition.principal.id;
692
+ const audience = definition.services.map((s) => s.service);
693
+ const payload = {
694
+ jti,
695
+ iss: issuer,
696
+ aud: audience,
697
+ iat: Math.floor(issuedAt.getTime() / 1e3),
698
+ exp: Math.floor(
699
+ (typeof definition.expires === "string" ? new Date(definition.expires) : definition.expires).getTime() / 1e3
700
+ ),
701
+ definition: serializeDefinition(definition)
702
+ };
703
+ if (definition.notBefore) {
704
+ payload.nbf = Math.floor(
705
+ (typeof definition.notBefore === "string" ? new Date(definition.notBefore) : definition.notBefore).getTime() / 1e3
706
+ );
707
+ }
708
+ const raw = await signToken(payload, options);
709
+ const sizeBytes = new TextEncoder().encode(raw).length;
710
+ if (sizeBytes > 8192) {
711
+ throw new MetadataValidationError(
712
+ `Token size is ${sizeBytes} bytes (max 8192). Issue multiple tokens for large authorization surfaces.`
713
+ );
714
+ }
715
+ const token = {
716
+ jti,
717
+ definition,
718
+ issuedAt,
719
+ signature: raw.split(".")[2],
720
+ issuer,
721
+ audience,
722
+ raw
723
+ };
724
+ return token;
725
+ }
726
+ function validateMetadata2(metadata) {
727
+ const keys = Object.keys(metadata);
728
+ if (keys.length > 20) {
729
+ throw new MetadataValidationError(
730
+ `Metadata has ${keys.length} keys (max 20)`
731
+ );
732
+ }
733
+ const serialized = JSON.stringify(metadata);
734
+ if (serialized.length > 1024) {
735
+ throw new MetadataValidationError(
736
+ `Metadata serialized size is ${serialized.length} bytes (max 1024)`
737
+ );
738
+ }
739
+ for (const key of keys) {
740
+ const value = metadata[key];
741
+ if (value !== null && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
742
+ throw new MetadataValidationError(
743
+ `Metadata key '${key}' has invalid type '${typeof value}'`,
744
+ key
745
+ );
746
+ }
747
+ }
748
+ }
749
+ function serializeDefinition(def) {
750
+ return {
751
+ ...def,
752
+ expires: def.expires instanceof Date ? def.expires.toISOString() : def.expires,
753
+ notBefore: def.notBefore instanceof Date ? def.notBefore.toISOString() : def.notBefore,
754
+ // Strip non-serializable onViolation callbacks from rules
755
+ rules: def.rules?.map(({ onViolation: _, ...rest }) => rest)
756
+ };
757
+ }
758
+
759
+ // src/token/validate.ts
760
+ async function validateToken(token, options) {
761
+ const errors = [];
762
+ const warnings = [];
763
+ let parsedToken;
764
+ const rawJwt = typeof token === "string" ? token : token.raw;
765
+ let publicKey;
766
+ if (options.publicKey) {
767
+ publicKey = options.publicKey;
768
+ } else if (options.keyResolver) {
769
+ try {
770
+ const header = decodeHeader(rawJwt);
771
+ const kid = header.kid;
772
+ if (kid) {
773
+ const resolved = await options.keyResolver.resolve(kid);
774
+ if (resolved) {
775
+ publicKey = resolved;
776
+ } else {
777
+ errors.push(`Key resolver returned null for kid '${kid}'`);
778
+ }
779
+ } else {
780
+ errors.push("Token has no kid in header and keyResolver requires one");
781
+ }
782
+ } catch {
783
+ errors.push("Failed to decode token header for key resolution");
784
+ }
785
+ } else if (options.publicKeyResolver) {
786
+ try {
787
+ const header = decodeHeader(rawJwt);
788
+ const payloadB64 = rawJwt.split(".")[1];
789
+ if (payloadB64) {
790
+ const payloadJson = new TextDecoder().decode(
791
+ base64urlDecode(payloadB64)
792
+ );
793
+ const payload2 = JSON.parse(payloadJson);
794
+ const issuer = payload2.iss;
795
+ if (issuer) {
796
+ publicKey = await options.publicKeyResolver(issuer);
797
+ } else {
798
+ errors.push("Token has no issuer (iss) claim for publicKeyResolver");
799
+ }
800
+ }
801
+ } catch {
802
+ errors.push("Failed to decode token for issuer-based key resolution");
803
+ }
804
+ } else {
805
+ errors.push(
806
+ "No key provided: supply publicKey, keyResolver, or publicKeyResolver"
807
+ );
808
+ }
809
+ let payload;
810
+ if (publicKey) {
811
+ try {
812
+ payload = await verifySignature(rawJwt, publicKey);
813
+ } catch {
814
+ errors.push("Signature verification failed");
815
+ }
816
+ }
817
+ if (!payload) {
818
+ try {
819
+ const payloadB64 = rawJwt.split(".")[1];
820
+ if (payloadB64) {
821
+ payload = JSON.parse(
822
+ new TextDecoder().decode(base64urlDecode(payloadB64))
823
+ );
824
+ }
825
+ } catch {
826
+ }
827
+ }
828
+ if (!payload) {
829
+ return { valid: false, errors: errors.length > 0 ? errors : ["Unable to decode token"], warnings };
830
+ }
831
+ try {
832
+ parsedToken = payloadToToken(payload, rawJwt);
833
+ } catch {
834
+ errors.push("Token payload has invalid structure");
835
+ }
836
+ const clockSkew = options.clockSkew;
837
+ if (payload.exp !== void 0) {
838
+ const expiresDate = new Date(payload.exp * 1e3);
839
+ if (isExpired(expiresDate, clockSkew)) {
840
+ errors.push(`Token expired at ${expiresDate.toISOString()}`);
841
+ }
842
+ } else {
843
+ errors.push("Token has no expiration (exp) claim");
844
+ }
845
+ if (payload.nbf !== void 0) {
846
+ const notBeforeDate = new Date(payload.nbf * 1e3);
847
+ if (isBeforeNotBefore(notBeforeDate, clockSkew)) {
848
+ errors.push(`Token not valid before ${notBeforeDate.toISOString()}`);
849
+ }
850
+ }
851
+ const checkRevocation = options.checkRevocation !== false;
852
+ if (checkRevocation && options.revocationStore && payload.jti) {
853
+ const revRecord = await options.revocationStore.check(
854
+ payload.jti
855
+ );
856
+ if (revRecord) {
857
+ errors.push(
858
+ `Token has been revoked (at ${revRecord.revokedAt.toISOString()} by ${revRecord.revokedBy})`
859
+ );
860
+ }
861
+ }
862
+ const sizeBytes = new TextEncoder().encode(rawJwt).length;
863
+ if (sizeBytes > 4096) {
864
+ warnings.push(`Token size is ${sizeBytes} bytes (exceeds 4KB recommended limit)`);
865
+ }
866
+ return {
867
+ valid: errors.length === 0,
868
+ errors,
869
+ token: parsedToken,
870
+ warnings: warnings.length > 0 ? warnings : void 0
871
+ };
872
+ }
873
+ function payloadToToken(payload, raw) {
874
+ const definition = payload.definition;
875
+ if (!definition) throw new Error("Missing definition in payload");
876
+ return {
877
+ jti: payload.jti,
878
+ definition,
879
+ issuedAt: new Date(payload.iat * 1e3),
880
+ signature: raw.split(".")[2],
881
+ issuer: payload.iss,
882
+ audience: payload.aud,
883
+ parentToken: definition.parentToken ?? void 0,
884
+ raw
885
+ };
886
+ }
887
+ function base64urlDecode(input) {
888
+ const padded = input + "=".repeat((4 - input.length % 4) % 4);
889
+ const binary = atob(padded.replace(/-/g, "+").replace(/_/g, "/"));
890
+ const bytes = new Uint8Array(binary.length);
891
+ for (let i = 0; i < binary.length; i++) {
892
+ bytes[i] = binary.charCodeAt(i);
893
+ }
894
+ return bytes;
895
+ }
896
+
897
+ // src/revocation/cascade.ts
898
+ var defaultStore4 = new MemoryRevocationStore();
899
+ async function cascadeRevoke(parentTokenId, childTokenIds, options, store) {
900
+ const s = store ?? defaultStore4;
901
+ const revokedAt = /* @__PURE__ */ new Date();
902
+ for (const childId of childTokenIds) {
903
+ const childRecord = {
904
+ tokenId: childId,
905
+ revokedAt,
906
+ revokedBy: options.revokedBy,
907
+ reason: options.reason ? `Cascade: ${options.reason}` : `Cascade revocation from parent ${parentTokenId}`,
908
+ cascaded: []
909
+ };
910
+ await s.add(childRecord);
911
+ }
912
+ const parentRecord = {
913
+ tokenId: parentTokenId,
914
+ revokedAt,
915
+ revokedBy: options.revokedBy,
916
+ reason: options.reason,
917
+ cascaded: childTokenIds
918
+ };
919
+ await s.add(parentRecord);
920
+ return parentRecord;
921
+ }
922
+
923
+ // src/scope/attenuate.ts
924
+ function verifyAttenuation(parent, child, currentDepth = 0) {
925
+ const parentDef = parent.definition;
926
+ if (parentDef.delegatable === false) {
927
+ throw new AttenuationViolationError(
928
+ "Parent token does not allow delegation",
929
+ parentDef.services.flatMap((s) => s.scopes),
930
+ child.services.flatMap((s) => s.scopes)
931
+ );
932
+ }
933
+ if (parentDef.maxDelegationDepth !== void 0) {
934
+ if (currentDepth >= parentDef.maxDelegationDepth) {
935
+ throw new AttenuationViolationError(
936
+ `Delegation depth ${currentDepth + 1} exceeds maxDelegationDepth ${parentDef.maxDelegationDepth}`,
937
+ parentDef.services.flatMap((s) => s.scopes),
938
+ child.services.flatMap((s) => s.scopes)
939
+ );
940
+ }
941
+ }
942
+ if (child.expires !== void 0) {
943
+ const childExp = child.expires instanceof Date ? child.expires.getTime() : new Date(child.expires).getTime();
944
+ const parentExp = parentDef.expires instanceof Date ? parentDef.expires.getTime() : new Date(parentDef.expires).getTime();
945
+ if (childExp > parentExp) {
946
+ throw new AttenuationViolationError(
947
+ "Child token expiration exceeds parent expiration",
948
+ parentDef.services.flatMap((s) => s.scopes),
949
+ child.services.flatMap((s) => s.scopes)
950
+ );
951
+ }
952
+ }
953
+ for (const childService of child.services) {
954
+ const parentService = parentDef.services.find(
955
+ (s) => s.service === childService.service
956
+ );
957
+ if (!parentService) {
958
+ throw new AttenuationViolationError(
959
+ `Child requests service '${childService.service}' not in parent token`,
960
+ parentDef.services.flatMap((s) => s.scopes),
961
+ child.services.flatMap((s) => s.scopes)
962
+ );
963
+ }
964
+ verifyScopeSubset(parentService, childService);
965
+ verifyConstraintsNotRelaxed(parentService, childService);
966
+ }
967
+ if (parentDef.rules && parentDef.rules.length > 0) {
968
+ verifyRulesNotRemoved(parentDef.rules.map((r) => r.id), child);
969
+ }
970
+ }
971
+ function verifyScopeSubset(parent, child) {
972
+ for (const childScope of child.scopes) {
973
+ const matched = parent.scopes.some(
974
+ (parentScope) => matchScope(parentScope, childScope)
975
+ );
976
+ if (!matched) {
977
+ throw new AttenuationViolationError(
978
+ `Child scope '${childScope}' on service '${child.service}' is not covered by parent scopes [${parent.scopes.join(", ")}]`,
979
+ parent.scopes,
980
+ child.scopes
981
+ );
982
+ }
983
+ }
984
+ }
985
+ function verifyConstraintsNotRelaxed(parent, child) {
986
+ if (!parent.constraints) return;
987
+ for (const [key, parentValue] of Object.entries(parent.constraints)) {
988
+ if (parentValue === false) {
989
+ const childValue = child.constraints?.[key];
990
+ if (childValue === true || childValue === void 0) {
991
+ if (childValue === true) {
992
+ throw new AttenuationViolationError(
993
+ `Child relaxes constraint '${key}' on service '${child.service}' (parent: false, child: true)`,
994
+ parent.scopes,
995
+ child.scopes
996
+ );
997
+ }
998
+ }
999
+ }
1000
+ }
1001
+ }
1002
+ function verifyRulesNotRemoved(parentRuleIds, child) {
1003
+ const childRuleIds = new Set(child.rules?.map((r) => r.id) ?? []);
1004
+ for (const parentRuleId of parentRuleIds) {
1005
+ if (!childRuleIds.has(parentRuleId)) {
1006
+ throw new AttenuationViolationError(
1007
+ `Child removes parent rule '${parentRuleId}'. Rules can only be added, not removed.`,
1008
+ [],
1009
+ []
1010
+ );
1011
+ }
1012
+ }
1013
+ }
1014
+
1015
+ // src/delegation/chain.ts
1016
+ async function delegate(parentToken, childDef, options) {
1017
+ const currentDepth = countDepth(parentToken);
1018
+ verifyAttenuation(parentToken, childDef, currentDepth);
1019
+ const parentDef = parentToken.definition;
1020
+ const parentRules = parentDef.rules ?? [];
1021
+ const childExtraRules = (childDef.rules ?? []).filter(
1022
+ (cr) => !parentRules.some((pr) => pr.id === cr.id)
1023
+ );
1024
+ const mergedRules = [...parentRules, ...childExtraRules];
1025
+ const childDepth = currentDepth + 1;
1026
+ const childMetadata = {
1027
+ ...childDef.metadata,
1028
+ _delegationDepth: childDepth
1029
+ };
1030
+ const fullDefinition = {
1031
+ principal: parentDef.principal,
1032
+ // Inherited — cannot be overridden
1033
+ agent: childDef.agent,
1034
+ agentProvider: parentDef.agentProvider,
1035
+ services: childDef.services,
1036
+ rules: mergedRules.length > 0 ? mergedRules : void 0,
1037
+ expires: childDef.expires ?? parentDef.expires,
1038
+ revocable: parentDef.revocable,
1039
+ delegatable: parentDef.delegatable,
1040
+ maxDelegationDepth: parentDef.maxDelegationDepth,
1041
+ metadata: childMetadata,
1042
+ legal: parentDef.legal
1043
+ };
1044
+ const childToken = await createToken(fullDefinition, options);
1045
+ childToken.parentToken = parentToken.jti;
1046
+ return childToken;
1047
+ }
1048
+ function countDepth(token) {
1049
+ const stored = token.definition.metadata?._delegationDepth;
1050
+ if (typeof stored === "number") return stored;
1051
+ return token.parentToken ? 1 : 0;
1052
+ }
1053
+
1054
+ // src/delegation/verify.ts
1055
+ async function verifyChain(chain, store) {
1056
+ const errors = [];
1057
+ let failedAt;
1058
+ if (chain.length === 0) {
1059
+ return {
1060
+ valid: false,
1061
+ depth: 0,
1062
+ errors: ["Chain is empty"],
1063
+ root: void 0,
1064
+ leaf: void 0
1065
+ };
1066
+ }
1067
+ if (chain.length === 1) {
1068
+ const token = chain[0];
1069
+ await checkTokenValidity(token, 0, errors, store);
1070
+ return {
1071
+ valid: errors.length === 0,
1072
+ depth: 0,
1073
+ errors,
1074
+ failedAt: errors.length > 0 ? 0 : void 0,
1075
+ root: token,
1076
+ leaf: token
1077
+ };
1078
+ }
1079
+ for (let i = 0; i < chain.length; i++) {
1080
+ const token = chain[i];
1081
+ const errorsBefore = errors.length;
1082
+ await checkTokenValidity(token, i, errors, store);
1083
+ if (i > 0) {
1084
+ const parent = chain[i - 1];
1085
+ checkAttenuation(parent, token, i, errors);
1086
+ if (token.parentToken !== parent.jti) {
1087
+ errors.push(
1088
+ `Chain link ${i}: parentToken '${token.parentToken}' does not match parent jti '${parent.jti}'`
1089
+ );
1090
+ }
1091
+ }
1092
+ if (failedAt === void 0 && errors.length > errorsBefore) {
1093
+ failedAt = i;
1094
+ }
1095
+ }
1096
+ return {
1097
+ valid: errors.length === 0,
1098
+ depth: chain.length - 1,
1099
+ errors,
1100
+ failedAt,
1101
+ root: chain[0],
1102
+ leaf: chain[chain.length - 1]
1103
+ };
1104
+ }
1105
+ async function checkTokenValidity(token, index, errors, store) {
1106
+ if (isExpired(token.definition.expires, 0)) {
1107
+ errors.push(`Chain link ${index}: token '${token.jti}' has expired`);
1108
+ }
1109
+ if (store) {
1110
+ const record = await store.check(token.jti);
1111
+ if (record) {
1112
+ errors.push(
1113
+ `Chain link ${index}: token '${token.jti}' has been revoked`
1114
+ );
1115
+ }
1116
+ }
1117
+ }
1118
+ function checkAttenuation(parent, child, index, errors) {
1119
+ for (const childService of child.definition.services) {
1120
+ const parentService = parent.definition.services.find(
1121
+ (s) => s.service === childService.service
1122
+ );
1123
+ if (!parentService) {
1124
+ errors.push(
1125
+ `Chain link ${index}: service '${childService.service}' not in parent token`
1126
+ );
1127
+ continue;
1128
+ }
1129
+ for (const childScope of childService.scopes) {
1130
+ const covered = parentService.scopes.some(
1131
+ (ps) => matchScope(ps, childScope)
1132
+ );
1133
+ if (!covered) {
1134
+ errors.push(
1135
+ `Chain link ${index}: scope '${childScope}' on '${childService.service}' not covered by parent`
1136
+ );
1137
+ }
1138
+ }
1139
+ }
1140
+ const childExp = child.definition.expires instanceof Date ? child.definition.expires.getTime() : new Date(child.definition.expires).getTime();
1141
+ const parentExp = parent.definition.expires instanceof Date ? parent.definition.expires.getTime() : new Date(parent.definition.expires).getTime();
1142
+ if (childExp > parentExp) {
1143
+ errors.push(
1144
+ `Chain link ${index}: child expiration exceeds parent expiration`
1145
+ );
1146
+ }
1147
+ }
1148
+
1149
+ // src/client.ts
1150
+ function createClient(options) {
1151
+ const revocationStore = options?.revocationStore ?? new MemoryRevocationStore();
1152
+ const auditStore = options?.auditStore ?? new MemoryAuditStore();
1153
+ const keyResolver = options?.keyResolver;
1154
+ const defaultSigningOptions = options?.defaultSigningOptions;
1155
+ function mergeSigningOptions(opts) {
1156
+ if (!opts && !defaultSigningOptions?.privateKey) {
1157
+ throw new Error("No signing options provided and no defaultSigningOptions.privateKey configured");
1158
+ }
1159
+ return {
1160
+ ...defaultSigningOptions,
1161
+ ...opts
1162
+ };
1163
+ }
1164
+ return {
1165
+ async createToken(definition, opts) {
1166
+ return createToken(definition, mergeSigningOptions(opts));
1167
+ },
1168
+ parseDefinition(input, format) {
1169
+ return parseDefinition(input, format);
1170
+ },
1171
+ async validateToken(token, opts) {
1172
+ return validateToken(token, {
1173
+ ...opts,
1174
+ keyResolver: opts?.keyResolver ?? keyResolver,
1175
+ revocationStore,
1176
+ checkRevocation: opts?.checkRevocation ?? true
1177
+ });
1178
+ },
1179
+ async generateKeyPair(algorithm) {
1180
+ return generateKeyPair2(algorithm);
1181
+ },
1182
+ checkScope(token, service, action) {
1183
+ return checkScope(token, service, action);
1184
+ },
1185
+ checkConstraint(token, service, constraint) {
1186
+ return checkConstraint(token, service, constraint);
1187
+ },
1188
+ async authorize(token, service, action, opts) {
1189
+ return authorize(token, service, action, {
1190
+ ...opts,
1191
+ revocationStore,
1192
+ auditStore
1193
+ });
1194
+ },
1195
+ async delegate(parentToken, childDef, opts) {
1196
+ return delegate(parentToken, childDef, mergeSigningOptions(opts));
1197
+ },
1198
+ async verifyChain(chain) {
1199
+ return verifyChain(chain, revocationStore);
1200
+ },
1201
+ async revoke(tokenId, opts) {
1202
+ return revoke(tokenId, opts, revocationStore);
1203
+ },
1204
+ async isRevoked(tokenId) {
1205
+ return isRevoked(tokenId, revocationStore);
1206
+ },
1207
+ async logAction(tokenId, entry) {
1208
+ return logAction(tokenId, entry, auditStore);
1209
+ },
1210
+ async getAuditTrail(tokenId, opts) {
1211
+ return getAuditTrail(tokenId, opts, auditStore);
1212
+ },
1213
+ async getAuditTrailByService(service, opts) {
1214
+ return getAuditTrailByService(service, opts, auditStore);
1215
+ }
1216
+ };
1217
+ }
1218
+ // Annotate the CommonJS export names for ESM import in node:
1219
+ 0 && (module.exports = {
1220
+ APOAError,
1221
+ AttenuationViolationError,
1222
+ ChainVerificationError,
1223
+ DefinitionValidationError,
1224
+ MemoryAuditStore,
1225
+ MemoryRevocationStore,
1226
+ MetadataValidationError,
1227
+ RevocationError,
1228
+ RuleEnforcementError,
1229
+ ScopeViolationError,
1230
+ TokenExpiredError,
1231
+ authorize,
1232
+ cascadeRevoke,
1233
+ checkConstraint,
1234
+ checkScope,
1235
+ createClient,
1236
+ createToken,
1237
+ decodeHeader,
1238
+ delegate,
1239
+ generateKeyPair,
1240
+ getAuditTrail,
1241
+ getAuditTrailByService,
1242
+ isBeforeNotBefore,
1243
+ isExpired,
1244
+ isRevoked,
1245
+ logAction,
1246
+ matchScope,
1247
+ parseDefinition,
1248
+ parseScope,
1249
+ revoke,
1250
+ sign,
1251
+ signToken,
1252
+ validateToken,
1253
+ verify,
1254
+ verifyAttenuation,
1255
+ verifyChain,
1256
+ verifySignature
1257
+ });
1258
+ //# sourceMappingURL=index.cjs.map