@simonfestl/husky-cli 1.30.0 → 1.31.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/commands/auth.js +43 -3
- package/package.json +1 -1
package/dist/commands/auth.js
CHANGED
|
@@ -6,6 +6,27 @@ const API_KEY_ROLES = [
|
|
|
6
6
|
"admin", "supervisor", "worker", "reviewer", "support",
|
|
7
7
|
"purchasing", "ops", "e2e_agent", "pr_agent"
|
|
8
8
|
];
|
|
9
|
+
/**
|
|
10
|
+
* Fetch VM Identity Token from GCP Metadata Server
|
|
11
|
+
* This token is cryptographically signed by Google and proves the caller is running on a specific GCP VM.
|
|
12
|
+
* Returns null if not running on GCP or metadata server is unavailable.
|
|
13
|
+
*/
|
|
14
|
+
async function getVMIdentityToken(audience = "husky-api") {
|
|
15
|
+
try {
|
|
16
|
+
const url = `http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=${audience}`;
|
|
17
|
+
const res = await fetch(url, {
|
|
18
|
+
headers: { "Metadata-Flavor": "Google" },
|
|
19
|
+
signal: AbortSignal.timeout(3000), // 3 second timeout
|
|
20
|
+
});
|
|
21
|
+
if (res.ok) {
|
|
22
|
+
return (await res.text()).trim();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// Not on GCP or metadata server unavailable - this is expected for local development
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
9
30
|
export const authCommand = new Command("auth")
|
|
10
31
|
.description("Manage API keys and authentication");
|
|
11
32
|
authCommand
|
|
@@ -250,7 +271,8 @@ authCommand
|
|
|
250
271
|
authCommand
|
|
251
272
|
.command("login")
|
|
252
273
|
.description("Create a session token for this agent")
|
|
253
|
-
.requiredOption("--agent <name>", "Agent name (must be registered in Firestore)")
|
|
274
|
+
.requiredOption("--agent <name>", "Agent name or alias (must be registered in Firestore)")
|
|
275
|
+
.option("--force", "Clear existing session before creating new one")
|
|
254
276
|
.option("--json", "Output as JSON")
|
|
255
277
|
.action(async (options) => {
|
|
256
278
|
try {
|
|
@@ -259,14 +281,26 @@ authCommand
|
|
|
259
281
|
console.error("API not configured. Run: husky config set api-url <url> && husky config set api-key <key>");
|
|
260
282
|
process.exit(1);
|
|
261
283
|
}
|
|
284
|
+
// Clear existing session if --force flag is set
|
|
285
|
+
if (options.force) {
|
|
286
|
+
clearSessionConfig();
|
|
287
|
+
clearPermissionsCache();
|
|
288
|
+
}
|
|
289
|
+
// Try to get VM Identity Token from GCP Metadata Server
|
|
290
|
+
// This will be null on local development machines
|
|
291
|
+
const vmIdentityToken = await getVMIdentityToken();
|
|
262
292
|
const url = new URL("/api/auth/session", config.apiUrl);
|
|
293
|
+
const requestBody = { agent: options.agent };
|
|
294
|
+
if (vmIdentityToken) {
|
|
295
|
+
requestBody.vmIdentityToken = vmIdentityToken;
|
|
296
|
+
}
|
|
263
297
|
const res = await fetch(url.toString(), {
|
|
264
298
|
method: "POST",
|
|
265
299
|
headers: {
|
|
266
300
|
"x-api-key": config.apiKey,
|
|
267
301
|
"Content-Type": "application/json",
|
|
268
302
|
},
|
|
269
|
-
body: JSON.stringify(
|
|
303
|
+
body: JSON.stringify(requestBody),
|
|
270
304
|
});
|
|
271
305
|
if (!res.ok) {
|
|
272
306
|
const error = await res.json().catch(() => ({ error: res.statusText }));
|
|
@@ -401,14 +435,20 @@ authCommand
|
|
|
401
435
|
console.error("No agent specified and no active session. Use: husky auth refresh --agent <name>");
|
|
402
436
|
process.exit(1);
|
|
403
437
|
}
|
|
438
|
+
// Try to get VM Identity Token from GCP Metadata Server
|
|
439
|
+
const vmIdentityToken = await getVMIdentityToken();
|
|
404
440
|
const url = new URL("/api/auth/session", config.apiUrl);
|
|
441
|
+
const requestBody = { agent: agentName };
|
|
442
|
+
if (vmIdentityToken) {
|
|
443
|
+
requestBody.vmIdentityToken = vmIdentityToken;
|
|
444
|
+
}
|
|
405
445
|
const res = await fetch(url.toString(), {
|
|
406
446
|
method: "POST",
|
|
407
447
|
headers: {
|
|
408
448
|
"x-api-key": config.apiKey,
|
|
409
449
|
"Content-Type": "application/json",
|
|
410
450
|
},
|
|
411
|
-
body: JSON.stringify(
|
|
451
|
+
body: JSON.stringify(requestBody),
|
|
412
452
|
});
|
|
413
453
|
if (!res.ok) {
|
|
414
454
|
const error = await res.json().catch(() => ({ error: res.statusText }));
|