@apoa/core 0.2.0 → 0.2.2

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 CHANGED
@@ -11,35 +11,35 @@ npm install @apoa/core
11
11
  ## Quick Start
12
12
 
13
13
  ```typescript
14
- import { createToken, checkScope, generateKeyPair, createClient } from '@apoa/core';
14
+ import { APOA, generateKeyPair } from '@apoa/core';
15
15
 
16
- // Generate keys and create a client
17
16
  const keys = await generateKeyPair();
18
- const client = createClient({ defaultSigningOptions: { privateKey: keys.privateKey } });
17
+ const apoa = new APOA({ privateKey: keys.privateKey });
19
18
 
20
- // Create a signed authorization token
21
- const token = await client.createToken({
22
- principal: { id: "did:apoa:you" },
19
+ const token = await apoa.tokens.createGrant({
20
+ principal: "did:apoa:you",
23
21
  agent: { id: "did:apoa:your-agent", name: "HomeBot Pro" },
24
- services: [{
25
- service: "nationwidemortgage.com",
26
- scopes: ["rate_lock:read", "documents:read"],
27
- constraints: { signing: false },
28
- accessMode: "browser",
29
- browserConfig: {
30
- allowedUrls: ["https://portal.nationwidemortgage.com/*"],
31
- credentialVaultRef: "1password://vault/mortgage-portal",
32
- },
33
- }],
34
- rules: [{ id: "no-signing", description: "Never sign anything", enforcement: "hard" }],
35
- expires: "2026-09-01",
22
+ service: "nationwidemortgage.com",
23
+ scopes: ["rate_lock:read", "documents:read"],
24
+ constraints: { signing: false },
25
+ expiresIn: "30d",
36
26
  });
37
27
 
38
- // Authorize actions
39
- const result = await client.authorize(token, "nationwidemortgage.com", "rate_lock:read");
28
+ const valid = await apoa.tokens.validate(token.raw, { publicKey: keys.publicKey });
29
+ console.log(valid.valid); // true
30
+
31
+ const result = await apoa.authorizations.check(
32
+ token,
33
+ "nationwidemortgage.com",
34
+ "rate_lock:read"
35
+ );
40
36
  // { authorized: true, checks: { revoked: false, scopeAllowed: true, ... } }
41
37
 
42
- const denied = await client.authorize(token, "nationwidemortgage.com", "documents:sign");
38
+ const denied = await apoa.authorizations.check(
39
+ token,
40
+ "nationwidemortgage.com",
41
+ "documents:sign"
42
+ );
43
43
  // { authorized: false, reason: "scope 'documents:sign' not in authorized scopes" }
44
44
  ```
45
45
 
@@ -58,7 +58,18 @@ const denied = await client.authorize(token, "nationwidemortgage.com", "document
58
58
  ## Two Usage Styles
59
59
 
60
60
  ```typescript
61
- // Style 1: Client instance (recommended for apps)
61
+ // Style 1: Application facade (recommended for apps)
62
+ const apoa = new APOA({ privateKey: keys.privateKey });
63
+ const token = await apoa.tokens.createGrant({
64
+ principal: "did:apoa:you",
65
+ agent: "did:apoa:agent",
66
+ service: "service.com",
67
+ scopes: ["action:read"],
68
+ expiresIn: "30d",
69
+ });
70
+ await apoa.authorizations.check(token, "service.com", "action:read");
71
+
72
+ // Style 2: Protocol client
62
73
  const client = createClient({
63
74
  revocationStore: new MemoryRevocationStore(),
64
75
  auditStore: new MemoryAuditStore(),
@@ -66,7 +77,7 @@ const client = createClient({
66
77
  });
67
78
  await client.authorize(token, "service.com", "action:read");
68
79
 
69
- // Style 2: Standalone imports (for scripts and tests)
80
+ // Style 3: Standalone imports (for scripts, tests, and adapters)
70
81
  import { checkScope, authorize, createToken } from '@apoa/core';
71
82
  checkScope(token, "service.com", "action:read");
72
83
  ```
package/dist/index.cjs CHANGED
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ APOA: () => APOA,
33
34
  APOAError: () => APOAError,
34
35
  AttenuationViolationError: () => AttenuationViolationError,
35
36
  ChainVerificationError: () => ChainVerificationError,
@@ -54,6 +55,7 @@ __export(index_exports, {
54
55
  generateKeyPair: () => generateKeyPair2,
55
56
  getAuditTrail: () => getAuditTrail,
56
57
  getAuditTrailByService: () => getAuditTrailByService,
58
+ getDelegationAncestorIds: () => getDelegationAncestorIds,
57
59
  isBeforeNotBefore: () => isBeforeNotBefore,
58
60
  isExpired: () => isExpired,
59
61
  isRevoked: () => isRevoked,
@@ -1212,6 +1214,37 @@ function checkAttenuation(parent, child, index, errors) {
1212
1214
  }
1213
1215
  }
1214
1216
 
1217
+ // src/delegation/ancestors.ts
1218
+ function getDelegationAncestorIds(input) {
1219
+ const ids = [];
1220
+ const seen = /* @__PURE__ */ new Set();
1221
+ const push = (value) => {
1222
+ if (typeof value !== "string" || value.length === 0 || seen.has(value)) {
1223
+ return;
1224
+ }
1225
+ seen.add(value);
1226
+ ids.push(value);
1227
+ };
1228
+ const token = input;
1229
+ const definition = hasDefinition(input) ? token.definition : input;
1230
+ push(token.parentToken);
1231
+ push(definition?.parentToken);
1232
+ const chain = definition?.delegationChain;
1233
+ if (Array.isArray(chain)) {
1234
+ for (const link of chain) {
1235
+ if (typeof link === "string") {
1236
+ push(link);
1237
+ } else if (link && typeof link === "object") {
1238
+ push(link.parentTokenId);
1239
+ }
1240
+ }
1241
+ }
1242
+ return ids;
1243
+ }
1244
+ function hasDefinition(input) {
1245
+ return Boolean(input && typeof input === "object" && "definition" in input);
1246
+ }
1247
+
1215
1248
  // src/jwks/index.ts
1216
1249
  var jose3 = __toESM(require("jose"), 1);
1217
1250
  async function publicKeyToJWK(publicKey, options) {
@@ -1296,7 +1329,9 @@ function createClient(options) {
1296
1329
  const defaultSigningOptions = options?.defaultSigningOptions;
1297
1330
  function mergeSigningOptions(opts) {
1298
1331
  if (!opts && !defaultSigningOptions?.privateKey) {
1299
- throw new Error("No signing options provided and no defaultSigningOptions.privateKey configured");
1332
+ throw new Error(
1333
+ "APOA needs a private key to create tokens. Pass `privateKey` to `new APOA({ privateKey })`, configure `createClient({ defaultSigningOptions: { privateKey } })`, or pass signing options to `createToken(...)`."
1334
+ );
1300
1335
  }
1301
1336
  return {
1302
1337
  ...defaultSigningOptions,
@@ -1357,8 +1392,148 @@ function createClient(options) {
1357
1392
  }
1358
1393
  };
1359
1394
  }
1395
+
1396
+ // src/apoa.ts
1397
+ var APOA = class {
1398
+ client;
1399
+ tokens;
1400
+ authorizations;
1401
+ constructor(options = {}) {
1402
+ const { privateKey, algorithm, kid, ...clientOptions } = options;
1403
+ this.client = createClient({
1404
+ ...clientOptions,
1405
+ defaultSigningOptions: privateKey ? { privateKey, algorithm, kid } : void 0
1406
+ });
1407
+ this.tokens = {
1408
+ create: (definition, signingOptions) => this.client.createToken(definition, signingOptions),
1409
+ createGrant: async (input, signingOptions) => this.client.createToken(normalizeGrantInput(input), signingOptions),
1410
+ validate: (token, validationOptions) => this.client.validateToken(token, validationOptions),
1411
+ parse: (input, format) => this.client.parseDefinition(input, format)
1412
+ };
1413
+ this.authorizations = {
1414
+ check: (token, service, action, authorizeOptions) => this.client.authorize(token, service, action, authorizeOptions)
1415
+ };
1416
+ }
1417
+ async generateKeyPair(algorithm) {
1418
+ return this.client.generateKeyPair(algorithm);
1419
+ }
1420
+ };
1421
+ function normalizeGrantInput(input) {
1422
+ const errors = [];
1423
+ if (!input || typeof input !== "object") {
1424
+ throw invalidGrantInput(["input must be an object"]);
1425
+ }
1426
+ const principal = normalizePrincipal(input.principal, errors);
1427
+ const agent = normalizeAgent(input.agent, errors);
1428
+ const services = normalizeServices(input, errors);
1429
+ const expires = normalizeExpires(input, errors);
1430
+ if (errors.length > 0 || !principal || !agent || services.length === 0 || !expires) {
1431
+ throw invalidGrantInput(errors);
1432
+ }
1433
+ return {
1434
+ principal,
1435
+ agent,
1436
+ services,
1437
+ expires,
1438
+ ...input.rules ? { rules: input.rules } : {},
1439
+ ...input.revocable !== void 0 ? { revocable: input.revocable } : {},
1440
+ ...input.delegatable !== void 0 ? { delegatable: input.delegatable } : {},
1441
+ ...input.maxDelegationDepth !== void 0 ? { maxDelegationDepth: input.maxDelegationDepth } : {},
1442
+ ...input.metadata ? { metadata: input.metadata } : {},
1443
+ ...input.agentProvider ? { agentProvider: input.agentProvider } : {},
1444
+ ...input.legal ? { legal: input.legal } : {}
1445
+ };
1446
+ }
1447
+ function normalizePrincipal(principal, errors) {
1448
+ if (typeof principal === "string" && principal.trim()) {
1449
+ return { id: principal.trim() };
1450
+ }
1451
+ if (principal && typeof principal === "object" && principal.id) {
1452
+ return principal;
1453
+ }
1454
+ errors.push("principal is required; pass a DID string or { id }");
1455
+ return void 0;
1456
+ }
1457
+ function normalizeAgent(agent, errors) {
1458
+ if (typeof agent === "string" && agent.trim()) {
1459
+ return { id: agent.trim() };
1460
+ }
1461
+ if (agent && typeof agent === "object" && agent.id) {
1462
+ return agent;
1463
+ }
1464
+ errors.push("agent is required; pass a DID string or { id }");
1465
+ return void 0;
1466
+ }
1467
+ function normalizeServices(input, errors) {
1468
+ if (input.services) {
1469
+ if (!Array.isArray(input.services) || input.services.length === 0) {
1470
+ errors.push("services must be a non-empty array when provided");
1471
+ return [];
1472
+ }
1473
+ return input.services;
1474
+ }
1475
+ if (!input.service) {
1476
+ errors.push("service is required unless services is provided");
1477
+ return [];
1478
+ }
1479
+ if (!input.scopes || !Array.isArray(input.scopes) || input.scopes.length === 0) {
1480
+ errors.push("scopes must be a non-empty array unless services is provided");
1481
+ return [];
1482
+ }
1483
+ return [{
1484
+ service: input.service,
1485
+ scopes: input.scopes,
1486
+ ...input.constraints ? { constraints: input.constraints } : {},
1487
+ ...input.accessMode ? { accessMode: input.accessMode } : {},
1488
+ ...input.browserConfig ? { browserConfig: input.browserConfig } : {},
1489
+ ...input.apiConfig ? { apiConfig: input.apiConfig } : {}
1490
+ }];
1491
+ }
1492
+ function normalizeExpires(input, errors) {
1493
+ if (input.expires && input.expiresIn) {
1494
+ errors.push("pass either expires or expiresIn, not both");
1495
+ return void 0;
1496
+ }
1497
+ if (input.expires) {
1498
+ return input.expires;
1499
+ }
1500
+ if (input.expiresIn) {
1501
+ return parseDurationFromNow(input.expiresIn);
1502
+ }
1503
+ errors.push("expires or expiresIn is required");
1504
+ return void 0;
1505
+ }
1506
+ function parseDurationFromNow(duration) {
1507
+ const match = /^(\d+)([smhd])$/.exec(duration);
1508
+ if (!match) {
1509
+ throw invalidGrantInput([
1510
+ `expiresIn must use a clear duration like '15m', '2h', or '30d'`
1511
+ ]);
1512
+ }
1513
+ const amount = Number(match[1]);
1514
+ if (!Number.isSafeInteger(amount) || amount <= 0) {
1515
+ throw invalidGrantInput(["expiresIn duration must be a positive integer"]);
1516
+ }
1517
+ const unitMs = {
1518
+ s: 1e3,
1519
+ m: 60 * 1e3,
1520
+ h: 60 * 60 * 1e3,
1521
+ d: 24 * 60 * 60 * 1e3
1522
+ };
1523
+ return new Date(Date.now() + amount * unitMs[match[2]]);
1524
+ }
1525
+ function invalidGrantInput(errors) {
1526
+ return new Error(
1527
+ [
1528
+ "Invalid APOA grant input.",
1529
+ ...errors.map((error) => `- ${error}`),
1530
+ "Minimal shape: { principal, agent, service, scopes, expiresIn }"
1531
+ ].join("\n")
1532
+ );
1533
+ }
1360
1534
  // Annotate the CommonJS export names for ESM import in node:
1361
1535
  0 && (module.exports = {
1536
+ APOA,
1362
1537
  APOAError,
1363
1538
  AttenuationViolationError,
1364
1539
  ChainVerificationError,
@@ -1383,6 +1558,7 @@ function createClient(options) {
1383
1558
  generateKeyPair,
1384
1559
  getAuditTrail,
1385
1560
  getAuditTrailByService,
1561
+ getDelegationAncestorIds,
1386
1562
  isBeforeNotBefore,
1387
1563
  isExpired,
1388
1564
  isRevoked,