@agentproto/auth 0.1.0-alpha.0 → 0.1.1
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/LICENSE +202 -21
- package/README.md +88 -0
- package/dist/index.d.ts +279 -18
- package/dist/index.mjs +621 -82
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -3
- package/skill/auth/SKILL.md +131 -0
package/dist/index.mjs
CHANGED
|
@@ -4,6 +4,9 @@ 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';
|
|
8
|
+
import { readFile, mkdir, writeFile } from 'fs/promises';
|
|
9
|
+
import { dirname } from 'path';
|
|
7
10
|
|
|
8
11
|
/**
|
|
9
12
|
* @agentproto/auth v0.1.0-alpha
|
|
@@ -12,6 +15,7 @@ import { promisify } from 'util';
|
|
|
12
15
|
|
|
13
16
|
var tokenStoreSpecSchema = z.object({
|
|
14
17
|
keychain: z.string().min(1),
|
|
18
|
+
path: z.string().min(1).optional(),
|
|
15
19
|
account: z.string().min(1).optional()
|
|
16
20
|
}).strict();
|
|
17
21
|
var patAuthConfigSchema = z.object({
|
|
@@ -24,9 +28,17 @@ var serviceAuthConfigSchema = z.object({
|
|
|
24
28
|
loginHint: z.string().min(1).optional(),
|
|
25
29
|
tokenStore: tokenStoreSpecSchema
|
|
26
30
|
}).strict();
|
|
31
|
+
var deviceCodeAuthConfigSchema = z.object({
|
|
32
|
+
flow: z.literal("device-code"),
|
|
33
|
+
clientId: z.string().min(1).optional(),
|
|
34
|
+
scope: z.string().min(1).optional(),
|
|
35
|
+
deviceLabel: z.string().min(1).optional(),
|
|
36
|
+
tokenStore: tokenStoreSpecSchema
|
|
37
|
+
}).strict();
|
|
27
38
|
var authConfigSchema = z.discriminatedUnion("flow", [
|
|
28
39
|
patAuthConfigSchema,
|
|
29
|
-
serviceAuthConfigSchema
|
|
40
|
+
serviceAuthConfigSchema,
|
|
41
|
+
deviceCodeAuthConfigSchema
|
|
30
42
|
]);
|
|
31
43
|
var installConfigSchema = z.object({
|
|
32
44
|
sealKey: z.string().min(1),
|
|
@@ -37,7 +49,9 @@ var authProviderFrontmatterSchema = z.object({
|
|
|
37
49
|
description: z.string().min(1).max(2e3),
|
|
38
50
|
apiBase: z.string().url(),
|
|
39
51
|
auth: authConfigSchema,
|
|
40
|
-
install: installConfigSchema.optional()
|
|
52
|
+
install: installConfigSchema.optional(),
|
|
53
|
+
// Recommended values "tunnel" | "api" | "mcp" — free-form allowed, not an enum.
|
|
54
|
+
audience: z.string().min(1).optional()
|
|
41
55
|
}).strict().describe(
|
|
42
56
|
"AIP-50 auth-provider manifest: how a CLI tool authenticates to an API server and (optionally) where to call the AIP-19 provision endpoints."
|
|
43
57
|
);
|
|
@@ -85,6 +99,7 @@ var guildeAuthProvider = defineAuthProvider({
|
|
|
85
99
|
id: "guilde",
|
|
86
100
|
description: "Guilde AI company platform. Authenticate via a browser-approve flow \u2014 no key to paste.",
|
|
87
101
|
apiBase: "https://api.guilde.work",
|
|
102
|
+
audience: "api",
|
|
88
103
|
auth: {
|
|
89
104
|
flow: "service-auth",
|
|
90
105
|
clientId: "agentproto-cli",
|
|
@@ -184,6 +199,7 @@ var asMetaSchema = z.object({
|
|
|
184
199
|
issuer: z.string().optional(),
|
|
185
200
|
token_endpoint: z.string().optional(),
|
|
186
201
|
revocation_endpoint: z.string().optional(),
|
|
202
|
+
device_authorization_endpoint: z.string().optional(),
|
|
187
203
|
grant_types_supported: z.array(z.string()).optional(),
|
|
188
204
|
agent_auth: z.object({
|
|
189
205
|
skill: z.string().optional(),
|
|
@@ -194,12 +210,12 @@ var asMetaSchema = z.object({
|
|
|
194
210
|
identity_assertion: z.object({ assertion_types_supported: z.array(z.string()).optional() }).loose().optional()
|
|
195
211
|
}).loose().optional()
|
|
196
212
|
}).loose();
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
213
|
+
var hostJsonSchema = z.object({
|
|
214
|
+
device_authorization_endpoint: z.string().optional(),
|
|
215
|
+
token_endpoint: z.string().optional(),
|
|
216
|
+
revocation_endpoint: z.string().optional()
|
|
217
|
+
}).loose();
|
|
218
|
+
async function discoverViaPRM(base, fetchOpts, opts) {
|
|
203
219
|
const prmUrl = `${base}/.well-known/oauth-protected-resource`;
|
|
204
220
|
let prm;
|
|
205
221
|
try {
|
|
@@ -208,19 +224,19 @@ async function discoverEndpoints(apiBase, opts = {}) {
|
|
|
208
224
|
if (!res.ok) {
|
|
209
225
|
throw new DiscoveryError(
|
|
210
226
|
`PRM returned ${res.status} ${res.statusText}`,
|
|
211
|
-
|
|
227
|
+
base
|
|
212
228
|
);
|
|
213
229
|
}
|
|
214
230
|
prm = prmSchema.parse(await res.json());
|
|
215
231
|
} catch (err) {
|
|
216
232
|
if (err instanceof DiscoveryError) throw err;
|
|
217
|
-
throw new DiscoveryError(`PRM fetch failed: ${err}`,
|
|
233
|
+
throw new DiscoveryError(`PRM fetch failed: ${err}`, base);
|
|
218
234
|
}
|
|
219
235
|
const authServerBase = prm.authorization_servers?.[0]?.replace(/\/$/, "");
|
|
220
236
|
if (!authServerBase) {
|
|
221
237
|
throw new DiscoveryError(
|
|
222
238
|
"PRM missing authorization_servers[0]",
|
|
223
|
-
|
|
239
|
+
base
|
|
224
240
|
);
|
|
225
241
|
}
|
|
226
242
|
const asUrl = `${authServerBase}/.well-known/oauth-authorization-server`;
|
|
@@ -231,23 +247,23 @@ async function discoverEndpoints(apiBase, opts = {}) {
|
|
|
231
247
|
if (!res.ok) {
|
|
232
248
|
throw new DiscoveryError(
|
|
233
249
|
`AS metadata returned ${res.status} ${res.statusText}`,
|
|
234
|
-
|
|
250
|
+
base
|
|
235
251
|
);
|
|
236
252
|
}
|
|
237
253
|
as = asMetaSchema.parse(await res.json());
|
|
238
254
|
} catch (err) {
|
|
239
255
|
if (err instanceof DiscoveryError) throw err;
|
|
240
|
-
throw new DiscoveryError(`AS metadata fetch failed: ${err}`,
|
|
256
|
+
throw new DiscoveryError(`AS metadata fetch failed: ${err}`, base);
|
|
241
257
|
}
|
|
242
258
|
const tokenEndpoint = as.token_endpoint;
|
|
243
259
|
const identityEndpoint = as.agent_auth?.identity_endpoint;
|
|
244
260
|
if (!tokenEndpoint) {
|
|
245
|
-
throw new DiscoveryError("AS metadata missing token_endpoint",
|
|
261
|
+
throw new DiscoveryError("AS metadata missing token_endpoint", base);
|
|
246
262
|
}
|
|
247
263
|
if (!identityEndpoint) {
|
|
248
264
|
throw new DiscoveryError(
|
|
249
265
|
"AS metadata missing agent_auth.identity_endpoint",
|
|
250
|
-
|
|
266
|
+
base
|
|
251
267
|
);
|
|
252
268
|
}
|
|
253
269
|
return {
|
|
@@ -258,10 +274,67 @@ async function discoverEndpoints(apiBase, opts = {}) {
|
|
|
258
274
|
revocationEndpoint: as.revocation_endpoint,
|
|
259
275
|
identityEndpoint,
|
|
260
276
|
claimEndpoint: as.agent_auth?.claim_endpoint,
|
|
277
|
+
deviceAuthorizationEndpoint: as.device_authorization_endpoint,
|
|
261
278
|
identityTypesSupported: as.agent_auth?.identity_types_supported ?? [],
|
|
262
279
|
grantTypesSupported: as.grant_types_supported ?? []
|
|
263
280
|
};
|
|
264
281
|
}
|
|
282
|
+
async function discoverViaHostJson(base, fetchOpts, opts) {
|
|
283
|
+
const url = `${base}/.well-known/agentproto-host.json`;
|
|
284
|
+
let doc;
|
|
285
|
+
try {
|
|
286
|
+
assertSecureUrl(url);
|
|
287
|
+
const res = await fetchWithDeadline(url, fetchOpts, opts.timeoutMs);
|
|
288
|
+
if (!res.ok) {
|
|
289
|
+
throw new DiscoveryError(
|
|
290
|
+
`agentproto-host.json returned ${res.status} ${res.statusText}`,
|
|
291
|
+
base
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
doc = hostJsonSchema.parse(await res.json());
|
|
295
|
+
} catch (err) {
|
|
296
|
+
if (err instanceof DiscoveryError) throw err;
|
|
297
|
+
throw new DiscoveryError(`agentproto-host.json fetch failed: ${err}`, base);
|
|
298
|
+
}
|
|
299
|
+
if (!doc.token_endpoint) {
|
|
300
|
+
throw new DiscoveryError(
|
|
301
|
+
"agentproto-host.json missing token_endpoint",
|
|
302
|
+
base
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
if (!doc.device_authorization_endpoint) {
|
|
306
|
+
throw new DiscoveryError(
|
|
307
|
+
"agentproto-host.json missing device_authorization_endpoint",
|
|
308
|
+
base
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
resource: base,
|
|
313
|
+
authServerBase: base,
|
|
314
|
+
tokenEndpoint: doc.token_endpoint,
|
|
315
|
+
revocationEndpoint: doc.revocation_endpoint,
|
|
316
|
+
deviceAuthorizationEndpoint: doc.device_authorization_endpoint,
|
|
317
|
+
identityTypesSupported: [],
|
|
318
|
+
grantTypesSupported: []
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
async function discoverEndpoints(apiBase, opts = {}) {
|
|
322
|
+
const base = apiBase.replace(/\/$/, "");
|
|
323
|
+
const fetchOpts = {
|
|
324
|
+
signal: opts.signal,
|
|
325
|
+
redirect: "manual"
|
|
326
|
+
};
|
|
327
|
+
try {
|
|
328
|
+
return await discoverViaPRM(base, fetchOpts, opts);
|
|
329
|
+
} catch (prmErr) {
|
|
330
|
+
if (opts.signal?.aborted) throw prmErr;
|
|
331
|
+
try {
|
|
332
|
+
return await discoverViaHostJson(base, fetchOpts, opts);
|
|
333
|
+
} catch {
|
|
334
|
+
throw prmErr;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
265
338
|
var exec = promisify(execFile);
|
|
266
339
|
function assertKeychainSupported() {
|
|
267
340
|
if (process.platform !== "darwin") {
|
|
@@ -307,6 +380,64 @@ async function writeKeychainToken(service, account, token) {
|
|
|
307
380
|
]);
|
|
308
381
|
}
|
|
309
382
|
|
|
383
|
+
// src/store/keychain-store.ts
|
|
384
|
+
var envelopeSchema = z.object({
|
|
385
|
+
v: z.string(),
|
|
386
|
+
k: z.enum(["pat", "assertion", "oat", "daemon"]),
|
|
387
|
+
e: z.string().optional(),
|
|
388
|
+
m: z.record(z.string(), z.unknown()).optional()
|
|
389
|
+
});
|
|
390
|
+
var KeychainStore = class {
|
|
391
|
+
async read(ref) {
|
|
392
|
+
const account = ref.account ?? ref.path;
|
|
393
|
+
const raw = await readKeychainToken(ref.path, account);
|
|
394
|
+
if (raw === void 0) return void 0;
|
|
395
|
+
try {
|
|
396
|
+
const envelope = envelopeSchema.parse(JSON.parse(raw));
|
|
397
|
+
return {
|
|
398
|
+
value: envelope.v,
|
|
399
|
+
kind: envelope.k,
|
|
400
|
+
...envelope.e !== void 0 ? { expiresAt: envelope.e } : {},
|
|
401
|
+
...envelope.m !== void 0 ? { metadata: envelope.m } : {}
|
|
402
|
+
};
|
|
403
|
+
} catch {
|
|
404
|
+
return { value: raw, kind: "pat" };
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
async write(ref, cred) {
|
|
408
|
+
const account = ref.account ?? ref.path;
|
|
409
|
+
const envelope = {
|
|
410
|
+
v: cred.value,
|
|
411
|
+
k: cred.kind,
|
|
412
|
+
...cred.expiresAt !== void 0 ? { e: cred.expiresAt } : {},
|
|
413
|
+
...cred.metadata !== void 0 ? { m: cred.metadata } : {}
|
|
414
|
+
};
|
|
415
|
+
await writeKeychainToken(ref.path, account, JSON.stringify(envelope));
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
// src/store/resolve-ref.ts
|
|
420
|
+
function resolveStoreRef(spec, server, audience) {
|
|
421
|
+
const path = spec.path ?? spec.keychain;
|
|
422
|
+
return {
|
|
423
|
+
path: audience ? `${audience}:${path}` : path,
|
|
424
|
+
account: resolveAccount(spec.account, server)
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
function resolveStoreRefs(spec, server, audience) {
|
|
428
|
+
const ref = resolveStoreRef(spec, server, audience);
|
|
429
|
+
const legacyRef = audience ? resolveStoreRef(spec, server) : ref;
|
|
430
|
+
return { ref, legacyRef };
|
|
431
|
+
}
|
|
432
|
+
async function readStoreRefWithFallback(store, ref, legacyRef) {
|
|
433
|
+
const stored = await store.read(ref);
|
|
434
|
+
if (stored) return stored;
|
|
435
|
+
if (ref.path === legacyRef.path && ref.account === legacyRef.account) {
|
|
436
|
+
return void 0;
|
|
437
|
+
}
|
|
438
|
+
return store.read(legacyRef);
|
|
439
|
+
}
|
|
440
|
+
|
|
310
441
|
// src/flow-engines/pat.ts
|
|
311
442
|
async function promptToken(label) {
|
|
312
443
|
const input = process.stdin;
|
|
@@ -377,39 +508,37 @@ var patFlowEngine = {
|
|
|
377
508
|
if (auth.flow !== "pat") {
|
|
378
509
|
throw new Error(`patFlowEngine: invoked with flow="${auth.flow}"`);
|
|
379
510
|
}
|
|
380
|
-
const
|
|
511
|
+
const store = opts.store ?? new KeychainStore();
|
|
512
|
+
const { ref, legacyRef } = resolveStoreRefs(
|
|
513
|
+
auth.tokenStore,
|
|
514
|
+
opts.server,
|
|
515
|
+
provider.audience
|
|
516
|
+
);
|
|
381
517
|
if (!opts.force) {
|
|
382
|
-
const existing = await
|
|
518
|
+
const existing = await readStoreRefWithFallback(store, ref, legacyRef);
|
|
383
519
|
if (existing) {
|
|
384
|
-
return { accessToken: existing, tokenKind: "pat" };
|
|
520
|
+
return { accessToken: existing.value, tokenKind: "pat" };
|
|
385
521
|
}
|
|
386
522
|
}
|
|
387
523
|
const token = await promptToken(`${provider.id} personal access token`);
|
|
388
524
|
if (!token) throw new Error("no token provided");
|
|
525
|
+
await store.write(ref, { value: token, kind: "pat" });
|
|
389
526
|
return { accessToken: token, tokenKind: "pat" };
|
|
390
527
|
}
|
|
391
528
|
};
|
|
392
529
|
var execAsync = promisify(execFile);
|
|
393
|
-
var CLAIM_GRANT_TYPE = "urn:workos:agent-auth:grant-type:claim";
|
|
394
|
-
var JWT_BEARER_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer";
|
|
395
|
-
var DEFAULT_POLL_INTERVAL_S = 5;
|
|
396
|
-
var identityResponseSchema = z.object({
|
|
397
|
-
registration_id: z.string(),
|
|
398
|
-
claim_token: z.string(),
|
|
399
|
-
claim: z.object({
|
|
400
|
-
user_code: z.string(),
|
|
401
|
-
verification_uri: z.string(),
|
|
402
|
-
expires_in: z.number(),
|
|
403
|
-
interval: z.number().optional()
|
|
404
|
-
}).loose()
|
|
405
|
-
}).loose();
|
|
406
530
|
var tokenSuccessSchema = z.object({
|
|
407
531
|
access_token: z.string(),
|
|
408
532
|
token_type: z.string().optional(),
|
|
409
533
|
refresh_token: z.string().optional(),
|
|
410
534
|
identity_assertion: z.string().optional(),
|
|
411
535
|
assertion_expires_in: z.number().optional(),
|
|
412
|
-
assertion_expires: z.string().optional()
|
|
536
|
+
assertion_expires: z.string().optional(),
|
|
537
|
+
// device-code flow fields (ignored by service-auth today; see WP-2).
|
|
538
|
+
expires_in: z.number().optional(),
|
|
539
|
+
scope: z.string().optional(),
|
|
540
|
+
subject: z.string().optional(),
|
|
541
|
+
revocation_id: z.string().optional()
|
|
413
542
|
}).loose();
|
|
414
543
|
var tokenErrorSchema = z.object({
|
|
415
544
|
error: z.string(),
|
|
@@ -423,20 +552,7 @@ async function openBrowser(url) {
|
|
|
423
552
|
} catch {
|
|
424
553
|
}
|
|
425
554
|
}
|
|
426
|
-
async function
|
|
427
|
-
const res = await fetchWithDeadline(url, {
|
|
428
|
-
method: "POST",
|
|
429
|
-
headers: { "Content-Type": "application/json" },
|
|
430
|
-
body: JSON.stringify(body),
|
|
431
|
-
signal
|
|
432
|
-
});
|
|
433
|
-
if (!res.ok) {
|
|
434
|
-
const text = await res.text();
|
|
435
|
-
throw new Error(`POST ${url} \u2192 ${res.status}: ${text}`);
|
|
436
|
-
}
|
|
437
|
-
return schema.parse(await res.json());
|
|
438
|
-
}
|
|
439
|
-
async function postForm(url, params, schema, signal) {
|
|
555
|
+
async function postFormOAuth(url, params, schema, signal) {
|
|
440
556
|
const res = await fetchWithDeadline(url, {
|
|
441
557
|
method: "POST",
|
|
442
558
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
@@ -445,21 +561,17 @@ async function postForm(url, params, schema, signal) {
|
|
|
445
561
|
});
|
|
446
562
|
return schema.parse(await res.json());
|
|
447
563
|
}
|
|
448
|
-
async function
|
|
449
|
-
const deadline = Date.now() + expiresIn * 1e3;
|
|
450
|
-
let pollMs = intervalS * 1e3;
|
|
564
|
+
async function pollDeviceGrant(o) {
|
|
565
|
+
const deadline = Date.now() + o.expiresIn * 1e3;
|
|
566
|
+
let pollMs = o.intervalS * 1e3;
|
|
451
567
|
while (Date.now() < deadline) {
|
|
452
|
-
if (signal?.aborted) throw new Error("auth cancelled");
|
|
568
|
+
if (o.signal?.aborted) throw new Error("auth cancelled");
|
|
453
569
|
await new Promise((r) => setTimeout(r, pollMs));
|
|
454
|
-
const data = await
|
|
455
|
-
tokenEndpoint,
|
|
456
|
-
{
|
|
457
|
-
grant_type: CLAIM_GRANT_TYPE,
|
|
458
|
-
claim_token: claimToken,
|
|
459
|
-
client_id: clientId
|
|
460
|
-
},
|
|
570
|
+
const data = await postFormOAuth(
|
|
571
|
+
o.tokenEndpoint,
|
|
572
|
+
{ grant_type: o.grantType, ...o.params },
|
|
461
573
|
tokenResponseSchema,
|
|
462
|
-
signal
|
|
574
|
+
o.signal
|
|
463
575
|
);
|
|
464
576
|
if (!("error" in data)) return data;
|
|
465
577
|
const pollErr = data.error;
|
|
@@ -479,9 +591,37 @@ async function pollForToken(tokenEndpoint, claimToken, clientId, intervalS, expi
|
|
|
479
591
|
}
|
|
480
592
|
throw new Error("auth timeout \u2014 approval window closed");
|
|
481
593
|
}
|
|
594
|
+
|
|
595
|
+
// src/flow-engines/service-auth.ts
|
|
596
|
+
var CLAIM_GRANT_TYPE = "urn:workos:agent-auth:grant-type:claim";
|
|
597
|
+
var JWT_BEARER_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer";
|
|
598
|
+
var DEFAULT_POLL_INTERVAL_S = 5;
|
|
599
|
+
var identityResponseSchema = z.object({
|
|
600
|
+
registration_id: z.string(),
|
|
601
|
+
claim_token: z.string(),
|
|
602
|
+
claim: z.object({
|
|
603
|
+
user_code: z.string(),
|
|
604
|
+
verification_uri: z.string(),
|
|
605
|
+
expires_in: z.number(),
|
|
606
|
+
interval: z.number().optional()
|
|
607
|
+
}).loose()
|
|
608
|
+
}).loose();
|
|
609
|
+
async function postJson(url, body, schema, signal) {
|
|
610
|
+
const res = await fetchWithDeadline(url, {
|
|
611
|
+
method: "POST",
|
|
612
|
+
headers: { "Content-Type": "application/json" },
|
|
613
|
+
body: JSON.stringify(body),
|
|
614
|
+
signal
|
|
615
|
+
});
|
|
616
|
+
if (!res.ok) {
|
|
617
|
+
const text = await res.text();
|
|
618
|
+
throw new Error(`POST ${url} \u2192 ${res.status}: ${text}`);
|
|
619
|
+
}
|
|
620
|
+
return schema.parse(await res.json());
|
|
621
|
+
}
|
|
482
622
|
async function exchangeAssertion(tokenEndpoint, assertion, clientId, signal) {
|
|
483
623
|
try {
|
|
484
|
-
const data = await
|
|
624
|
+
const data = await postFormOAuth(
|
|
485
625
|
tokenEndpoint,
|
|
486
626
|
{
|
|
487
627
|
grant_type: JWT_BEARER_GRANT_TYPE,
|
|
@@ -511,24 +651,27 @@ var serviceAuthFlowEngine = {
|
|
|
511
651
|
const tokenEndpoint = discovered?.tokenEndpoint ?? `${server}/oauth/token`;
|
|
512
652
|
assertSecureUrl(identityEndpoint);
|
|
513
653
|
assertSecureUrl(tokenEndpoint);
|
|
514
|
-
const
|
|
515
|
-
const
|
|
654
|
+
const store = opts.store ?? new KeychainStore();
|
|
655
|
+
const { ref, legacyRef } = resolveStoreRefs(
|
|
656
|
+
config.tokenStore,
|
|
657
|
+
server,
|
|
658
|
+
provider.audience
|
|
659
|
+
);
|
|
516
660
|
if (!opts.force) {
|
|
517
|
-
const
|
|
518
|
-
if (
|
|
661
|
+
const stored = await readStoreRefWithFallback(store, ref, legacyRef);
|
|
662
|
+
if (stored) {
|
|
519
663
|
const exchanged = await exchangeAssertion(
|
|
520
664
|
tokenEndpoint,
|
|
521
|
-
|
|
665
|
+
stored.value,
|
|
522
666
|
clientId,
|
|
523
667
|
opts.signal
|
|
524
668
|
);
|
|
525
669
|
if (exchanged) {
|
|
526
670
|
if (exchanged.identity_assertion) {
|
|
527
|
-
await
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
);
|
|
671
|
+
await store.write(ref, {
|
|
672
|
+
value: exchanged.identity_assertion,
|
|
673
|
+
kind: "assertion"
|
|
674
|
+
});
|
|
532
675
|
}
|
|
533
676
|
return {
|
|
534
677
|
accessToken: exchanged.access_token,
|
|
@@ -561,23 +704,22 @@ var serviceAuthFlowEngine = {
|
|
|
561
704
|
process.stderr.write(` URL: ${claim.verification_uri}
|
|
562
705
|
|
|
563
706
|
`);
|
|
564
|
-
|
|
707
|
+
if (opts.openBrowser !== false) {
|
|
708
|
+
await openBrowser(claim.verification_uri);
|
|
709
|
+
}
|
|
565
710
|
process.stderr.write(` Waiting for approval (${windowMin} min)\u2026
|
|
566
711
|
|
|
567
712
|
`);
|
|
568
|
-
const tokenResult = await
|
|
713
|
+
const tokenResult = await pollDeviceGrant({
|
|
569
714
|
tokenEndpoint,
|
|
570
|
-
|
|
571
|
-
clientId,
|
|
715
|
+
grantType: CLAIM_GRANT_TYPE,
|
|
716
|
+
params: { claim_token, client_id: clientId },
|
|
572
717
|
intervalS,
|
|
573
|
-
claim.expires_in,
|
|
574
|
-
opts.signal
|
|
575
|
-
);
|
|
718
|
+
expiresIn: claim.expires_in,
|
|
719
|
+
signal: opts.signal
|
|
720
|
+
});
|
|
576
721
|
const accessToken = tokenResult.access_token;
|
|
577
722
|
const assertion = tokenResult.identity_assertion;
|
|
578
|
-
if (assertion) {
|
|
579
|
-
await writeKeychainToken(primarySlot, account, assertion);
|
|
580
|
-
}
|
|
581
723
|
let assertionExpires;
|
|
582
724
|
if (tokenResult.assertion_expires) {
|
|
583
725
|
assertionExpires = tokenResult.assertion_expires;
|
|
@@ -586,6 +728,13 @@ var serviceAuthFlowEngine = {
|
|
|
586
728
|
Date.now() + tokenResult.assertion_expires_in * 1e3
|
|
587
729
|
).toISOString();
|
|
588
730
|
}
|
|
731
|
+
if (assertion) {
|
|
732
|
+
await store.write(ref, {
|
|
733
|
+
value: assertion,
|
|
734
|
+
kind: "assertion",
|
|
735
|
+
...assertionExpires ? { expiresAt: assertionExpires } : {}
|
|
736
|
+
});
|
|
737
|
+
}
|
|
589
738
|
return {
|
|
590
739
|
accessToken,
|
|
591
740
|
identityAssertion: assertion,
|
|
@@ -594,11 +743,217 @@ var serviceAuthFlowEngine = {
|
|
|
594
743
|
};
|
|
595
744
|
}
|
|
596
745
|
};
|
|
746
|
+
var DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
747
|
+
var REFRESH_GRANT_TYPE = "refresh_token";
|
|
748
|
+
var DEFAULT_POLL_INTERVAL_S2 = 5;
|
|
749
|
+
var CeremonyRequiredError = class extends Error {
|
|
750
|
+
constructor(message) {
|
|
751
|
+
super(message);
|
|
752
|
+
this.name = "CeremonyRequiredError";
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
var deviceAuthorizationResponseSchema = z.object({
|
|
756
|
+
device_code: z.string(),
|
|
757
|
+
user_code: z.string(),
|
|
758
|
+
verification_uri: z.string(),
|
|
759
|
+
verification_uri_complete: z.string().optional(),
|
|
760
|
+
expires_in: z.number(),
|
|
761
|
+
interval: z.number().optional()
|
|
762
|
+
}).loose();
|
|
763
|
+
async function postDeviceAuthorization(url, params, signal) {
|
|
764
|
+
const res = await fetchWithDeadline(url, {
|
|
765
|
+
method: "POST",
|
|
766
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
767
|
+
body: new URLSearchParams(params).toString(),
|
|
768
|
+
signal
|
|
769
|
+
});
|
|
770
|
+
if (!res.ok) {
|
|
771
|
+
const text = await res.text();
|
|
772
|
+
throw new Error(`POST ${url} \u2192 ${res.status}: ${text}`);
|
|
773
|
+
}
|
|
774
|
+
return deviceAuthorizationResponseSchema.parse(await res.json());
|
|
775
|
+
}
|
|
776
|
+
function metaString(metadata, key) {
|
|
777
|
+
const v = metadata?.[key];
|
|
778
|
+
return typeof v === "string" ? v : void 0;
|
|
779
|
+
}
|
|
780
|
+
function isExpired(cred) {
|
|
781
|
+
if (!cred.expiresAt) return false;
|
|
782
|
+
const t = Date.parse(cred.expiresAt);
|
|
783
|
+
if (Number.isNaN(t)) return true;
|
|
784
|
+
return t <= Date.now();
|
|
785
|
+
}
|
|
786
|
+
function resultFromStored(cred) {
|
|
787
|
+
const refreshToken = metaString(cred.metadata, "refreshToken");
|
|
788
|
+
const scope = metaString(cred.metadata, "scope");
|
|
789
|
+
const subject = metaString(cred.metadata, "subject");
|
|
790
|
+
const revocationId = metaString(cred.metadata, "revocationId");
|
|
791
|
+
return {
|
|
792
|
+
accessToken: cred.value,
|
|
793
|
+
tokenKind: "daemon",
|
|
794
|
+
...refreshToken ? { refreshToken } : {},
|
|
795
|
+
...scope ? { scope } : {},
|
|
796
|
+
...subject ? { subject } : {},
|
|
797
|
+
...revocationId ? { revocationId } : {}
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
async function refreshAccessToken(tokenEndpoint, refreshToken, clientId, signal) {
|
|
801
|
+
try {
|
|
802
|
+
const data = await postFormOAuth(
|
|
803
|
+
tokenEndpoint,
|
|
804
|
+
{
|
|
805
|
+
grant_type: REFRESH_GRANT_TYPE,
|
|
806
|
+
refresh_token: refreshToken,
|
|
807
|
+
client_id: clientId
|
|
808
|
+
},
|
|
809
|
+
tokenResponseSchema,
|
|
810
|
+
signal
|
|
811
|
+
);
|
|
812
|
+
if ("error" in data) return null;
|
|
813
|
+
return data;
|
|
814
|
+
} catch {
|
|
815
|
+
return null;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
async function persistAndResult(store, ref, o) {
|
|
819
|
+
const expiresAt = o.expiresIn ? new Date(Date.now() + o.expiresIn * 1e3).toISOString() : void 0;
|
|
820
|
+
const metadata = {
|
|
821
|
+
obtainedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
822
|
+
...o.refreshToken ? { refreshToken: o.refreshToken } : {},
|
|
823
|
+
...o.scope ? { scope: o.scope } : {},
|
|
824
|
+
...o.subject ? { subject: o.subject } : {},
|
|
825
|
+
...o.deviceLabel ? { deviceLabel: o.deviceLabel } : {},
|
|
826
|
+
...o.revocationId ? { revocationId: o.revocationId } : {}
|
|
827
|
+
};
|
|
828
|
+
await store.write(ref, {
|
|
829
|
+
value: o.accessToken,
|
|
830
|
+
kind: "daemon",
|
|
831
|
+
...expiresAt ? { expiresAt } : {},
|
|
832
|
+
metadata
|
|
833
|
+
});
|
|
834
|
+
return {
|
|
835
|
+
accessToken: o.accessToken,
|
|
836
|
+
tokenKind: "daemon",
|
|
837
|
+
...o.refreshToken ? { refreshToken: o.refreshToken } : {},
|
|
838
|
+
...o.scope ? { scope: o.scope } : {},
|
|
839
|
+
...o.subject ? { subject: o.subject } : {},
|
|
840
|
+
...o.revocationId ? { revocationId: o.revocationId } : {}
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
var deviceCodeFlowEngine = {
|
|
844
|
+
id: "device-code",
|
|
845
|
+
async run(provider, discovered, opts) {
|
|
846
|
+
const { auth } = provider;
|
|
847
|
+
if (auth.flow !== "device-code") {
|
|
848
|
+
throw new Error(`deviceCodeFlowEngine: invoked with flow="${auth.flow}"`);
|
|
849
|
+
}
|
|
850
|
+
const config = auth;
|
|
851
|
+
const clientId = config.clientId ?? "agentproto-cli";
|
|
852
|
+
const server = opts.server.replace(/\/$/, "");
|
|
853
|
+
const deviceAuthorizationEndpoint = discovered?.deviceAuthorizationEndpoint ?? `${server}/oauth/device`;
|
|
854
|
+
const tokenEndpoint = discovered?.tokenEndpoint ?? `${server}/oauth/token`;
|
|
855
|
+
assertSecureUrl(deviceAuthorizationEndpoint);
|
|
856
|
+
assertSecureUrl(tokenEndpoint);
|
|
857
|
+
const store = opts.store ?? new KeychainStore();
|
|
858
|
+
const { ref, legacyRef } = resolveStoreRefs(
|
|
859
|
+
config.tokenStore,
|
|
860
|
+
server,
|
|
861
|
+
provider.audience
|
|
862
|
+
);
|
|
863
|
+
async function tryRefresh(stored) {
|
|
864
|
+
const refreshToken = metaString(stored.metadata, "refreshToken");
|
|
865
|
+
if (!refreshToken) return null;
|
|
866
|
+
const refreshed = await refreshAccessToken(
|
|
867
|
+
tokenEndpoint,
|
|
868
|
+
refreshToken,
|
|
869
|
+
clientId,
|
|
870
|
+
opts.signal
|
|
871
|
+
);
|
|
872
|
+
if (!refreshed) return null;
|
|
873
|
+
return persistAndResult(store, ref, {
|
|
874
|
+
accessToken: refreshed.access_token,
|
|
875
|
+
expiresIn: refreshed.expires_in,
|
|
876
|
+
refreshToken: refreshed.refresh_token ?? refreshToken,
|
|
877
|
+
scope: refreshed.scope ?? metaString(stored.metadata, "scope"),
|
|
878
|
+
subject: refreshed.subject ?? metaString(stored.metadata, "subject"),
|
|
879
|
+
deviceLabel: config.deviceLabel ?? metaString(stored.metadata, "deviceLabel"),
|
|
880
|
+
revocationId: refreshed.revocation_id ?? metaString(stored.metadata, "revocationId")
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
if (opts.refreshOnly) {
|
|
884
|
+
const stored = await readStoreRefWithFallback(store, ref, legacyRef);
|
|
885
|
+
if (stored) {
|
|
886
|
+
if (!isExpired(stored)) {
|
|
887
|
+
return resultFromStored(stored);
|
|
888
|
+
}
|
|
889
|
+
const refreshed = await tryRefresh(stored);
|
|
890
|
+
if (refreshed) return refreshed;
|
|
891
|
+
}
|
|
892
|
+
throw new CeremonyRequiredError(
|
|
893
|
+
`${provider.id}: no fresh or refreshable device-code credential \u2014 an interactive ceremony would be required, but refreshOnly was set`
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
if (!opts.force) {
|
|
897
|
+
const stored = await readStoreRefWithFallback(store, ref, legacyRef);
|
|
898
|
+
if (stored) {
|
|
899
|
+
if (!isExpired(stored)) {
|
|
900
|
+
return resultFromStored(stored);
|
|
901
|
+
}
|
|
902
|
+
const refreshed = await tryRefresh(stored);
|
|
903
|
+
if (refreshed) return refreshed;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
const params = { client_id: clientId };
|
|
907
|
+
if (config.scope) params.scope = config.scope;
|
|
908
|
+
if (config.deviceLabel) params.device_label = config.deviceLabel;
|
|
909
|
+
const authz = await postDeviceAuthorization(
|
|
910
|
+
deviceAuthorizationEndpoint,
|
|
911
|
+
params,
|
|
912
|
+
opts.signal
|
|
913
|
+
);
|
|
914
|
+
const intervalS = authz.interval ?? DEFAULT_POLL_INTERVAL_S2;
|
|
915
|
+
const windowMin = Math.round(authz.expires_in / 60);
|
|
916
|
+
process.stderr.write(`
|
|
917
|
+
`);
|
|
918
|
+
process.stderr.write(` Approve ${provider.id} access in your browser
|
|
919
|
+
|
|
920
|
+
`);
|
|
921
|
+
process.stderr.write(` Code: ${authz.user_code}
|
|
922
|
+
`);
|
|
923
|
+
process.stderr.write(` URL: ${authz.verification_uri}
|
|
924
|
+
|
|
925
|
+
`);
|
|
926
|
+
if (opts.openBrowser !== false) {
|
|
927
|
+
await openBrowser(authz.verification_uri_complete ?? authz.verification_uri);
|
|
928
|
+
}
|
|
929
|
+
process.stderr.write(` Waiting for approval (${windowMin} min)\u2026
|
|
930
|
+
|
|
931
|
+
`);
|
|
932
|
+
const tokenResult = await pollDeviceGrant({
|
|
933
|
+
tokenEndpoint,
|
|
934
|
+
grantType: DEVICE_CODE_GRANT_TYPE,
|
|
935
|
+
params: { device_code: authz.device_code, client_id: clientId },
|
|
936
|
+
intervalS,
|
|
937
|
+
expiresIn: authz.expires_in,
|
|
938
|
+
signal: opts.signal
|
|
939
|
+
});
|
|
940
|
+
return persistAndResult(store, ref, {
|
|
941
|
+
accessToken: tokenResult.access_token,
|
|
942
|
+
expiresIn: tokenResult.expires_in,
|
|
943
|
+
refreshToken: tokenResult.refresh_token,
|
|
944
|
+
scope: tokenResult.scope,
|
|
945
|
+
subject: tokenResult.subject,
|
|
946
|
+
deviceLabel: config.deviceLabel,
|
|
947
|
+
revocationId: tokenResult.revocation_id
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
};
|
|
597
951
|
|
|
598
952
|
// src/flow-engines/index.ts
|
|
599
953
|
var FLOW_ENGINES = {
|
|
600
954
|
[patFlowEngine.id]: patFlowEngine,
|
|
601
|
-
[serviceAuthFlowEngine.id]: serviceAuthFlowEngine
|
|
955
|
+
[serviceAuthFlowEngine.id]: serviceAuthFlowEngine,
|
|
956
|
+
[deviceCodeFlowEngine.id]: deviceCodeFlowEngine
|
|
602
957
|
};
|
|
603
958
|
|
|
604
959
|
// src/run-flow.ts
|
|
@@ -611,7 +966,7 @@ async function runAuthFlow(provider, opts) {
|
|
|
611
966
|
);
|
|
612
967
|
}
|
|
613
968
|
let discovered = opts.discovered !== void 0 ? opts.discovered : null;
|
|
614
|
-
if (discovered === null && flowId === "service-auth") {
|
|
969
|
+
if (discovered === null && (flowId === "service-auth" || flowId === "device-code")) {
|
|
615
970
|
try {
|
|
616
971
|
discovered = await discoverEndpoints(opts.server, { signal: opts.signal });
|
|
617
972
|
} catch (err) {
|
|
@@ -625,10 +980,194 @@ async function runAuthFlow(provider, opts) {
|
|
|
625
980
|
return engine.run(provider, discovered, opts);
|
|
626
981
|
}
|
|
627
982
|
|
|
983
|
+
// src/store/memory-store.ts
|
|
984
|
+
function keyOf(ref) {
|
|
985
|
+
return `${ref.path}\0${ref.account ?? ref.path}`;
|
|
986
|
+
}
|
|
987
|
+
var MemoryStore = class {
|
|
988
|
+
entries = /* @__PURE__ */ new Map();
|
|
989
|
+
async read(ref) {
|
|
990
|
+
const entry = this.entries.get(keyOf(ref));
|
|
991
|
+
return entry ? { ...entry } : void 0;
|
|
992
|
+
}
|
|
993
|
+
async write(ref, cred) {
|
|
994
|
+
this.entries.set(keyOf(ref), { ...cred });
|
|
995
|
+
}
|
|
996
|
+
async delete(ref) {
|
|
997
|
+
this.entries.delete(keyOf(ref));
|
|
998
|
+
}
|
|
999
|
+
};
|
|
1000
|
+
var ALGORITHM = "aes-256-gcm";
|
|
1001
|
+
var KEY_ENV_VAR = "AGENTPROTO_STORE_KEY";
|
|
1002
|
+
var IV_BYTES = 12;
|
|
1003
|
+
var sealedEntrySchema = z.object({
|
|
1004
|
+
iv: z.string(),
|
|
1005
|
+
tag: z.string(),
|
|
1006
|
+
ciphertext: z.string()
|
|
1007
|
+
});
|
|
1008
|
+
var sealedFileSchema = z.record(z.string(), sealedEntrySchema);
|
|
1009
|
+
var storedCredentialSchema = z.object({
|
|
1010
|
+
value: z.string(),
|
|
1011
|
+
kind: z.enum(["pat", "assertion", "oat", "daemon"]),
|
|
1012
|
+
expiresAt: z.string().optional(),
|
|
1013
|
+
metadata: z.record(z.string(), z.unknown()).optional()
|
|
1014
|
+
});
|
|
1015
|
+
function keyOf2(ref) {
|
|
1016
|
+
return `${ref.path} ${ref.account ?? ref.path}`;
|
|
1017
|
+
}
|
|
1018
|
+
function isEnoent(err) {
|
|
1019
|
+
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
1020
|
+
}
|
|
1021
|
+
function loadKey() {
|
|
1022
|
+
const raw = process.env[KEY_ENV_VAR];
|
|
1023
|
+
if (!raw) {
|
|
1024
|
+
throw new Error(
|
|
1025
|
+
`FileStore: missing ${KEY_ENV_VAR} \u2014 set a 32-byte key (64 hex chars, or base64) to use the encrypted file store. Refusing to store credentials in plaintext.`
|
|
1026
|
+
);
|
|
1027
|
+
}
|
|
1028
|
+
const key = /^[0-9a-fA-F]{64}$/.test(raw) ? Buffer.from(raw, "hex") : Buffer.from(raw, "base64");
|
|
1029
|
+
if (key.length !== 32) {
|
|
1030
|
+
throw new Error(
|
|
1031
|
+
`FileStore: ${KEY_ENV_VAR} must decode to exactly 32 bytes (got ${key.length}). Provide a 64-char hex string or base64 encoding 32 bytes.`
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
1034
|
+
return key;
|
|
1035
|
+
}
|
|
1036
|
+
function seal(key, plaintext) {
|
|
1037
|
+
const iv = randomBytes(IV_BYTES);
|
|
1038
|
+
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
1039
|
+
const ciphertext = Buffer.concat([
|
|
1040
|
+
cipher.update(plaintext, "utf8"),
|
|
1041
|
+
cipher.final()
|
|
1042
|
+
]);
|
|
1043
|
+
return {
|
|
1044
|
+
iv: iv.toString("base64"),
|
|
1045
|
+
tag: cipher.getAuthTag().toString("base64"),
|
|
1046
|
+
ciphertext: ciphertext.toString("base64")
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
function unseal(key, entry) {
|
|
1050
|
+
const decipher = createDecipheriv(
|
|
1051
|
+
ALGORITHM,
|
|
1052
|
+
key,
|
|
1053
|
+
Buffer.from(entry.iv, "base64")
|
|
1054
|
+
);
|
|
1055
|
+
decipher.setAuthTag(Buffer.from(entry.tag, "base64"));
|
|
1056
|
+
const plaintext = Buffer.concat([
|
|
1057
|
+
decipher.update(Buffer.from(entry.ciphertext, "base64")),
|
|
1058
|
+
decipher.final()
|
|
1059
|
+
]);
|
|
1060
|
+
return plaintext.toString("utf8");
|
|
1061
|
+
}
|
|
1062
|
+
var FileStore = class {
|
|
1063
|
+
constructor(filePath) {
|
|
1064
|
+
this.filePath = filePath;
|
|
1065
|
+
}
|
|
1066
|
+
filePath;
|
|
1067
|
+
async read(ref) {
|
|
1068
|
+
const key = loadKey();
|
|
1069
|
+
const file = await this.readFile();
|
|
1070
|
+
const sealed = file[keyOf2(ref)];
|
|
1071
|
+
if (!sealed) return void 0;
|
|
1072
|
+
return storedCredentialSchema.parse(JSON.parse(unseal(key, sealed)));
|
|
1073
|
+
}
|
|
1074
|
+
async write(ref, cred) {
|
|
1075
|
+
const key = loadKey();
|
|
1076
|
+
const file = await this.readFile();
|
|
1077
|
+
file[keyOf2(ref)] = seal(key, JSON.stringify(cred));
|
|
1078
|
+
await this.writeFile(file);
|
|
1079
|
+
}
|
|
1080
|
+
async delete(ref) {
|
|
1081
|
+
const file = await this.readFile();
|
|
1082
|
+
delete file[keyOf2(ref)];
|
|
1083
|
+
await this.writeFile(file);
|
|
1084
|
+
}
|
|
1085
|
+
async readFile() {
|
|
1086
|
+
try {
|
|
1087
|
+
const raw = await readFile(this.filePath, "utf8");
|
|
1088
|
+
return sealedFileSchema.parse(JSON.parse(raw));
|
|
1089
|
+
} catch (err) {
|
|
1090
|
+
if (isEnoent(err)) return {};
|
|
1091
|
+
throw err;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
async writeFile(file) {
|
|
1095
|
+
await mkdir(dirname(this.filePath), { recursive: true });
|
|
1096
|
+
await writeFile(this.filePath, JSON.stringify(file, null, 2), "utf8");
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
|
|
1100
|
+
// src/broker.ts
|
|
1101
|
+
var EXPIRY_SKEW_MS = 6e4;
|
|
1102
|
+
function parsePath(path) {
|
|
1103
|
+
const slash = path.indexOf("/");
|
|
1104
|
+
if (slash === -1) return { providerId: path };
|
|
1105
|
+
return { providerId: path.slice(0, slash), account: path.slice(slash + 1) };
|
|
1106
|
+
}
|
|
1107
|
+
function isFresh(cred) {
|
|
1108
|
+
if (!cred.expiresAt) return true;
|
|
1109
|
+
const expiresAtMs = Date.parse(cred.expiresAt);
|
|
1110
|
+
if (Number.isNaN(expiresAtMs)) return false;
|
|
1111
|
+
return expiresAtMs - EXPIRY_SKEW_MS > Date.now();
|
|
1112
|
+
}
|
|
1113
|
+
function bearerHeaders(value, kind) {
|
|
1114
|
+
if (kind === "pat" || kind === "oat" || kind === "daemon") {
|
|
1115
|
+
return { Authorization: `Bearer ${value}` };
|
|
1116
|
+
}
|
|
1117
|
+
return void 0;
|
|
1118
|
+
}
|
|
1119
|
+
var CredentialBroker = class {
|
|
1120
|
+
store;
|
|
1121
|
+
getProvider;
|
|
1122
|
+
constructor(opts) {
|
|
1123
|
+
this.store = opts.store;
|
|
1124
|
+
this.getProvider = opts.getProvider;
|
|
1125
|
+
}
|
|
1126
|
+
async resolveHeaders(o) {
|
|
1127
|
+
const { providerId, account } = parsePath(o.path);
|
|
1128
|
+
const provider = this.getProvider(providerId);
|
|
1129
|
+
if (!provider) {
|
|
1130
|
+
throw new Error(`CredentialBroker: unknown auth provider "${providerId}"`);
|
|
1131
|
+
}
|
|
1132
|
+
if (o.audience !== void 0 && provider.audience !== void 0 && o.audience !== provider.audience) {
|
|
1133
|
+
throw new Error(
|
|
1134
|
+
`CredentialBroker: provider "${providerId}" is scoped to audience "${provider.audience}", requested "${o.audience}"`
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
const server = o.server ?? provider.apiBase;
|
|
1138
|
+
const { ref, legacyRef } = resolveStoreRefs(
|
|
1139
|
+
provider.auth.tokenStore,
|
|
1140
|
+
server,
|
|
1141
|
+
provider.audience
|
|
1142
|
+
);
|
|
1143
|
+
if (account !== void 0) {
|
|
1144
|
+
ref.account = account;
|
|
1145
|
+
legacyRef.account = account;
|
|
1146
|
+
}
|
|
1147
|
+
const stored = await readStoreRefWithFallback(this.store, ref, legacyRef);
|
|
1148
|
+
if (stored && isFresh(stored)) {
|
|
1149
|
+
const headers2 = bearerHeaders(stored.value, stored.kind);
|
|
1150
|
+
if (headers2) return headers2;
|
|
1151
|
+
}
|
|
1152
|
+
const result = await runAuthFlow(provider, {
|
|
1153
|
+
server,
|
|
1154
|
+
store: this.store,
|
|
1155
|
+
signal: o.signal
|
|
1156
|
+
});
|
|
1157
|
+
const headers = result.accessToken !== void 0 ? bearerHeaders(result.accessToken, result.tokenKind) : void 0;
|
|
1158
|
+
if (!headers) {
|
|
1159
|
+
throw new Error(
|
|
1160
|
+
`CredentialBroker: auth flow for "${providerId}" produced no usable access token`
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
return headers;
|
|
1164
|
+
}
|
|
1165
|
+
};
|
|
1166
|
+
|
|
628
1167
|
// src/index.ts
|
|
629
1168
|
var SPEC_NAME = "agentauth";
|
|
630
1169
|
var SPEC_VERSION = "v1";
|
|
631
1170
|
|
|
632
|
-
export { BUILTIN_AUTH_PROVIDERS, DiscoveryError, FLOW_ENGINES, SPEC_NAME, SPEC_VERSION, authConfigSchema, authProviderFrontmatterSchema, defineAuthProvider, discoverEndpoints, getAuthProvider, guildeAuthProvider, installConfigSchema, listAuthProviderIds, listAuthProviders, parseAuthProviderManifest, parseAuthProviderManifestRaw, readKeychainToken, registerAuthProvider, resolveAccount, runAuthFlow, tokenStoreSpecSchema, writeKeychainToken };
|
|
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 };
|
|
633
1172
|
//# sourceMappingURL=index.mjs.map
|
|
634
1173
|
//# sourceMappingURL=index.mjs.map
|