@agentproto/auth 0.2.0 → 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 +325 -31
- package/dist/index.mjs +255 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -4,7 +4,7 @@ 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
9
|
import { dirname, resolve, join } from 'path';
|
|
10
10
|
import { homedir } from 'os';
|
|
@@ -380,6 +380,21 @@ async function writeKeychainToken(service, account, token) {
|
|
|
380
380
|
"agentproto auth token"
|
|
381
381
|
]);
|
|
382
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
|
+
}
|
|
383
398
|
|
|
384
399
|
// src/store/keychain-store.ts
|
|
385
400
|
var envelopeSchema = z.object({
|
|
@@ -415,6 +430,10 @@ var KeychainStore = class {
|
|
|
415
430
|
};
|
|
416
431
|
await writeKeychainToken(ref.path, account, JSON.stringify(envelope));
|
|
417
432
|
}
|
|
433
|
+
async delete(ref) {
|
|
434
|
+
const account = ref.account ?? ref.path;
|
|
435
|
+
await deleteKeychainToken(ref.path, account);
|
|
436
|
+
}
|
|
418
437
|
};
|
|
419
438
|
|
|
420
439
|
// src/store/resolve-ref.ts
|
|
@@ -1165,13 +1184,30 @@ var CredentialBroker = class {
|
|
|
1165
1184
|
}
|
|
1166
1185
|
};
|
|
1167
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
|
+
});
|
|
1168
1196
|
var authProfileSchema = z.object({
|
|
1169
1197
|
id: z.string(),
|
|
1170
1198
|
vendor: z.string(),
|
|
1171
1199
|
method: authMethodSchema,
|
|
1172
|
-
credentialRef: z.string(),
|
|
1173
|
-
|
|
1174
|
-
|
|
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 }));
|
|
1175
1211
|
var authProfilesFileSchema = z.object({
|
|
1176
1212
|
version: z.literal(1),
|
|
1177
1213
|
profiles: z.record(z.string(), authProfileSchema)
|
|
@@ -1193,15 +1229,24 @@ async function loadAuthProfiles() {
|
|
|
1193
1229
|
async function writeAuthProfiles(file) {
|
|
1194
1230
|
const dir = join(homedir(), ".agentproto");
|
|
1195
1231
|
await mkdir(dir, { recursive: true });
|
|
1196
|
-
|
|
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", {
|
|
1197
1242
|
encoding: "utf8",
|
|
1198
1243
|
mode: 384
|
|
1199
1244
|
});
|
|
1200
1245
|
}
|
|
1201
|
-
async function listAuthProfiles(
|
|
1246
|
+
async function listAuthProfiles(endpoint) {
|
|
1202
1247
|
const file = await loadAuthProfiles();
|
|
1203
1248
|
const all = Object.values(file.profiles);
|
|
1204
|
-
return
|
|
1249
|
+
return endpoint === void 0 ? all : all.filter((p) => p.endpoint === endpoint);
|
|
1205
1250
|
}
|
|
1206
1251
|
async function getAuthProfile(id) {
|
|
1207
1252
|
const file = await loadAuthProfiles();
|
|
@@ -1219,30 +1264,226 @@ async function removeAuthProfile(id) {
|
|
|
1219
1264
|
await writeAuthProfiles(file);
|
|
1220
1265
|
return true;
|
|
1221
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
|
+
}
|
|
1222
1461
|
|
|
1223
1462
|
// src/eligibility.ts
|
|
1224
1463
|
function resolveEndpoint(manifest, route) {
|
|
1225
|
-
const
|
|
1226
|
-
if (
|
|
1464
|
+
const endpoint = manifest.endpointByRoute[route];
|
|
1465
|
+
if (endpoint === void 0) {
|
|
1227
1466
|
throw new Error(
|
|
1228
|
-
`resolveEndpoint: adapter "${manifest.id}" declares no
|
|
1467
|
+
`resolveEndpoint: adapter "${manifest.id}" declares no endpoint for route "${route}"`
|
|
1229
1468
|
);
|
|
1230
1469
|
}
|
|
1231
|
-
return {
|
|
1470
|
+
return { endpoint };
|
|
1232
1471
|
}
|
|
1233
1472
|
function methodsPresentable(manifest, route) {
|
|
1234
1473
|
return [...manifest.methodsByRoute[route] ?? []];
|
|
1235
1474
|
}
|
|
1236
1475
|
function eligibleProfiles(profiles, manifest, route) {
|
|
1237
|
-
const {
|
|
1476
|
+
const { endpoint } = resolveEndpoint(manifest, route);
|
|
1238
1477
|
const methods = methodsPresentable(manifest, route);
|
|
1239
|
-
return profiles.filter(
|
|
1478
|
+
return profiles.filter(
|
|
1479
|
+
(p) => !p.disabled && p.endpoint === endpoint && methods.includes(p.method)
|
|
1480
|
+
);
|
|
1240
1481
|
}
|
|
1241
1482
|
|
|
1242
1483
|
// src/index.ts
|
|
1243
1484
|
var SPEC_NAME = "agentauth";
|
|
1244
1485
|
var SPEC_VERSION = "v1";
|
|
1245
1486
|
|
|
1246
|
-
export { BUILTIN_AUTH_PROVIDERS, CeremonyRequiredError, CredentialBroker, DiscoveryError, FLOW_ENGINES, FileStore, KeychainStore, MemoryStore, SPEC_NAME, SPEC_VERSION, addAuthProfile, authConfigSchema, authProfilesPath, authProviderFrontmatterSchema, defineAuthProvider, discoverEndpoints, eligibleProfiles, getAuthProfile, getAuthProvider, guildeAuthProvider, installConfigSchema, listAuthProfiles, listAuthProviderIds, listAuthProviders, loadAuthProfiles, methodsPresentable, parseAuthProviderManifest, parseAuthProviderManifestRaw, readKeychainToken, registerAuthProvider, removeAuthProfile, resolveAccount, resolveEndpoint, 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 };
|
|
1247
1488
|
//# sourceMappingURL=index.mjs.map
|
|
1248
1489
|
//# sourceMappingURL=index.mjs.map
|