@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 +34 -23
- package/dist/index.cjs +177 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +69 -1
- package/dist/index.d.ts +69 -1
- package/dist/index.js +175 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,35 +11,35 @@ npm install @apoa/core
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
13
13
|
```typescript
|
|
14
|
-
import {
|
|
14
|
+
import { APOA, generateKeyPair } from '@apoa/core';
|
|
15
15
|
|
|
16
|
-
// Generate keys and create a client
|
|
17
16
|
const keys = await generateKeyPair();
|
|
18
|
-
const
|
|
17
|
+
const apoa = new APOA({ privateKey: keys.privateKey });
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
39
|
-
|
|
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
|
|
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:
|
|
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
|
|
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(
|
|
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,
|