@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/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
- label: z.string().optional()
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
- await writeFile(authProfilesPath(), JSON.stringify(file, null, 2) + "\n", {
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(vendor) {
1246
+ async function listAuthProfiles(endpoint) {
1202
1247
  const file = await loadAuthProfiles();
1203
1248
  const all = Object.values(file.profiles);
1204
- return vendor === void 0 ? all : all.filter((p) => p.vendor === vendor);
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 vendor = manifest.vendorByRoute[route];
1226
- if (vendor === void 0) {
1464
+ const endpoint = manifest.endpointByRoute[route];
1465
+ if (endpoint === void 0) {
1227
1466
  throw new Error(
1228
- `resolveEndpoint: adapter "${manifest.id}" declares no vendor for route "${route}"`
1467
+ `resolveEndpoint: adapter "${manifest.id}" declares no endpoint for route "${route}"`
1229
1468
  );
1230
1469
  }
1231
- return { vendor };
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 { vendor } = resolveEndpoint(manifest, route);
1476
+ const { endpoint } = resolveEndpoint(manifest, route);
1238
1477
  const methods = methodsPresentable(manifest, route);
1239
- return profiles.filter((p) => p.vendor === vendor && methods.includes(p.method));
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