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