@apoa/core 0.2.1 → 0.2.3
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 +53 -24
- package/dist/index.cjs +144 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -1
- package/dist/index.d.ts +51 -1
- package/dist/index.js +143 -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
|
|
|
@@ -55,19 +55,48 @@ const denied = await client.authorize(token, "nationwidemortgage.com", "document
|
|
|
55
55
|
- **Browser mode**: credential vault injection config (the AI never sees passwords)
|
|
56
56
|
- **Comprehensive test suite** with cross-SDK fixture verification against the [Python SDK](https://pypi.org/project/apoa/)
|
|
57
57
|
|
|
58
|
-
##
|
|
58
|
+
## Usage Styles
|
|
59
|
+
|
|
60
|
+
### Application facade
|
|
61
|
+
|
|
62
|
+
Recommended for apps. Configure keys once, then use namespaced resources.
|
|
59
63
|
|
|
60
64
|
```typescript
|
|
61
|
-
|
|
65
|
+
import { APOA } from '@apoa/core';
|
|
66
|
+
|
|
67
|
+
const apoa = new APOA({ privateKey: keys.privateKey });
|
|
68
|
+
const token = await apoa.tokens.createGrant({
|
|
69
|
+
principal: "did:apoa:you",
|
|
70
|
+
agent: "did:apoa:agent",
|
|
71
|
+
service: "service.com",
|
|
72
|
+
scopes: ["action:read"],
|
|
73
|
+
expiresIn: "30d",
|
|
74
|
+
});
|
|
75
|
+
await apoa.authorizations.check(token, "service.com", "action:read");
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Protocol client
|
|
79
|
+
|
|
80
|
+
Use this when you want direct access to stores, resolvers, and protocol-level options.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
import { createClient, MemoryAuditStore, MemoryRevocationStore } from '@apoa/core';
|
|
84
|
+
|
|
62
85
|
const client = createClient({
|
|
63
86
|
revocationStore: new MemoryRevocationStore(),
|
|
64
87
|
auditStore: new MemoryAuditStore(),
|
|
65
88
|
defaultSigningOptions: { privateKey: keys.privateKey },
|
|
66
89
|
});
|
|
67
90
|
await client.authorize(token, "service.com", "action:read");
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Standalone imports
|
|
68
94
|
|
|
69
|
-
|
|
95
|
+
Useful for scripts, tests, adapters, and focused protocol operations.
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
70
98
|
import { checkScope, authorize, createToken } from '@apoa/core';
|
|
99
|
+
|
|
71
100
|
checkScope(token, "service.com", "action:read");
|
|
72
101
|
```
|
|
73
102
|
|
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,
|
|
@@ -1328,7 +1329,9 @@ function createClient(options) {
|
|
|
1328
1329
|
const defaultSigningOptions = options?.defaultSigningOptions;
|
|
1329
1330
|
function mergeSigningOptions(opts) {
|
|
1330
1331
|
if (!opts && !defaultSigningOptions?.privateKey) {
|
|
1331
|
-
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
|
+
);
|
|
1332
1335
|
}
|
|
1333
1336
|
return {
|
|
1334
1337
|
...defaultSigningOptions,
|
|
@@ -1389,8 +1392,148 @@ function createClient(options) {
|
|
|
1389
1392
|
}
|
|
1390
1393
|
};
|
|
1391
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
|
+
}
|
|
1392
1534
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1393
1535
|
0 && (module.exports = {
|
|
1536
|
+
APOA,
|
|
1394
1537
|
APOAError,
|
|
1395
1538
|
AttenuationViolationError,
|
|
1396
1539
|
ChainVerificationError,
|