@agentunion/fastaun-browser 0.5.4 → 0.5.6
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/CHANGELOG.md +130 -94
- package/_packed_docs/AUN/347/246/273/347/272/277/346/216/250/351/200/201/346/216/245/345/205/245/344/270/216/346/274/224/347/244/272/346/214/207/345/215/227.md +290 -0
- package/_packed_docs/AUN/347/246/273/347/272/277/346/216/250/351/200/201/346/234/215/345/212/241/346/236/266/346/236/204/344/270/216/350/257/246/347/273/206/345/256/236/347/216/260/350/256/241/345/210/222-codex.md +994 -0
- package/_packed_docs/AUN/347/246/273/347/272/277/346/216/250/351/200/201/346/234/215/345/212/241/350/277/220/347/273/264/344/270/216/345/217/221/345/270/203/346/214/207/345/215/227.md +144 -0
- package/_packed_docs/CHANGELOG.md +130 -94
- package/_packed_docs/INDEX.md +127 -69
- package/_packed_docs/KITE_DOCS_GUIDE.md +63 -29
- package/_packed_docs/audit/AUN/346/234/215/345/212/241Go/345/214/226/351/207/215/346/236/204/347/262/276/347/273/206/345/214/226/346/272/220/347/240/201/345/256/241/346/237/245-20260718.md +366 -0
- package/_packed_docs/aun/345/205/254/347/275/221/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +227 -0
- package/_packed_docs/aun/345/210/206/345/270/203/345/274/217/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +1004 -0
- package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +999 -0
- package/_packed_docs/cli/CLI/346/211/213/345/206/214.md +6 -4
- package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +58 -20
- package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +219 -247
- package/_packed_docs/protocol/12-Stream-/345/255/220/345/215/217/350/256/256.md +14 -14
- package/_packed_docs/protocol/13-Agent/350/241/214/344/270/272/350/247/204/350/214/203.md +3 -3
- package/_packed_docs/protocol/15-/347/246/273/347/272/277/346/216/250/351/200/201/351/200/232/347/237/245/345/215/217/350/256/256.md +165 -421
- package/_packed_docs/protocol/README.md +1 -0
- package/_packed_docs/protocol/aun-docs-guide.md +7 -4
- package/_packed_docs/protocol/index.md +25 -19
- package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +62 -17
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +22 -0
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +67 -78
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +6 -2
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +50 -14
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +18 -5
- package/_packed_docs/sdk/09-group-rpc-manual.md +312 -282
- package/_packed_docs/sdk/09-message-rpc-manual.md +142 -103
- package/_packed_docs/sdk/09-payload-reference.md +1 -1
- package/_packed_docs/sdk/09-stream-rpc-manual.md +8 -8
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +26 -17
- package/_packed_docs/sdk/INDEX.md +42 -34
- package/_packed_docs/sdk/README.md +7 -6
- package/_packed_docs//346/266/210/346/201/257/345/220/214/346/255/245/344/270/216/345/216/206/345/217/262/346/213/211/345/217/226/344/273/243/347/240/201/345/256/241/346/237/245/351/227/256/351/242/230/346/270/205/345/215/225.md +229 -0
- package/_packed_docs//346/266/210/346/201/257/345/220/214/346/255/245/344/270/216/345/216/206/345/217/262/346/213/211/345/217/226/346/224/271/351/200/240/346/226/271/346/241/210.md +750 -0
- package/dist/agent-md.d.ts +4 -5
- package/dist/agent-md.d.ts.map +1 -1
- package/dist/agent-md.js +66 -43
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +2 -6
- package/dist/aid-store.d.ts.map +1 -1
- package/dist/aid-store.js +20 -14
- package/dist/aid-store.js.map +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +30 -11
- package/dist/auth.js.map +1 -1
- package/dist/bundle.js +3297 -1023
- package/dist/client/delivery.d.ts +75 -8
- package/dist/client/delivery.d.ts.map +1 -1
- package/dist/client/delivery.js +1419 -245
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/group-state.d.ts.map +1 -1
- package/dist/client/group-state.js +27 -24
- package/dist/client/group-state.js.map +1 -1
- package/dist/client/lifecycle.d.ts.map +1 -1
- package/dist/client/lifecycle.js +48 -4
- package/dist/client/lifecycle.js.map +1 -1
- package/dist/client/mention-mode.d.ts +7 -0
- package/dist/client/mention-mode.d.ts.map +1 -0
- package/dist/client/mention-mode.js +184 -0
- package/dist/client/mention-mode.js.map +1 -0
- package/dist/client/peers.d.ts +1 -1
- package/dist/client/peers.d.ts.map +1 -1
- package/dist/client/peers.js +26 -3
- package/dist/client/peers.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +22 -1
- package/dist/client/rpc-pipeline.d.ts.map +1 -1
- package/dist/client/rpc-pipeline.js +309 -74
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +25 -2
- package/dist/client/v2-e2ee.d.ts.map +1 -1
- package/dist/client/v2-e2ee.js +606 -97
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +24 -5
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +404 -215
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +4 -1
- package/dist/errors.js.map +1 -1
- package/dist/facades.d.ts +3 -0
- package/dist/facades.d.ts.map +1 -1
- package/dist/facades.js +91 -53
- package/dist/facades.js.map +1 -1
- package/dist/group-index.d.ts +6 -1
- package/dist/group-index.d.ts.map +1 -1
- package/dist/group-index.js +44 -25
- package/dist/group-index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/keystore/index.d.ts +10 -5
- package/dist/keystore/index.d.ts.map +1 -1
- package/dist/keystore/indexeddb-identity-store.d.ts +0 -12
- package/dist/keystore/indexeddb-identity-store.d.ts.map +1 -1
- package/dist/keystore/indexeddb-identity-store.js +0 -60
- package/dist/keystore/indexeddb-identity-store.js.map +1 -1
- package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
- package/dist/keystore/indexeddb-shared.js +9 -5
- package/dist/keystore/indexeddb-shared.js.map +1 -1
- package/dist/keystore/indexeddb-token-store.d.ts +3 -1
- package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
- package/dist/keystore/indexeddb-token-store.js +47 -2
- package/dist/keystore/indexeddb-token-store.js.map +1 -1
- package/dist/register-flow.d.ts.map +1 -1
- package/dist/register-flow.js +28 -3
- package/dist/register-flow.js.map +1 -1
- package/dist/seq-tracker.d.ts +28 -8
- package/dist/seq-tracker.d.ts.map +1 -1
- package/dist/seq-tracker.js +218 -61
- package/dist/seq-tracker.js.map +1 -1
- package/dist/storage/vfs.d.ts +1 -0
- package/dist/storage/vfs.d.ts.map +1 -1
- package/dist/storage/vfs.js +26 -2
- package/dist/storage/vfs.js.map +1 -1
- package/dist/tools/cross-sdk-agent.js +376 -10
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +12 -0
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +218 -101
- package/dist/transport.js.map +1 -1
- package/dist/v2/session/session.d.ts.map +1 -1
- package/dist/v2/session/session.js +3 -4
- package/dist/v2/session/session.js.map +1 -1
- package/dist/v2/state/commitment.d.ts.map +1 -1
- package/dist/v2/state/commitment.js +1 -2
- package/dist/v2/state/commitment.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +6 -5
- package/dist/group-resources.d.ts +0 -98
- package/dist/group-resources.d.ts.map +0 -1
- package/dist/group-resources.js +0 -635
- package/dist/group-resources.js.map +0 -1
- package/dist/keystore/indexeddb.d.ts +0 -179
- package/dist/keystore/indexeddb.d.ts.map +0 -1
- package/dist/keystore/indexeddb.js +0 -2031
- package/dist/keystore/indexeddb.js.map +0 -1
- package/dist/namespaces/auth.d.ts +0 -98
- package/dist/namespaces/auth.d.ts.map +0 -1
- package/dist/namespaces/auth.js +0 -992
- package/dist/namespaces/auth.js.map +0 -1
- package/dist/namespaces/custody.d.ts +0 -51
- package/dist/namespaces/custody.d.ts.map +0 -1
- package/dist/namespaces/custody.js +0 -302
- package/dist/namespaces/custody.js.map +0 -1
- package/dist/namespaces/meta.d.ts +0 -109
- package/dist/namespaces/meta.d.ts.map +0 -1
- package/dist/namespaces/meta.js +0 -549
- package/dist/namespaces/meta.js.map +0 -1
package/dist/namespaces/auth.js
DELETED
|
@@ -1,992 +0,0 @@
|
|
|
1
|
-
// ── AuthNamespace(认证命名空间 — 完整实现)──────────────────
|
|
2
|
-
import { AUNError, NotFoundError, StateError, ValidationError } from '../errors.js';
|
|
3
|
-
import { isJsonObject } from '../types.js';
|
|
4
|
-
import { base64ToUint8, pemToArrayBuffer, uint8ToBase64, certificateSha256Fingerprint, ecdsaSignDer, ecdsaVerifyDer, importCertPublicKeyEcdsa, importPrivateKeyEcdsa, } from '../crypto.js';
|
|
5
|
-
const _noopLog = { error: () => { }, warn: () => { }, info: () => { }, debug: () => { } };
|
|
6
|
-
const AGENT_MD_HTTP_TIMEOUT_MS = 30_000;
|
|
7
|
-
const AGENT_MD_SIGNATURE_MARKER = '<!-- AUN-SIGNATURE';
|
|
8
|
-
const AGENT_MD_SIGNATURE_RE = /^<!-- AUN-SIGNATURE\r?\n(?<body>[\s\S]*?)\r?\n-->\s*$/;
|
|
9
|
-
const AGENT_MD_FINGERPRINT_RE = /^sha256:[0-9a-f]{64}$/;
|
|
10
|
-
function agentMdHttpScheme(gatewayUrl) {
|
|
11
|
-
const raw = String(gatewayUrl ?? '').trim().toLowerCase();
|
|
12
|
-
return raw.startsWith('ws://') ? 'http' : 'https';
|
|
13
|
-
}
|
|
14
|
-
function agentMdAuthority(aid, discoveryPort) {
|
|
15
|
-
const host = String(aid ?? '').trim();
|
|
16
|
-
if (!host)
|
|
17
|
-
return '';
|
|
18
|
-
if (discoveryPort && !host.includes(':')) {
|
|
19
|
-
return `${host}:${discoveryPort}`;
|
|
20
|
-
}
|
|
21
|
-
return host;
|
|
22
|
-
}
|
|
23
|
-
function parseAgentMdTailSignature(content) {
|
|
24
|
-
const idx = content.lastIndexOf(AGENT_MD_SIGNATURE_MARKER);
|
|
25
|
-
if (idx < 0) {
|
|
26
|
-
return { payload: content, fields: null };
|
|
27
|
-
}
|
|
28
|
-
if (idx > 0 && content[idx - 1] !== '\n' && content[idx - 1] !== '\r') {
|
|
29
|
-
return { payload: content, fields: null };
|
|
30
|
-
}
|
|
31
|
-
const tail = content.slice(idx);
|
|
32
|
-
const match = tail.match(AGENT_MD_SIGNATURE_RE);
|
|
33
|
-
if (!match) {
|
|
34
|
-
return { payload: content.slice(0, idx), fields: null, parseError: 'malformed signature block' };
|
|
35
|
-
}
|
|
36
|
-
const fields = {};
|
|
37
|
-
for (const rawLine of match.groups?.body?.split(/\r?\n/) ?? []) {
|
|
38
|
-
const line = rawLine.trim();
|
|
39
|
-
if (!line)
|
|
40
|
-
continue;
|
|
41
|
-
const colon = line.indexOf(':');
|
|
42
|
-
if (colon < 0) {
|
|
43
|
-
return { payload: content.slice(0, idx), fields: null, parseError: `malformed signature field: ${line}` };
|
|
44
|
-
}
|
|
45
|
-
const key = line.slice(0, colon).trim().toLowerCase();
|
|
46
|
-
const value = line.slice(colon + 1).trim();
|
|
47
|
-
fields[key] = value;
|
|
48
|
-
}
|
|
49
|
-
for (const required of ['cert_fingerprint', 'timestamp', 'signature']) {
|
|
50
|
-
if (!fields[required]) {
|
|
51
|
-
return { payload: content.slice(0, idx), fields: null, parseError: `signature block missing ${required}` };
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
if (!AGENT_MD_FINGERPRINT_RE.test(fields.cert_fingerprint.toLowerCase())) {
|
|
55
|
-
return { payload: content.slice(0, idx), fields: null, parseError: 'invalid cert_fingerprint' };
|
|
56
|
-
}
|
|
57
|
-
if (!Number.isFinite(Number(fields.timestamp))) {
|
|
58
|
-
return { payload: content.slice(0, idx), fields: null, parseError: 'invalid timestamp' };
|
|
59
|
-
}
|
|
60
|
-
return { payload: content.slice(0, idx), fields, parseError: undefined };
|
|
61
|
-
}
|
|
62
|
-
function extractAgentMDAid(payload) {
|
|
63
|
-
const lines = payload.replace(/^\ufeff/, '').split(/\r?\n/);
|
|
64
|
-
if (!lines.length || lines[0].trim() !== '---') {
|
|
65
|
-
return '';
|
|
66
|
-
}
|
|
67
|
-
for (const line of lines.slice(1)) {
|
|
68
|
-
const trimmed = line.trim();
|
|
69
|
-
if (trimmed === '---')
|
|
70
|
-
break;
|
|
71
|
-
if (trimmed.startsWith('aid:')) {
|
|
72
|
-
let value = trimmed.slice(4).trim();
|
|
73
|
-
if (value.length >= 2 && value[0] === value[value.length - 1] && (value[0] === '"' || value[0] === '\'')) {
|
|
74
|
-
value = value.slice(1, -1);
|
|
75
|
-
}
|
|
76
|
-
return value.trim();
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return '';
|
|
80
|
-
}
|
|
81
|
-
function agentMdResult(status, payload, reason = '', aid = '', certFingerprint = '', timestamp) {
|
|
82
|
-
const result = {
|
|
83
|
-
status,
|
|
84
|
-
verified: status === 'verified',
|
|
85
|
-
payload,
|
|
86
|
-
};
|
|
87
|
-
if (reason)
|
|
88
|
-
result.reason = reason;
|
|
89
|
-
if (aid)
|
|
90
|
-
result.aid = aid;
|
|
91
|
-
if (certFingerprint)
|
|
92
|
-
result.cert_fingerprint = certFingerprint;
|
|
93
|
-
if (timestamp !== undefined)
|
|
94
|
-
result.timestamp = timestamp;
|
|
95
|
-
return result;
|
|
96
|
-
}
|
|
97
|
-
function normalizeIdentityCertPem(identity) {
|
|
98
|
-
if (!identity)
|
|
99
|
-
return '';
|
|
100
|
-
return String(identity.cert ?? identity.cert_pem ?? '').trim();
|
|
101
|
-
}
|
|
102
|
-
function parseAgentMdTimestamp(value) {
|
|
103
|
-
const parsed = Number(value.trim());
|
|
104
|
-
return Number.isFinite(parsed) ? Math.trunc(parsed) : undefined;
|
|
105
|
-
}
|
|
106
|
-
function readDerLength(bytes, offset) {
|
|
107
|
-
const first = bytes[offset];
|
|
108
|
-
if (first === undefined)
|
|
109
|
-
return null;
|
|
110
|
-
if (first < 0x80) {
|
|
111
|
-
return { length: first, lengthBytes: 1 };
|
|
112
|
-
}
|
|
113
|
-
const count = first & 0x7f;
|
|
114
|
-
if (count === 0 || count > 4 || offset + count >= bytes.length) {
|
|
115
|
-
return null;
|
|
116
|
-
}
|
|
117
|
-
let length = 0;
|
|
118
|
-
for (let i = 0; i < count; i++) {
|
|
119
|
-
length = (length << 8) | bytes[offset + 1 + i];
|
|
120
|
-
}
|
|
121
|
-
return { length, lengthBytes: 1 + count };
|
|
122
|
-
}
|
|
123
|
-
function extractCommonNameFromCertPem(certPem) {
|
|
124
|
-
try {
|
|
125
|
-
const bytes = new Uint8Array(pemToArrayBuffer(certPem));
|
|
126
|
-
const oid = [0x06, 0x03, 0x55, 0x04, 0x03];
|
|
127
|
-
const decoder = new TextDecoder();
|
|
128
|
-
let commonName = '';
|
|
129
|
-
for (let i = 0; i <= bytes.length - oid.length - 2; i++) {
|
|
130
|
-
let matched = true;
|
|
131
|
-
for (let j = 0; j < oid.length; j++) {
|
|
132
|
-
if (bytes[i + j] !== oid[j]) {
|
|
133
|
-
matched = false;
|
|
134
|
-
break;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
if (!matched)
|
|
138
|
-
continue;
|
|
139
|
-
const tag = bytes[i + oid.length];
|
|
140
|
-
const lengthInfo = readDerLength(bytes, i + oid.length + 1);
|
|
141
|
-
if (!lengthInfo)
|
|
142
|
-
continue;
|
|
143
|
-
const start = i + oid.length + 1 + lengthInfo.lengthBytes;
|
|
144
|
-
const end = start + lengthInfo.length;
|
|
145
|
-
if (end > bytes.length)
|
|
146
|
-
continue;
|
|
147
|
-
const valueBytes = bytes.slice(start, end);
|
|
148
|
-
if (tag === 0x0c || tag === 0x13 || tag === 0x16 || tag === 0x14 || tag === 0x1e) {
|
|
149
|
-
commonName = decoder.decode(valueBytes).trim();
|
|
150
|
-
}
|
|
151
|
-
else {
|
|
152
|
-
commonName = decoder.decode(valueBytes).trim();
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return commonName;
|
|
156
|
-
}
|
|
157
|
-
catch {
|
|
158
|
-
return '';
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
/** 从 PEM 证书 DER 中解析 validity(notBefore / notAfter)时间戳 */
|
|
162
|
-
function parseCertValidity(certPem) {
|
|
163
|
-
try {
|
|
164
|
-
const der = new Uint8Array(pemToArrayBuffer(certPem));
|
|
165
|
-
// ASN.1 X.509 结构:SEQUENCE { tbsCertificate SEQUENCE { version, serial, sigAlg, issuer, validity, ... } }
|
|
166
|
-
// validity 是 tbsCertificate 中第 5 个字段(0-indexed: 4)
|
|
167
|
-
// 简化方法:搜索连续两个 UTCTime 或 GeneralizedTime 标签
|
|
168
|
-
const dates = [];
|
|
169
|
-
for (let i = 0; i < der.length - 2 && dates.length < 2; i++) {
|
|
170
|
-
const tag = der[i];
|
|
171
|
-
if (tag !== 0x17 && tag !== 0x18)
|
|
172
|
-
continue; // UTCTime=0x17, GeneralizedTime=0x18
|
|
173
|
-
const len = der[i + 1];
|
|
174
|
-
if (len === 0 || len > 20 || i + 2 + len > der.length)
|
|
175
|
-
continue;
|
|
176
|
-
const str = new TextDecoder().decode(der.slice(i + 2, i + 2 + len));
|
|
177
|
-
const ts = parseAsn1Time(tag, str);
|
|
178
|
-
if (ts !== null) {
|
|
179
|
-
dates.push(ts);
|
|
180
|
-
i += 1 + len; // 跳过已解析的时间
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
if (dates.length >= 2) {
|
|
184
|
-
return { notBefore: dates[0], notAfter: dates[1] };
|
|
185
|
-
}
|
|
186
|
-
return null;
|
|
187
|
-
}
|
|
188
|
-
catch {
|
|
189
|
-
return null;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
function parseAsn1Time(tag, str) {
|
|
193
|
-
try {
|
|
194
|
-
if (tag === 0x17) {
|
|
195
|
-
// UTCTime: YYMMDDHHMMSSZ
|
|
196
|
-
const cleaned = str.replace(/Z$/i, '');
|
|
197
|
-
if (cleaned.length < 12)
|
|
198
|
-
return null;
|
|
199
|
-
let year = parseInt(cleaned.slice(0, 2), 10);
|
|
200
|
-
year += year >= 50 ? 1900 : 2000;
|
|
201
|
-
const month = parseInt(cleaned.slice(2, 4), 10) - 1;
|
|
202
|
-
const day = parseInt(cleaned.slice(4, 6), 10);
|
|
203
|
-
const hour = parseInt(cleaned.slice(6, 8), 10);
|
|
204
|
-
const min = parseInt(cleaned.slice(8, 10), 10);
|
|
205
|
-
const sec = parseInt(cleaned.slice(10, 12), 10);
|
|
206
|
-
return Date.UTC(year, month, day, hour, min, sec);
|
|
207
|
-
}
|
|
208
|
-
else if (tag === 0x18) {
|
|
209
|
-
// GeneralizedTime: YYYYMMDDHHMMSSZ
|
|
210
|
-
const cleaned = str.replace(/Z$/i, '');
|
|
211
|
-
if (cleaned.length < 14)
|
|
212
|
-
return null;
|
|
213
|
-
const year = parseInt(cleaned.slice(0, 4), 10);
|
|
214
|
-
const month = parseInt(cleaned.slice(4, 6), 10) - 1;
|
|
215
|
-
const day = parseInt(cleaned.slice(6, 8), 10);
|
|
216
|
-
const hour = parseInt(cleaned.slice(8, 10), 10);
|
|
217
|
-
const min = parseInt(cleaned.slice(10, 12), 10);
|
|
218
|
-
const sec = parseInt(cleaned.slice(12, 14), 10);
|
|
219
|
-
return Date.UTC(year, month, day, hour, min, sec);
|
|
220
|
-
}
|
|
221
|
-
return null;
|
|
222
|
-
}
|
|
223
|
-
catch {
|
|
224
|
-
return null;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
async function fetchWithTimeout(input, init, timeoutMs = AGENT_MD_HTTP_TIMEOUT_MS) {
|
|
228
|
-
const controller = new AbortController();
|
|
229
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
230
|
-
try {
|
|
231
|
-
return await fetch(input, { ...init, signal: controller.signal });
|
|
232
|
-
}
|
|
233
|
-
catch (error) {
|
|
234
|
-
if (controller.signal.aborted) {
|
|
235
|
-
throw new AUNError(`agent.md request timed out after ${timeoutMs}ms`);
|
|
236
|
-
}
|
|
237
|
-
throw error;
|
|
238
|
-
}
|
|
239
|
-
finally {
|
|
240
|
-
clearTimeout(timer);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
|
-
* 认证命名空间 — 提供 AID 注册、登录认证等高级操作。
|
|
245
|
-
* 通过 AUNClient 内部的 AuthFlow 完成完整的 PKI 注册和两阶段认证。
|
|
246
|
-
*/
|
|
247
|
-
export class AuthNamespace {
|
|
248
|
-
_client;
|
|
249
|
-
_log = _noopLog;
|
|
250
|
-
_agentMdCache = new Map();
|
|
251
|
-
setLogger(log) { this._log = log; }
|
|
252
|
-
constructor(client) {
|
|
253
|
-
this._client = client;
|
|
254
|
-
}
|
|
255
|
-
get _internal() {
|
|
256
|
-
return this._client;
|
|
257
|
-
}
|
|
258
|
-
/**
|
|
259
|
-
* 解析 gateway URL。
|
|
260
|
-
* 优先使用已预置的 gatewayUrl,否则基于 AID 自动发现。
|
|
261
|
-
*
|
|
262
|
-
* 发现流程:
|
|
263
|
-
* 1. 若 gatewayUrl 已预置(内存),直接返回
|
|
264
|
-
* 2. 从 keystore metadata 读 cached gateway_url(跨进程/会话复用)
|
|
265
|
-
* 3. https://{aid}/.well-known/aun-gateway(泛域名 nameservice)
|
|
266
|
-
* 4. https://gateway.{issuer}/.well-known/aun-gateway(Gateway 直连)
|
|
267
|
-
* 发现成功后写入 keystore metadata,避免每次都做 well-known discovery。
|
|
268
|
-
*/
|
|
269
|
-
async _resolveGateway(aid) {
|
|
270
|
-
if (this._client.gatewayUrl) {
|
|
271
|
-
return this._client.gatewayUrl;
|
|
272
|
-
}
|
|
273
|
-
const resolvedAid = aid ?? this._client.aid;
|
|
274
|
-
if (resolvedAid) {
|
|
275
|
-
// 从 keystore metadata 读持久化的 gateway_url
|
|
276
|
-
try {
|
|
277
|
-
const cached = await this._loadCachedGatewayUrl(resolvedAid);
|
|
278
|
-
if (cached) {
|
|
279
|
-
this._log.debug(`_resolveGateway from keystore cache aid=${resolvedAid} gateway=${cached}`);
|
|
280
|
-
this._client.gatewayUrl = cached;
|
|
281
|
-
return cached;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
catch (exc) {
|
|
285
|
-
this._log.debug(`_resolveGateway load cache failed: ${exc instanceof Error ? exc.message : String(exc)}`);
|
|
286
|
-
}
|
|
287
|
-
const parts = resolvedAid.split('.');
|
|
288
|
-
const issuerDomain = parts.length > 1 ? parts.slice(1).join('.') : resolvedAid;
|
|
289
|
-
const port = this._client.configModel.discoveryPort;
|
|
290
|
-
const portSuffix = port ? `:${port}` : '';
|
|
291
|
-
// 尝试 nameservice 发现
|
|
292
|
-
const primaryUrl = `https://${resolvedAid}${portSuffix}/.well-known/aun-gateway`;
|
|
293
|
-
try {
|
|
294
|
-
const discovered = await this._client.discovery.discover(primaryUrl);
|
|
295
|
-
await this._persistGatewayUrl(resolvedAid, discovered);
|
|
296
|
-
return discovered;
|
|
297
|
-
}
|
|
298
|
-
catch {
|
|
299
|
-
// 降级到 Gateway 直连
|
|
300
|
-
}
|
|
301
|
-
const fallbackUrl = `https://gateway.${issuerDomain}${portSuffix}/.well-known/aun-gateway`;
|
|
302
|
-
const discovered = await this._client.discovery.discover(fallbackUrl);
|
|
303
|
-
await this._persistGatewayUrl(resolvedAid, discovered);
|
|
304
|
-
return discovered;
|
|
305
|
-
}
|
|
306
|
-
throw new ValidationError("unable to resolve gateway: set client.gatewayUrl or provide 'aid' for auto-discovery");
|
|
307
|
-
}
|
|
308
|
-
/** 从 keystore metadata 读取 cached gateway_url(跨进程/会话复用) */
|
|
309
|
-
async _loadCachedGatewayUrl(aid) {
|
|
310
|
-
if (!aid)
|
|
311
|
-
return '';
|
|
312
|
-
const ks = this._internal._keystore;
|
|
313
|
-
if (!ks || typeof ks.getMetadata !== 'function')
|
|
314
|
-
return '';
|
|
315
|
-
try {
|
|
316
|
-
const value = await ks.getMetadata(aid, 'gateway_url');
|
|
317
|
-
const raw = String(value ?? '').trim();
|
|
318
|
-
if (!raw)
|
|
319
|
-
return '';
|
|
320
|
-
// 兼容 JSON 编码(Python SDK 通过 save_identity 走 json.dumps)
|
|
321
|
-
// 与裸字符串两种格式:先尝试 JSON.parse,失败则当裸字符串使用
|
|
322
|
-
if (raw.startsWith('"') && raw.endsWith('"')) {
|
|
323
|
-
try {
|
|
324
|
-
const parsed = JSON.parse(raw);
|
|
325
|
-
if (typeof parsed === 'string')
|
|
326
|
-
return parsed.trim();
|
|
327
|
-
}
|
|
328
|
-
catch {
|
|
329
|
-
// JSON 解析失败,按裸字符串处理
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
return raw;
|
|
333
|
-
}
|
|
334
|
-
catch {
|
|
335
|
-
return '';
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
/** 持久化 gateway_url 到 keystore metadata,供跨进程复用 */
|
|
339
|
-
async _persistGatewayUrl(aid, gatewayUrl) {
|
|
340
|
-
if (!gatewayUrl || !aid)
|
|
341
|
-
return;
|
|
342
|
-
const ks = this._internal._keystore;
|
|
343
|
-
if (!ks || typeof ks.setMetadata !== 'function')
|
|
344
|
-
return;
|
|
345
|
-
try {
|
|
346
|
-
// JS IndexedDB keystore 的 saveIdentity 把 metadata 字段以原值存储(不 JSON.stringify),
|
|
347
|
-
// setMetadata 也用原值;所以 _persistGatewayUrl 保持原值写入与读取兼容。
|
|
348
|
-
// _loadCachedGatewayUrl 兼容裸字符串和 JSON 编码两种格式。
|
|
349
|
-
await ks.setMetadata(aid, 'gateway_url', gatewayUrl);
|
|
350
|
-
}
|
|
351
|
-
catch (exc) {
|
|
352
|
-
this._log.debug(`persist gateway_url failed aid=${aid} err=${exc instanceof Error ? exc.message : String(exc)}`);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
/** 内部访问 client 私有属性 */
|
|
356
|
-
/**
|
|
357
|
-
* 严格注册新 AID(对齐 TS registerAid)。
|
|
358
|
-
* 通过 well-known 发现 gateway → 调用 AuthFlow.registerAid 注册。
|
|
359
|
-
*/
|
|
360
|
-
async registerAid(params) {
|
|
361
|
-
const tStart = Date.now();
|
|
362
|
-
const aid = String(params?.aid ?? '');
|
|
363
|
-
this._log.debug(`registerAid enter: aid=${aid}`);
|
|
364
|
-
try {
|
|
365
|
-
if (!aid)
|
|
366
|
-
throw new ValidationError("auth.registerAid requires 'aid'");
|
|
367
|
-
const gatewayUrl = await this._resolveGateway(aid);
|
|
368
|
-
this._client.gatewayUrl = gatewayUrl;
|
|
369
|
-
const auth = this._internal._auth;
|
|
370
|
-
const result = await auth.registerAid(gatewayUrl, aid);
|
|
371
|
-
this._internal._aid = aid;
|
|
372
|
-
this._internal._identity = await auth.loadIdentityOrNone(aid);
|
|
373
|
-
this._log.debug(`registerAid exit: elapsed=${Date.now() - tStart}ms aid=${aid}`);
|
|
374
|
-
return {
|
|
375
|
-
aid: aid,
|
|
376
|
-
cert_pem: result.cert,
|
|
377
|
-
gateway: gatewayUrl,
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
catch (err) {
|
|
381
|
-
this._log.debug(`registerAid exit (error): elapsed=${Date.now() - tStart}ms aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
|
|
382
|
-
throw err;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
/**
|
|
386
|
-
* 认证已有 AID(login1 + login2 两阶段认证)。
|
|
387
|
-
* 通过 well-known 发现 gateway → 调用 AuthFlow.authenticate。
|
|
388
|
-
*/
|
|
389
|
-
async authenticate(params) {
|
|
390
|
-
const tStart = Date.now();
|
|
391
|
-
const aid = (params?.aid ?? '');
|
|
392
|
-
this._log.debug(`authenticate enter: aid=${aid || '<current>'}`);
|
|
393
|
-
try {
|
|
394
|
-
const request = { ...(params ?? {}) };
|
|
395
|
-
const requestAid = request.aid;
|
|
396
|
-
const gatewayUrl = await this._resolveGateway(requestAid);
|
|
397
|
-
this._client.gatewayUrl = gatewayUrl;
|
|
398
|
-
const auth = this._internal._auth;
|
|
399
|
-
const result = await auth.authenticate(gatewayUrl, requestAid);
|
|
400
|
-
this._internal._aid = result.aid ?? null;
|
|
401
|
-
this._internal._identity = await auth.loadIdentityOrNone(String(result.aid));
|
|
402
|
-
this._log.debug(`authenticate exit: elapsed=${Date.now() - tStart}ms aid=${result.aid}`);
|
|
403
|
-
return result; // 包含 aid, access_token, refresh_token, expires_at, gateway
|
|
404
|
-
}
|
|
405
|
-
catch (err) {
|
|
406
|
-
this._log.debug(`authenticate exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
407
|
-
throw err;
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
/** 只读加载本地已注册身份(密钥对 + 证书 + 实例状态)。无副作用,不触发网络请求。 */
|
|
411
|
-
async loadIdentity(params) {
|
|
412
|
-
const aid = String((params ?? {})?.aid ?? '').trim() || undefined;
|
|
413
|
-
const identity = await this._internal._auth.loadIdentityOrNone(aid);
|
|
414
|
-
if (!identity) {
|
|
415
|
-
throw new StateError(`identity not found for aid: ${aid ?? '<default>'}`);
|
|
416
|
-
}
|
|
417
|
-
return identity;
|
|
418
|
-
}
|
|
419
|
-
/** 只读加载本地已注册身份,不存在时返回 null。 */
|
|
420
|
-
async loadIdentityOrNull(params) {
|
|
421
|
-
const aid = String((params ?? {})?.aid ?? '').trim() || undefined;
|
|
422
|
-
return await this._internal._auth.loadIdentityOrNone(aid);
|
|
423
|
-
}
|
|
424
|
-
/** 获取对端 AID 的证书 PEM(本地缓存优先,未命中走 PKI HTTP + 链验证)。 */
|
|
425
|
-
async fetchPeerCert(params) {
|
|
426
|
-
const aid = String(params?.aid ?? '').trim();
|
|
427
|
-
if (!aid)
|
|
428
|
-
throw new Error("auth.fetchPeerCert requires 'aid'");
|
|
429
|
-
const fp = String(params?.cert_fingerprint ?? '').trim() || undefined;
|
|
430
|
-
if (typeof this._internal._fetchPeerCert !== 'function') {
|
|
431
|
-
throw new Error('client does not support _fetchPeerCert');
|
|
432
|
-
}
|
|
433
|
-
return String(await this._internal._fetchPeerCert(aid, fp)).trim();
|
|
434
|
-
}
|
|
435
|
-
async signAgentMd(content, opts) {
|
|
436
|
-
const tStart = Date.now();
|
|
437
|
-
this._log.debug(`signAgentMd enter: aid=${opts?.aid ?? '<current>'} content_len=${String(content ?? '').length}`);
|
|
438
|
-
try {
|
|
439
|
-
const client = this._internal;
|
|
440
|
-
const targetAid = String(opts?.aid ?? client._aid ?? '').trim();
|
|
441
|
-
const identity = await client._auth.loadIdentityOrNone(targetAid || undefined);
|
|
442
|
-
if (!identity) {
|
|
443
|
-
throw new StateError('no local identity found, call auth.registerAid() first');
|
|
444
|
-
}
|
|
445
|
-
const privateKeyPem = String(identity.private_key_pem ?? '').trim();
|
|
446
|
-
const certPem = normalizeIdentityCertPem(identity);
|
|
447
|
-
if (!privateKeyPem || !certPem) {
|
|
448
|
-
throw new StateError('local identity missing private key or certificate');
|
|
449
|
-
}
|
|
450
|
-
let payload = parseAgentMdTailSignature(String(content ?? '')).payload;
|
|
451
|
-
if (payload && !payload.endsWith('\n') && !payload.endsWith('\r')) {
|
|
452
|
-
payload += '\n';
|
|
453
|
-
}
|
|
454
|
-
const privateKey = await importPrivateKeyEcdsa(privateKeyPem);
|
|
455
|
-
const signatureDer = await ecdsaSignDer(privateKey, new TextEncoder().encode(payload));
|
|
456
|
-
const fingerprint = await certificateSha256Fingerprint(certPem);
|
|
457
|
-
if (!fingerprint) {
|
|
458
|
-
throw new StateError('agent.md cert fingerprint failed');
|
|
459
|
-
}
|
|
460
|
-
const signedBlock = [
|
|
461
|
-
AGENT_MD_SIGNATURE_MARKER,
|
|
462
|
-
`cert_fingerprint: ${fingerprint}`,
|
|
463
|
-
`timestamp: ${Math.floor(Date.now() / 1000)}`,
|
|
464
|
-
`signature: ${uint8ToBase64(signatureDer)}`,
|
|
465
|
-
'-->',
|
|
466
|
-
].join('\n');
|
|
467
|
-
this._log.debug(`signAgentMd exit: elapsed=${Date.now() - tStart}ms aid=${targetAid}`);
|
|
468
|
-
return payload + signedBlock;
|
|
469
|
-
}
|
|
470
|
-
catch (err) {
|
|
471
|
-
this._log.debug(`signAgentMd exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
472
|
-
throw err;
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
async verifyAgentMd(content, opts) {
|
|
476
|
-
const tStart = Date.now();
|
|
477
|
-
this._log.debug(`verifyAgentMd enter: aid=${opts?.aid ?? '<infer>'} content_len=${String(content ?? '').length}`);
|
|
478
|
-
try {
|
|
479
|
-
const result = await this._verifyAgentMdImpl(content, opts);
|
|
480
|
-
this._log.debug(`verifyAgentMd exit: elapsed=${Date.now() - tStart}ms status=${result.status} verified=${!!result.verified}`);
|
|
481
|
-
return result;
|
|
482
|
-
}
|
|
483
|
-
catch (err) {
|
|
484
|
-
this._log.debug(`verifyAgentMd exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
485
|
-
throw err;
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
async _verifyAgentMdImpl(content, opts) {
|
|
489
|
-
const { payload, fields, parseError } = parseAgentMdTailSignature(String(content ?? ''));
|
|
490
|
-
if (!fields) {
|
|
491
|
-
if (!parseError) {
|
|
492
|
-
return agentMdResult('unsigned', payload);
|
|
493
|
-
}
|
|
494
|
-
return agentMdResult('invalid', payload, parseError);
|
|
495
|
-
}
|
|
496
|
-
let expectedAid = String(opts?.aid ?? '').trim();
|
|
497
|
-
const payloadAid = extractAgentMDAid(payload);
|
|
498
|
-
if (expectedAid && payloadAid && expectedAid !== payloadAid) {
|
|
499
|
-
return agentMdResult('invalid', payload, 'aid mismatch', payloadAid);
|
|
500
|
-
}
|
|
501
|
-
if (!expectedAid) {
|
|
502
|
-
expectedAid = payloadAid;
|
|
503
|
-
}
|
|
504
|
-
let certPem = String(opts?.certPem ?? opts?.cert_pem ?? '').trim();
|
|
505
|
-
if (!certPem) {
|
|
506
|
-
if (!expectedAid) {
|
|
507
|
-
return agentMdResult('invalid', payload, 'aid required to verify agent.md');
|
|
508
|
-
}
|
|
509
|
-
// 通过 _internal 直接调用,保留 this 绑定(解构后调用会丢失 this,导致 _clientLog 等成员访问失败)
|
|
510
|
-
if (typeof this._internal._fetchPeerCert !== 'function') {
|
|
511
|
-
return agentMdResult('invalid', payload, 'aid required to verify agent.md', expectedAid);
|
|
512
|
-
}
|
|
513
|
-
try {
|
|
514
|
-
certPem = String(await this._internal._fetchPeerCert(expectedAid, fields.cert_fingerprint)).trim();
|
|
515
|
-
}
|
|
516
|
-
catch (error) {
|
|
517
|
-
return agentMdResult('invalid', payload, String(error), expectedAid, fields.cert_fingerprint);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
let publicKey;
|
|
521
|
-
try {
|
|
522
|
-
publicKey = await importCertPublicKeyEcdsa(certPem);
|
|
523
|
-
}
|
|
524
|
-
catch (error) {
|
|
525
|
-
return agentMdResult('invalid', payload, `invalid certificate: ${String(error)}`, expectedAid, fields.cert_fingerprint);
|
|
526
|
-
}
|
|
527
|
-
const actualFingerprint = await certificateSha256Fingerprint(certPem);
|
|
528
|
-
if (!actualFingerprint || actualFingerprint.toLowerCase() !== fields.cert_fingerprint.toLowerCase()) {
|
|
529
|
-
return agentMdResult('invalid', payload, 'certificate fingerprint mismatch', expectedAid, fields.cert_fingerprint);
|
|
530
|
-
}
|
|
531
|
-
const cn = extractCommonNameFromCertPem(certPem);
|
|
532
|
-
if (expectedAid && cn && cn !== expectedAid) {
|
|
533
|
-
return agentMdResult('invalid', payload, 'certificate aid mismatch', expectedAid, fields.cert_fingerprint);
|
|
534
|
-
}
|
|
535
|
-
let signature;
|
|
536
|
-
try {
|
|
537
|
-
signature = base64ToUint8(fields.signature);
|
|
538
|
-
}
|
|
539
|
-
catch {
|
|
540
|
-
return agentMdResult('invalid', payload, 'invalid signature', expectedAid, fields.cert_fingerprint);
|
|
541
|
-
}
|
|
542
|
-
if (!signature.length) {
|
|
543
|
-
return agentMdResult('invalid', payload, 'invalid signature', expectedAid, fields.cert_fingerprint);
|
|
544
|
-
}
|
|
545
|
-
const ok = await ecdsaVerifyDer(publicKey, signature, new TextEncoder().encode(payload));
|
|
546
|
-
if (!ok) {
|
|
547
|
-
return agentMdResult('invalid', payload, 'signature verification failed', expectedAid, fields.cert_fingerprint, parseAgentMdTimestamp(fields.timestamp));
|
|
548
|
-
}
|
|
549
|
-
return agentMdResult('verified', payload, '', expectedAid || payloadAid, fields.cert_fingerprint, parseAgentMdTimestamp(fields.timestamp));
|
|
550
|
-
}
|
|
551
|
-
async _resolveAgentMdUrl(aid) {
|
|
552
|
-
const resolvedAid = String(aid ?? '').trim();
|
|
553
|
-
if (!resolvedAid) {
|
|
554
|
-
throw new ValidationError('agent.md requires non-empty aid');
|
|
555
|
-
}
|
|
556
|
-
let gatewayUrl = this._client.gatewayUrl ?? '';
|
|
557
|
-
if (!gatewayUrl) {
|
|
558
|
-
try {
|
|
559
|
-
gatewayUrl = await this._resolveGateway(resolvedAid);
|
|
560
|
-
}
|
|
561
|
-
catch {
|
|
562
|
-
gatewayUrl = '';
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
const authority = agentMdAuthority(resolvedAid, this._client.configModel.discoveryPort);
|
|
566
|
-
return `${agentMdHttpScheme(gatewayUrl)}://${authority}/agent.md`;
|
|
567
|
-
}
|
|
568
|
-
async _ensureAgentMdUploadToken(aid, gatewayUrl) {
|
|
569
|
-
const auth = this._internal._auth;
|
|
570
|
-
let identity = await auth.loadIdentityOrNone(aid);
|
|
571
|
-
if (!identity) {
|
|
572
|
-
throw new StateError('no local identity found, call auth.registerAid() first');
|
|
573
|
-
}
|
|
574
|
-
const cachedToken = String(identity.access_token ?? '');
|
|
575
|
-
const expiresAt = auth.getAccessTokenExpiry(identity);
|
|
576
|
-
if (cachedToken && (expiresAt === null || expiresAt > Date.now() / 1000 + 30)) {
|
|
577
|
-
return cachedToken;
|
|
578
|
-
}
|
|
579
|
-
if (typeof auth.refreshCachedTokens === 'function' && identity.refresh_token) {
|
|
580
|
-
try {
|
|
581
|
-
identity = await auth.refreshCachedTokens(gatewayUrl, identity);
|
|
582
|
-
const refreshedToken = String(identity.access_token ?? '');
|
|
583
|
-
const refreshedExpiry = auth.getAccessTokenExpiry(identity);
|
|
584
|
-
if (refreshedToken && (refreshedExpiry === null || refreshedExpiry > Date.now() / 1000 + 30)) {
|
|
585
|
-
return refreshedToken;
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
catch {
|
|
589
|
-
// refresh 失败时回退到完整 authenticate
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
const result = await this.authenticate({ aid });
|
|
593
|
-
const token = String(result.access_token ?? '');
|
|
594
|
-
if (!token) {
|
|
595
|
-
throw new StateError('authenticate did not return access_token');
|
|
596
|
-
}
|
|
597
|
-
return token;
|
|
598
|
-
}
|
|
599
|
-
async uploadAgentMd(content) {
|
|
600
|
-
const tStart = Date.now();
|
|
601
|
-
this._log.debug(`uploadAgentMd enter: content_len=${String(content ?? '').length}`);
|
|
602
|
-
try {
|
|
603
|
-
const auth = this._internal._auth;
|
|
604
|
-
const identity = await auth.loadIdentityOrNone(this._client.aid ?? undefined);
|
|
605
|
-
if (!identity) {
|
|
606
|
-
throw new StateError('no local identity found, call auth.registerAid() first');
|
|
607
|
-
}
|
|
608
|
-
const aid = String(identity.aid ?? this._client.aid ?? '').trim();
|
|
609
|
-
if (!aid) {
|
|
610
|
-
throw new StateError('no local identity found, call auth.registerAid() first');
|
|
611
|
-
}
|
|
612
|
-
const gatewayUrl = await this._resolveGateway(aid);
|
|
613
|
-
this._client.gatewayUrl = gatewayUrl;
|
|
614
|
-
const token = await this._ensureAgentMdUploadToken(aid, gatewayUrl);
|
|
615
|
-
const response = await fetchWithTimeout(await this._resolveAgentMdUrl(aid), {
|
|
616
|
-
method: 'PUT',
|
|
617
|
-
headers: {
|
|
618
|
-
Authorization: `Bearer ${token}`,
|
|
619
|
-
'Content-Type': 'text/markdown; charset=utf-8',
|
|
620
|
-
},
|
|
621
|
-
body: content,
|
|
622
|
-
});
|
|
623
|
-
if (response.status === 404) {
|
|
624
|
-
throw new NotFoundError(`agent.md endpoint not found for aid: ${aid}`);
|
|
625
|
-
}
|
|
626
|
-
if (!response.ok) {
|
|
627
|
-
const message = (await response.text()).trim();
|
|
628
|
-
throw new AUNError(`upload agent.md failed: HTTP ${response.status}${message ? ` - ${message}` : ''}`);
|
|
629
|
-
}
|
|
630
|
-
const payload = await response.json();
|
|
631
|
-
if (!isJsonObject(payload)) {
|
|
632
|
-
throw new AUNError('upload agent.md returned invalid JSON payload');
|
|
633
|
-
}
|
|
634
|
-
this._log.debug(`uploadAgentMd exit: elapsed=${Date.now() - tStart}ms aid=${aid}`);
|
|
635
|
-
return payload;
|
|
636
|
-
}
|
|
637
|
-
catch (err) {
|
|
638
|
-
this._log.debug(`uploadAgentMd exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
639
|
-
throw err;
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
async headAgentMd(aid) {
|
|
643
|
-
const tStart = Date.now();
|
|
644
|
-
const targetAid = String(aid ?? '').trim();
|
|
645
|
-
if (!targetAid) {
|
|
646
|
-
throw new ValidationError('headAgentMd requires non-empty aid');
|
|
647
|
-
}
|
|
648
|
-
this._log.debug(`headAgentMd enter: aid=${targetAid}`);
|
|
649
|
-
try {
|
|
650
|
-
const response = await fetchWithTimeout(await this._resolveAgentMdUrl(targetAid), {
|
|
651
|
-
method: 'HEAD',
|
|
652
|
-
headers: { Accept: 'text/markdown' },
|
|
653
|
-
});
|
|
654
|
-
const respHeaders = response.headers;
|
|
655
|
-
const etag = respHeaders ? String(respHeaders.get('ETag') ?? '').trim() : '';
|
|
656
|
-
const lastModified = respHeaders ? String(respHeaders.get('Last-Modified') ?? '').trim() : '';
|
|
657
|
-
if (response.status === 404) {
|
|
658
|
-
this._log.debug(`headAgentMd exit (not_found): elapsed=${Date.now() - tStart}ms aid=${targetAid}`);
|
|
659
|
-
return { aid: targetAid, found: false, etag: '', last_modified: '', status: 404 };
|
|
660
|
-
}
|
|
661
|
-
if (response.status < 200 || response.status >= 300) {
|
|
662
|
-
throw new AUNError(`head agent.md failed: HTTP ${response.status}`);
|
|
663
|
-
}
|
|
664
|
-
if (etag || lastModified) {
|
|
665
|
-
const cached = this._agentMdCache.get(targetAid) ?? { text: '', etag: '', lastModified: '' };
|
|
666
|
-
this._agentMdCache.set(targetAid, {
|
|
667
|
-
text: cached.text ?? '',
|
|
668
|
-
etag,
|
|
669
|
-
lastModified,
|
|
670
|
-
});
|
|
671
|
-
}
|
|
672
|
-
this._log.debug(`headAgentMd exit: elapsed=${Date.now() - tStart}ms aid=${targetAid} status=${response.status} etag=${etag || '-'}`);
|
|
673
|
-
return { aid: targetAid, found: true, etag, last_modified: lastModified, status: response.status };
|
|
674
|
-
}
|
|
675
|
-
catch (err) {
|
|
676
|
-
this._log.debug(`headAgentMd exit (error): elapsed=${Date.now() - tStart}ms aid=${targetAid} err=${err instanceof Error ? err.message : String(err)}`);
|
|
677
|
-
throw err;
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
async downloadAgentMd(aid) {
|
|
681
|
-
const tStart = Date.now();
|
|
682
|
-
this._log.debug(`downloadAgentMd enter: aid=${aid}`);
|
|
683
|
-
try {
|
|
684
|
-
const targetAid = String(aid ?? '').trim();
|
|
685
|
-
if (!targetAid) {
|
|
686
|
-
throw new ValidationError('downloadAgentMd requires non-empty aid');
|
|
687
|
-
}
|
|
688
|
-
const cached = this._agentMdCache.get(targetAid);
|
|
689
|
-
const requestHeaders = { Accept: 'text/markdown' };
|
|
690
|
-
// 不发送条件请求头,始终做无条件 GET(302 由 fetch 自动跟随)
|
|
691
|
-
const agentMdUrl = await this._resolveAgentMdUrl(targetAid);
|
|
692
|
-
const response = await fetchWithTimeout(agentMdUrl, {
|
|
693
|
-
method: 'GET',
|
|
694
|
-
headers: requestHeaders,
|
|
695
|
-
redirect: 'follow',
|
|
696
|
-
});
|
|
697
|
-
if (response.status === 304) {
|
|
698
|
-
// 304 不应出现(我们不发条件头),但防御性处理
|
|
699
|
-
if (cached?.text) {
|
|
700
|
-
this._log.debug(`downloadAgentMd exit (not_modified): elapsed=${Date.now() - tStart}ms aid=${targetAid}`);
|
|
701
|
-
return cached.text;
|
|
702
|
-
}
|
|
703
|
-
// 本地缓存为空却收到 304,警告并重试无条件 GET
|
|
704
|
-
this._log.warn(`downloadAgentMd got 304 but no local cache, retrying unconditional GET: aid=${targetAid}`);
|
|
705
|
-
const retryResp = await fetchWithTimeout(agentMdUrl, {
|
|
706
|
-
method: 'GET',
|
|
707
|
-
headers: { Accept: 'text/markdown' },
|
|
708
|
-
redirect: 'follow',
|
|
709
|
-
});
|
|
710
|
-
if (retryResp.status === 404) {
|
|
711
|
-
throw new NotFoundError(`agent.md not found for aid: ${targetAid}`);
|
|
712
|
-
}
|
|
713
|
-
if (!retryResp.ok) {
|
|
714
|
-
const message = (await retryResp.text()).trim();
|
|
715
|
-
throw new AUNError(`download agent.md failed (retry): HTTP ${retryResp.status}${message ? ` - ${message}` : ''}`);
|
|
716
|
-
}
|
|
717
|
-
const retryText = await retryResp.text();
|
|
718
|
-
this._log.debug(`downloadAgentMd exit (retry): elapsed=${Date.now() - tStart}ms aid=${targetAid} bytes=${retryText.length}`);
|
|
719
|
-
return retryText;
|
|
720
|
-
}
|
|
721
|
-
if (response.status === 404) {
|
|
722
|
-
throw new NotFoundError(`agent.md not found for aid: ${targetAid}`);
|
|
723
|
-
}
|
|
724
|
-
if (!response.ok) {
|
|
725
|
-
const message = (await response.text()).trim();
|
|
726
|
-
throw new AUNError(`download agent.md failed: HTTP ${response.status}${message ? ` - ${message}` : ''}`);
|
|
727
|
-
}
|
|
728
|
-
const text = await response.text();
|
|
729
|
-
// headers 在某些 fake response(测试 mock)下可能缺失,做一次容错处理。
|
|
730
|
-
const respHeaders = response.headers;
|
|
731
|
-
const etag = respHeaders ? String(respHeaders.get('ETag') ?? '').trim() : '';
|
|
732
|
-
const lastModified = respHeaders ? String(respHeaders.get('Last-Modified') ?? '').trim() : '';
|
|
733
|
-
if (etag || lastModified) {
|
|
734
|
-
this._agentMdCache.set(targetAid, { text, etag, lastModified });
|
|
735
|
-
}
|
|
736
|
-
this._log.debug(`downloadAgentMd exit: elapsed=${Date.now() - tStart}ms aid=${targetAid} bytes=${text.length}`);
|
|
737
|
-
return text;
|
|
738
|
-
}
|
|
739
|
-
catch (err) {
|
|
740
|
-
this._log.debug(`downloadAgentMd exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
741
|
-
throw err;
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
/** 下载证书(透传 RPC) */
|
|
745
|
-
async downloadCert(params) {
|
|
746
|
-
const tStart = Date.now();
|
|
747
|
-
this._log.debug('downloadCert enter');
|
|
748
|
-
try {
|
|
749
|
-
const r = await this._client.call('auth.download_cert', params ?? {});
|
|
750
|
-
this._log.debug(`downloadCert exit: elapsed=${Date.now() - tStart}ms`);
|
|
751
|
-
return r;
|
|
752
|
-
}
|
|
753
|
-
catch (err) {
|
|
754
|
-
this._log.debug(`downloadCert exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
755
|
-
throw err;
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
/** 请求签发证书(透传 RPC) */
|
|
759
|
-
async requestCert(params) {
|
|
760
|
-
const tStart = Date.now();
|
|
761
|
-
this._log.debug('requestCert enter');
|
|
762
|
-
try {
|
|
763
|
-
const r = await this._client.call('auth.request_cert', params);
|
|
764
|
-
this._log.debug(`requestCert exit: elapsed=${Date.now() - tStart}ms`);
|
|
765
|
-
return r;
|
|
766
|
-
}
|
|
767
|
-
catch (err) {
|
|
768
|
-
this._log.debug(`requestCert exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
769
|
-
throw err;
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
/** 续期证书(透传 RPC) */
|
|
773
|
-
async renewCert(params) {
|
|
774
|
-
const tStart = Date.now();
|
|
775
|
-
this._log.debug('renewCert enter');
|
|
776
|
-
try {
|
|
777
|
-
const r = await this._client.call('auth.renew_cert', params ?? {});
|
|
778
|
-
this._log.debug(`renewCert exit: elapsed=${Date.now() - tStart}ms`);
|
|
779
|
-
return r;
|
|
780
|
-
}
|
|
781
|
-
catch (err) {
|
|
782
|
-
this._log.debug(`renewCert exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
783
|
-
throw err;
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
/** 密钥轮换(透传 RPC) */
|
|
787
|
-
async rekey(params) {
|
|
788
|
-
const tStart = Date.now();
|
|
789
|
-
this._log.debug('rekey enter');
|
|
790
|
-
try {
|
|
791
|
-
const r = await this._client.call('auth.rekey', params ?? {});
|
|
792
|
-
this._log.debug(`rekey exit: elapsed=${Date.now() - tStart}ms`);
|
|
793
|
-
return r;
|
|
794
|
-
}
|
|
795
|
-
catch (err) {
|
|
796
|
-
this._log.debug(`rekey exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
797
|
-
throw err;
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
/** 获取信任根(透传 RPC) */
|
|
801
|
-
async trustRoots(params) {
|
|
802
|
-
const tStart = Date.now();
|
|
803
|
-
this._log.debug('trustRoots enter');
|
|
804
|
-
try {
|
|
805
|
-
const r = await this._client.call('meta.trust_roots', params ?? {});
|
|
806
|
-
this._log.debug(`trustRoots exit: elapsed=${Date.now() - tStart}ms`);
|
|
807
|
-
return r;
|
|
808
|
-
}
|
|
809
|
-
catch (err) {
|
|
810
|
-
this._log.debug(`trustRoots exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
811
|
-
throw err;
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
/**
|
|
815
|
-
* 检查指定 AID 的本地和远程状态。
|
|
816
|
-
* 与 Python SDK namespaces/auth_namespace.py:check_aid 对应。
|
|
817
|
-
*/
|
|
818
|
-
async checkAid(params) {
|
|
819
|
-
const tStart = Date.now();
|
|
820
|
-
const aid = String(params?.aid ?? '').trim();
|
|
821
|
-
if (!aid)
|
|
822
|
-
throw new ValidationError("auth.check_aid requires 'aid'");
|
|
823
|
-
this._log.debug(`checkAid enter: aid=${aid}`);
|
|
824
|
-
try {
|
|
825
|
-
const result = await this._checkLocalAid(aid);
|
|
826
|
-
const local = result.local;
|
|
827
|
-
if (!local?.complete) {
|
|
828
|
-
const remote = await this._checkRemoteAidRegistration(aid);
|
|
829
|
-
result.remote = remote;
|
|
830
|
-
const remoteStatus = remote?.status;
|
|
831
|
-
if (remoteStatus === 'available') {
|
|
832
|
-
result.status = 'available';
|
|
833
|
-
result.can_register = true;
|
|
834
|
-
}
|
|
835
|
-
else if (remoteStatus === 'registered') {
|
|
836
|
-
result.status = 'registered_remote';
|
|
837
|
-
result.can_register = false;
|
|
838
|
-
}
|
|
839
|
-
else {
|
|
840
|
-
result.status = 'unknown';
|
|
841
|
-
result.can_register = false;
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
this._log.debug(`checkAid exit: elapsed=${Date.now() - tStart}ms aid=${aid} status=${String(result.status)}`);
|
|
845
|
-
return result;
|
|
846
|
-
}
|
|
847
|
-
catch (err) {
|
|
848
|
-
this._log.debug(`checkAid exit (error): elapsed=${Date.now() - tStart}ms aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
|
|
849
|
-
throw err;
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
async _checkLocalAid(aid) {
|
|
853
|
-
const client = this._internal;
|
|
854
|
-
const ks = client._keystore;
|
|
855
|
-
const identity = await client._auth.loadIdentityOrNone(aid);
|
|
856
|
-
let keyPair = null;
|
|
857
|
-
let keyError = '';
|
|
858
|
-
try {
|
|
859
|
-
if (ks && typeof ks.loadKeyPair === 'function') {
|
|
860
|
-
keyPair = await ks.loadKeyPair(aid);
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
catch (e) {
|
|
864
|
-
keyError = e instanceof Error ? e.message : String(e);
|
|
865
|
-
}
|
|
866
|
-
let certPem = null;
|
|
867
|
-
let certError = '';
|
|
868
|
-
try {
|
|
869
|
-
if (ks && typeof ks.loadCert === 'function') {
|
|
870
|
-
certPem = await ks.loadCert(aid);
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
catch (e) {
|
|
874
|
-
certError = e instanceof Error ? e.message : String(e);
|
|
875
|
-
}
|
|
876
|
-
const privateKeyPresent = !!(keyPair && keyPair.private_key_pem);
|
|
877
|
-
const publicKeyPresent = !!(keyPair && keyPair.public_key_der_b64);
|
|
878
|
-
const certPresent = !!certPem;
|
|
879
|
-
const certInfo = certPresent
|
|
880
|
-
? await this._inspectCertBrowser(aid, certPem)
|
|
881
|
-
: { present: false, valid: false, expired: false };
|
|
882
|
-
const certValid = !!certInfo.valid;
|
|
883
|
-
const localComplete = privateKeyPresent && publicKeyPresent && certPresent && certValid;
|
|
884
|
-
const issues = [];
|
|
885
|
-
if (!identity)
|
|
886
|
-
issues.push('local identity not found');
|
|
887
|
-
if (!privateKeyPresent)
|
|
888
|
-
issues.push('private key missing');
|
|
889
|
-
if (!publicKeyPresent)
|
|
890
|
-
issues.push('public key missing');
|
|
891
|
-
if (!certPresent) {
|
|
892
|
-
issues.push('certificate missing');
|
|
893
|
-
}
|
|
894
|
-
else if (certInfo.parse_error) {
|
|
895
|
-
issues.push(`certificate invalid: ${certInfo.parse_error}`);
|
|
896
|
-
}
|
|
897
|
-
else if (certInfo.expired) {
|
|
898
|
-
issues.push('certificate expired');
|
|
899
|
-
}
|
|
900
|
-
else if (!certValid) {
|
|
901
|
-
issues.push('certificate not currently valid');
|
|
902
|
-
}
|
|
903
|
-
if (keyError)
|
|
904
|
-
issues.push(`key load error: ${keyError}`);
|
|
905
|
-
if (certError)
|
|
906
|
-
issues.push(`certificate load error: ${certError}`);
|
|
907
|
-
return {
|
|
908
|
-
aid,
|
|
909
|
-
status: localComplete ? 'local_ready' : 'local_incomplete',
|
|
910
|
-
can_register: localComplete ? false : null,
|
|
911
|
-
local: {
|
|
912
|
-
exists: identity !== null,
|
|
913
|
-
complete: localComplete,
|
|
914
|
-
private_key: privateKeyPresent,
|
|
915
|
-
public_key: publicKeyPresent,
|
|
916
|
-
certificate: certInfo,
|
|
917
|
-
issues,
|
|
918
|
-
},
|
|
919
|
-
remote: {
|
|
920
|
-
status: localComplete ? 'not_checked' : 'pending',
|
|
921
|
-
},
|
|
922
|
-
};
|
|
923
|
-
}
|
|
924
|
-
/** 浏览器环境证书检查(无 node:crypto X509Certificate) */
|
|
925
|
-
async _inspectCertBrowser(aid, certPem) {
|
|
926
|
-
const result = { present: true, valid: false, expired: false };
|
|
927
|
-
try {
|
|
928
|
-
const fingerprint = await certificateSha256Fingerprint(certPem);
|
|
929
|
-
const cn = extractCommonNameFromCertPem(certPem);
|
|
930
|
-
const aidMatches = !cn || cn === aid;
|
|
931
|
-
// 从 DER 解析有效期
|
|
932
|
-
const validity = parseCertValidity(certPem);
|
|
933
|
-
if (validity) {
|
|
934
|
-
const now = Date.now();
|
|
935
|
-
const valid = now >= validity.notBefore && now <= validity.notAfter && aidMatches;
|
|
936
|
-
const expired = now > validity.notAfter;
|
|
937
|
-
result.valid = valid;
|
|
938
|
-
result.expired = expired;
|
|
939
|
-
result.not_before = new Date(validity.notBefore).toISOString();
|
|
940
|
-
result.not_after = new Date(validity.notAfter).toISOString();
|
|
941
|
-
result.expires_at = Math.floor(validity.notAfter / 1000);
|
|
942
|
-
result.seconds_until_expiry = Math.floor((validity.notAfter - now) / 1000);
|
|
943
|
-
}
|
|
944
|
-
else {
|
|
945
|
-
// 无法解析有效期,仅检查 CN
|
|
946
|
-
result.valid = aidMatches;
|
|
947
|
-
}
|
|
948
|
-
result.fingerprint = fingerprint;
|
|
949
|
-
result.subject_cn = cn;
|
|
950
|
-
result.aid_matches = aidMatches;
|
|
951
|
-
if (cn && cn !== aid) {
|
|
952
|
-
result.valid = false;
|
|
953
|
-
result.parse_error = `certificate CN mismatch: ${cn}`;
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
catch (e) {
|
|
957
|
-
result.parse_error = e instanceof Error ? e.message : String(e);
|
|
958
|
-
}
|
|
959
|
-
return result;
|
|
960
|
-
}
|
|
961
|
-
async _checkRemoteAidRegistration(aid) {
|
|
962
|
-
try {
|
|
963
|
-
const content = await this.downloadAgentMd(aid);
|
|
964
|
-
return {
|
|
965
|
-
status: 'registered',
|
|
966
|
-
registered: true,
|
|
967
|
-
available: false,
|
|
968
|
-
source: 'agent.md',
|
|
969
|
-
agent_md_bytes: new TextEncoder().encode(content).length,
|
|
970
|
-
agent_md_aid: extractAgentMDAid(content),
|
|
971
|
-
};
|
|
972
|
-
}
|
|
973
|
-
catch (err) {
|
|
974
|
-
if (err instanceof NotFoundError) {
|
|
975
|
-
return {
|
|
976
|
-
status: 'available',
|
|
977
|
-
registered: false,
|
|
978
|
-
available: true,
|
|
979
|
-
source: 'agent.md',
|
|
980
|
-
};
|
|
981
|
-
}
|
|
982
|
-
return {
|
|
983
|
-
status: 'unknown',
|
|
984
|
-
registered: null,
|
|
985
|
-
available: null,
|
|
986
|
-
source: 'agent.md',
|
|
987
|
-
error: err instanceof Error ? err.message : String(err),
|
|
988
|
-
};
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
//# sourceMappingURL=auth.js.map
|