@attest-it/cli 0.7.0 → 0.9.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 +0 -10
- package/dist/bin/attest-it.js +272 -1037
- package/dist/bin/attest-it.js.map +1 -1
- package/dist/index.cjs +271 -1035
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +272 -1037
- package/dist/index.js.map +1 -1
- package/dist/templates/config.yaml +55 -0
- package/package.json +2 -3
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
var commander = require('commander');
|
|
4
4
|
var fs = require('fs');
|
|
5
5
|
var path = require('path');
|
|
6
|
+
var url = require('url');
|
|
6
7
|
var chromaterm = require('chromaterm');
|
|
7
8
|
var prompts = require('@inquirer/prompts');
|
|
8
9
|
var core = require('@attest-it/core');
|
|
@@ -14,9 +15,7 @@ var React7 = require('react');
|
|
|
14
15
|
var ink = require('ink');
|
|
15
16
|
var jsxRuntime = require('react/jsx-runtime');
|
|
16
17
|
var promises = require('fs/promises');
|
|
17
|
-
var ui = require('@inkjs/ui');
|
|
18
18
|
var yaml = require('yaml');
|
|
19
|
-
var url = require('url');
|
|
20
19
|
|
|
21
20
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
22
21
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -278,47 +277,93 @@ async function offerCompletionInstall() {
|
|
|
278
277
|
return false;
|
|
279
278
|
}
|
|
280
279
|
}
|
|
280
|
+
function hasVersion(data) {
|
|
281
|
+
return typeof data === "object" && data !== null && "version" in data && // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
282
|
+
typeof data.version === "string";
|
|
283
|
+
}
|
|
284
|
+
var cachedVersion;
|
|
285
|
+
function getPackageVersion() {
|
|
286
|
+
if (cachedVersion !== void 0) {
|
|
287
|
+
return cachedVersion;
|
|
288
|
+
}
|
|
289
|
+
const __filename = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
290
|
+
const __dirname = path.dirname(__filename);
|
|
291
|
+
const possiblePaths = [path.join(__dirname, "../package.json"), path.join(__dirname, "../../package.json")];
|
|
292
|
+
for (const packageJsonPath of possiblePaths) {
|
|
293
|
+
try {
|
|
294
|
+
const content = fs.readFileSync(packageJsonPath, "utf-8");
|
|
295
|
+
const packageJsonData = JSON.parse(content);
|
|
296
|
+
if (!hasVersion(packageJsonData)) {
|
|
297
|
+
throw new Error(`Invalid package.json at ${packageJsonPath}: missing version field`);
|
|
298
|
+
}
|
|
299
|
+
cachedVersion = packageJsonData.version;
|
|
300
|
+
return cachedVersion;
|
|
301
|
+
} catch (error2) {
|
|
302
|
+
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") {
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
throw error2;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
throw new Error("Could not find package.json");
|
|
309
|
+
}
|
|
281
310
|
|
|
282
311
|
// src/commands/init.ts
|
|
283
312
|
var initCommand = new commander.Command("init").description("Initialize attest-it configuration").option("-p, --path <path>", "Config file path", ".attest-it/config.yaml").option("-f, --force", "Overwrite existing config").action(async (options) => {
|
|
284
313
|
await runInit(options);
|
|
285
314
|
});
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
315
|
+
function loadConfigTemplate() {
|
|
316
|
+
const __filename = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
317
|
+
const __dirname = path.dirname(__filename);
|
|
318
|
+
const possiblePaths = [
|
|
319
|
+
path.join(__dirname, "../../templates/config.yaml"),
|
|
320
|
+
path.join(__dirname, "../templates/config.yaml")
|
|
321
|
+
];
|
|
322
|
+
for (const templatePath of possiblePaths) {
|
|
323
|
+
try {
|
|
324
|
+
return fs__namespace.readFileSync(templatePath, "utf-8");
|
|
325
|
+
} catch (error2) {
|
|
326
|
+
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") {
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
throw error2;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
throw new Error("Could not find config.yaml template");
|
|
333
|
+
}
|
|
334
|
+
function isPackageJson(data) {
|
|
335
|
+
return typeof data === "object" && data !== null && "name" in data && "version" in data && // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
336
|
+
typeof data.name === "string" && // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
337
|
+
typeof data.version === "string";
|
|
338
|
+
}
|
|
339
|
+
function detectPackageManager() {
|
|
340
|
+
if (fs__namespace.existsSync("pnpm-lock.yaml")) return "pnpm";
|
|
341
|
+
if (fs__namespace.existsSync("yarn.lock")) return "yarn";
|
|
342
|
+
if (fs__namespace.existsSync("bun.lockb")) return "bun";
|
|
343
|
+
return "npm";
|
|
344
|
+
}
|
|
345
|
+
async function ensureDevDependency() {
|
|
346
|
+
const packageJsonPath = "package.json";
|
|
347
|
+
const packageManager = detectPackageManager();
|
|
348
|
+
let created = false;
|
|
349
|
+
let packageJson;
|
|
350
|
+
if (fs__namespace.existsSync(packageJsonPath)) {
|
|
351
|
+
const content = await fs__namespace.promises.readFile(packageJsonPath, "utf8");
|
|
352
|
+
const parsed = JSON.parse(content);
|
|
353
|
+
if (!isPackageJson(parsed)) {
|
|
354
|
+
throw new Error("Invalid package.json: missing required name or version field");
|
|
355
|
+
}
|
|
356
|
+
packageJson = parsed;
|
|
357
|
+
} else {
|
|
358
|
+
packageJson = { name: path__namespace.basename(process.cwd()), version: "1.0.0" };
|
|
359
|
+
created = true;
|
|
360
|
+
}
|
|
361
|
+
const devDeps = packageJson.devDependencies ?? {};
|
|
362
|
+
devDeps["attest-it"] = "^" + getPackageVersion();
|
|
363
|
+
packageJson.devDependencies = devDeps;
|
|
364
|
+
await fs__namespace.promises.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n");
|
|
365
|
+
return { packageManager, created };
|
|
366
|
+
}
|
|
322
367
|
async function runInit(options) {
|
|
323
368
|
try {
|
|
324
369
|
const configPath = path__namespace.resolve(options.path);
|
|
@@ -333,14 +378,22 @@ async function runInit(options) {
|
|
|
333
378
|
process.exit(ExitCode.CANCELLED);
|
|
334
379
|
}
|
|
335
380
|
}
|
|
381
|
+
const { packageManager, created } = await ensureDevDependency();
|
|
382
|
+
if (created) {
|
|
383
|
+
success("Created package.json");
|
|
384
|
+
} else {
|
|
385
|
+
success("Updated package.json with attest-it devDependency");
|
|
386
|
+
}
|
|
336
387
|
await fs__namespace.promises.mkdir(configDir, { recursive: true });
|
|
337
|
-
|
|
388
|
+
const configTemplate = loadConfigTemplate();
|
|
389
|
+
await fs__namespace.promises.writeFile(configPath, configTemplate, "utf-8");
|
|
338
390
|
success(`Configuration created at ${configPath}`);
|
|
339
391
|
log("");
|
|
340
392
|
log("Next steps:");
|
|
341
|
-
log(` 1.
|
|
342
|
-
log(" 2. Run: attest-it
|
|
343
|
-
log(" 3. Run: attest-it
|
|
393
|
+
log(` 1. Run: ${packageManager} install`);
|
|
394
|
+
log(" 2. Run: attest-it identity create (if you haven't already)");
|
|
395
|
+
log(" 3. Run: attest-it team join");
|
|
396
|
+
log(" 4. Edit .attest-it/config.yaml to define your gates and suites");
|
|
344
397
|
await offerCompletionInstall();
|
|
345
398
|
} catch (err) {
|
|
346
399
|
if (err instanceof Error) {
|
|
@@ -363,7 +416,7 @@ async function runStatus(gates, options) {
|
|
|
363
416
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
364
417
|
}
|
|
365
418
|
const projectRoot = process.cwd();
|
|
366
|
-
const sealsFile = core.readSealsSync(projectRoot);
|
|
419
|
+
const sealsFile = core.readSealsSync(projectRoot, attestItConfig.settings.sealsPath);
|
|
367
420
|
const gatesToCheck = gates.length > 0 ? gates : Object.keys(attestItConfig.gates);
|
|
368
421
|
for (const gateId of gatesToCheck) {
|
|
369
422
|
if (!attestItConfig.gates[gateId]) {
|
|
@@ -572,14 +625,14 @@ function SelectionPrompt({
|
|
|
572
625
|
onSelect,
|
|
573
626
|
groups
|
|
574
627
|
}) {
|
|
575
|
-
ink.useInput((
|
|
576
|
-
const matchedOption = options.find((opt) => opt.hint ===
|
|
628
|
+
ink.useInput((input4) => {
|
|
629
|
+
const matchedOption = options.find((opt) => opt.hint === input4);
|
|
577
630
|
if (matchedOption) {
|
|
578
631
|
onSelect(matchedOption.value);
|
|
579
632
|
return;
|
|
580
633
|
}
|
|
581
634
|
if (groups) {
|
|
582
|
-
const matchedGroup = groups.find((group) => group.name ===
|
|
635
|
+
const matchedGroup = groups.find((group) => group.name === input4);
|
|
583
636
|
if (matchedGroup) {
|
|
584
637
|
onSelect(matchedGroup.name);
|
|
585
638
|
}
|
|
@@ -629,17 +682,17 @@ function SuiteSelector({
|
|
|
629
682
|
return next;
|
|
630
683
|
});
|
|
631
684
|
}, []);
|
|
632
|
-
ink.useInput((
|
|
633
|
-
if (
|
|
685
|
+
ink.useInput((input4, key) => {
|
|
686
|
+
if (input4 === "a") {
|
|
634
687
|
setSelectedSuites(new Set(pendingSuites.map((s) => s.name)));
|
|
635
688
|
return;
|
|
636
689
|
}
|
|
637
|
-
if (
|
|
690
|
+
if (input4 === "n") {
|
|
638
691
|
onExit();
|
|
639
692
|
return;
|
|
640
693
|
}
|
|
641
|
-
if (/^[1-9]$/.test(
|
|
642
|
-
const idx = parseInt(
|
|
694
|
+
if (/^[1-9]$/.test(input4)) {
|
|
695
|
+
const idx = parseInt(input4, 10) - 1;
|
|
643
696
|
if (idx < pendingSuites.length) {
|
|
644
697
|
const suite = pendingSuites[idx];
|
|
645
698
|
if (suite) {
|
|
@@ -648,8 +701,8 @@ function SuiteSelector({
|
|
|
648
701
|
}
|
|
649
702
|
return;
|
|
650
703
|
}
|
|
651
|
-
if (
|
|
652
|
-
const groupIdx = parseInt(
|
|
704
|
+
if (input4.startsWith("g") && groups) {
|
|
705
|
+
const groupIdx = parseInt(input4.slice(1), 10) - 1;
|
|
653
706
|
const groupNames = Object.keys(groups);
|
|
654
707
|
if (groupIdx >= 0 && groupIdx < groupNames.length) {
|
|
655
708
|
const groupName = groupNames[groupIdx];
|
|
@@ -666,7 +719,7 @@ function SuiteSelector({
|
|
|
666
719
|
onSelect(Array.from(selectedSuites));
|
|
667
720
|
return;
|
|
668
721
|
}
|
|
669
|
-
if (
|
|
722
|
+
if (input4 === " ") {
|
|
670
723
|
const currentSuite = pendingSuites[cursorIndex];
|
|
671
724
|
if (currentSuite) {
|
|
672
725
|
toggleSuite(currentSuite.name);
|
|
@@ -799,11 +852,11 @@ function TestRunner({
|
|
|
799
852
|
};
|
|
800
853
|
}, [currentIndex, phase, suites, executeTest, onComplete]);
|
|
801
854
|
ink.useInput(
|
|
802
|
-
(
|
|
855
|
+
(input4, key) => {
|
|
803
856
|
if (phase !== "confirming") return;
|
|
804
857
|
const currentSuite2 = suites[currentIndex];
|
|
805
858
|
if (!currentSuite2) return;
|
|
806
|
-
if (
|
|
859
|
+
if (input4.toLowerCase() === "y" || key.return) {
|
|
807
860
|
createAttestation3(currentSuite2).then(() => {
|
|
808
861
|
setResults((prev) => ({
|
|
809
862
|
...prev,
|
|
@@ -820,7 +873,7 @@ function TestRunner({
|
|
|
820
873
|
setPhase("running");
|
|
821
874
|
});
|
|
822
875
|
}
|
|
823
|
-
if (
|
|
876
|
+
if (input4.toLowerCase() === "n") {
|
|
824
877
|
setResults((prev) => ({
|
|
825
878
|
...prev,
|
|
826
879
|
skipped: [...prev.skipped, currentSuite2]
|
|
@@ -1015,12 +1068,24 @@ async function getAllSuiteStatuses(config) {
|
|
|
1015
1068
|
const attestations = attestationsFile?.attestations ?? [];
|
|
1016
1069
|
const results = [];
|
|
1017
1070
|
for (const [suiteName, suiteConfig] of Object.entries(config.suites)) {
|
|
1018
|
-
|
|
1071
|
+
let packages;
|
|
1072
|
+
let ignore;
|
|
1073
|
+
if (suiteConfig.gate && config.gates) {
|
|
1074
|
+
const gateConfig = config.gates[suiteConfig.gate];
|
|
1075
|
+
if (gateConfig) {
|
|
1076
|
+
packages = gateConfig.fingerprint.paths;
|
|
1077
|
+
ignore = gateConfig.fingerprint.exclude;
|
|
1078
|
+
}
|
|
1079
|
+
} else if (suiteConfig.packages) {
|
|
1080
|
+
packages = suiteConfig.packages;
|
|
1081
|
+
ignore = suiteConfig.ignore;
|
|
1082
|
+
}
|
|
1083
|
+
if (!packages || packages.length === 0) {
|
|
1019
1084
|
continue;
|
|
1020
1085
|
}
|
|
1021
1086
|
const fingerprintResult = await core.computeFingerprint({
|
|
1022
|
-
packages
|
|
1023
|
-
...
|
|
1087
|
+
packages,
|
|
1088
|
+
...ignore && { ignore }
|
|
1024
1089
|
});
|
|
1025
1090
|
const attestation = core.findAttestation(
|
|
1026
1091
|
{ schemaVersion: "1", attestations, signature: "" },
|
|
@@ -1521,9 +1586,9 @@ async function runSingleSuite(suiteName, config, options) {
|
|
|
1521
1586
|
if (!await keyProvider.keyExists(keyRef)) {
|
|
1522
1587
|
error(`Private key not found in ${keyProvider.displayName}`);
|
|
1523
1588
|
if (keyProvider.type === "filesystem") {
|
|
1524
|
-
error('Run "attest-it
|
|
1589
|
+
error('Run "attest-it identity create" first to generate a keypair.');
|
|
1525
1590
|
} else {
|
|
1526
|
-
error('Run "attest-it
|
|
1591
|
+
error('Run "attest-it identity create" to generate and store a key.');
|
|
1527
1592
|
}
|
|
1528
1593
|
process.exit(ExitCode.MISSING_KEY);
|
|
1529
1594
|
}
|
|
@@ -1547,7 +1612,7 @@ async function promptForSeal(suiteName, gateId, config) {
|
|
|
1547
1612
|
const localConfig = core.loadLocalConfigSync();
|
|
1548
1613
|
if (!localConfig) {
|
|
1549
1614
|
warn("No local identity configuration found - cannot create seal");
|
|
1550
|
-
warn('Run "attest-it
|
|
1615
|
+
warn('Run "attest-it identity create" to set up your identity');
|
|
1551
1616
|
return;
|
|
1552
1617
|
}
|
|
1553
1618
|
const identity = core.getActiveIdentity(localConfig);
|
|
@@ -1582,21 +1647,22 @@ async function promptForSeal(suiteName, gateId, config) {
|
|
|
1582
1647
|
const keyProvider = createKeyProviderFromIdentity(identity);
|
|
1583
1648
|
const keyRef = getKeyRefFromIdentity(identity);
|
|
1584
1649
|
const keyResult = await keyProvider.getPrivateKey(keyRef);
|
|
1585
|
-
const
|
|
1586
|
-
const privateKeyPem = await
|
|
1650
|
+
const fs3 = await import('fs/promises');
|
|
1651
|
+
const privateKeyPem = await fs3.readFile(keyResult.keyPath, "utf8");
|
|
1587
1652
|
await keyResult.cleanup();
|
|
1653
|
+
const identitySlug = localConfig.activeIdentity;
|
|
1588
1654
|
const seal = core.createSeal({
|
|
1589
1655
|
gateId,
|
|
1590
1656
|
fingerprint: gateFingerprint.fingerprint,
|
|
1591
|
-
sealedBy:
|
|
1657
|
+
sealedBy: identitySlug,
|
|
1592
1658
|
privateKey: privateKeyPem
|
|
1593
1659
|
});
|
|
1594
1660
|
const projectRoot = process.cwd();
|
|
1595
|
-
const sealsFile = core.readSealsSync(projectRoot);
|
|
1661
|
+
const sealsFile = core.readSealsSync(projectRoot, attestItConfig.settings.sealsPath);
|
|
1596
1662
|
sealsFile.seals[gateId] = seal;
|
|
1597
|
-
core.writeSealsSync(projectRoot, sealsFile);
|
|
1663
|
+
core.writeSealsSync(projectRoot, sealsFile, attestItConfig.settings.sealsPath);
|
|
1598
1664
|
success(`Seal created for gate '${gateId}'`);
|
|
1599
|
-
log(` Sealed by: ${identity.name}`);
|
|
1665
|
+
log(` Sealed by: ${identitySlug} (${identity.name})`);
|
|
1600
1666
|
log(` Timestamp: ${seal.timestamp}`);
|
|
1601
1667
|
} catch (err) {
|
|
1602
1668
|
if (err instanceof Error) {
|
|
@@ -1664,626 +1730,6 @@ function getKeyRefFromIdentity(identity) {
|
|
|
1664
1730
|
}
|
|
1665
1731
|
}
|
|
1666
1732
|
}
|
|
1667
|
-
function KeygenInteractive(props) {
|
|
1668
|
-
const { onComplete, onCancel, onError } = props;
|
|
1669
|
-
const [step, setStep] = React7.useState("checking-providers");
|
|
1670
|
-
const [opAvailable, setOpAvailable] = React7.useState(false);
|
|
1671
|
-
const [keychainAvailable, setKeychainAvailable] = React7.useState(false);
|
|
1672
|
-
const [yubiKeyAvailable, setYubiKeyAvailable] = React7.useState(false);
|
|
1673
|
-
const [accounts, setAccounts] = React7.useState([]);
|
|
1674
|
-
const [vaults, setVaults] = React7.useState([]);
|
|
1675
|
-
const [yubiKeyDevices, setYubiKeyDevices] = React7.useState([]);
|
|
1676
|
-
const [_selectedProvider, setSelectedProvider] = React7.useState();
|
|
1677
|
-
const [selectedAccount, setSelectedAccount] = React7.useState();
|
|
1678
|
-
const [selectedVault, setSelectedVault] = React7.useState();
|
|
1679
|
-
const [itemName, setItemName] = React7.useState("attest-it-private-key");
|
|
1680
|
-
const [keychainItemName, setKeychainItemName] = React7.useState("attest-it-private-key");
|
|
1681
|
-
const [selectedYubiKeySerial, setSelectedYubiKeySerial] = React7.useState();
|
|
1682
|
-
const [selectedYubiKeySlot, setSelectedYubiKeySlot] = React7.useState(2);
|
|
1683
|
-
const [slot1Configured, setSlot1Configured] = React7.useState(false);
|
|
1684
|
-
const [slot2Configured, setSlot2Configured] = React7.useState(false);
|
|
1685
|
-
ink.useInput((_input, key) => {
|
|
1686
|
-
if (key.escape) {
|
|
1687
|
-
onCancel();
|
|
1688
|
-
}
|
|
1689
|
-
});
|
|
1690
|
-
React7.useEffect(() => {
|
|
1691
|
-
const checkProviders = async () => {
|
|
1692
|
-
try {
|
|
1693
|
-
const isInstalled = await core.OnePasswordKeyProvider.isInstalled();
|
|
1694
|
-
setOpAvailable(isInstalled);
|
|
1695
|
-
if (isInstalled) {
|
|
1696
|
-
const accountList = await core.OnePasswordKeyProvider.listAccounts();
|
|
1697
|
-
setAccounts(accountList);
|
|
1698
|
-
}
|
|
1699
|
-
} catch {
|
|
1700
|
-
setOpAvailable(false);
|
|
1701
|
-
}
|
|
1702
|
-
const isKeychainAvailable = core.MacOSKeychainKeyProvider.isAvailable();
|
|
1703
|
-
setKeychainAvailable(isKeychainAvailable);
|
|
1704
|
-
try {
|
|
1705
|
-
const isInstalled = await core.YubiKeyProvider.isInstalled();
|
|
1706
|
-
if (isInstalled) {
|
|
1707
|
-
const isConnected = await core.YubiKeyProvider.isConnected();
|
|
1708
|
-
if (isConnected) {
|
|
1709
|
-
const devices = await core.YubiKeyProvider.listDevices();
|
|
1710
|
-
setYubiKeyDevices(devices);
|
|
1711
|
-
setYubiKeyAvailable(devices.length > 0);
|
|
1712
|
-
}
|
|
1713
|
-
}
|
|
1714
|
-
} catch {
|
|
1715
|
-
setYubiKeyAvailable(false);
|
|
1716
|
-
}
|
|
1717
|
-
setStep("select-provider");
|
|
1718
|
-
};
|
|
1719
|
-
void checkProviders();
|
|
1720
|
-
}, []);
|
|
1721
|
-
React7.useEffect(() => {
|
|
1722
|
-
if (step === "select-vault" && selectedAccount) {
|
|
1723
|
-
const fetchVaults = async () => {
|
|
1724
|
-
try {
|
|
1725
|
-
const vaultList = await core.OnePasswordKeyProvider.listVaults(selectedAccount);
|
|
1726
|
-
setVaults(vaultList);
|
|
1727
|
-
} catch (err) {
|
|
1728
|
-
onError(err instanceof Error ? err : new Error("Failed to fetch vaults"));
|
|
1729
|
-
}
|
|
1730
|
-
};
|
|
1731
|
-
void fetchVaults();
|
|
1732
|
-
}
|
|
1733
|
-
}, [step, selectedAccount, onError]);
|
|
1734
|
-
const checkYubiKeySlots = async (serial) => {
|
|
1735
|
-
try {
|
|
1736
|
-
const slot1 = await core.YubiKeyProvider.isChallengeResponseConfigured(1, serial);
|
|
1737
|
-
const slot2 = await core.YubiKeyProvider.isChallengeResponseConfigured(2, serial);
|
|
1738
|
-
setSlot1Configured(slot1);
|
|
1739
|
-
setSlot2Configured(slot2);
|
|
1740
|
-
if (slot1 && slot2) {
|
|
1741
|
-
setStep("select-yubikey-slot");
|
|
1742
|
-
} else if (slot2) {
|
|
1743
|
-
setSelectedYubiKeySlot(2);
|
|
1744
|
-
void generateKeys("yubikey");
|
|
1745
|
-
} else if (slot1) {
|
|
1746
|
-
setSelectedYubiKeySlot(1);
|
|
1747
|
-
void generateKeys("yubikey");
|
|
1748
|
-
} else {
|
|
1749
|
-
setStep("yubikey-offer-setup");
|
|
1750
|
-
}
|
|
1751
|
-
} catch (err) {
|
|
1752
|
-
onError(err instanceof Error ? err : new Error("Failed to check YubiKey slots"));
|
|
1753
|
-
}
|
|
1754
|
-
};
|
|
1755
|
-
const handleProviderSelect = (value) => {
|
|
1756
|
-
if (value === "filesystem") {
|
|
1757
|
-
setSelectedProvider("filesystem");
|
|
1758
|
-
void generateKeys("filesystem");
|
|
1759
|
-
} else if (value === "1password") {
|
|
1760
|
-
setSelectedProvider("1password");
|
|
1761
|
-
if (accounts.length === 1 && accounts[0]) {
|
|
1762
|
-
setSelectedAccount(accounts[0].user_uuid);
|
|
1763
|
-
setStep("select-vault");
|
|
1764
|
-
} else {
|
|
1765
|
-
setStep("select-account");
|
|
1766
|
-
}
|
|
1767
|
-
} else if (value === "macos-keychain") {
|
|
1768
|
-
setSelectedProvider("macos-keychain");
|
|
1769
|
-
setStep("enter-keychain-item-name");
|
|
1770
|
-
} else if (value === "yubikey") {
|
|
1771
|
-
setSelectedProvider("yubikey");
|
|
1772
|
-
if (yubiKeyDevices.length > 1) {
|
|
1773
|
-
setStep("select-yubikey-device");
|
|
1774
|
-
} else if (yubiKeyDevices.length === 1 && yubiKeyDevices[0]) {
|
|
1775
|
-
setSelectedYubiKeySerial(yubiKeyDevices[0].serial);
|
|
1776
|
-
void checkYubiKeySlots(yubiKeyDevices[0].serial);
|
|
1777
|
-
}
|
|
1778
|
-
}
|
|
1779
|
-
};
|
|
1780
|
-
const handleAccountSelect = (value) => {
|
|
1781
|
-
setSelectedAccount(value);
|
|
1782
|
-
setStep("select-vault");
|
|
1783
|
-
};
|
|
1784
|
-
const handleVaultSelect = (value) => {
|
|
1785
|
-
setSelectedVault(value);
|
|
1786
|
-
setStep("enter-item-name");
|
|
1787
|
-
};
|
|
1788
|
-
const handleItemNameSubmit = (value) => {
|
|
1789
|
-
setItemName(value);
|
|
1790
|
-
void generateKeys("1password");
|
|
1791
|
-
};
|
|
1792
|
-
const handleKeychainItemNameSubmit = (value) => {
|
|
1793
|
-
setKeychainItemName(value);
|
|
1794
|
-
void generateKeys("macos-keychain");
|
|
1795
|
-
};
|
|
1796
|
-
const handleYubiKeyDeviceSelect = (value) => {
|
|
1797
|
-
setSelectedYubiKeySerial(value);
|
|
1798
|
-
void checkYubiKeySlots(value);
|
|
1799
|
-
};
|
|
1800
|
-
const handleYubiKeySlotSelect = (value) => {
|
|
1801
|
-
const slot = value === "1" ? 1 : 2;
|
|
1802
|
-
setSelectedYubiKeySlot(slot);
|
|
1803
|
-
void generateKeys("yubikey");
|
|
1804
|
-
};
|
|
1805
|
-
const handleYubiKeySetupConfirm = (value) => {
|
|
1806
|
-
if (value === "yes") {
|
|
1807
|
-
void setupYubiKeySlot();
|
|
1808
|
-
} else {
|
|
1809
|
-
onError(new Error("YubiKey setup cancelled"));
|
|
1810
|
-
}
|
|
1811
|
-
};
|
|
1812
|
-
const setupYubiKeySlot = async () => {
|
|
1813
|
-
setStep("yubikey-configuring");
|
|
1814
|
-
try {
|
|
1815
|
-
const { spawn: spawn3 } = await import('child_process');
|
|
1816
|
-
const args = ["otp", "chalresp", "--touch", "--generate", "2"];
|
|
1817
|
-
if (selectedYubiKeySerial) {
|
|
1818
|
-
args.unshift("--device", selectedYubiKeySerial);
|
|
1819
|
-
}
|
|
1820
|
-
await new Promise((resolve2, reject) => {
|
|
1821
|
-
const proc = spawn3("ykman", args, { stdio: "inherit" });
|
|
1822
|
-
proc.on("close", (code) => {
|
|
1823
|
-
if (code === 0) {
|
|
1824
|
-
resolve2();
|
|
1825
|
-
} else {
|
|
1826
|
-
reject(new Error(`ykman exited with code ${String(code)}`));
|
|
1827
|
-
}
|
|
1828
|
-
});
|
|
1829
|
-
proc.on("error", reject);
|
|
1830
|
-
});
|
|
1831
|
-
setSelectedYubiKeySlot(2);
|
|
1832
|
-
void generateKeys("yubikey");
|
|
1833
|
-
} catch (err) {
|
|
1834
|
-
onError(err instanceof Error ? err : new Error("Failed to configure YubiKey"));
|
|
1835
|
-
}
|
|
1836
|
-
};
|
|
1837
|
-
const generateKeys = async (provider) => {
|
|
1838
|
-
setStep("generating");
|
|
1839
|
-
try {
|
|
1840
|
-
const publicKeyPath = props.publicKeyPath ?? core.getDefaultPublicKeyPath();
|
|
1841
|
-
if (provider === "filesystem") {
|
|
1842
|
-
const fsProvider = new core.FilesystemKeyProvider();
|
|
1843
|
-
const genOptions = { publicKeyPath };
|
|
1844
|
-
if (props.force !== void 0) {
|
|
1845
|
-
genOptions.force = props.force;
|
|
1846
|
-
}
|
|
1847
|
-
const result = await fsProvider.generateKeyPair(genOptions);
|
|
1848
|
-
onComplete({
|
|
1849
|
-
provider: "filesystem",
|
|
1850
|
-
publicKeyPath: result.publicKeyPath,
|
|
1851
|
-
privateKeyRef: result.privateKeyRef,
|
|
1852
|
-
storageDescription: result.storageDescription
|
|
1853
|
-
});
|
|
1854
|
-
} else if (provider === "1password") {
|
|
1855
|
-
if (!selectedVault || !itemName) {
|
|
1856
|
-
throw new Error("Vault and item name are required for 1Password");
|
|
1857
|
-
}
|
|
1858
|
-
const vault = vaults.find((v) => v.id === selectedVault);
|
|
1859
|
-
if (!vault) {
|
|
1860
|
-
throw new Error("Selected vault not found");
|
|
1861
|
-
}
|
|
1862
|
-
const providerOptions = {
|
|
1863
|
-
vault: vault.name,
|
|
1864
|
-
itemName
|
|
1865
|
-
};
|
|
1866
|
-
if (selectedAccount !== void 0) {
|
|
1867
|
-
providerOptions.account = selectedAccount;
|
|
1868
|
-
}
|
|
1869
|
-
const opProvider = new core.OnePasswordKeyProvider(providerOptions);
|
|
1870
|
-
const genOptions = { publicKeyPath };
|
|
1871
|
-
if (props.force !== void 0) {
|
|
1872
|
-
genOptions.force = props.force;
|
|
1873
|
-
}
|
|
1874
|
-
const result = await opProvider.generateKeyPair(genOptions);
|
|
1875
|
-
const completionResult = {
|
|
1876
|
-
provider: "1password",
|
|
1877
|
-
publicKeyPath: result.publicKeyPath,
|
|
1878
|
-
privateKeyRef: result.privateKeyRef,
|
|
1879
|
-
storageDescription: result.storageDescription,
|
|
1880
|
-
vault: vault.name,
|
|
1881
|
-
itemName
|
|
1882
|
-
};
|
|
1883
|
-
if (selectedAccount !== void 0) {
|
|
1884
|
-
completionResult.account = selectedAccount;
|
|
1885
|
-
}
|
|
1886
|
-
onComplete(completionResult);
|
|
1887
|
-
} else if (provider === "macos-keychain") {
|
|
1888
|
-
if (!keychainItemName) {
|
|
1889
|
-
throw new Error("Item name is required for macOS Keychain");
|
|
1890
|
-
}
|
|
1891
|
-
const keychainProvider = new core.MacOSKeychainKeyProvider({
|
|
1892
|
-
itemName: keychainItemName
|
|
1893
|
-
});
|
|
1894
|
-
const genOptions = { publicKeyPath };
|
|
1895
|
-
if (props.force !== void 0) {
|
|
1896
|
-
genOptions.force = props.force;
|
|
1897
|
-
}
|
|
1898
|
-
const result = await keychainProvider.generateKeyPair(genOptions);
|
|
1899
|
-
onComplete({
|
|
1900
|
-
provider: "macos-keychain",
|
|
1901
|
-
publicKeyPath: result.publicKeyPath,
|
|
1902
|
-
privateKeyRef: result.privateKeyRef,
|
|
1903
|
-
storageDescription: result.storageDescription,
|
|
1904
|
-
itemName: keychainItemName
|
|
1905
|
-
});
|
|
1906
|
-
} else {
|
|
1907
|
-
const encryptedKeyPath = core.getDefaultYubiKeyEncryptedKeyPath();
|
|
1908
|
-
const providerOptions = {
|
|
1909
|
-
encryptedKeyPath,
|
|
1910
|
-
slot: selectedYubiKeySlot
|
|
1911
|
-
};
|
|
1912
|
-
if (selectedYubiKeySerial !== void 0) {
|
|
1913
|
-
providerOptions.serial = selectedYubiKeySerial;
|
|
1914
|
-
}
|
|
1915
|
-
const ykProvider = new core.YubiKeyProvider(providerOptions);
|
|
1916
|
-
const genOptions = { publicKeyPath };
|
|
1917
|
-
if (props.force !== void 0) {
|
|
1918
|
-
genOptions.force = props.force;
|
|
1919
|
-
}
|
|
1920
|
-
const result = await ykProvider.generateKeyPair(genOptions);
|
|
1921
|
-
const completionResult = {
|
|
1922
|
-
provider: "yubikey",
|
|
1923
|
-
publicKeyPath: result.publicKeyPath,
|
|
1924
|
-
privateKeyRef: result.privateKeyRef,
|
|
1925
|
-
storageDescription: result.storageDescription,
|
|
1926
|
-
slot: selectedYubiKeySlot,
|
|
1927
|
-
encryptedKeyPath
|
|
1928
|
-
};
|
|
1929
|
-
if (selectedYubiKeySerial !== void 0) {
|
|
1930
|
-
completionResult.serial = selectedYubiKeySerial;
|
|
1931
|
-
}
|
|
1932
|
-
onComplete(completionResult);
|
|
1933
|
-
}
|
|
1934
|
-
setStep("done");
|
|
1935
|
-
} catch (err) {
|
|
1936
|
-
onError(err instanceof Error ? err : new Error("Key generation failed"));
|
|
1937
|
-
}
|
|
1938
|
-
};
|
|
1939
|
-
if (step === "checking-providers") {
|
|
1940
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { flexDirection: "column", children: /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", gap: 1, children: [
|
|
1941
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {}),
|
|
1942
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: "Checking available key storage providers..." })
|
|
1943
|
-
] }) });
|
|
1944
|
-
}
|
|
1945
|
-
if (step === "select-provider") {
|
|
1946
|
-
const options = [
|
|
1947
|
-
{
|
|
1948
|
-
label: `Local Filesystem (${core.getDefaultPrivateKeyPath()})`,
|
|
1949
|
-
value: "filesystem"
|
|
1950
|
-
}
|
|
1951
|
-
];
|
|
1952
|
-
if (keychainAvailable) {
|
|
1953
|
-
options.push({
|
|
1954
|
-
label: "macOS Keychain",
|
|
1955
|
-
value: "macos-keychain"
|
|
1956
|
-
});
|
|
1957
|
-
}
|
|
1958
|
-
if (yubiKeyAvailable) {
|
|
1959
|
-
options.push({
|
|
1960
|
-
label: "YubiKey (hardware security key)",
|
|
1961
|
-
value: "yubikey"
|
|
1962
|
-
});
|
|
1963
|
-
}
|
|
1964
|
-
if (opAvailable) {
|
|
1965
|
-
options.push({
|
|
1966
|
-
label: "1Password (requires op CLI)",
|
|
1967
|
-
value: "1password"
|
|
1968
|
-
});
|
|
1969
|
-
}
|
|
1970
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
1971
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Where would you like to store your private key?" }),
|
|
1972
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
|
|
1973
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Select, { options, onChange: handleProviderSelect })
|
|
1974
|
-
] });
|
|
1975
|
-
}
|
|
1976
|
-
if (step === "select-account") {
|
|
1977
|
-
const options = accounts.map((account) => ({
|
|
1978
|
-
label: account.name ? `${account.name} (${account.email})` : account.email,
|
|
1979
|
-
value: account.user_uuid
|
|
1980
|
-
}));
|
|
1981
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
1982
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Select 1Password account:" }),
|
|
1983
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
|
|
1984
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Select, { options, onChange: handleAccountSelect })
|
|
1985
|
-
] });
|
|
1986
|
-
}
|
|
1987
|
-
if (step === "select-vault") {
|
|
1988
|
-
if (vaults.length === 0) {
|
|
1989
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { flexDirection: "column", children: /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", gap: 1, children: [
|
|
1990
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {}),
|
|
1991
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: "Loading vaults..." })
|
|
1992
|
-
] }) });
|
|
1993
|
-
}
|
|
1994
|
-
const options = vaults.map((vault) => ({
|
|
1995
|
-
label: vault.name,
|
|
1996
|
-
value: vault.id
|
|
1997
|
-
}));
|
|
1998
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
1999
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Select vault for private key storage:" }),
|
|
2000
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
|
|
2001
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Select, { options, onChange: handleVaultSelect })
|
|
2002
|
-
] });
|
|
2003
|
-
}
|
|
2004
|
-
if (step === "enter-item-name") {
|
|
2005
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
2006
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Enter name for the key item:" }),
|
|
2007
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "(This will be visible in your 1Password vault)" }),
|
|
2008
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
|
|
2009
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.TextInput, { defaultValue: itemName, onSubmit: handleItemNameSubmit })
|
|
2010
|
-
] });
|
|
2011
|
-
}
|
|
2012
|
-
if (step === "enter-keychain-item-name") {
|
|
2013
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
2014
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Enter name for the keychain item:" }),
|
|
2015
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "(This will be the service name in your macOS Keychain)" }),
|
|
2016
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
|
|
2017
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.TextInput, { defaultValue: keychainItemName, onSubmit: handleKeychainItemNameSubmit })
|
|
2018
|
-
] });
|
|
2019
|
-
}
|
|
2020
|
-
if (step === "select-yubikey-device") {
|
|
2021
|
-
const options = yubiKeyDevices.map((device) => ({
|
|
2022
|
-
label: `${device.type} (Serial: ${device.serial})`,
|
|
2023
|
-
value: device.serial
|
|
2024
|
-
}));
|
|
2025
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
2026
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Select YubiKey device:" }),
|
|
2027
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
|
|
2028
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Select, { options, onChange: handleYubiKeyDeviceSelect })
|
|
2029
|
-
] });
|
|
2030
|
-
}
|
|
2031
|
-
if (step === "select-yubikey-slot") {
|
|
2032
|
-
const options = [];
|
|
2033
|
-
if (slot2Configured) {
|
|
2034
|
-
options.push({
|
|
2035
|
-
label: "Slot 2 (recommended)",
|
|
2036
|
-
value: "2"
|
|
2037
|
-
});
|
|
2038
|
-
}
|
|
2039
|
-
if (slot1Configured) {
|
|
2040
|
-
options.push({
|
|
2041
|
-
label: "Slot 1",
|
|
2042
|
-
value: "1"
|
|
2043
|
-
});
|
|
2044
|
-
}
|
|
2045
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
2046
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Select YubiKey slot for challenge-response:" }),
|
|
2047
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
|
|
2048
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Select, { options, onChange: handleYubiKeySlotSelect })
|
|
2049
|
-
] });
|
|
2050
|
-
}
|
|
2051
|
-
if (step === "yubikey-offer-setup") {
|
|
2052
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
2053
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Your YubiKey is not configured for challenge-response." }),
|
|
2054
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
|
|
2055
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: "Would you like to configure slot 2 for challenge-response now?" }),
|
|
2056
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "This will enable touch-to-sign functionality." }),
|
|
2057
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
|
|
2058
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2059
|
-
ui.Select,
|
|
2060
|
-
{
|
|
2061
|
-
options: [
|
|
2062
|
-
{ label: "Yes, configure my YubiKey", value: "yes" },
|
|
2063
|
-
{ label: "No, cancel", value: "no" }
|
|
2064
|
-
],
|
|
2065
|
-
onChange: handleYubiKeySetupConfirm
|
|
2066
|
-
}
|
|
2067
|
-
)
|
|
2068
|
-
] });
|
|
2069
|
-
}
|
|
2070
|
-
if (step === "yubikey-configuring") {
|
|
2071
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
2072
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", gap: 1, children: [
|
|
2073
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {}),
|
|
2074
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: "Configuring YubiKey slot 2 for challenge-response..." })
|
|
2075
|
-
] }),
|
|
2076
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Touch your YubiKey when it flashes." })
|
|
2077
|
-
] });
|
|
2078
|
-
}
|
|
2079
|
-
if (step === "generating") {
|
|
2080
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { flexDirection: "column", children: /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", gap: 1, children: [
|
|
2081
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {}),
|
|
2082
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: "Generating RSA-2048 keypair..." })
|
|
2083
|
-
] }) });
|
|
2084
|
-
}
|
|
2085
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ink.Box, {});
|
|
2086
|
-
}
|
|
2087
|
-
async function runKeygenInteractive(options) {
|
|
2088
|
-
return new Promise((resolve2, reject) => {
|
|
2089
|
-
const props = {
|
|
2090
|
-
onComplete: (result) => {
|
|
2091
|
-
unmount();
|
|
2092
|
-
resolve2(result);
|
|
2093
|
-
},
|
|
2094
|
-
onCancel: () => {
|
|
2095
|
-
unmount();
|
|
2096
|
-
reject(new Error("Keygen cancelled"));
|
|
2097
|
-
},
|
|
2098
|
-
onError: (error2) => {
|
|
2099
|
-
unmount();
|
|
2100
|
-
reject(error2);
|
|
2101
|
-
}
|
|
2102
|
-
};
|
|
2103
|
-
if (options.publicKeyPath !== void 0) {
|
|
2104
|
-
props.publicKeyPath = options.publicKeyPath;
|
|
2105
|
-
}
|
|
2106
|
-
if (options.force !== void 0) {
|
|
2107
|
-
props.force = options.force;
|
|
2108
|
-
}
|
|
2109
|
-
const { unmount } = ink.render(/* @__PURE__ */ jsxRuntime.jsx(KeygenInteractive, { ...props }));
|
|
2110
|
-
});
|
|
2111
|
-
}
|
|
2112
|
-
|
|
2113
|
-
// src/commands/keygen.ts
|
|
2114
|
-
var keygenCommand = new commander.Command("keygen").description("Generate a new RSA keypair for signing attestations").option("-o, --output <path>", "Public key output path").option("-p, --private <path>", "Private key output path (filesystem only)").option(
|
|
2115
|
-
"--provider <type>",
|
|
2116
|
-
"Key provider: filesystem, 1password, or macos-keychain (skips interactive)"
|
|
2117
|
-
).option("--vault <name>", "1Password vault name").option("--item-name <name>", "1Password/macOS Keychain item name").option("--account <email>", "1Password account").option("-f, --force", "Overwrite existing keys").option("--no-interactive", "Disable interactive mode").action(async (options) => {
|
|
2118
|
-
await runKeygen(options);
|
|
2119
|
-
});
|
|
2120
|
-
async function runKeygen(options) {
|
|
2121
|
-
try {
|
|
2122
|
-
const useInteractive = options.interactive !== false && !options.provider;
|
|
2123
|
-
if (useInteractive) {
|
|
2124
|
-
const interactiveOptions = {};
|
|
2125
|
-
if (options.output !== void 0) {
|
|
2126
|
-
interactiveOptions.publicKeyPath = options.output;
|
|
2127
|
-
}
|
|
2128
|
-
if (options.force !== void 0) {
|
|
2129
|
-
interactiveOptions.force = options.force;
|
|
2130
|
-
}
|
|
2131
|
-
const result = await runKeygenInteractive(interactiveOptions);
|
|
2132
|
-
success("Keypair generated successfully!");
|
|
2133
|
-
log("");
|
|
2134
|
-
log("Private key stored in:");
|
|
2135
|
-
log(` ${result.storageDescription}`);
|
|
2136
|
-
log("");
|
|
2137
|
-
log("Public key (commit to repo):");
|
|
2138
|
-
log(` ${result.publicKeyPath}`);
|
|
2139
|
-
log("");
|
|
2140
|
-
if (result.provider === "1password") {
|
|
2141
|
-
log("Add to your .attest-it/config.yaml:");
|
|
2142
|
-
log("");
|
|
2143
|
-
log("settings:");
|
|
2144
|
-
log(` publicKeyPath: ${result.publicKeyPath}`);
|
|
2145
|
-
log(" keyProvider:");
|
|
2146
|
-
log(" type: 1password");
|
|
2147
|
-
log(" options:");
|
|
2148
|
-
if (result.account) {
|
|
2149
|
-
log(` account: ${result.account}`);
|
|
2150
|
-
}
|
|
2151
|
-
log(` vault: ${result.vault ?? ""}`);
|
|
2152
|
-
log(` itemName: ${result.itemName ?? ""}`);
|
|
2153
|
-
log("");
|
|
2154
|
-
} else if (result.provider === "macos-keychain") {
|
|
2155
|
-
log("Add to your .attest-it/config.yaml:");
|
|
2156
|
-
log("");
|
|
2157
|
-
log("settings:");
|
|
2158
|
-
log(` publicKeyPath: ${result.publicKeyPath}`);
|
|
2159
|
-
log(" keyProvider:");
|
|
2160
|
-
log(" type: macos-keychain");
|
|
2161
|
-
log(" options:");
|
|
2162
|
-
log(` itemName: ${result.itemName ?? ""}`);
|
|
2163
|
-
log("");
|
|
2164
|
-
}
|
|
2165
|
-
log("Next steps:");
|
|
2166
|
-
log(` 1. git add ${result.publicKeyPath}`);
|
|
2167
|
-
if (result.provider === "1password" || result.provider === "macos-keychain") {
|
|
2168
|
-
log(" 2. Update .attest-it/config.yaml with keyProvider settings");
|
|
2169
|
-
} else {
|
|
2170
|
-
log(" 2. Update .attest-it/config.yaml publicKeyPath if needed");
|
|
2171
|
-
}
|
|
2172
|
-
log(" 3. attest-it run --suite <suite-name>");
|
|
2173
|
-
} else {
|
|
2174
|
-
await runNonInteractiveKeygen(options);
|
|
2175
|
-
}
|
|
2176
|
-
} catch (err) {
|
|
2177
|
-
if (err instanceof Error) {
|
|
2178
|
-
error(err.message);
|
|
2179
|
-
} else {
|
|
2180
|
-
error("Unknown error occurred");
|
|
2181
|
-
}
|
|
2182
|
-
process.exit(ExitCode.CONFIG_ERROR);
|
|
2183
|
-
}
|
|
2184
|
-
}
|
|
2185
|
-
async function runNonInteractiveKeygen(options) {
|
|
2186
|
-
log("Checking OpenSSL...");
|
|
2187
|
-
const version = await core.checkOpenSSL();
|
|
2188
|
-
info(`OpenSSL: ${version}`);
|
|
2189
|
-
const publicPath = options.output ?? core.getDefaultPublicKeyPath();
|
|
2190
|
-
if (options.provider === "1password") {
|
|
2191
|
-
if (!options.vault || !options.itemName) {
|
|
2192
|
-
throw new Error("--vault and --item-name are required for 1password provider");
|
|
2193
|
-
}
|
|
2194
|
-
const providerOptions = {
|
|
2195
|
-
vault: options.vault,
|
|
2196
|
-
itemName: options.itemName
|
|
2197
|
-
};
|
|
2198
|
-
if (options.account !== void 0) {
|
|
2199
|
-
providerOptions.account = options.account;
|
|
2200
|
-
}
|
|
2201
|
-
const provider = new core.OnePasswordKeyProvider(providerOptions);
|
|
2202
|
-
log(`Generating keypair with 1Password storage...`);
|
|
2203
|
-
log(`Vault: ${options.vault}`);
|
|
2204
|
-
log(`Item: ${options.itemName}`);
|
|
2205
|
-
const genOptions = { publicKeyPath: publicPath };
|
|
2206
|
-
if (options.force !== void 0) {
|
|
2207
|
-
genOptions.force = options.force;
|
|
2208
|
-
}
|
|
2209
|
-
const result = await provider.generateKeyPair(genOptions);
|
|
2210
|
-
success("Keypair generated successfully!");
|
|
2211
|
-
log("");
|
|
2212
|
-
log("Private key stored in:");
|
|
2213
|
-
log(` ${result.storageDescription}`);
|
|
2214
|
-
log("");
|
|
2215
|
-
log("Public key (commit to repo):");
|
|
2216
|
-
log(` ${result.publicKeyPath}`);
|
|
2217
|
-
} else if (options.provider === "macos-keychain") {
|
|
2218
|
-
if (!options.itemName) {
|
|
2219
|
-
throw new Error("--item-name is required for macos-keychain provider");
|
|
2220
|
-
}
|
|
2221
|
-
const isAvailable = core.MacOSKeychainKeyProvider.isAvailable();
|
|
2222
|
-
if (!isAvailable) {
|
|
2223
|
-
throw new Error("macOS Keychain is not available on this platform");
|
|
2224
|
-
}
|
|
2225
|
-
const provider = new core.MacOSKeychainKeyProvider({
|
|
2226
|
-
itemName: options.itemName
|
|
2227
|
-
});
|
|
2228
|
-
log(`Generating keypair with macOS Keychain storage...`);
|
|
2229
|
-
log(`Item: ${options.itemName}`);
|
|
2230
|
-
const genOptions = { publicKeyPath: publicPath };
|
|
2231
|
-
if (options.force !== void 0) {
|
|
2232
|
-
genOptions.force = options.force;
|
|
2233
|
-
}
|
|
2234
|
-
const result = await provider.generateKeyPair(genOptions);
|
|
2235
|
-
success("Keypair generated successfully!");
|
|
2236
|
-
log("");
|
|
2237
|
-
log("Private key stored in:");
|
|
2238
|
-
log(` ${result.storageDescription}`);
|
|
2239
|
-
log("");
|
|
2240
|
-
log("Public key (commit to repo):");
|
|
2241
|
-
log(` ${result.publicKeyPath}`);
|
|
2242
|
-
} else {
|
|
2243
|
-
const privatePath = options.private ?? core.getDefaultPrivateKeyPath();
|
|
2244
|
-
log(`Private key: ${privatePath}`);
|
|
2245
|
-
log(`Public key: ${publicPath}`);
|
|
2246
|
-
const privateExists = fs__namespace.existsSync(privatePath);
|
|
2247
|
-
const publicExists = fs__namespace.existsSync(publicPath);
|
|
2248
|
-
if ((privateExists || publicExists) && !options.force) {
|
|
2249
|
-
if (privateExists) {
|
|
2250
|
-
warn(`Private key already exists: ${privatePath}`);
|
|
2251
|
-
}
|
|
2252
|
-
if (publicExists) {
|
|
2253
|
-
warn(`Public key already exists: ${publicPath}`);
|
|
2254
|
-
}
|
|
2255
|
-
const shouldOverwrite = await confirmAction({
|
|
2256
|
-
message: "Overwrite existing keys?",
|
|
2257
|
-
default: false
|
|
2258
|
-
});
|
|
2259
|
-
if (!shouldOverwrite) {
|
|
2260
|
-
error("Keygen cancelled");
|
|
2261
|
-
process.exit(ExitCode.CANCELLED);
|
|
2262
|
-
}
|
|
2263
|
-
}
|
|
2264
|
-
log("\nGenerating RSA-2048 keypair...");
|
|
2265
|
-
const result = await core.generateKeyPair({
|
|
2266
|
-
privatePath,
|
|
2267
|
-
publicPath,
|
|
2268
|
-
force: true
|
|
2269
|
-
});
|
|
2270
|
-
await core.setKeyPermissions(result.privatePath);
|
|
2271
|
-
success("Keypair generated successfully!");
|
|
2272
|
-
log("");
|
|
2273
|
-
log("Private key (KEEP SECRET):");
|
|
2274
|
-
log(` ${result.privatePath}`);
|
|
2275
|
-
log("");
|
|
2276
|
-
log("Public key (commit to repo):");
|
|
2277
|
-
log(` ${result.publicPath}`);
|
|
2278
|
-
}
|
|
2279
|
-
log("");
|
|
2280
|
-
info("Important: Back up your private key securely!");
|
|
2281
|
-
log("");
|
|
2282
|
-
log("Next steps:");
|
|
2283
|
-
log(` 1. git add ${publicPath}`);
|
|
2284
|
-
log(" 2. Update .attest-it/config.yaml publicKeyPath if needed");
|
|
2285
|
-
log(" 3. attest-it run --suite <suite-name>");
|
|
2286
|
-
}
|
|
2287
1733
|
var pruneCommand = new commander.Command("prune").description("Remove stale attestations").option("-n, --dry-run", "Show what would be removed without removing").option("-k, --keep-days <n>", "Keep attestations newer than n days", "30").action(async (options) => {
|
|
2288
1734
|
await runPrune(options);
|
|
2289
1735
|
});
|
|
@@ -2388,7 +1834,7 @@ async function runVerify(gates, options) {
|
|
|
2388
1834
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
2389
1835
|
}
|
|
2390
1836
|
const projectRoot = process.cwd();
|
|
2391
|
-
const sealsFile = core.readSealsSync(projectRoot);
|
|
1837
|
+
const sealsFile = core.readSealsSync(projectRoot, attestItConfig.settings.sealsPath);
|
|
2392
1838
|
const gatesToVerify = gates.length > 0 ? gates : Object.keys(attestItConfig.gates);
|
|
2393
1839
|
for (const gateId of gatesToVerify) {
|
|
2394
1840
|
if (!attestItConfig.gates[gateId]) {
|
|
@@ -2535,7 +1981,7 @@ async function runSeal(gates, options) {
|
|
|
2535
1981
|
const localConfig = core.loadLocalConfigSync();
|
|
2536
1982
|
if (!localConfig) {
|
|
2537
1983
|
error("No local identity configuration found");
|
|
2538
|
-
error('Run "attest-it
|
|
1984
|
+
error('Run "attest-it identity create" first to set up your identity');
|
|
2539
1985
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
2540
1986
|
}
|
|
2541
1987
|
const identity = core.getActiveIdentity(localConfig);
|
|
@@ -2544,7 +1990,7 @@ async function runSeal(gates, options) {
|
|
|
2544
1990
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
2545
1991
|
}
|
|
2546
1992
|
const projectRoot = process.cwd();
|
|
2547
|
-
const sealsFile = core.readSealsSync(projectRoot);
|
|
1993
|
+
const sealsFile = core.readSealsSync(projectRoot, attestItConfig.settings.sealsPath);
|
|
2548
1994
|
const gatesToSeal = gates.length > 0 ? gates : getAllGateIds(attestItConfig);
|
|
2549
1995
|
for (const gateId of gatesToSeal) {
|
|
2550
1996
|
if (!attestItConfig.gates[gateId]) {
|
|
@@ -2557,9 +2003,17 @@ async function runSeal(gates, options) {
|
|
|
2557
2003
|
skipped: [],
|
|
2558
2004
|
failed: []
|
|
2559
2005
|
};
|
|
2006
|
+
const identitySlug = localConfig.activeIdentity;
|
|
2560
2007
|
for (const gateId of gatesToSeal) {
|
|
2561
2008
|
try {
|
|
2562
|
-
const result = await processSingleGate(
|
|
2009
|
+
const result = await processSingleGate(
|
|
2010
|
+
gateId,
|
|
2011
|
+
attestItConfig,
|
|
2012
|
+
identity,
|
|
2013
|
+
identitySlug,
|
|
2014
|
+
sealsFile,
|
|
2015
|
+
options
|
|
2016
|
+
);
|
|
2563
2017
|
if (result.sealed) {
|
|
2564
2018
|
summary.sealed.push(gateId);
|
|
2565
2019
|
} else if (result.skipped) {
|
|
@@ -2571,7 +2025,7 @@ async function runSeal(gates, options) {
|
|
|
2571
2025
|
}
|
|
2572
2026
|
}
|
|
2573
2027
|
if (!options.dryRun && summary.sealed.length > 0) {
|
|
2574
|
-
core.writeSealsSync(projectRoot, sealsFile);
|
|
2028
|
+
core.writeSealsSync(projectRoot, sealsFile, attestItConfig.settings.sealsPath);
|
|
2575
2029
|
}
|
|
2576
2030
|
displaySummary(summary, options.dryRun);
|
|
2577
2031
|
if (summary.failed.length > 0) {
|
|
@@ -2590,7 +2044,7 @@ async function runSeal(gates, options) {
|
|
|
2590
2044
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
2591
2045
|
}
|
|
2592
2046
|
}
|
|
2593
|
-
async function processSingleGate(gateId, config, identity, sealsFile, options) {
|
|
2047
|
+
async function processSingleGate(gateId, config, identity, identitySlug, sealsFile, options) {
|
|
2594
2048
|
verbose(`Processing gate: ${gateId}`);
|
|
2595
2049
|
const gate = core.getGate(config, gateId);
|
|
2596
2050
|
if (!gate) {
|
|
@@ -2624,18 +2078,18 @@ async function processSingleGate(gateId, config, identity, sealsFile, options) {
|
|
|
2624
2078
|
const keyProvider = createKeyProviderFromIdentity2(identity);
|
|
2625
2079
|
const keyRef = getKeyRefFromIdentity2(identity);
|
|
2626
2080
|
const keyResult = await keyProvider.getPrivateKey(keyRef);
|
|
2627
|
-
const
|
|
2628
|
-
const privateKeyPem = await
|
|
2081
|
+
const fs3 = await import('fs/promises');
|
|
2082
|
+
const privateKeyPem = await fs3.readFile(keyResult.keyPath, "utf8");
|
|
2629
2083
|
await keyResult.cleanup();
|
|
2630
2084
|
const seal = core.createSeal({
|
|
2631
2085
|
gateId,
|
|
2632
2086
|
fingerprint: fingerprintResult.fingerprint,
|
|
2633
|
-
sealedBy:
|
|
2087
|
+
sealedBy: identitySlug,
|
|
2634
2088
|
privateKey: privateKeyPem
|
|
2635
2089
|
});
|
|
2636
2090
|
sealsFile.seals[gateId] = seal;
|
|
2637
2091
|
log(` Sealed gate: ${gateId}`);
|
|
2638
|
-
verbose(` Sealed by: ${identity.name}`);
|
|
2092
|
+
verbose(` Sealed by: ${identitySlug} (${identity.name})`);
|
|
2639
2093
|
verbose(` Timestamp: ${seal.timestamp}`);
|
|
2640
2094
|
return { sealed: true, skipped: false };
|
|
2641
2095
|
}
|
|
@@ -3177,9 +2631,9 @@ async function runCreate() {
|
|
|
3177
2631
|
log("");
|
|
3178
2632
|
log(theme3.blue.bold()("Public key saved to:"));
|
|
3179
2633
|
log(` ${publicKeyResult.homePath}`);
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
2634
|
+
log("");
|
|
2635
|
+
log("To add yourself to a project, run:");
|
|
2636
|
+
log(theme3.blue(" attest-it team join"));
|
|
3183
2637
|
log("");
|
|
3184
2638
|
if (!existingConfig) {
|
|
3185
2639
|
success(`Set as active identity`);
|
|
@@ -3300,155 +2754,6 @@ async function runShow(slug) {
|
|
|
3300
2754
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
3301
2755
|
}
|
|
3302
2756
|
}
|
|
3303
|
-
var editCommand = new commander.Command("edit").description("Edit identity or rotate keypair").argument("<slug>", "Identity slug to edit").action(async (slug) => {
|
|
3304
|
-
await runEdit(slug);
|
|
3305
|
-
});
|
|
3306
|
-
async function runEdit(slug) {
|
|
3307
|
-
try {
|
|
3308
|
-
const config = await core.loadLocalConfig();
|
|
3309
|
-
if (!config) {
|
|
3310
|
-
error("No identities configured");
|
|
3311
|
-
process.exit(ExitCode.CONFIG_ERROR);
|
|
3312
|
-
}
|
|
3313
|
-
const identity = config.identities[slug];
|
|
3314
|
-
if (!identity) {
|
|
3315
|
-
error(`Identity "${slug}" not found`);
|
|
3316
|
-
process.exit(ExitCode.CONFIG_ERROR);
|
|
3317
|
-
}
|
|
3318
|
-
const theme3 = getTheme2();
|
|
3319
|
-
log("");
|
|
3320
|
-
log(theme3.blue.bold()(`Edit Identity: ${slug}`));
|
|
3321
|
-
log("");
|
|
3322
|
-
const name = await prompts.input({
|
|
3323
|
-
message: "Display name:",
|
|
3324
|
-
default: identity.name,
|
|
3325
|
-
validate: (value) => {
|
|
3326
|
-
if (!value || value.trim().length === 0) {
|
|
3327
|
-
return "Name cannot be empty";
|
|
3328
|
-
}
|
|
3329
|
-
return true;
|
|
3330
|
-
}
|
|
3331
|
-
});
|
|
3332
|
-
const email = await prompts.input({
|
|
3333
|
-
message: "Email (optional):",
|
|
3334
|
-
default: identity.email ?? ""
|
|
3335
|
-
});
|
|
3336
|
-
const github = await prompts.input({
|
|
3337
|
-
message: "GitHub username (optional):",
|
|
3338
|
-
default: identity.github ?? ""
|
|
3339
|
-
});
|
|
3340
|
-
const rotateKey = await prompts.confirm({
|
|
3341
|
-
message: "Rotate keypair (generate new keys)?",
|
|
3342
|
-
default: false
|
|
3343
|
-
});
|
|
3344
|
-
let publicKey = identity.publicKey;
|
|
3345
|
-
const privateKeyRef = identity.privateKey;
|
|
3346
|
-
if (rotateKey) {
|
|
3347
|
-
log("");
|
|
3348
|
-
log("Generating new Ed25519 keypair...");
|
|
3349
|
-
const keyPair = core.generateEd25519KeyPair();
|
|
3350
|
-
publicKey = keyPair.publicKey;
|
|
3351
|
-
switch (identity.privateKey.type) {
|
|
3352
|
-
case "file": {
|
|
3353
|
-
await promises.writeFile(identity.privateKey.path, keyPair.privateKey, { mode: 384 });
|
|
3354
|
-
log(` Updated private key at: ${identity.privateKey.path}`);
|
|
3355
|
-
break;
|
|
3356
|
-
}
|
|
3357
|
-
case "keychain": {
|
|
3358
|
-
const { execFile } = await import('child_process');
|
|
3359
|
-
const { promisify } = await import('util');
|
|
3360
|
-
const execFileAsync = promisify(execFile);
|
|
3361
|
-
const encodedKey = Buffer.from(keyPair.privateKey).toString("base64");
|
|
3362
|
-
try {
|
|
3363
|
-
await execFileAsync("security", [
|
|
3364
|
-
"delete-generic-password",
|
|
3365
|
-
"-s",
|
|
3366
|
-
identity.privateKey.service,
|
|
3367
|
-
"-a",
|
|
3368
|
-
identity.privateKey.account
|
|
3369
|
-
]);
|
|
3370
|
-
await execFileAsync("security", [
|
|
3371
|
-
"add-generic-password",
|
|
3372
|
-
"-s",
|
|
3373
|
-
identity.privateKey.service,
|
|
3374
|
-
"-a",
|
|
3375
|
-
identity.privateKey.account,
|
|
3376
|
-
"-w",
|
|
3377
|
-
encodedKey,
|
|
3378
|
-
"-U"
|
|
3379
|
-
]);
|
|
3380
|
-
log(` Updated private key in macOS Keychain`);
|
|
3381
|
-
} catch (err) {
|
|
3382
|
-
throw new Error(
|
|
3383
|
-
`Failed to update key in macOS Keychain: ${err instanceof Error ? err.message : String(err)}`
|
|
3384
|
-
);
|
|
3385
|
-
}
|
|
3386
|
-
break;
|
|
3387
|
-
}
|
|
3388
|
-
case "1password": {
|
|
3389
|
-
const { execFile } = await import('child_process');
|
|
3390
|
-
const { promisify } = await import('util');
|
|
3391
|
-
const execFileAsync = promisify(execFile);
|
|
3392
|
-
try {
|
|
3393
|
-
const opArgs = [
|
|
3394
|
-
"item",
|
|
3395
|
-
"edit",
|
|
3396
|
-
identity.privateKey.item,
|
|
3397
|
-
"--vault",
|
|
3398
|
-
identity.privateKey.vault,
|
|
3399
|
-
`privateKey[password]=${keyPair.privateKey}`
|
|
3400
|
-
];
|
|
3401
|
-
if (identity.privateKey.account) {
|
|
3402
|
-
opArgs.push("--account", identity.privateKey.account);
|
|
3403
|
-
}
|
|
3404
|
-
await execFileAsync("op", opArgs);
|
|
3405
|
-
log(` Updated private key in 1Password`);
|
|
3406
|
-
} catch (err) {
|
|
3407
|
-
throw new Error(
|
|
3408
|
-
`Failed to update key in 1Password: ${err instanceof Error ? err.message : String(err)}`
|
|
3409
|
-
);
|
|
3410
|
-
}
|
|
3411
|
-
break;
|
|
3412
|
-
}
|
|
3413
|
-
}
|
|
3414
|
-
}
|
|
3415
|
-
const updatedIdentity = {
|
|
3416
|
-
name,
|
|
3417
|
-
publicKey,
|
|
3418
|
-
privateKey: privateKeyRef,
|
|
3419
|
-
...email && { email },
|
|
3420
|
-
...github && { github }
|
|
3421
|
-
};
|
|
3422
|
-
const newConfig = {
|
|
3423
|
-
...config,
|
|
3424
|
-
identities: {
|
|
3425
|
-
...config.identities,
|
|
3426
|
-
[slug]: updatedIdentity
|
|
3427
|
-
}
|
|
3428
|
-
};
|
|
3429
|
-
await core.saveLocalConfig(newConfig);
|
|
3430
|
-
log("");
|
|
3431
|
-
success("Identity updated successfully");
|
|
3432
|
-
log("");
|
|
3433
|
-
if (rotateKey) {
|
|
3434
|
-
log(" New Public Key: " + publicKey.slice(0, 32) + "...");
|
|
3435
|
-
log("");
|
|
3436
|
-
log(
|
|
3437
|
-
theme3.yellow(
|
|
3438
|
-
" Warning: If this identity is used in team configurations,\n you must update those configurations with the new public key."
|
|
3439
|
-
)
|
|
3440
|
-
);
|
|
3441
|
-
log("");
|
|
3442
|
-
}
|
|
3443
|
-
} catch (err) {
|
|
3444
|
-
if (err instanceof Error) {
|
|
3445
|
-
error(err.message);
|
|
3446
|
-
} else {
|
|
3447
|
-
error("Unknown error occurred");
|
|
3448
|
-
}
|
|
3449
|
-
process.exit(ExitCode.CONFIG_ERROR);
|
|
3450
|
-
}
|
|
3451
|
-
}
|
|
3452
2757
|
|
|
3453
2758
|
// src/utils/format-key-location.ts
|
|
3454
2759
|
function formatKeyLocation(privateKey) {
|
|
@@ -3667,7 +2972,7 @@ async function runExport(slug) {
|
|
|
3667
2972
|
}
|
|
3668
2973
|
|
|
3669
2974
|
// src/commands/identity/index.ts
|
|
3670
|
-
var identityCommand = new commander.Command("identity").description("Manage local identities and keypairs").addCommand(listCommand).addCommand(createCommand).addCommand(useCommand).addCommand(showCommand).addCommand(
|
|
2975
|
+
var identityCommand = new commander.Command("identity").description("Manage local identities and keypairs").addCommand(listCommand).addCommand(createCommand).addCommand(useCommand).addCommand(showCommand).addCommand(removeCommand).addCommand(exportCommand);
|
|
3671
2976
|
var whoamiCommand = new commander.Command("whoami").description("Show the current active identity").action(async () => {
|
|
3672
2977
|
await runWhoami();
|
|
3673
2978
|
});
|
|
@@ -3768,6 +3073,49 @@ async function runList2() {
|
|
|
3768
3073
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
3769
3074
|
}
|
|
3770
3075
|
}
|
|
3076
|
+
async function promptForGateAuthorization(gates) {
|
|
3077
|
+
if (!gates || Object.keys(gates).length === 0) {
|
|
3078
|
+
return [];
|
|
3079
|
+
}
|
|
3080
|
+
const gateChoices = Object.entries(gates).map(([gateId, gate]) => ({
|
|
3081
|
+
name: `${gateId} - ${gate.name}`,
|
|
3082
|
+
value: gateId
|
|
3083
|
+
}));
|
|
3084
|
+
const authorizedGates = await prompts.checkbox({
|
|
3085
|
+
message: "Select gates to authorize (use space to select):",
|
|
3086
|
+
choices: gateChoices
|
|
3087
|
+
});
|
|
3088
|
+
return authorizedGates;
|
|
3089
|
+
}
|
|
3090
|
+
function addTeamMemberToConfig(config, memberSlug, memberData, authorizedGates) {
|
|
3091
|
+
const existingTeam = config.team ?? {};
|
|
3092
|
+
const updatedConfig = {
|
|
3093
|
+
...config,
|
|
3094
|
+
team: {
|
|
3095
|
+
...existingTeam,
|
|
3096
|
+
[memberSlug]: {
|
|
3097
|
+
name: memberData.name,
|
|
3098
|
+
publicKey: memberData.publicKey,
|
|
3099
|
+
publicKeyAlgorithm: memberData.publicKeyAlgorithm ?? "ed25519",
|
|
3100
|
+
...memberData.email ? { email: memberData.email } : {},
|
|
3101
|
+
...memberData.github ? { github: memberData.github } : {}
|
|
3102
|
+
}
|
|
3103
|
+
}
|
|
3104
|
+
};
|
|
3105
|
+
if (authorizedGates.length > 0 && updatedConfig.gates) {
|
|
3106
|
+
for (const gateId of authorizedGates) {
|
|
3107
|
+
const gate = updatedConfig.gates[gateId];
|
|
3108
|
+
if (gate) {
|
|
3109
|
+
if (!gate.authorizedSigners.includes(memberSlug)) {
|
|
3110
|
+
gate.authorizedSigners.push(memberSlug);
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
3115
|
+
return updatedConfig;
|
|
3116
|
+
}
|
|
3117
|
+
|
|
3118
|
+
// src/commands/team/add.ts
|
|
3771
3119
|
var addCommand = new commander.Command("add").description("Add a new team member").action(async () => {
|
|
3772
3120
|
await runAdd();
|
|
3773
3121
|
});
|
|
@@ -3839,45 +3187,22 @@ async function runAdd() {
|
|
|
3839
3187
|
message: "Public key:",
|
|
3840
3188
|
validate: validatePublicKey
|
|
3841
3189
|
});
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
const gateChoices = Object.entries(attestItConfig.gates).map(([gateId, gate]) => ({
|
|
3846
|
-
name: `${gateId} - ${gate.name}`,
|
|
3847
|
-
value: gateId
|
|
3848
|
-
}));
|
|
3849
|
-
authorizedGates = await prompts.checkbox({
|
|
3850
|
-
message: "Select gates to authorize (use space to select):",
|
|
3851
|
-
choices: gateChoices
|
|
3852
|
-
});
|
|
3853
|
-
}
|
|
3854
|
-
const teamMember = {
|
|
3190
|
+
log("");
|
|
3191
|
+
const authorizedGates = await promptForGateAuthorization(attestItConfig.gates);
|
|
3192
|
+
const memberData = {
|
|
3855
3193
|
name,
|
|
3856
|
-
publicKey: publicKey.trim()
|
|
3194
|
+
publicKey: publicKey.trim(),
|
|
3195
|
+
publicKeyAlgorithm: "ed25519"
|
|
3857
3196
|
};
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
teamMember.github = github.trim();
|
|
3197
|
+
const trimmedEmail = email.trim();
|
|
3198
|
+
const trimmedGithub = github.trim();
|
|
3199
|
+
if (trimmedEmail && trimmedEmail.length > 0) {
|
|
3200
|
+
memberData.email = trimmedEmail;
|
|
3863
3201
|
}
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
team: {
|
|
3867
|
-
...existingTeam,
|
|
3868
|
-
[slug]: teamMember
|
|
3869
|
-
}
|
|
3870
|
-
};
|
|
3871
|
-
if (authorizedGates.length > 0 && updatedConfig.gates) {
|
|
3872
|
-
for (const gateId of authorizedGates) {
|
|
3873
|
-
const gate = updatedConfig.gates[gateId];
|
|
3874
|
-
if (gate) {
|
|
3875
|
-
if (!gate.authorizedSigners.includes(slug)) {
|
|
3876
|
-
gate.authorizedSigners.push(slug);
|
|
3877
|
-
}
|
|
3878
|
-
}
|
|
3879
|
-
}
|
|
3202
|
+
if (trimmedGithub && trimmedGithub.length > 0) {
|
|
3203
|
+
memberData.github = trimmedGithub;
|
|
3880
3204
|
}
|
|
3205
|
+
const updatedConfig = addTeamMemberToConfig(config, slug, memberData, authorizedGates);
|
|
3881
3206
|
const configPath = core.findConfigPath();
|
|
3882
3207
|
if (!configPath) {
|
|
3883
3208
|
error("Configuration file not found");
|
|
@@ -3900,127 +3225,73 @@ async function runAdd() {
|
|
|
3900
3225
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
3901
3226
|
}
|
|
3902
3227
|
}
|
|
3903
|
-
var
|
|
3904
|
-
await
|
|
3228
|
+
var joinCommand = new commander.Command("join").description("Add yourself to the project team using your active identity").action(async () => {
|
|
3229
|
+
await runJoin();
|
|
3905
3230
|
});
|
|
3906
|
-
function
|
|
3907
|
-
if (!value || value.trim().length === 0) {
|
|
3908
|
-
return "Public key cannot be empty";
|
|
3909
|
-
}
|
|
3910
|
-
const base64Regex = /^[A-Za-z0-9+/]+=*$/;
|
|
3911
|
-
if (!base64Regex.test(value)) {
|
|
3912
|
-
return "Public key must be valid Base64";
|
|
3913
|
-
}
|
|
3914
|
-
if (value.length !== 44) {
|
|
3915
|
-
return "Public key must be 44 characters (32 bytes in Base64)";
|
|
3916
|
-
}
|
|
3917
|
-
try {
|
|
3918
|
-
const decoded = Buffer.from(value, "base64");
|
|
3919
|
-
if (decoded.length !== 32) {
|
|
3920
|
-
return "Public key must decode to 32 bytes";
|
|
3921
|
-
}
|
|
3922
|
-
} catch {
|
|
3923
|
-
return "Invalid Base64 encoding";
|
|
3924
|
-
}
|
|
3925
|
-
return true;
|
|
3926
|
-
}
|
|
3927
|
-
async function runEdit2(slug) {
|
|
3231
|
+
async function runJoin() {
|
|
3928
3232
|
try {
|
|
3929
3233
|
const theme3 = getTheme2();
|
|
3930
|
-
const config = await core.loadConfig();
|
|
3931
|
-
const attestItConfig = core.toAttestItConfig(config);
|
|
3932
|
-
const existingMember = attestItConfig.team?.[slug];
|
|
3933
|
-
if (!existingMember) {
|
|
3934
|
-
error(`Team member "${slug}" not found`);
|
|
3935
|
-
process.exit(ExitCode.CONFIG_ERROR);
|
|
3936
|
-
}
|
|
3937
3234
|
log("");
|
|
3938
|
-
log(theme3.blue.bold()(
|
|
3235
|
+
log(theme3.blue.bold()("Join Project Team"));
|
|
3939
3236
|
log("");
|
|
3940
|
-
|
|
3237
|
+
const localConfig = await core.loadLocalConfig();
|
|
3238
|
+
if (!localConfig) {
|
|
3239
|
+
error('No identity found. Run "attest-it identity create" first.');
|
|
3240
|
+
process.exit(ExitCode.CONFIG_ERROR);
|
|
3241
|
+
}
|
|
3242
|
+
const activeIdentity = core.getActiveIdentity(localConfig);
|
|
3243
|
+
if (!activeIdentity) {
|
|
3244
|
+
error('No active identity. Run "attest-it identity use <slug>" to select one.');
|
|
3245
|
+
process.exit(ExitCode.CONFIG_ERROR);
|
|
3246
|
+
}
|
|
3247
|
+
const activeSlug = localConfig.activeIdentity;
|
|
3248
|
+
info(`Using identity: ${activeSlug}`);
|
|
3249
|
+
log(` Name: ${activeIdentity.name}`);
|
|
3250
|
+
log(` Public Key: ${activeIdentity.publicKey.slice(0, 32)}...`);
|
|
3941
3251
|
log("");
|
|
3942
|
-
const
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
});
|
|
3952
|
-
const email = await prompts.input({
|
|
3953
|
-
message: "Email (optional):",
|
|
3954
|
-
default: existingMember.email ?? ""
|
|
3955
|
-
});
|
|
3956
|
-
const github = await prompts.input({
|
|
3957
|
-
message: "GitHub username (optional):",
|
|
3958
|
-
default: existingMember.github ?? ""
|
|
3959
|
-
});
|
|
3960
|
-
const updateKey = await prompts.confirm({
|
|
3961
|
-
message: "Update public key?",
|
|
3962
|
-
default: false
|
|
3963
|
-
});
|
|
3964
|
-
let publicKey = existingMember.publicKey;
|
|
3965
|
-
if (updateKey) {
|
|
3966
|
-
log("");
|
|
3967
|
-
log("Paste the new public key:");
|
|
3968
|
-
publicKey = await prompts.input({
|
|
3969
|
-
message: "Public key:",
|
|
3970
|
-
default: existingMember.publicKey,
|
|
3971
|
-
validate: validatePublicKey2
|
|
3972
|
-
});
|
|
3252
|
+
const config = await core.loadConfig();
|
|
3253
|
+
const attestItConfig = core.toAttestItConfig(config);
|
|
3254
|
+
const existingTeam = attestItConfig.team ?? {};
|
|
3255
|
+
const existingMemberWithKey = Object.entries(existingTeam).find(
|
|
3256
|
+
([, member]) => member.publicKey === activeIdentity.publicKey
|
|
3257
|
+
);
|
|
3258
|
+
if (existingMemberWithKey) {
|
|
3259
|
+
error(`You're already a team member as "${existingMemberWithKey[0]}"`);
|
|
3260
|
+
process.exit(ExitCode.CONFIG_ERROR);
|
|
3973
3261
|
}
|
|
3974
|
-
|
|
3975
|
-
if (
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3262
|
+
let slug = activeSlug;
|
|
3263
|
+
if (existingTeam[slug]) {
|
|
3264
|
+
log(`Slug "${slug}" is already taken by another team member.`);
|
|
3265
|
+
slug = await prompts.input({
|
|
3266
|
+
message: "Choose a different slug:",
|
|
3267
|
+
validate: (value) => {
|
|
3268
|
+
if (!value || value.trim().length === 0) {
|
|
3269
|
+
return "Slug cannot be empty";
|
|
3270
|
+
}
|
|
3271
|
+
if (!/^[a-z0-9-]+$/.test(value)) {
|
|
3272
|
+
return "Slug must contain only lowercase letters, numbers, and hyphens";
|
|
3273
|
+
}
|
|
3274
|
+
if (existingTeam[value]) {
|
|
3275
|
+
return `Slug "${value}" is already taken`;
|
|
3276
|
+
}
|
|
3277
|
+
return true;
|
|
3979
3278
|
}
|
|
3980
|
-
}
|
|
3981
|
-
}
|
|
3982
|
-
let selectedGates = currentGates;
|
|
3983
|
-
if (attestItConfig.gates && Object.keys(attestItConfig.gates).length > 0) {
|
|
3984
|
-
log("");
|
|
3985
|
-
const gateChoices = Object.entries(attestItConfig.gates).map(([gateId, gate]) => ({
|
|
3986
|
-
name: `${gateId} - ${gate.name}`,
|
|
3987
|
-
value: gateId,
|
|
3988
|
-
checked: currentGates.includes(gateId)
|
|
3989
|
-
}));
|
|
3990
|
-
selectedGates = await prompts.checkbox({
|
|
3991
|
-
message: "Select gates to authorize (use space to select):",
|
|
3992
|
-
choices: gateChoices
|
|
3993
3279
|
});
|
|
3994
3280
|
}
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3281
|
+
log("");
|
|
3282
|
+
const authorizedGates = await promptForGateAuthorization(attestItConfig.gates);
|
|
3283
|
+
const memberData = {
|
|
3284
|
+
name: activeIdentity.name,
|
|
3285
|
+
publicKey: activeIdentity.publicKey,
|
|
3286
|
+
publicKeyAlgorithm: "ed25519"
|
|
3998
3287
|
};
|
|
3999
|
-
if (email
|
|
4000
|
-
|
|
3288
|
+
if (activeIdentity.email) {
|
|
3289
|
+
memberData.email = activeIdentity.email;
|
|
4001
3290
|
}
|
|
4002
|
-
if (github
|
|
4003
|
-
|
|
4004
|
-
}
|
|
4005
|
-
const updatedConfig = {
|
|
4006
|
-
...config,
|
|
4007
|
-
team: {
|
|
4008
|
-
...attestItConfig.team,
|
|
4009
|
-
[slug]: updatedMember
|
|
4010
|
-
}
|
|
4011
|
-
};
|
|
4012
|
-
if (updatedConfig.gates) {
|
|
4013
|
-
for (const [gateId, gate] of Object.entries(updatedConfig.gates)) {
|
|
4014
|
-
if (currentGates.includes(gateId) && !selectedGates.includes(gateId)) {
|
|
4015
|
-
gate.authorizedSigners = gate.authorizedSigners.filter((s) => s !== slug);
|
|
4016
|
-
}
|
|
4017
|
-
if (!currentGates.includes(gateId) && selectedGates.includes(gateId)) {
|
|
4018
|
-
if (!gate.authorizedSigners.includes(slug)) {
|
|
4019
|
-
gate.authorizedSigners.push(slug);
|
|
4020
|
-
}
|
|
4021
|
-
}
|
|
4022
|
-
}
|
|
3291
|
+
if (activeIdentity.github) {
|
|
3292
|
+
memberData.github = activeIdentity.github;
|
|
4023
3293
|
}
|
|
3294
|
+
const updatedConfig = addTeamMemberToConfig(config, slug, memberData, authorizedGates);
|
|
4024
3295
|
const configPath = core.findConfigPath();
|
|
4025
3296
|
if (!configPath) {
|
|
4026
3297
|
error("Configuration file not found");
|
|
@@ -4029,11 +3300,9 @@ async function runEdit2(slug) {
|
|
|
4029
3300
|
const yamlContent = yaml.stringify(updatedConfig);
|
|
4030
3301
|
await promises.writeFile(configPath, yamlContent, "utf8");
|
|
4031
3302
|
log("");
|
|
4032
|
-
success(`Team member "${slug}"
|
|
4033
|
-
if (
|
|
4034
|
-
log(`Authorized for gates: ${
|
|
4035
|
-
} else {
|
|
4036
|
-
log("Not authorized for any gates");
|
|
3303
|
+
success(`Team member "${slug}" added successfully`);
|
|
3304
|
+
if (authorizedGates.length > 0) {
|
|
3305
|
+
log(`Authorized for gates: ${authorizedGates.join(", ")}`);
|
|
4037
3306
|
}
|
|
4038
3307
|
log("");
|
|
4039
3308
|
} catch (err) {
|
|
@@ -4072,7 +3341,7 @@ async function runRemove2(slug, options) {
|
|
|
4072
3341
|
const projectRoot = process.cwd();
|
|
4073
3342
|
let sealsFile;
|
|
4074
3343
|
try {
|
|
4075
|
-
sealsFile = core.readSealsSync(projectRoot);
|
|
3344
|
+
sealsFile = core.readSealsSync(projectRoot, attestItConfig.settings.sealsPath);
|
|
4076
3345
|
} catch {
|
|
4077
3346
|
sealsFile = { version: 1, seals: {} };
|
|
4078
3347
|
}
|
|
@@ -4148,7 +3417,7 @@ async function runRemove2(slug, options) {
|
|
|
4148
3417
|
}
|
|
4149
3418
|
|
|
4150
3419
|
// src/commands/team/index.ts
|
|
4151
|
-
var teamCommand = new commander.Command("team").description("Manage team members and authorizations").addCommand(listCommand2).addCommand(addCommand).addCommand(
|
|
3420
|
+
var teamCommand = new commander.Command("team").description("Manage team members and authorizations").addCommand(listCommand2).addCommand(addCommand).addCommand(joinCommand).addCommand(removeCommand2);
|
|
4152
3421
|
var PROGRAM_NAME2 = "attest-it";
|
|
4153
3422
|
var PROGRAM_ALIAS2 = "attest";
|
|
4154
3423
|
var PROGRAM_NAMES = [PROGRAM_NAME2, PROGRAM_ALIAS2];
|
|
@@ -4169,7 +3438,6 @@ async function getCompletions(env) {
|
|
|
4169
3438
|
{ name: "run", description: "Run test suites interactively" },
|
|
4170
3439
|
{ name: "verify", description: "Verify all seals are valid" },
|
|
4171
3440
|
{ name: "seal", description: "Create a seal for a gate" },
|
|
4172
|
-
{ name: "keygen", description: "Generate a new keypair" },
|
|
4173
3441
|
{ name: "prune", description: "Remove stale attestations" },
|
|
4174
3442
|
{ name: "identity", description: "Manage identities" },
|
|
4175
3443
|
{ name: "team", description: "Manage team members" },
|
|
@@ -4274,7 +3542,6 @@ async function getCompletions(env) {
|
|
|
4274
3542
|
"run",
|
|
4275
3543
|
"verify",
|
|
4276
3544
|
"seal",
|
|
4277
|
-
"keygen",
|
|
4278
3545
|
"prune",
|
|
4279
3546
|
"identity",
|
|
4280
3547
|
"team",
|
|
@@ -4409,43 +3676,12 @@ function createCompletionServerCommand() {
|
|
|
4409
3676
|
}
|
|
4410
3677
|
});
|
|
4411
3678
|
}
|
|
4412
|
-
function hasVersion(data) {
|
|
4413
|
-
return typeof data === "object" && data !== null && "version" in data && // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
4414
|
-
typeof data.version === "string";
|
|
4415
|
-
}
|
|
4416
|
-
var cachedVersion;
|
|
4417
|
-
function getPackageVersion() {
|
|
4418
|
-
if (cachedVersion !== void 0) {
|
|
4419
|
-
return cachedVersion;
|
|
4420
|
-
}
|
|
4421
|
-
const __filename = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
4422
|
-
const __dirname = path.dirname(__filename);
|
|
4423
|
-
const possiblePaths = [path.join(__dirname, "../package.json"), path.join(__dirname, "../../package.json")];
|
|
4424
|
-
for (const packageJsonPath of possiblePaths) {
|
|
4425
|
-
try {
|
|
4426
|
-
const content = fs.readFileSync(packageJsonPath, "utf-8");
|
|
4427
|
-
const packageJsonData = JSON.parse(content);
|
|
4428
|
-
if (!hasVersion(packageJsonData)) {
|
|
4429
|
-
throw new Error(`Invalid package.json at ${packageJsonPath}: missing version field`);
|
|
4430
|
-
}
|
|
4431
|
-
cachedVersion = packageJsonData.version;
|
|
4432
|
-
return cachedVersion;
|
|
4433
|
-
} catch (error2) {
|
|
4434
|
-
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") {
|
|
4435
|
-
continue;
|
|
4436
|
-
}
|
|
4437
|
-
throw error2;
|
|
4438
|
-
}
|
|
4439
|
-
}
|
|
4440
|
-
throw new Error("Could not find package.json");
|
|
4441
|
-
}
|
|
4442
3679
|
var program = new commander.Command();
|
|
4443
3680
|
program.name("attest-it").description("Human-gated test attestation system").option("-c, --config <path>", "Path to config file").option("-v, --verbose", "Verbose output").option("-q, --quiet", "Minimal output");
|
|
4444
3681
|
program.option("-V, --version", "output the version number");
|
|
4445
3682
|
program.addCommand(initCommand);
|
|
4446
3683
|
program.addCommand(statusCommand);
|
|
4447
3684
|
program.addCommand(runCommand);
|
|
4448
|
-
program.addCommand(keygenCommand);
|
|
4449
3685
|
program.addCommand(pruneCommand);
|
|
4450
3686
|
program.addCommand(verifyCommand);
|
|
4451
3687
|
program.addCommand(sealCommand);
|