@holokai/neuron-sdk 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -3
- package/dist/auth.d.mts +187 -0
- package/dist/auth.d.ts +187 -0
- package/dist/auth.js +354 -0
- package/dist/auth.js.map +1 -0
- package/dist/auth.mjs +343 -0
- package/dist/auth.mjs.map +1 -0
- package/dist/cli.js +436 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.mts +21 -8
- package/dist/index.d.ts +21 -8
- package/dist/index.js +100 -29
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +100 -29
- package/dist/index.mjs.map +1 -1
- package/dist/ipc.d.mts +1 -1
- package/dist/ipc.d.ts +1 -1
- package/dist/{transport-QqkooS7K.d.mts → transport-Cs5tY2IO.d.mts} +91 -2
- package/dist/{transport-QqkooS7K.d.ts → transport-Cs5tY2IO.d.ts} +91 -2
- package/package.json +12 -3
package/dist/auth.js
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var fs = require('fs');
|
|
4
|
+
var os = require('os');
|
|
5
|
+
var path = require('path');
|
|
6
|
+
|
|
7
|
+
// src/logger.ts
|
|
8
|
+
var noopLogger = {
|
|
9
|
+
debug() {
|
|
10
|
+
},
|
|
11
|
+
info() {
|
|
12
|
+
},
|
|
13
|
+
warn() {
|
|
14
|
+
},
|
|
15
|
+
error() {
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
function defaultCredentialPath() {
|
|
19
|
+
return path.join(os.homedir(), ".holokai", "neuron-credentials.json");
|
|
20
|
+
}
|
|
21
|
+
function readCredentialFile(path) {
|
|
22
|
+
let raw;
|
|
23
|
+
try {
|
|
24
|
+
raw = fs.readFileSync(path, "utf8");
|
|
25
|
+
} catch (err) {
|
|
26
|
+
if (err.code === "ENOENT") return null;
|
|
27
|
+
throw err;
|
|
28
|
+
}
|
|
29
|
+
const parsed = JSON.parse(raw);
|
|
30
|
+
if (typeof parsed.client_id !== "string" || typeof parsed.client_secret !== "string" || typeof parsed.token_endpoint !== "string" || typeof parsed.moku_base_url !== "string") {
|
|
31
|
+
throw new Error(`credential file ${path} is malformed \u2014 expected client_id/client_secret/token_endpoint/moku_base_url`);
|
|
32
|
+
}
|
|
33
|
+
return parsed;
|
|
34
|
+
}
|
|
35
|
+
function writeCredentialFile(path$1, credential) {
|
|
36
|
+
fs.mkdirSync(path.dirname(path$1), { recursive: true, mode: 448 });
|
|
37
|
+
fs.writeFileSync(path$1, `${JSON.stringify(credential, null, 2)}
|
|
38
|
+
`, { mode: 384 });
|
|
39
|
+
fs.chmodSync(path$1, 384);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/auth/types.ts
|
|
43
|
+
function clientSecretPost(clientId, clientSecret) {
|
|
44
|
+
return {
|
|
45
|
+
apply(form) {
|
|
46
|
+
form.set("client_id", clientId);
|
|
47
|
+
form.set("client_secret", clientSecret);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
var OAuthTokenError = class extends Error {
|
|
52
|
+
constructor(error, status, description) {
|
|
53
|
+
super(`token endpoint returned ${error}${description ? `: ${description}` : ""}`);
|
|
54
|
+
this.error = error;
|
|
55
|
+
this.status = status;
|
|
56
|
+
this.name = "OAuthTokenError";
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
var CredentialRevokedError = class extends Error {
|
|
60
|
+
constructor(message) {
|
|
61
|
+
super(message);
|
|
62
|
+
this.name = "CredentialRevokedError";
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var EnrollmentError = class extends Error {
|
|
66
|
+
constructor(message, code) {
|
|
67
|
+
super(message);
|
|
68
|
+
this.code = code;
|
|
69
|
+
this.name = "EnrollmentError";
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// src/auth/token-client.ts
|
|
74
|
+
async function requestToken(tokenEndpoint, form, fetchImpl) {
|
|
75
|
+
const res = await fetchImpl(tokenEndpoint, {
|
|
76
|
+
method: "POST",
|
|
77
|
+
headers: {
|
|
78
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
79
|
+
Accept: "application/json"
|
|
80
|
+
},
|
|
81
|
+
body: form.toString()
|
|
82
|
+
});
|
|
83
|
+
const body = await res.json().catch(() => ({}));
|
|
84
|
+
if (!res.ok) {
|
|
85
|
+
const error = typeof body["error"] === "string" ? body["error"] : `http_${res.status}`;
|
|
86
|
+
const description = typeof body["error_description"] === "string" ? body["error_description"] : void 0;
|
|
87
|
+
throw new OAuthTokenError(error, res.status, description);
|
|
88
|
+
}
|
|
89
|
+
if (typeof body["access_token"] !== "string" || typeof body["expires_in"] !== "number") {
|
|
90
|
+
throw new OAuthTokenError("invalid_response", res.status, "missing access_token/expires_in");
|
|
91
|
+
}
|
|
92
|
+
return body;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/auth/client-credentials.ts
|
|
96
|
+
var DEFAULT_REFRESH_BUFFER_MS = 6e4;
|
|
97
|
+
function createClientCredentialsAuth(credentialPath, opts = {}) {
|
|
98
|
+
const fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
|
|
99
|
+
const logger = opts.logger ?? noopLogger;
|
|
100
|
+
const refreshBufferMs = opts.refreshBufferMs ?? DEFAULT_REFRESH_BUFFER_MS;
|
|
101
|
+
const makeAuthenticator = opts.authenticator ?? ((c) => clientSecretPost(c.client_id, c.client_secret));
|
|
102
|
+
const now = opts.now ?? Date.now;
|
|
103
|
+
let credential = null;
|
|
104
|
+
let cached = null;
|
|
105
|
+
let inFlight = null;
|
|
106
|
+
function loadCredential() {
|
|
107
|
+
const loaded = readCredentialFile(credentialPath);
|
|
108
|
+
if (!loaded) {
|
|
109
|
+
throw new CredentialRevokedError(
|
|
110
|
+
`no credential at ${credentialPath} \u2014 enroll this neuron first (neuron-sdk enroll)`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
return loaded;
|
|
114
|
+
}
|
|
115
|
+
async function mintWith(cred) {
|
|
116
|
+
const form = new URLSearchParams({ grant_type: "client_credentials" });
|
|
117
|
+
await makeAuthenticator(cred).apply(form);
|
|
118
|
+
const res = await requestToken(cred.token_endpoint, form, fetchImpl);
|
|
119
|
+
return { token: res.access_token, expiresIn: res.expires_in };
|
|
120
|
+
}
|
|
121
|
+
async function mint() {
|
|
122
|
+
credential ??= loadCredential();
|
|
123
|
+
let minted;
|
|
124
|
+
try {
|
|
125
|
+
minted = await mintWith(credential);
|
|
126
|
+
} catch (err) {
|
|
127
|
+
if (!(err instanceof OAuthTokenError) || err.error !== "invalid_client") throw err;
|
|
128
|
+
logger.warn(
|
|
129
|
+
{ clientId: credential.client_id },
|
|
130
|
+
"token mint rejected (invalid_client); re-reading credential file"
|
|
131
|
+
);
|
|
132
|
+
const reread = readCredentialFile(credentialPath);
|
|
133
|
+
const changed = reread && (reread.client_secret !== credential.client_secret || reread.client_id !== credential.client_id);
|
|
134
|
+
if (!changed) {
|
|
135
|
+
throw new CredentialRevokedError(
|
|
136
|
+
`Moku rejected client ${credential.client_id} (invalid_client) and the credential file is unchanged \u2014 the credential was revoked or rotated server-side; re-enroll this neuron`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
credential = reread;
|
|
140
|
+
try {
|
|
141
|
+
minted = await mintWith(credential);
|
|
142
|
+
} catch (retryErr) {
|
|
143
|
+
if (retryErr instanceof OAuthTokenError && retryErr.error === "invalid_client") {
|
|
144
|
+
throw new CredentialRevokedError(
|
|
145
|
+
`Moku rejected client ${credential.client_id} (invalid_client) after credential re-read \u2014 re-enroll this neuron`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
throw retryErr;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const lifetimeMs = minted.expiresIn * 1e3;
|
|
152
|
+
const refreshAtMs = now() + Math.max(lifetimeMs - refreshBufferMs, lifetimeMs / 2);
|
|
153
|
+
cached = { token: minted.token, refreshAtMs };
|
|
154
|
+
logger.debug(
|
|
155
|
+
{ clientId: credential.client_id, expiresInSeconds: minted.expiresIn },
|
|
156
|
+
"minted bigbrain access token"
|
|
157
|
+
);
|
|
158
|
+
return minted.token;
|
|
159
|
+
}
|
|
160
|
+
return async function auth() {
|
|
161
|
+
if (cached && now() < cached.refreshAtMs) return cached.token;
|
|
162
|
+
inFlight ??= mint().finally(() => {
|
|
163
|
+
inFlight = null;
|
|
164
|
+
});
|
|
165
|
+
return inFlight;
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/auth/discovery.ts
|
|
170
|
+
var DEFAULT_TOKEN_PATH = "/api/v1/auth/token";
|
|
171
|
+
var DEFAULT_DEVICE_AUTHORIZATION_PATH = "/api/v1/auth/device";
|
|
172
|
+
var DISCOVERY_PATH = "/.well-known/oauth-authorization-server";
|
|
173
|
+
async function discoverEndpoints(mokuBaseUrl, opts = {}) {
|
|
174
|
+
const base = mokuBaseUrl.replace(/\/+$/, "");
|
|
175
|
+
const fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
|
|
176
|
+
const logger = opts.logger ?? noopLogger;
|
|
177
|
+
const fallback = {
|
|
178
|
+
tokenEndpoint: `${base}${DEFAULT_TOKEN_PATH}`,
|
|
179
|
+
deviceAuthorizationEndpoint: `${base}${DEFAULT_DEVICE_AUTHORIZATION_PATH}`
|
|
180
|
+
};
|
|
181
|
+
let res;
|
|
182
|
+
try {
|
|
183
|
+
res = await fetchImpl(`${base}${DISCOVERY_PATH}`, {
|
|
184
|
+
headers: { Accept: "application/json" }
|
|
185
|
+
});
|
|
186
|
+
} catch (err) {
|
|
187
|
+
logger.warn(
|
|
188
|
+
{ err: err.message },
|
|
189
|
+
"OAuth discovery unreachable; using default endpoint paths"
|
|
190
|
+
);
|
|
191
|
+
return fallback;
|
|
192
|
+
}
|
|
193
|
+
if (!res.ok) {
|
|
194
|
+
logger.warn(
|
|
195
|
+
{ status: res.status },
|
|
196
|
+
"OAuth discovery not served; using default endpoint paths"
|
|
197
|
+
);
|
|
198
|
+
return fallback;
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
const doc = await res.json();
|
|
202
|
+
const token = doc["token_endpoint"];
|
|
203
|
+
const device = doc["device_authorization_endpoint"];
|
|
204
|
+
if (typeof token !== "string" || typeof device !== "string") {
|
|
205
|
+
logger.warn({}, "OAuth discovery document incomplete; using default endpoint paths");
|
|
206
|
+
return fallback;
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
tokenEndpoint: reRoot(token, base),
|
|
210
|
+
deviceAuthorizationEndpoint: reRoot(device, base)
|
|
211
|
+
};
|
|
212
|
+
} catch {
|
|
213
|
+
logger.warn({}, "OAuth discovery document malformed; using default endpoint paths");
|
|
214
|
+
return fallback;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function reRoot(servedUrl, base) {
|
|
218
|
+
try {
|
|
219
|
+
const parsed = new URL(servedUrl);
|
|
220
|
+
return `${base}${parsed.pathname}`;
|
|
221
|
+
} catch {
|
|
222
|
+
return `${base}${servedUrl.startsWith("/") ? "" : "/"}${servedUrl}`;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
var DEVICE_CODE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
|
|
226
|
+
async function enroll(opts) {
|
|
227
|
+
const credentialPath = opts.credentialPath ?? defaultCredentialPath();
|
|
228
|
+
const fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
|
|
229
|
+
const logger = opts.logger ?? noopLogger;
|
|
230
|
+
const out = opts.out ?? ((line) => console.log(line));
|
|
231
|
+
const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
232
|
+
const existing = readCredentialFile(credentialPath);
|
|
233
|
+
if (existing) {
|
|
234
|
+
try {
|
|
235
|
+
await createClientCredentialsAuth(credentialPath, { fetch: fetchImpl, logger })();
|
|
236
|
+
out(`Already enrolled as client ${existing.client_id} (credential: ${credentialPath}).`);
|
|
237
|
+
return { status: "already-enrolled", clientId: existing.client_id, credentialPath };
|
|
238
|
+
} catch (err) {
|
|
239
|
+
if (!(err instanceof CredentialRevokedError)) throw err;
|
|
240
|
+
logger.warn(
|
|
241
|
+
{ clientId: existing.client_id },
|
|
242
|
+
"existing credential no longer mints; starting fresh enrollment"
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const endpoints = await discoverEndpoints(opts.mokuBaseUrl, { fetch: fetchImpl, logger });
|
|
247
|
+
const deviceForm = new URLSearchParams({ client_name: opts.clientName });
|
|
248
|
+
deviceForm.set("hostname", opts.hostname ?? os.hostname());
|
|
249
|
+
if (opts.capabilityTypes?.length) {
|
|
250
|
+
deviceForm.set("capability_types", opts.capabilityTypes.join(","));
|
|
251
|
+
}
|
|
252
|
+
const deviceRes = await fetchImpl(endpoints.deviceAuthorizationEndpoint, {
|
|
253
|
+
method: "POST",
|
|
254
|
+
headers: {
|
|
255
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
256
|
+
Accept: "application/json"
|
|
257
|
+
},
|
|
258
|
+
body: deviceForm.toString()
|
|
259
|
+
});
|
|
260
|
+
if (!deviceRes.ok) {
|
|
261
|
+
const text = await deviceRes.text().catch(() => "");
|
|
262
|
+
throw new EnrollmentError(
|
|
263
|
+
`device authorization request failed: ${deviceRes.status}${text ? ` \u2014 ${text}` : ""}`,
|
|
264
|
+
"device_request_failed"
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
const device = await deviceRes.json();
|
|
268
|
+
if (!device.device_code || !device.user_code || !device.verification_uri) {
|
|
269
|
+
throw new EnrollmentError("device authorization response incomplete", "invalid_response");
|
|
270
|
+
}
|
|
271
|
+
out("To authorize this neuron, an org admin must visit:");
|
|
272
|
+
out(` ${device.verification_uri_complete ?? device.verification_uri}`);
|
|
273
|
+
out(`and enter code: ${device.user_code}`);
|
|
274
|
+
out(`Waiting for approval (expires in ${Math.round(device.expires_in / 60)} min)\u2026`);
|
|
275
|
+
let intervalMs = (device.interval ?? 5) * 1e3;
|
|
276
|
+
const deadline = Date.now() + device.expires_in * 1e3;
|
|
277
|
+
for (; ; ) {
|
|
278
|
+
if (Date.now() >= deadline) {
|
|
279
|
+
throw new EnrollmentError("enrollment request expired before approval", "expired_token");
|
|
280
|
+
}
|
|
281
|
+
await sleep(intervalMs);
|
|
282
|
+
const pollForm = new URLSearchParams({
|
|
283
|
+
grant_type: DEVICE_CODE_GRANT,
|
|
284
|
+
device_code: device.device_code
|
|
285
|
+
});
|
|
286
|
+
let token;
|
|
287
|
+
try {
|
|
288
|
+
token = await requestToken(endpoints.tokenEndpoint, pollForm, fetchImpl);
|
|
289
|
+
} catch (err) {
|
|
290
|
+
if (err instanceof OAuthTokenError) {
|
|
291
|
+
switch (err.error) {
|
|
292
|
+
case "authorization_pending":
|
|
293
|
+
continue;
|
|
294
|
+
case "slow_down":
|
|
295
|
+
intervalMs += 5e3;
|
|
296
|
+
continue;
|
|
297
|
+
case "expired_token":
|
|
298
|
+
throw new EnrollmentError("enrollment request expired before approval", "expired_token");
|
|
299
|
+
case "access_denied":
|
|
300
|
+
throw new EnrollmentError("enrollment was denied by the admin", "access_denied");
|
|
301
|
+
}
|
|
302
|
+
if (err.status === 409 || err.status >= 500) {
|
|
303
|
+
logger.warn(
|
|
304
|
+
{ error: err.error, status: err.status },
|
|
305
|
+
"transient token-endpoint error while polling; will retry"
|
|
306
|
+
);
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (!(err instanceof OAuthTokenError) && !(err instanceof EnrollmentError)) {
|
|
311
|
+
logger.warn(
|
|
312
|
+
{ err: err.message },
|
|
313
|
+
"token endpoint unreachable while polling; will retry"
|
|
314
|
+
);
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
throw err;
|
|
318
|
+
}
|
|
319
|
+
if (!token.client_id || !token.client_secret) {
|
|
320
|
+
throw new EnrollmentError(
|
|
321
|
+
"redemption response did not include a client credential",
|
|
322
|
+
"invalid_response"
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
const credential = {
|
|
326
|
+
client_id: token.client_id,
|
|
327
|
+
client_secret: token.client_secret,
|
|
328
|
+
token_endpoint: endpoints.tokenEndpoint,
|
|
329
|
+
moku_base_url: opts.mokuBaseUrl.replace(/\/+$/, "")
|
|
330
|
+
};
|
|
331
|
+
writeCredentialFile(credentialPath, credential);
|
|
332
|
+
out(`Enrolled as client ${credential.client_id}. Credential written to ${credentialPath}.`);
|
|
333
|
+
return {
|
|
334
|
+
status: "enrolled",
|
|
335
|
+
clientId: credential.client_id,
|
|
336
|
+
credentialPath,
|
|
337
|
+
accessToken: token.access_token,
|
|
338
|
+
expiresIn: token.expires_in
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
exports.CredentialRevokedError = CredentialRevokedError;
|
|
344
|
+
exports.EnrollmentError = EnrollmentError;
|
|
345
|
+
exports.OAuthTokenError = OAuthTokenError;
|
|
346
|
+
exports.clientSecretPost = clientSecretPost;
|
|
347
|
+
exports.createClientCredentialsAuth = createClientCredentialsAuth;
|
|
348
|
+
exports.defaultCredentialPath = defaultCredentialPath;
|
|
349
|
+
exports.discoverEndpoints = discoverEndpoints;
|
|
350
|
+
exports.enroll = enroll;
|
|
351
|
+
exports.readCredentialFile = readCredentialFile;
|
|
352
|
+
exports.writeCredentialFile = writeCredentialFile;
|
|
353
|
+
//# sourceMappingURL=auth.js.map
|
|
354
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/logger.ts","../src/auth/credential-file.ts","../src/auth/types.ts","../src/auth/token-client.ts","../src/auth/client-credentials.ts","../src/auth/discovery.ts","../src/auth/enrollment.ts"],"names":["join","homedir","readFileSync","path","mkdirSync","dirname","writeFileSync","chmodSync","osHostname"],"mappings":"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","file":"auth.js","sourcesContent":["import type { NeuronLogger } from './types.js';\n\n/**\n * Silent logger used as a default when no consumer provides one. Single\n * shared instance to avoid each module defining its own copy.\n */\nexport const noopLogger: NeuronLogger = {\n debug() {},\n info() {},\n warn() {},\n error() {},\n};\n","import { chmodSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\n\nimport type { StoredCredential } from './types.js';\n\n/** `~/.holokai/neuron-credentials.json` unless the caller overrides. */\nexport function defaultCredentialPath(): string {\n return join(homedir(), '.holokai', 'neuron-credentials.json');\n}\n\n/**\n * Read a stored credential. Returns null when the file does not exist\n * (unenrolled); throws on a present-but-malformed file, which is an\n * operator problem the caller must not paper over.\n */\nexport function readCredentialFile(path: string): StoredCredential | null {\n let raw: string;\n try {\n raw = readFileSync(path, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;\n throw err;\n }\n const parsed = JSON.parse(raw) as Partial<StoredCredential>;\n if (\n typeof parsed.client_id !== 'string' ||\n typeof parsed.client_secret !== 'string' ||\n typeof parsed.token_endpoint !== 'string' ||\n typeof parsed.moku_base_url !== 'string'\n ) {\n throw new Error(`credential file ${path} is malformed — expected client_id/client_secret/token_endpoint/moku_base_url`);\n }\n return parsed as StoredCredential;\n}\n\n/** Write the credential with owner-only permissions (0700 dir, 0600 file). */\nexport function writeCredentialFile(path: string, credential: StoredCredential): void {\n mkdirSync(dirname(path), { recursive: true, mode: 0o700 });\n writeFileSync(path, `${JSON.stringify(credential, null, 2)}\\n`, { mode: 0o600 });\n // writeFileSync's mode only applies on creation; enforce on overwrite too.\n chmodSync(path, 0o600);\n}\n","/**\n * OAuth endpoint set resolved from RFC 8414 discovery (or the fixed\n * fallback paths when discovery is unreachable — see `discovery.ts`).\n */\nexport interface OAuthEndpoints {\n tokenEndpoint: string;\n deviceAuthorizationEndpoint: string;\n}\n\n/**\n * The durable artifact on a neuron host: a revocable Moku OAuth client\n * credential (never a bearer JWT). Field names mirror the wire/JSON\n * shape so the file is greppable against Moku's own vocabulary.\n */\nexport interface StoredCredential {\n client_id: string;\n client_secret: string;\n token_endpoint: string;\n moku_base_url: string;\n}\n\n/**\n * How the client proves its identity to Moku's token endpoint.\n *\n * v1 ships `client_secret_post` only (matching Moku's discovery doc).\n * The interface exists so `private_key_jwt` (RFC 7523, HOL-2543) can\n * slot in later without touching `createClientCredentialsAuth` callers:\n * a keypair-backed implementation would append a signed\n * `client_assertion` instead of a shared secret.\n */\nexport interface ClientAuthenticator {\n /** Apply client authentication to the token-endpoint form body. */\n apply(form: URLSearchParams): void | Promise<void>;\n}\n\n/** RFC 6749 §2.3.1-style shared-secret client auth, in the body. */\nexport function clientSecretPost(clientId: string, clientSecret: string): ClientAuthenticator {\n return {\n apply(form: URLSearchParams): void {\n form.set('client_id', clientId);\n form.set('client_secret', clientSecret);\n },\n };\n}\n\n/** Successful token-endpoint response (RFC 6749 §5.1). On device-flow\n * redemption Moku additionally delivers the client credential — once. */\nexport interface TokenResponse {\n access_token: string;\n token_type: string;\n expires_in: number;\n scope?: string;\n client_id?: string;\n client_secret?: string;\n}\n\n/** Token-endpoint error (`{error, error_description}`, RFC 6749 §5.2). */\nexport class OAuthTokenError extends Error {\n constructor(\n public readonly error: string,\n public readonly status: number,\n description?: string,\n ) {\n super(`token endpoint returned ${error}${description ? `: ${description}` : ''}`);\n this.name = 'OAuthTokenError';\n }\n}\n\n/**\n * Terminal auth failure: the stored credential no longer mints (revoked,\n * deleted, or rotated away underneath us) or does not exist. The neuron\n * must be re-enrolled; retrying against Moku is pointless and the SDK\n * will not loop on it.\n */\nexport class CredentialRevokedError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'CredentialRevokedError';\n }\n}\n\n/** Enrollment (device flow) failed terminally. */\nexport class EnrollmentError extends Error {\n constructor(\n message: string,\n public readonly code:\n | 'expired_token'\n | 'access_denied'\n | 'invalid_response'\n | 'device_request_failed',\n ) {\n super(message);\n this.name = 'EnrollmentError';\n }\n}\n","import { OAuthTokenError, type TokenResponse } from './types.js';\n\n/**\n * POST a form to an OAuth token endpoint and parse the RFC 6749 response.\n * All Moku token-endpoint interactions are form-urlencoded (never JSON);\n * errors come back as `{error, error_description}`.\n */\nexport async function requestToken(\n tokenEndpoint: string,\n form: URLSearchParams,\n fetchImpl: typeof fetch,\n): Promise<TokenResponse> {\n const res = await fetchImpl(tokenEndpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n Accept: 'application/json',\n },\n body: form.toString(),\n });\n\n const body = (await res.json().catch(() => ({}))) as Record<string, unknown>;\n\n if (!res.ok) {\n const error = typeof body['error'] === 'string' ? body['error'] : `http_${res.status}`;\n const description =\n typeof body['error_description'] === 'string' ? body['error_description'] : undefined;\n throw new OAuthTokenError(error, res.status, description);\n }\n\n if (typeof body['access_token'] !== 'string' || typeof body['expires_in'] !== 'number') {\n throw new OAuthTokenError('invalid_response', res.status, 'missing access_token/expires_in');\n }\n return body as unknown as TokenResponse;\n}\n","import { noopLogger } from '../logger.js';\nimport type { AuthCallback, NeuronLogger } from '../types.js';\n\nimport { readCredentialFile } from './credential-file.js';\nimport { requestToken } from './token-client.js';\nimport {\n clientSecretPost,\n CredentialRevokedError,\n OAuthTokenError,\n type ClientAuthenticator,\n type StoredCredential,\n} from './types.js';\n\nexport interface ClientCredentialsAuthOptions {\n fetch?: typeof fetch;\n logger?: NeuronLogger;\n /**\n * Refresh this long before the token's `expires_in` elapses. The\n * response's `expires_in` is authoritative (Moku default is 15 min,\n * per-audience cap 1h) — never assume a lifetime.\n */\n refreshBufferMs?: number;\n /**\n * Client-auth method factory. Defaults to `client_secret_post` from the\n * stored credential; a `private_key_jwt` implementation (HOL-2543) can\n * be injected here without changing any other consumer code.\n */\n authenticator?: (credential: StoredCredential) => ClientAuthenticator;\n /** Clock override for tests. */\n now?: () => number;\n}\n\nconst DEFAULT_REFRESH_BUFFER_MS = 60_000;\n\n/**\n * Steady-state auth for enrolled (headless/enterprise) neurons: returns an\n * `auth()` callback for `HttpTransport` that mints short-lived\n * `aud=bigbrain` access tokens from Moku via the OAuth2 `client_credentials`\n * grant, using the credential file written by `enroll`.\n *\n * Behavior:\n * - In-memory cache driven by the response's `expires_in`, with proactive\n * refresh `refreshBufferMs` before expiry.\n * - Single-flight: concurrent callers share one mint request.\n * - On `invalid_client`, the credential file is re-read once before\n * failing — an operator-rotated secret is picked up without a restart.\n * - If the re-read credential still fails (revoked/deleted client), a\n * `CredentialRevokedError` is thrown; the SDK's transport will not\n * retry-loop against Moku on it. Re-enrollment is required.\n */\nexport function createClientCredentialsAuth(\n credentialPath: string,\n opts: ClientCredentialsAuthOptions = {},\n): AuthCallback {\n const fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);\n const logger = opts.logger ?? noopLogger;\n const refreshBufferMs = opts.refreshBufferMs ?? DEFAULT_REFRESH_BUFFER_MS;\n const makeAuthenticator =\n opts.authenticator ?? ((c: StoredCredential) => clientSecretPost(c.client_id, c.client_secret));\n const now = opts.now ?? Date.now;\n\n let credential: StoredCredential | null = null;\n let cached: { token: string; refreshAtMs: number } | null = null;\n let inFlight: Promise<string> | null = null;\n\n function loadCredential(): StoredCredential {\n const loaded = readCredentialFile(credentialPath);\n if (!loaded) {\n throw new CredentialRevokedError(\n `no credential at ${credentialPath} — enroll this neuron first (neuron-sdk enroll)`,\n );\n }\n return loaded;\n }\n\n async function mintWith(cred: StoredCredential): Promise<{ token: string; expiresIn: number }> {\n const form = new URLSearchParams({ grant_type: 'client_credentials' });\n await makeAuthenticator(cred).apply(form);\n const res = await requestToken(cred.token_endpoint, form, fetchImpl);\n return { token: res.access_token, expiresIn: res.expires_in };\n }\n\n async function mint(): Promise<string> {\n credential ??= loadCredential();\n let minted: { token: string; expiresIn: number };\n try {\n minted = await mintWith(credential);\n } catch (err) {\n if (!(err instanceof OAuthTokenError) || err.error !== 'invalid_client') throw err;\n // The secret may have been rotated on disk by an operator —\n // re-read once before declaring the credential dead.\n logger.warn(\n { clientId: credential.client_id },\n 'token mint rejected (invalid_client); re-reading credential file',\n );\n const reread = readCredentialFile(credentialPath);\n const changed =\n reread &&\n (reread.client_secret !== credential.client_secret ||\n reread.client_id !== credential.client_id);\n if (!changed) {\n throw new CredentialRevokedError(\n `Moku rejected client ${credential.client_id} (invalid_client) and the credential file is unchanged — the credential was revoked or rotated server-side; re-enroll this neuron`,\n );\n }\n credential = reread;\n try {\n minted = await mintWith(credential);\n } catch (retryErr) {\n if (retryErr instanceof OAuthTokenError && retryErr.error === 'invalid_client') {\n throw new CredentialRevokedError(\n `Moku rejected client ${credential.client_id} (invalid_client) after credential re-read — re-enroll this neuron`,\n );\n }\n throw retryErr;\n }\n }\n\n // Refresh ahead of expiry, but never treat a token as expired the\n // instant it is minted (very short expires_in vs. large buffer).\n const lifetimeMs = minted.expiresIn * 1000;\n const refreshAtMs = now() + Math.max(lifetimeMs - refreshBufferMs, lifetimeMs / 2);\n cached = { token: minted.token, refreshAtMs };\n logger.debug(\n { clientId: credential.client_id, expiresInSeconds: minted.expiresIn },\n 'minted bigbrain access token',\n );\n return minted.token;\n }\n\n return async function auth(): Promise<string> {\n if (cached && now() < cached.refreshAtMs) return cached.token;\n inFlight ??= mint().finally(() => {\n inFlight = null;\n });\n return inFlight;\n };\n}\n","import { noopLogger } from '../logger.js';\nimport type { NeuronLogger } from '../types.js';\n\nimport type { OAuthEndpoints } from './types.js';\n\n/**\n * Fixed fallback paths, pinned to the Moku contract (HOL-1922 /\n * NEURON_AUTH_SPEC.md §4). Moku's public nginx allowlist may not proxy\n * `/.well-known/oauth-authorization-server`, so discovery failing is an\n * expected deployment shape, not an error.\n */\nexport const DEFAULT_TOKEN_PATH = '/api/v1/auth/token';\nexport const DEFAULT_DEVICE_AUTHORIZATION_PATH = '/api/v1/auth/device';\n\nconst DISCOVERY_PATH = '/.well-known/oauth-authorization-server';\n\n/**\n * Resolve OAuth endpoints for a Moku base URL via RFC 8414 discovery,\n * falling back to the fixed default paths when the metadata document is\n * unreachable or malformed.\n *\n * Served endpoint URLs are re-rooted onto `mokuBaseUrl` (path is taken\n * from the document, origin from the caller): the metadata document\n * derives its URLs from the inbound request URL, which behind a\n * TLS-terminating proxy can name an origin the neuron cannot reach.\n * The caller-supplied base URL is the one address known to work.\n */\nexport async function discoverEndpoints(\n mokuBaseUrl: string,\n opts: { fetch?: typeof fetch; logger?: NeuronLogger } = {},\n): Promise<OAuthEndpoints> {\n const base = mokuBaseUrl.replace(/\\/+$/, '');\n const fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);\n const logger = opts.logger ?? noopLogger;\n\n const fallback: OAuthEndpoints = {\n tokenEndpoint: `${base}${DEFAULT_TOKEN_PATH}`,\n deviceAuthorizationEndpoint: `${base}${DEFAULT_DEVICE_AUTHORIZATION_PATH}`,\n };\n\n let res: Response;\n try {\n res = await fetchImpl(`${base}${DISCOVERY_PATH}`, {\n headers: { Accept: 'application/json' },\n });\n } catch (err) {\n logger.warn(\n { err: (err as Error).message },\n 'OAuth discovery unreachable; using default endpoint paths',\n );\n return fallback;\n }\n\n if (!res.ok) {\n logger.warn(\n { status: res.status },\n 'OAuth discovery not served; using default endpoint paths',\n );\n return fallback;\n }\n\n try {\n const doc = (await res.json()) as Record<string, unknown>;\n const token = doc['token_endpoint'];\n const device = doc['device_authorization_endpoint'];\n if (typeof token !== 'string' || typeof device !== 'string') {\n logger.warn({}, 'OAuth discovery document incomplete; using default endpoint paths');\n return fallback;\n }\n return {\n tokenEndpoint: reRoot(token, base),\n deviceAuthorizationEndpoint: reRoot(device, base),\n };\n } catch {\n logger.warn({}, 'OAuth discovery document malformed; using default endpoint paths');\n return fallback;\n }\n}\n\nfunction reRoot(servedUrl: string, base: string): string {\n try {\n const parsed = new URL(servedUrl);\n return `${base}${parsed.pathname}`;\n } catch {\n // Relative path in the document — treat it as one.\n return `${base}${servedUrl.startsWith('/') ? '' : '/'}${servedUrl}`;\n }\n}\n","import { hostname as osHostname } from 'node:os';\n\nimport { noopLogger } from '../logger.js';\nimport type { NeuronLogger } from '../types.js';\n\nimport { createClientCredentialsAuth } from './client-credentials.js';\nimport { defaultCredentialPath, readCredentialFile, writeCredentialFile } from './credential-file.js';\nimport { discoverEndpoints } from './discovery.js';\nimport { requestToken } from './token-client.js';\nimport {\n CredentialRevokedError,\n EnrollmentError,\n OAuthTokenError,\n type StoredCredential,\n} from './types.js';\n\nconst DEVICE_CODE_GRANT = 'urn:ietf:params:oauth:grant-type:device_code';\n\nexport interface EnrollOptions {\n /** Moku base URL — the only required config (endpoints are discovered). */\n mokuBaseUrl: string;\n /** Display name shown to the approving admin. Required by Moku. */\n clientName: string;\n /** Display hint for the approval screen; defaults to `os.hostname()`. */\n hostname?: string;\n /**\n * Capability types this neuron intends to serve — display hints for the\n * approval screen ONLY. Authorization comes from the org's capability\n * catalog, not from this list (requested ≠ granted).\n */\n capabilityTypes?: string[];\n /** Where to persist the credential. Default: `~/.holokai/neuron-credentials.json`. */\n credentialPath?: string;\n fetch?: typeof fetch;\n logger?: NeuronLogger;\n /** Where user-facing lines (verification URI + user code) go. Default: console.log. */\n out?: (line: string) => void;\n /** Sleep override for tests. */\n sleep?: (ms: number) => Promise<void>;\n}\n\nexport interface EnrollResult {\n status: 'already-enrolled' | 'enrolled';\n clientId: string;\n credentialPath: string;\n /** Bootstrap token from the redemption response (only when freshly enrolled). */\n accessToken?: string;\n expiresIn?: number;\n}\n\ninterface DeviceAuthorizationResponse {\n device_code: string;\n user_code: string;\n verification_uri: string;\n verification_uri_complete?: string;\n expires_in: number;\n interval?: number;\n}\n\n/**\n * One-time enrollment of a headless neuron via Moku's RFC 8628 device\n * authorization grant.\n *\n * Flow: start a device authorization → print the verification URI +\n * user code (container-log friendly) → poll the token endpoint until an\n * org ADMIN approves in Moku → persist the client credential delivered\n * in the redemption token response (delivered exactly once).\n *\n * Idempotent: if a credential file already exists and still mints, this\n * is a no-op reporting `already-enrolled`. A revoked existing credential\n * falls through to a fresh enrollment.\n */\nexport async function enroll(opts: EnrollOptions): Promise<EnrollResult> {\n const credentialPath = opts.credentialPath ?? defaultCredentialPath();\n const fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);\n const logger = opts.logger ?? noopLogger;\n const out = opts.out ?? ((line: string) => console.log(line));\n const sleep = opts.sleep ?? ((ms: number) => new Promise<void>((r) => setTimeout(r, ms)));\n\n const existing = readCredentialFile(credentialPath);\n if (existing) {\n try {\n await createClientCredentialsAuth(credentialPath, { fetch: fetchImpl, logger })();\n out(`Already enrolled as client ${existing.client_id} (credential: ${credentialPath}).`);\n return { status: 'already-enrolled', clientId: existing.client_id, credentialPath };\n } catch (err) {\n if (!(err instanceof CredentialRevokedError)) throw err;\n logger.warn(\n { clientId: existing.client_id },\n 'existing credential no longer mints; starting fresh enrollment',\n );\n }\n }\n\n const endpoints = await discoverEndpoints(opts.mokuBaseUrl, { fetch: fetchImpl, logger });\n\n const deviceForm = new URLSearchParams({ client_name: opts.clientName });\n deviceForm.set('hostname', opts.hostname ?? osHostname());\n if (opts.capabilityTypes?.length) {\n deviceForm.set('capability_types', opts.capabilityTypes.join(','));\n }\n\n const deviceRes = await fetchImpl(endpoints.deviceAuthorizationEndpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n Accept: 'application/json',\n },\n body: deviceForm.toString(),\n });\n if (!deviceRes.ok) {\n const text = await deviceRes.text().catch(() => '');\n throw new EnrollmentError(\n `device authorization request failed: ${deviceRes.status}${text ? ` — ${text}` : ''}`,\n 'device_request_failed',\n );\n }\n const device = (await deviceRes.json()) as DeviceAuthorizationResponse;\n if (!device.device_code || !device.user_code || !device.verification_uri) {\n throw new EnrollmentError('device authorization response incomplete', 'invalid_response');\n }\n\n out('To authorize this neuron, an org admin must visit:');\n out(` ${device.verification_uri_complete ?? device.verification_uri}`);\n out(`and enter code: ${device.user_code}`);\n out(`Waiting for approval (expires in ${Math.round(device.expires_in / 60)} min)…`);\n\n let intervalMs = (device.interval ?? 5) * 1000;\n const deadline = Date.now() + device.expires_in * 1000;\n\n for (;;) {\n if (Date.now() >= deadline) {\n throw new EnrollmentError('enrollment request expired before approval', 'expired_token');\n }\n await sleep(intervalMs);\n\n const pollForm = new URLSearchParams({\n grant_type: DEVICE_CODE_GRANT,\n device_code: device.device_code,\n });\n let token;\n try {\n token = await requestToken(endpoints.tokenEndpoint, pollForm, fetchImpl);\n } catch (err) {\n if (err instanceof OAuthTokenError) {\n switch (err.error) {\n case 'authorization_pending':\n continue;\n case 'slow_down':\n intervalMs += 5_000; // RFC 8628 §3.5\n continue;\n case 'expired_token':\n throw new EnrollmentError('enrollment request expired before approval', 'expired_token');\n case 'access_denied':\n throw new EnrollmentError('enrollment was denied by the admin', 'access_denied');\n }\n // Non-RFC server hiccups are transient for a poller: a 409 (e.g. a\n // poll racing the admin's approval write) or 5xx resolves by the\n // next poll, and the device-code deadline bounds the loop. Only\n // definitive 4xx protocol errors (invalid_grant, …) are fatal.\n if (err.status === 409 || err.status >= 500) {\n logger.warn(\n { error: err.error, status: err.status },\n 'transient token-endpoint error while polling; will retry',\n );\n continue;\n }\n }\n // Network-level failures are equally transient while polling.\n if (!(err instanceof OAuthTokenError) && !(err instanceof EnrollmentError)) {\n logger.warn(\n { err: (err as Error).message },\n 'token endpoint unreachable while polling; will retry',\n );\n continue;\n }\n throw err;\n }\n\n // Redemption: the client credential is delivered exactly once, here.\n if (!token.client_id || !token.client_secret) {\n throw new EnrollmentError(\n 'redemption response did not include a client credential',\n 'invalid_response',\n );\n }\n const credential: StoredCredential = {\n client_id: token.client_id,\n client_secret: token.client_secret,\n token_endpoint: endpoints.tokenEndpoint,\n moku_base_url: opts.mokuBaseUrl.replace(/\\/+$/, ''),\n };\n writeCredentialFile(credentialPath, credential);\n out(`Enrolled as client ${credential.client_id}. Credential written to ${credentialPath}.`);\n return {\n status: 'enrolled',\n clientId: credential.client_id,\n credentialPath,\n accessToken: token.access_token,\n expiresIn: token.expires_in,\n };\n }\n}\n"]}
|