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