@agentproto/auth 0.1.1 → 1.0.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 +2 -1
- package/dist/index.d.ts +445 -1
- package/dist/index.mjs +324 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -4,9 +4,10 @@ import matter from 'gray-matter';
|
|
|
4
4
|
import { createInterface } from 'readline';
|
|
5
5
|
import { execFile } from 'child_process';
|
|
6
6
|
import { promisify } from 'util';
|
|
7
|
-
import { createDecipheriv, randomBytes, createCipheriv } from 'crypto';
|
|
7
|
+
import { createHash, createDecipheriv, randomBytes, createCipheriv } from 'crypto';
|
|
8
8
|
import { readFile, mkdir, writeFile } from 'fs/promises';
|
|
9
|
-
import { dirname } from 'path';
|
|
9
|
+
import { dirname, resolve, join } from 'path';
|
|
10
|
+
import { homedir } from 'os';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* @agentproto/auth v0.1.0-alpha
|
|
@@ -379,6 +380,21 @@ async function writeKeychainToken(service, account, token) {
|
|
|
379
380
|
"agentproto auth token"
|
|
380
381
|
]);
|
|
381
382
|
}
|
|
383
|
+
async function deleteKeychainToken(service, account) {
|
|
384
|
+
assertKeychainSupported();
|
|
385
|
+
try {
|
|
386
|
+
await exec("security", [
|
|
387
|
+
"delete-generic-password",
|
|
388
|
+
"-s",
|
|
389
|
+
service,
|
|
390
|
+
"-a",
|
|
391
|
+
account
|
|
392
|
+
]);
|
|
393
|
+
return true;
|
|
394
|
+
} catch {
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
382
398
|
|
|
383
399
|
// src/store/keychain-store.ts
|
|
384
400
|
var envelopeSchema = z.object({
|
|
@@ -414,6 +430,10 @@ var KeychainStore = class {
|
|
|
414
430
|
};
|
|
415
431
|
await writeKeychainToken(ref.path, account, JSON.stringify(envelope));
|
|
416
432
|
}
|
|
433
|
+
async delete(ref) {
|
|
434
|
+
const account = ref.account ?? ref.path;
|
|
435
|
+
await deleteKeychainToken(ref.path, account);
|
|
436
|
+
}
|
|
417
437
|
};
|
|
418
438
|
|
|
419
439
|
// src/store/resolve-ref.ts
|
|
@@ -443,16 +463,16 @@ async function promptToken(label) {
|
|
|
443
463
|
const input = process.stdin;
|
|
444
464
|
const out = process.stderr;
|
|
445
465
|
if (!input.isTTY) {
|
|
446
|
-
return new Promise((
|
|
466
|
+
return new Promise((resolve2) => {
|
|
447
467
|
const rl = createInterface({ input, output: out });
|
|
448
468
|
out.write(`${label}: `);
|
|
449
469
|
rl.question("", (answer) => {
|
|
450
470
|
rl.close();
|
|
451
|
-
|
|
471
|
+
resolve2(answer.trim());
|
|
452
472
|
});
|
|
453
473
|
});
|
|
454
474
|
}
|
|
455
|
-
return new Promise((
|
|
475
|
+
return new Promise((resolve2, reject) => {
|
|
456
476
|
out.write(`${label}: `);
|
|
457
477
|
const chars = [];
|
|
458
478
|
input.setRawMode(true);
|
|
@@ -470,7 +490,7 @@ async function promptToken(label) {
|
|
|
470
490
|
case "\n":
|
|
471
491
|
out.write("\n");
|
|
472
492
|
cleanup();
|
|
473
|
-
|
|
493
|
+
resolve2(chars.join("").trim());
|
|
474
494
|
return;
|
|
475
495
|
case "":
|
|
476
496
|
out.write("\n");
|
|
@@ -480,7 +500,7 @@ async function promptToken(label) {
|
|
|
480
500
|
case "":
|
|
481
501
|
out.write("\n");
|
|
482
502
|
cleanup();
|
|
483
|
-
|
|
503
|
+
resolve2(chars.join("").trim());
|
|
484
504
|
return;
|
|
485
505
|
case "\x7F":
|
|
486
506
|
// Backspace / Delete
|
|
@@ -1163,11 +1183,307 @@ var CredentialBroker = class {
|
|
|
1163
1183
|
return headers;
|
|
1164
1184
|
}
|
|
1165
1185
|
};
|
|
1186
|
+
var authMethodSchema = z.enum(["oauth-bearer", "api-key"]);
|
|
1187
|
+
var modelCurationSchema = z.object({
|
|
1188
|
+
mode: z.enum(["all", "allow"]),
|
|
1189
|
+
ids: z.array(z.string())
|
|
1190
|
+
});
|
|
1191
|
+
var costBudgetSchema = z.object({
|
|
1192
|
+
maxCostUsd: z.number(),
|
|
1193
|
+
window: z.string(),
|
|
1194
|
+
scope: z.enum(["session", "profile"])
|
|
1195
|
+
});
|
|
1196
|
+
var authProfileSchema = z.object({
|
|
1197
|
+
id: z.string(),
|
|
1198
|
+
vendor: z.string(),
|
|
1199
|
+
method: authMethodSchema,
|
|
1200
|
+
credentialRef: z.string().optional(),
|
|
1201
|
+
source: z.string().optional(),
|
|
1202
|
+
label: z.string().optional(),
|
|
1203
|
+
// Additive, back-compat fields — an entry that predates them parses
|
|
1204
|
+
// unchanged (both optional), and `endpoint` still shadows the on-disk
|
|
1205
|
+
// `vendor` alias below.
|
|
1206
|
+
disabled: z.boolean().optional(),
|
|
1207
|
+
models: modelCurationSchema.optional(),
|
|
1208
|
+
costBudget: costBudgetSchema.optional(),
|
|
1209
|
+
origin: z.string().optional()
|
|
1210
|
+
}).transform(({ vendor, ...profile }) => ({ ...profile, endpoint: vendor }));
|
|
1211
|
+
var authProfilesFileSchema = z.object({
|
|
1212
|
+
version: z.literal(1),
|
|
1213
|
+
profiles: z.record(z.string(), authProfileSchema)
|
|
1214
|
+
});
|
|
1215
|
+
function emptyFile() {
|
|
1216
|
+
return { version: 1, profiles: {} };
|
|
1217
|
+
}
|
|
1218
|
+
function authProfilesPath() {
|
|
1219
|
+
return resolve(homedir(), ".agentproto", "auth-profiles.json");
|
|
1220
|
+
}
|
|
1221
|
+
async function loadAuthProfiles() {
|
|
1222
|
+
try {
|
|
1223
|
+
const raw = await readFile(authProfilesPath(), "utf8");
|
|
1224
|
+
return authProfilesFileSchema.parse(JSON.parse(raw));
|
|
1225
|
+
} catch {
|
|
1226
|
+
return emptyFile();
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
async function writeAuthProfiles(file) {
|
|
1230
|
+
const dir = join(homedir(), ".agentproto");
|
|
1231
|
+
await mkdir(dir, { recursive: true });
|
|
1232
|
+
const diskFile = {
|
|
1233
|
+
version: file.version,
|
|
1234
|
+
profiles: Object.fromEntries(
|
|
1235
|
+
Object.entries(file.profiles).map(([id, { endpoint, ...profile }]) => [
|
|
1236
|
+
id,
|
|
1237
|
+
{ ...profile, vendor: endpoint }
|
|
1238
|
+
])
|
|
1239
|
+
)
|
|
1240
|
+
};
|
|
1241
|
+
await writeFile(authProfilesPath(), JSON.stringify(diskFile, null, 2) + "\n", {
|
|
1242
|
+
encoding: "utf8",
|
|
1243
|
+
mode: 384
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
async function listAuthProfiles(endpoint) {
|
|
1247
|
+
const file = await loadAuthProfiles();
|
|
1248
|
+
const all = Object.values(file.profiles);
|
|
1249
|
+
return endpoint === void 0 ? all : all.filter((p) => p.endpoint === endpoint);
|
|
1250
|
+
}
|
|
1251
|
+
async function getAuthProfile(id) {
|
|
1252
|
+
const file = await loadAuthProfiles();
|
|
1253
|
+
return file.profiles[id];
|
|
1254
|
+
}
|
|
1255
|
+
async function addAuthProfile(profile) {
|
|
1256
|
+
const file = await loadAuthProfiles();
|
|
1257
|
+
file.profiles[profile.id] = profile;
|
|
1258
|
+
await writeAuthProfiles(file);
|
|
1259
|
+
}
|
|
1260
|
+
async function removeAuthProfile(id) {
|
|
1261
|
+
const file = await loadAuthProfiles();
|
|
1262
|
+
if (!(id in file.profiles)) return false;
|
|
1263
|
+
delete file.profiles[id];
|
|
1264
|
+
await writeAuthProfiles(file);
|
|
1265
|
+
return true;
|
|
1266
|
+
}
|
|
1267
|
+
var AuthProfileValidationError = class extends Error {
|
|
1268
|
+
constructor(message) {
|
|
1269
|
+
super(message);
|
|
1270
|
+
this.name = "AuthProfileValidationError";
|
|
1271
|
+
}
|
|
1272
|
+
};
|
|
1273
|
+
var AUTH_METHODS = ["oauth-bearer", "api-key"];
|
|
1274
|
+
var SLUG_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
1275
|
+
function validateCreateInput(input) {
|
|
1276
|
+
const id = (input.id ?? "").trim();
|
|
1277
|
+
if (!id) throw new AuthProfileValidationError("id is required");
|
|
1278
|
+
if (!SLUG_RE.test(id)) {
|
|
1279
|
+
throw new AuthProfileValidationError(
|
|
1280
|
+
`id "${id}" is invalid \u2014 use letters, digits, ".", "_" or "-", starting with a letter or digit`
|
|
1281
|
+
);
|
|
1282
|
+
}
|
|
1283
|
+
const endpoint = (input.endpoint ?? "").trim();
|
|
1284
|
+
if (!endpoint) throw new AuthProfileValidationError("endpoint is required");
|
|
1285
|
+
if (!SLUG_RE.test(endpoint)) {
|
|
1286
|
+
throw new AuthProfileValidationError(
|
|
1287
|
+
`endpoint "${endpoint}" is invalid \u2014 use letters, digits, ".", "_" or "-"`
|
|
1288
|
+
);
|
|
1289
|
+
}
|
|
1290
|
+
const method = input.method;
|
|
1291
|
+
if (!AUTH_METHODS.includes(method)) {
|
|
1292
|
+
throw new AuthProfileValidationError(
|
|
1293
|
+
`method must be one of ${AUTH_METHODS.join(" | ")} (got "${String(method)}")`
|
|
1294
|
+
);
|
|
1295
|
+
}
|
|
1296
|
+
const credential = input.credential !== void 0 ? input.credential.trim() : void 0;
|
|
1297
|
+
const source = input.source?.trim();
|
|
1298
|
+
if (method === "api-key") {
|
|
1299
|
+
if (source) {
|
|
1300
|
+
throw new AuthProfileValidationError(
|
|
1301
|
+
"source is only supported for oauth-bearer profiles \u2014 api-key profiles require a credential"
|
|
1302
|
+
);
|
|
1303
|
+
}
|
|
1304
|
+
if (!credential) throw new AuthProfileValidationError("credential is required");
|
|
1305
|
+
} else {
|
|
1306
|
+
if (credential && source) {
|
|
1307
|
+
throw new AuthProfileValidationError(
|
|
1308
|
+
"give either credential or source, not both \u2014 a source-backed profile stores no secret"
|
|
1309
|
+
);
|
|
1310
|
+
}
|
|
1311
|
+
if (!credential && !source) {
|
|
1312
|
+
throw new AuthProfileValidationError("credential or source is required");
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
const label = input.label?.trim();
|
|
1316
|
+
const credentialRef = input.credentialRef?.trim();
|
|
1317
|
+
if (credentialRef !== void 0 && credentialRef !== "" && !SLUG_RE.test(credentialRef)) {
|
|
1318
|
+
throw new AuthProfileValidationError(
|
|
1319
|
+
`credentialRef "${credentialRef}" is invalid \u2014 use letters, digits, ".", "_" or "-"`
|
|
1320
|
+
);
|
|
1321
|
+
}
|
|
1322
|
+
const origin = input.origin?.trim();
|
|
1323
|
+
return {
|
|
1324
|
+
id,
|
|
1325
|
+
endpoint,
|
|
1326
|
+
method,
|
|
1327
|
+
...credential ? { credential } : {},
|
|
1328
|
+
...source ? { source } : {},
|
|
1329
|
+
...label ? { label } : {},
|
|
1330
|
+
...credentialRef ? { credentialRef } : {},
|
|
1331
|
+
...origin ? { origin } : {}
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
function deriveCredentialRef(input) {
|
|
1335
|
+
const parts = ["agentproto", "auth", input.endpoint];
|
|
1336
|
+
if (input.method === "oauth-bearer") parts.push("sub");
|
|
1337
|
+
if (input.qualifier) parts.push(input.qualifier);
|
|
1338
|
+
return parts.join(".");
|
|
1339
|
+
}
|
|
1340
|
+
function fingerprintCredential(secret) {
|
|
1341
|
+
return createHash("sha256").update(secret, "utf8").digest("hex").slice(0, 12);
|
|
1342
|
+
}
|
|
1343
|
+
var MIN_LEN_FOR_LAST4 = 8;
|
|
1344
|
+
function credentialIdentity(secret) {
|
|
1345
|
+
return {
|
|
1346
|
+
fingerprint: fingerprintCredential(secret),
|
|
1347
|
+
...secret.length >= MIN_LEN_FOR_LAST4 ? { last4: secret.slice(-4) } : {}
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
function credentialKind(method) {
|
|
1351
|
+
return method === "oauth-bearer" ? "oat" : "pat";
|
|
1352
|
+
}
|
|
1353
|
+
async function createAuthProfile(input, deps) {
|
|
1354
|
+
const v = validateCreateInput(input);
|
|
1355
|
+
if (await deps.getProfile(v.id)) {
|
|
1356
|
+
throw new AuthProfileValidationError(
|
|
1357
|
+
`a profile with id "${v.id}" already exists \u2014 delete it first or pick another id`
|
|
1358
|
+
);
|
|
1359
|
+
}
|
|
1360
|
+
if (v.source !== void 0) {
|
|
1361
|
+
const profile2 = {
|
|
1362
|
+
id: v.id,
|
|
1363
|
+
endpoint: v.endpoint,
|
|
1364
|
+
method: v.method,
|
|
1365
|
+
source: v.source,
|
|
1366
|
+
...v.label ? { label: v.label } : {},
|
|
1367
|
+
...v.origin ? { origin: v.origin } : {}
|
|
1368
|
+
};
|
|
1369
|
+
await deps.addProfile(profile2);
|
|
1370
|
+
return {
|
|
1371
|
+
id: profile2.id,
|
|
1372
|
+
endpoint: profile2.endpoint,
|
|
1373
|
+
method: profile2.method,
|
|
1374
|
+
source: v.source,
|
|
1375
|
+
...profile2.label ? { label: profile2.label } : {},
|
|
1376
|
+
...profile2.origin ? { origin: profile2.origin } : {}
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
const credential = v.credential;
|
|
1380
|
+
const existing = await deps.listProfiles();
|
|
1381
|
+
const takenRefs = new Set(existing.map((p) => p.credentialRef));
|
|
1382
|
+
let credentialRef = v.credentialRef ?? deriveCredentialRef(v);
|
|
1383
|
+
if (!v.credentialRef && takenRefs.has(credentialRef)) {
|
|
1384
|
+
credentialRef = deriveCredentialRef({ ...v, qualifier: v.id });
|
|
1385
|
+
}
|
|
1386
|
+
await deps.store.write(
|
|
1387
|
+
{ path: credentialRef },
|
|
1388
|
+
{ value: credential, kind: credentialKind(v.method) }
|
|
1389
|
+
);
|
|
1390
|
+
const profile = {
|
|
1391
|
+
id: v.id,
|
|
1392
|
+
endpoint: v.endpoint,
|
|
1393
|
+
method: v.method,
|
|
1394
|
+
credentialRef,
|
|
1395
|
+
...v.label ? { label: v.label } : {},
|
|
1396
|
+
...v.origin ? { origin: v.origin } : {}
|
|
1397
|
+
};
|
|
1398
|
+
await deps.addProfile(profile);
|
|
1399
|
+
return {
|
|
1400
|
+
id: profile.id,
|
|
1401
|
+
endpoint: profile.endpoint,
|
|
1402
|
+
method: profile.method,
|
|
1403
|
+
credentialRef,
|
|
1404
|
+
...profile.label ? { label: profile.label } : {},
|
|
1405
|
+
...profile.origin ? { origin: profile.origin } : {},
|
|
1406
|
+
fingerprint: fingerprintCredential(credential)
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
async function deleteAuthProfile(id, deps) {
|
|
1410
|
+
const trimmed = (id ?? "").trim();
|
|
1411
|
+
if (!trimmed) throw new AuthProfileValidationError("id is required");
|
|
1412
|
+
const profile = await deps.getProfile(trimmed);
|
|
1413
|
+
if (!profile) return { deleted: false, id: trimmed };
|
|
1414
|
+
await deps.removeProfile(trimmed);
|
|
1415
|
+
if (profile.credentialRef === void 0) {
|
|
1416
|
+
return { deleted: true, id: trimmed };
|
|
1417
|
+
}
|
|
1418
|
+
const stillReferenced = (await deps.listProfiles()).some(
|
|
1419
|
+
(p) => p.credentialRef === profile.credentialRef
|
|
1420
|
+
);
|
|
1421
|
+
if (!stillReferenced && deps.store.delete) {
|
|
1422
|
+
await deps.store.delete({ path: profile.credentialRef });
|
|
1423
|
+
}
|
|
1424
|
+
return { deleted: true, id: trimmed, credentialRef: profile.credentialRef };
|
|
1425
|
+
}
|
|
1426
|
+
async function setAuthProfileEnabled(id, enabled, deps) {
|
|
1427
|
+
const trimmed = (id ?? "").trim();
|
|
1428
|
+
if (!trimmed) throw new AuthProfileValidationError("id is required");
|
|
1429
|
+
const profile = await deps.getProfile(trimmed);
|
|
1430
|
+
if (!profile) {
|
|
1431
|
+
throw new AuthProfileValidationError(`no profile with id "${trimmed}"`);
|
|
1432
|
+
}
|
|
1433
|
+
const { disabled: _drop, ...rest } = profile;
|
|
1434
|
+
const updated = enabled ? { ...rest } : { ...rest, disabled: true };
|
|
1435
|
+
await deps.addProfile(updated);
|
|
1436
|
+
return updated;
|
|
1437
|
+
}
|
|
1438
|
+
async function setAuthProfileModels(id, curation, deps) {
|
|
1439
|
+
const trimmed = (id ?? "").trim();
|
|
1440
|
+
if (!trimmed) throw new AuthProfileValidationError("id is required");
|
|
1441
|
+
if (curation.mode !== "all" && curation.mode !== "allow") {
|
|
1442
|
+
throw new AuthProfileValidationError(
|
|
1443
|
+
`models.mode must be "all" or "allow" (got "${String(curation.mode)}")`
|
|
1444
|
+
);
|
|
1445
|
+
}
|
|
1446
|
+
const profile = await deps.getProfile(trimmed);
|
|
1447
|
+
if (!profile) {
|
|
1448
|
+
throw new AuthProfileValidationError(`no profile with id "${trimmed}"`);
|
|
1449
|
+
}
|
|
1450
|
+
const { models: _drop, ...rest } = profile;
|
|
1451
|
+
if (curation.mode === "all") {
|
|
1452
|
+
const updated2 = { ...rest };
|
|
1453
|
+
await deps.addProfile(updated2);
|
|
1454
|
+
return updated2;
|
|
1455
|
+
}
|
|
1456
|
+
const ids = [...new Set((curation.ids ?? []).map((s) => s.trim()).filter(Boolean))];
|
|
1457
|
+
const updated = { ...rest, models: { mode: "allow", ids } };
|
|
1458
|
+
await deps.addProfile(updated);
|
|
1459
|
+
return updated;
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
// src/eligibility.ts
|
|
1463
|
+
function resolveEndpoint(manifest, route) {
|
|
1464
|
+
const endpoint = manifest.endpointByRoute[route];
|
|
1465
|
+
if (endpoint === void 0) {
|
|
1466
|
+
throw new Error(
|
|
1467
|
+
`resolveEndpoint: adapter "${manifest.id}" declares no endpoint for route "${route}"`
|
|
1468
|
+
);
|
|
1469
|
+
}
|
|
1470
|
+
return { endpoint };
|
|
1471
|
+
}
|
|
1472
|
+
function methodsPresentable(manifest, route) {
|
|
1473
|
+
return [...manifest.methodsByRoute[route] ?? []];
|
|
1474
|
+
}
|
|
1475
|
+
function eligibleProfiles(profiles, manifest, route) {
|
|
1476
|
+
const { endpoint } = resolveEndpoint(manifest, route);
|
|
1477
|
+
const methods = methodsPresentable(manifest, route);
|
|
1478
|
+
return profiles.filter(
|
|
1479
|
+
(p) => !p.disabled && p.endpoint === endpoint && methods.includes(p.method)
|
|
1480
|
+
);
|
|
1481
|
+
}
|
|
1166
1482
|
|
|
1167
1483
|
// src/index.ts
|
|
1168
1484
|
var SPEC_NAME = "agentauth";
|
|
1169
1485
|
var SPEC_VERSION = "v1";
|
|
1170
1486
|
|
|
1171
|
-
export { BUILTIN_AUTH_PROVIDERS, CeremonyRequiredError, CredentialBroker, DiscoveryError, FLOW_ENGINES, FileStore, KeychainStore, MemoryStore, SPEC_NAME, SPEC_VERSION, authConfigSchema, authProviderFrontmatterSchema, defineAuthProvider, discoverEndpoints, getAuthProvider, guildeAuthProvider, installConfigSchema, listAuthProviderIds, listAuthProviders, parseAuthProviderManifest, parseAuthProviderManifestRaw, readKeychainToken, registerAuthProvider, resolveAccount, resolveStoreRef, runAuthFlow, tokenStoreSpecSchema, writeKeychainToken };
|
|
1487
|
+
export { AuthProfileValidationError, BUILTIN_AUTH_PROVIDERS, CeremonyRequiredError, CredentialBroker, DiscoveryError, FLOW_ENGINES, FileStore, KeychainStore, MemoryStore, SPEC_NAME, SPEC_VERSION, addAuthProfile, authConfigSchema, authProfilesPath, authProviderFrontmatterSchema, createAuthProfile, credentialIdentity, defineAuthProvider, deleteAuthProfile, deleteKeychainToken, deriveCredentialRef, discoverEndpoints, eligibleProfiles, fingerprintCredential, getAuthProfile, getAuthProvider, guildeAuthProvider, installConfigSchema, listAuthProfiles, listAuthProviderIds, listAuthProviders, loadAuthProfiles, methodsPresentable, parseAuthProviderManifest, parseAuthProviderManifestRaw, readKeychainToken, registerAuthProvider, removeAuthProfile, resolveAccount, resolveEndpoint, resolveStoreRef, runAuthFlow, setAuthProfileEnabled, setAuthProfileModels, tokenStoreSpecSchema, validateCreateInput, writeKeychainToken };
|
|
1172
1488
|
//# sourceMappingURL=index.mjs.map
|
|
1173
1489
|
//# sourceMappingURL=index.mjs.map
|