@attest-it/cli 0.8.0 → 0.9.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/README.md +0 -10
- package/dist/bin/attest-it.js +542 -1279
- package/dist/bin/attest-it.js.map +1 -1
- package/dist/index.cjs +371 -1170
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +372 -1172
- 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) {
|
|
@@ -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]
|
|
@@ -1533,9 +1586,9 @@ async function runSingleSuite(suiteName, config, options) {
|
|
|
1533
1586
|
if (!await keyProvider.keyExists(keyRef)) {
|
|
1534
1587
|
error(`Private key not found in ${keyProvider.displayName}`);
|
|
1535
1588
|
if (keyProvider.type === "filesystem") {
|
|
1536
|
-
error('Run "attest-it
|
|
1589
|
+
error('Run "attest-it identity create" first to generate a keypair.');
|
|
1537
1590
|
} else {
|
|
1538
|
-
error('Run "attest-it
|
|
1591
|
+
error('Run "attest-it identity create" to generate and store a key.');
|
|
1539
1592
|
}
|
|
1540
1593
|
process.exit(ExitCode.MISSING_KEY);
|
|
1541
1594
|
}
|
|
@@ -1559,7 +1612,7 @@ async function promptForSeal(suiteName, gateId, config) {
|
|
|
1559
1612
|
const localConfig = core.loadLocalConfigSync();
|
|
1560
1613
|
if (!localConfig) {
|
|
1561
1614
|
warn("No local identity configuration found - cannot create seal");
|
|
1562
|
-
warn('Run "attest-it
|
|
1615
|
+
warn('Run "attest-it identity create" to set up your identity');
|
|
1563
1616
|
return;
|
|
1564
1617
|
}
|
|
1565
1618
|
const identity = core.getActiveIdentity(localConfig);
|
|
@@ -1594,8 +1647,8 @@ async function promptForSeal(suiteName, gateId, config) {
|
|
|
1594
1647
|
const keyProvider = createKeyProviderFromIdentity(identity);
|
|
1595
1648
|
const keyRef = getKeyRefFromIdentity(identity);
|
|
1596
1649
|
const keyResult = await keyProvider.getPrivateKey(keyRef);
|
|
1597
|
-
const
|
|
1598
|
-
const privateKeyPem = await
|
|
1650
|
+
const fs3 = await import('fs/promises');
|
|
1651
|
+
const privateKeyPem = await fs3.readFile(keyResult.keyPath, "utf8");
|
|
1599
1652
|
await keyResult.cleanup();
|
|
1600
1653
|
const identitySlug = localConfig.activeIdentity;
|
|
1601
1654
|
const seal = core.createSeal({
|
|
@@ -1677,697 +1730,6 @@ function getKeyRefFromIdentity(identity) {
|
|
|
1677
1730
|
}
|
|
1678
1731
|
}
|
|
1679
1732
|
}
|
|
1680
|
-
var MIN_PASSPHRASE_LENGTH = 8;
|
|
1681
|
-
function KeygenInteractive(props) {
|
|
1682
|
-
const { onComplete, onCancel, onError } = props;
|
|
1683
|
-
const [step, setStep] = React7.useState("checking-providers");
|
|
1684
|
-
const [opAvailable, setOpAvailable] = React7.useState(false);
|
|
1685
|
-
const [keychainAvailable, setKeychainAvailable] = React7.useState(false);
|
|
1686
|
-
const [yubiKeyAvailable, setYubiKeyAvailable] = React7.useState(false);
|
|
1687
|
-
const [accounts, setAccounts] = React7.useState([]);
|
|
1688
|
-
const [vaults, setVaults] = React7.useState([]);
|
|
1689
|
-
const [yubiKeyDevices, setYubiKeyDevices] = React7.useState([]);
|
|
1690
|
-
const [_selectedProvider, setSelectedProvider] = React7.useState();
|
|
1691
|
-
const [selectedAccount, setSelectedAccount] = React7.useState();
|
|
1692
|
-
const [selectedVault, setSelectedVault] = React7.useState();
|
|
1693
|
-
const [itemName, setItemName] = React7.useState("attest-it-private-key");
|
|
1694
|
-
const [keychainItemName, setKeychainItemName] = React7.useState("attest-it-private-key");
|
|
1695
|
-
const [selectedYubiKeySerial, setSelectedYubiKeySerial] = React7.useState();
|
|
1696
|
-
const [selectedYubiKeySlot, setSelectedYubiKeySlot] = React7.useState(2);
|
|
1697
|
-
const [slot1Configured, setSlot1Configured] = React7.useState(false);
|
|
1698
|
-
const [slot2Configured, setSlot2Configured] = React7.useState(false);
|
|
1699
|
-
const [encryptionPassphrase, setEncryptionPassphrase] = React7.useState();
|
|
1700
|
-
ink.useInput((_input, key) => {
|
|
1701
|
-
if (key.escape) {
|
|
1702
|
-
onCancel();
|
|
1703
|
-
}
|
|
1704
|
-
});
|
|
1705
|
-
React7.useEffect(() => {
|
|
1706
|
-
const checkProviders = async () => {
|
|
1707
|
-
try {
|
|
1708
|
-
const isInstalled = await core.OnePasswordKeyProvider.isInstalled();
|
|
1709
|
-
setOpAvailable(isInstalled);
|
|
1710
|
-
if (isInstalled) {
|
|
1711
|
-
const accountList = await core.OnePasswordKeyProvider.listAccounts();
|
|
1712
|
-
setAccounts(accountList);
|
|
1713
|
-
}
|
|
1714
|
-
} catch {
|
|
1715
|
-
setOpAvailable(false);
|
|
1716
|
-
}
|
|
1717
|
-
const isKeychainAvailable = core.MacOSKeychainKeyProvider.isAvailable();
|
|
1718
|
-
setKeychainAvailable(isKeychainAvailable);
|
|
1719
|
-
try {
|
|
1720
|
-
const isInstalled = await core.YubiKeyProvider.isInstalled();
|
|
1721
|
-
if (isInstalled) {
|
|
1722
|
-
const isConnected = await core.YubiKeyProvider.isConnected();
|
|
1723
|
-
if (isConnected) {
|
|
1724
|
-
const devices = await core.YubiKeyProvider.listDevices();
|
|
1725
|
-
setYubiKeyDevices(devices);
|
|
1726
|
-
setYubiKeyAvailable(devices.length > 0);
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1729
|
-
} catch {
|
|
1730
|
-
setYubiKeyAvailable(false);
|
|
1731
|
-
}
|
|
1732
|
-
setStep("select-provider");
|
|
1733
|
-
};
|
|
1734
|
-
void checkProviders();
|
|
1735
|
-
}, []);
|
|
1736
|
-
React7.useEffect(() => {
|
|
1737
|
-
if (step === "select-vault" && selectedAccount) {
|
|
1738
|
-
const fetchVaults = async () => {
|
|
1739
|
-
try {
|
|
1740
|
-
const vaultList = await core.OnePasswordKeyProvider.listVaults(selectedAccount);
|
|
1741
|
-
setVaults(vaultList);
|
|
1742
|
-
} catch (err) {
|
|
1743
|
-
onError(err instanceof Error ? err : new Error("Failed to fetch vaults"));
|
|
1744
|
-
}
|
|
1745
|
-
};
|
|
1746
|
-
void fetchVaults();
|
|
1747
|
-
}
|
|
1748
|
-
}, [step, selectedAccount, onError]);
|
|
1749
|
-
const checkYubiKeySlots = async (serial) => {
|
|
1750
|
-
try {
|
|
1751
|
-
const slot1 = await core.YubiKeyProvider.isChallengeResponseConfigured(1, serial);
|
|
1752
|
-
const slot2 = await core.YubiKeyProvider.isChallengeResponseConfigured(2, serial);
|
|
1753
|
-
setSlot1Configured(slot1);
|
|
1754
|
-
setSlot2Configured(slot2);
|
|
1755
|
-
if (slot1 && slot2) {
|
|
1756
|
-
setStep("select-yubikey-slot");
|
|
1757
|
-
} else if (slot2) {
|
|
1758
|
-
setSelectedYubiKeySlot(2);
|
|
1759
|
-
void generateKeys("yubikey");
|
|
1760
|
-
} else if (slot1) {
|
|
1761
|
-
setSelectedYubiKeySlot(1);
|
|
1762
|
-
void generateKeys("yubikey");
|
|
1763
|
-
} else {
|
|
1764
|
-
setStep("yubikey-offer-setup");
|
|
1765
|
-
}
|
|
1766
|
-
} catch (err) {
|
|
1767
|
-
onError(err instanceof Error ? err : new Error("Failed to check YubiKey slots"));
|
|
1768
|
-
}
|
|
1769
|
-
};
|
|
1770
|
-
const handleProviderSelect = (value) => {
|
|
1771
|
-
if (value === "filesystem") {
|
|
1772
|
-
setSelectedProvider("filesystem");
|
|
1773
|
-
setStep("select-filesystem-encryption");
|
|
1774
|
-
} else if (value === "1password") {
|
|
1775
|
-
setSelectedProvider("1password");
|
|
1776
|
-
if (accounts.length === 1 && accounts[0]) {
|
|
1777
|
-
setSelectedAccount(accounts[0].user_uuid);
|
|
1778
|
-
setStep("select-vault");
|
|
1779
|
-
} else {
|
|
1780
|
-
setStep("select-account");
|
|
1781
|
-
}
|
|
1782
|
-
} else if (value === "macos-keychain") {
|
|
1783
|
-
setSelectedProvider("macos-keychain");
|
|
1784
|
-
setStep("enter-keychain-item-name");
|
|
1785
|
-
} else if (value === "yubikey") {
|
|
1786
|
-
setSelectedProvider("yubikey");
|
|
1787
|
-
if (yubiKeyDevices.length > 1) {
|
|
1788
|
-
setStep("select-yubikey-device");
|
|
1789
|
-
} else if (yubiKeyDevices.length === 1 && yubiKeyDevices[0]) {
|
|
1790
|
-
setSelectedYubiKeySerial(yubiKeyDevices[0].serial);
|
|
1791
|
-
void checkYubiKeySlots(yubiKeyDevices[0].serial);
|
|
1792
|
-
}
|
|
1793
|
-
}
|
|
1794
|
-
};
|
|
1795
|
-
const handleAccountSelect = (value) => {
|
|
1796
|
-
setSelectedAccount(value);
|
|
1797
|
-
setStep("select-vault");
|
|
1798
|
-
};
|
|
1799
|
-
const handleVaultSelect = (value) => {
|
|
1800
|
-
setSelectedVault(value);
|
|
1801
|
-
setStep("enter-item-name");
|
|
1802
|
-
};
|
|
1803
|
-
const handleItemNameSubmit = (value) => {
|
|
1804
|
-
setItemName(value);
|
|
1805
|
-
void generateKeys("1password");
|
|
1806
|
-
};
|
|
1807
|
-
const handleKeychainItemNameSubmit = (value) => {
|
|
1808
|
-
setKeychainItemName(value);
|
|
1809
|
-
void generateKeys("macos-keychain");
|
|
1810
|
-
};
|
|
1811
|
-
const handleYubiKeyDeviceSelect = (value) => {
|
|
1812
|
-
setSelectedYubiKeySerial(value);
|
|
1813
|
-
void checkYubiKeySlots(value);
|
|
1814
|
-
};
|
|
1815
|
-
const handleYubiKeySlotSelect = (value) => {
|
|
1816
|
-
const slot = value === "1" ? 1 : 2;
|
|
1817
|
-
setSelectedYubiKeySlot(slot);
|
|
1818
|
-
void generateKeys("yubikey");
|
|
1819
|
-
};
|
|
1820
|
-
const handleYubiKeySetupConfirm = (value) => {
|
|
1821
|
-
if (value === "yes") {
|
|
1822
|
-
void setupYubiKeySlot();
|
|
1823
|
-
} else {
|
|
1824
|
-
onError(new Error("YubiKey setup cancelled"));
|
|
1825
|
-
}
|
|
1826
|
-
};
|
|
1827
|
-
const handleEncryptionMethodSelect = (value) => {
|
|
1828
|
-
if (value === "passphrase") {
|
|
1829
|
-
setStep("enter-encryption-passphrase");
|
|
1830
|
-
} else {
|
|
1831
|
-
setEncryptionPassphrase(void 0);
|
|
1832
|
-
void generateKeys("filesystem");
|
|
1833
|
-
}
|
|
1834
|
-
};
|
|
1835
|
-
const handleEncryptionPassphrase = (value) => {
|
|
1836
|
-
if (value.length < MIN_PASSPHRASE_LENGTH) {
|
|
1837
|
-
onError(new Error(`Passphrase must be at least ${String(MIN_PASSPHRASE_LENGTH)} characters`));
|
|
1838
|
-
return;
|
|
1839
|
-
}
|
|
1840
|
-
setEncryptionPassphrase(value);
|
|
1841
|
-
setStep("confirm-encryption-passphrase");
|
|
1842
|
-
};
|
|
1843
|
-
const handleConfirmPassphrase = (value) => {
|
|
1844
|
-
if (value !== encryptionPassphrase) {
|
|
1845
|
-
onError(new Error("Passphrases do not match. Please try again."));
|
|
1846
|
-
setEncryptionPassphrase(void 0);
|
|
1847
|
-
setStep("enter-encryption-passphrase");
|
|
1848
|
-
return;
|
|
1849
|
-
}
|
|
1850
|
-
void generateKeys("filesystem");
|
|
1851
|
-
};
|
|
1852
|
-
const setupYubiKeySlot = async () => {
|
|
1853
|
-
setStep("yubikey-configuring");
|
|
1854
|
-
try {
|
|
1855
|
-
const { spawn: spawn3 } = await import('child_process');
|
|
1856
|
-
const args = ["otp", "chalresp", "--touch", "--generate", "2"];
|
|
1857
|
-
if (selectedYubiKeySerial) {
|
|
1858
|
-
args.unshift("--device", selectedYubiKeySerial);
|
|
1859
|
-
}
|
|
1860
|
-
await new Promise((resolve2, reject) => {
|
|
1861
|
-
const proc = spawn3("ykman", args, { stdio: "inherit" });
|
|
1862
|
-
proc.on("close", (code) => {
|
|
1863
|
-
if (code === 0) {
|
|
1864
|
-
resolve2();
|
|
1865
|
-
} else {
|
|
1866
|
-
reject(new Error(`ykman exited with code ${String(code)}`));
|
|
1867
|
-
}
|
|
1868
|
-
});
|
|
1869
|
-
proc.on("error", reject);
|
|
1870
|
-
});
|
|
1871
|
-
setSelectedYubiKeySlot(2);
|
|
1872
|
-
void generateKeys("yubikey");
|
|
1873
|
-
} catch (err) {
|
|
1874
|
-
onError(err instanceof Error ? err : new Error("Failed to configure YubiKey"));
|
|
1875
|
-
}
|
|
1876
|
-
};
|
|
1877
|
-
const generateKeys = async (provider) => {
|
|
1878
|
-
setStep("generating");
|
|
1879
|
-
try {
|
|
1880
|
-
const publicKeyPath = props.publicKeyPath ?? core.getDefaultPublicKeyPath();
|
|
1881
|
-
if (provider === "filesystem") {
|
|
1882
|
-
const fsProvider = new core.FilesystemKeyProvider();
|
|
1883
|
-
const genOptions = {
|
|
1884
|
-
publicKeyPath
|
|
1885
|
-
};
|
|
1886
|
-
if (props.force !== void 0) {
|
|
1887
|
-
genOptions.force = props.force;
|
|
1888
|
-
}
|
|
1889
|
-
if (encryptionPassphrase !== void 0) {
|
|
1890
|
-
genOptions.passphrase = encryptionPassphrase;
|
|
1891
|
-
}
|
|
1892
|
-
const result = await fsProvider.generateKeyPair(genOptions);
|
|
1893
|
-
const completionResult = {
|
|
1894
|
-
provider: "filesystem",
|
|
1895
|
-
publicKeyPath: result.publicKeyPath,
|
|
1896
|
-
privateKeyRef: result.privateKeyRef,
|
|
1897
|
-
storageDescription: result.storageDescription
|
|
1898
|
-
};
|
|
1899
|
-
if (result.encrypted) {
|
|
1900
|
-
completionResult.encrypted = result.encrypted;
|
|
1901
|
-
}
|
|
1902
|
-
onComplete(completionResult);
|
|
1903
|
-
} else if (provider === "1password") {
|
|
1904
|
-
if (!selectedVault || !itemName) {
|
|
1905
|
-
throw new Error("Vault and item name are required for 1Password");
|
|
1906
|
-
}
|
|
1907
|
-
const vault = vaults.find((v) => v.id === selectedVault);
|
|
1908
|
-
if (!vault) {
|
|
1909
|
-
throw new Error("Selected vault not found");
|
|
1910
|
-
}
|
|
1911
|
-
const providerOptions = {
|
|
1912
|
-
vault: vault.name,
|
|
1913
|
-
itemName
|
|
1914
|
-
};
|
|
1915
|
-
if (selectedAccount !== void 0) {
|
|
1916
|
-
providerOptions.account = selectedAccount;
|
|
1917
|
-
}
|
|
1918
|
-
const opProvider = new core.OnePasswordKeyProvider(providerOptions);
|
|
1919
|
-
const genOptions = { publicKeyPath };
|
|
1920
|
-
if (props.force !== void 0) {
|
|
1921
|
-
genOptions.force = props.force;
|
|
1922
|
-
}
|
|
1923
|
-
const result = await opProvider.generateKeyPair(genOptions);
|
|
1924
|
-
const completionResult = {
|
|
1925
|
-
provider: "1password",
|
|
1926
|
-
publicKeyPath: result.publicKeyPath,
|
|
1927
|
-
privateKeyRef: result.privateKeyRef,
|
|
1928
|
-
storageDescription: result.storageDescription,
|
|
1929
|
-
vault: vault.name,
|
|
1930
|
-
itemName
|
|
1931
|
-
};
|
|
1932
|
-
if (selectedAccount !== void 0) {
|
|
1933
|
-
completionResult.account = selectedAccount;
|
|
1934
|
-
}
|
|
1935
|
-
onComplete(completionResult);
|
|
1936
|
-
} else if (provider === "macos-keychain") {
|
|
1937
|
-
if (!keychainItemName) {
|
|
1938
|
-
throw new Error("Item name is required for macOS Keychain");
|
|
1939
|
-
}
|
|
1940
|
-
const keychainProvider = new core.MacOSKeychainKeyProvider({
|
|
1941
|
-
itemName: keychainItemName
|
|
1942
|
-
});
|
|
1943
|
-
const genOptions = { publicKeyPath };
|
|
1944
|
-
if (props.force !== void 0) {
|
|
1945
|
-
genOptions.force = props.force;
|
|
1946
|
-
}
|
|
1947
|
-
const result = await keychainProvider.generateKeyPair(genOptions);
|
|
1948
|
-
onComplete({
|
|
1949
|
-
provider: "macos-keychain",
|
|
1950
|
-
publicKeyPath: result.publicKeyPath,
|
|
1951
|
-
privateKeyRef: result.privateKeyRef,
|
|
1952
|
-
storageDescription: result.storageDescription,
|
|
1953
|
-
itemName: keychainItemName
|
|
1954
|
-
});
|
|
1955
|
-
} else {
|
|
1956
|
-
const encryptedKeyPath = core.getDefaultYubiKeyEncryptedKeyPath();
|
|
1957
|
-
const providerOptions = {
|
|
1958
|
-
encryptedKeyPath,
|
|
1959
|
-
slot: selectedYubiKeySlot
|
|
1960
|
-
};
|
|
1961
|
-
if (selectedYubiKeySerial !== void 0) {
|
|
1962
|
-
providerOptions.serial = selectedYubiKeySerial;
|
|
1963
|
-
}
|
|
1964
|
-
const ykProvider = new core.YubiKeyProvider(providerOptions);
|
|
1965
|
-
const genOptions = { publicKeyPath };
|
|
1966
|
-
if (props.force !== void 0) {
|
|
1967
|
-
genOptions.force = props.force;
|
|
1968
|
-
}
|
|
1969
|
-
const result = await ykProvider.generateKeyPair(genOptions);
|
|
1970
|
-
const completionResult = {
|
|
1971
|
-
provider: "yubikey",
|
|
1972
|
-
publicKeyPath: result.publicKeyPath,
|
|
1973
|
-
privateKeyRef: result.privateKeyRef,
|
|
1974
|
-
storageDescription: result.storageDescription,
|
|
1975
|
-
slot: selectedYubiKeySlot,
|
|
1976
|
-
encryptedKeyPath
|
|
1977
|
-
};
|
|
1978
|
-
if (selectedYubiKeySerial !== void 0) {
|
|
1979
|
-
completionResult.serial = selectedYubiKeySerial;
|
|
1980
|
-
}
|
|
1981
|
-
onComplete(completionResult);
|
|
1982
|
-
}
|
|
1983
|
-
setStep("done");
|
|
1984
|
-
} catch (err) {
|
|
1985
|
-
onError(err instanceof Error ? err : new Error("Key generation failed"));
|
|
1986
|
-
} finally {
|
|
1987
|
-
setEncryptionPassphrase(void 0);
|
|
1988
|
-
}
|
|
1989
|
-
};
|
|
1990
|
-
if (step === "checking-providers") {
|
|
1991
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { flexDirection: "column", children: /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", gap: 1, children: [
|
|
1992
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {}),
|
|
1993
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: "Checking available key storage providers..." })
|
|
1994
|
-
] }) });
|
|
1995
|
-
}
|
|
1996
|
-
if (step === "select-provider") {
|
|
1997
|
-
const options = [
|
|
1998
|
-
{
|
|
1999
|
-
label: `Local Filesystem (${core.getDefaultPrivateKeyPath()})`,
|
|
2000
|
-
value: "filesystem"
|
|
2001
|
-
}
|
|
2002
|
-
];
|
|
2003
|
-
if (keychainAvailable) {
|
|
2004
|
-
options.push({
|
|
2005
|
-
label: "macOS Keychain",
|
|
2006
|
-
value: "macos-keychain"
|
|
2007
|
-
});
|
|
2008
|
-
}
|
|
2009
|
-
if (yubiKeyAvailable) {
|
|
2010
|
-
options.push({
|
|
2011
|
-
label: "YubiKey (hardware security key)",
|
|
2012
|
-
value: "yubikey"
|
|
2013
|
-
});
|
|
2014
|
-
}
|
|
2015
|
-
if (opAvailable) {
|
|
2016
|
-
options.push({
|
|
2017
|
-
label: "1Password (requires op CLI)",
|
|
2018
|
-
value: "1password"
|
|
2019
|
-
});
|
|
2020
|
-
}
|
|
2021
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
2022
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Where would you like to store your private key?" }),
|
|
2023
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
|
|
2024
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Select, { options, onChange: handleProviderSelect })
|
|
2025
|
-
] });
|
|
2026
|
-
}
|
|
2027
|
-
if (step === "select-filesystem-encryption") {
|
|
2028
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
2029
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Would you like to encrypt your private key with a passphrase?" }),
|
|
2030
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "A passphrase adds extra security but must be entered each time you sign." }),
|
|
2031
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
|
|
2032
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2033
|
-
ui.Select,
|
|
2034
|
-
{
|
|
2035
|
-
options: [
|
|
2036
|
-
{ label: "No encryption (key protected by file permissions only)", value: "none" },
|
|
2037
|
-
{ label: "Passphrase protection (AES-256 encryption)", value: "passphrase" }
|
|
2038
|
-
],
|
|
2039
|
-
onChange: handleEncryptionMethodSelect
|
|
2040
|
-
}
|
|
2041
|
-
)
|
|
2042
|
-
] });
|
|
2043
|
-
}
|
|
2044
|
-
if (step === "enter-encryption-passphrase") {
|
|
2045
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
2046
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Enter a passphrase to encrypt your private key:" }),
|
|
2047
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: `(Minimum ${String(MIN_PASSPHRASE_LENGTH)} characters. You will need this passphrase each time you sign.)` }),
|
|
2048
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
|
|
2049
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.PasswordInput, { onSubmit: handleEncryptionPassphrase })
|
|
2050
|
-
] });
|
|
2051
|
-
}
|
|
2052
|
-
if (step === "confirm-encryption-passphrase") {
|
|
2053
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
2054
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Confirm your passphrase:" }),
|
|
2055
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "(Enter the same passphrase again to confirm.)" }),
|
|
2056
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
|
|
2057
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.PasswordInput, { onSubmit: handleConfirmPassphrase })
|
|
2058
|
-
] });
|
|
2059
|
-
}
|
|
2060
|
-
if (step === "select-account") {
|
|
2061
|
-
const options = accounts.map((account) => ({
|
|
2062
|
-
label: account.name ? `${account.name} (${account.email})` : account.email,
|
|
2063
|
-
value: account.user_uuid
|
|
2064
|
-
}));
|
|
2065
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
2066
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Select 1Password account:" }),
|
|
2067
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
|
|
2068
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Select, { options, onChange: handleAccountSelect })
|
|
2069
|
-
] });
|
|
2070
|
-
}
|
|
2071
|
-
if (step === "select-vault") {
|
|
2072
|
-
if (vaults.length === 0) {
|
|
2073
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { flexDirection: "column", children: /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", gap: 1, children: [
|
|
2074
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {}),
|
|
2075
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: "Loading vaults..." })
|
|
2076
|
-
] }) });
|
|
2077
|
-
}
|
|
2078
|
-
const options = vaults.map((vault) => ({
|
|
2079
|
-
label: vault.name,
|
|
2080
|
-
value: vault.id
|
|
2081
|
-
}));
|
|
2082
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
2083
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Select vault for private key storage:" }),
|
|
2084
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
|
|
2085
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Select, { options, onChange: handleVaultSelect })
|
|
2086
|
-
] });
|
|
2087
|
-
}
|
|
2088
|
-
if (step === "enter-item-name") {
|
|
2089
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
2090
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Enter name for the key item:" }),
|
|
2091
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "(This will be visible in your 1Password vault)" }),
|
|
2092
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
|
|
2093
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.TextInput, { defaultValue: itemName, onSubmit: handleItemNameSubmit })
|
|
2094
|
-
] });
|
|
2095
|
-
}
|
|
2096
|
-
if (step === "enter-keychain-item-name") {
|
|
2097
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
2098
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Enter name for the keychain item:" }),
|
|
2099
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "(This will be the service name in your macOS Keychain)" }),
|
|
2100
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
|
|
2101
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.TextInput, { defaultValue: keychainItemName, onSubmit: handleKeychainItemNameSubmit })
|
|
2102
|
-
] });
|
|
2103
|
-
}
|
|
2104
|
-
if (step === "select-yubikey-device") {
|
|
2105
|
-
const options = yubiKeyDevices.map((device) => ({
|
|
2106
|
-
label: `${device.type} (Serial: ${device.serial})`,
|
|
2107
|
-
value: device.serial
|
|
2108
|
-
}));
|
|
2109
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
2110
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Select YubiKey device:" }),
|
|
2111
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
|
|
2112
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Select, { options, onChange: handleYubiKeyDeviceSelect })
|
|
2113
|
-
] });
|
|
2114
|
-
}
|
|
2115
|
-
if (step === "select-yubikey-slot") {
|
|
2116
|
-
const options = [];
|
|
2117
|
-
if (slot2Configured) {
|
|
2118
|
-
options.push({
|
|
2119
|
-
label: "Slot 2 (recommended)",
|
|
2120
|
-
value: "2"
|
|
2121
|
-
});
|
|
2122
|
-
}
|
|
2123
|
-
if (slot1Configured) {
|
|
2124
|
-
options.push({
|
|
2125
|
-
label: "Slot 1",
|
|
2126
|
-
value: "1"
|
|
2127
|
-
});
|
|
2128
|
-
}
|
|
2129
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
2130
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Select YubiKey slot for challenge-response:" }),
|
|
2131
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
|
|
2132
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Select, { options, onChange: handleYubiKeySlotSelect })
|
|
2133
|
-
] });
|
|
2134
|
-
}
|
|
2135
|
-
if (step === "yubikey-offer-setup") {
|
|
2136
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
2137
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Your YubiKey is not configured for challenge-response." }),
|
|
2138
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
|
|
2139
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: "Would you like to configure slot 2 for challenge-response now?" }),
|
|
2140
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "This will enable touch-to-sign functionality." }),
|
|
2141
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
|
|
2142
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2143
|
-
ui.Select,
|
|
2144
|
-
{
|
|
2145
|
-
options: [
|
|
2146
|
-
{ label: "Yes, configure my YubiKey", value: "yes" },
|
|
2147
|
-
{ label: "No, cancel", value: "no" }
|
|
2148
|
-
],
|
|
2149
|
-
onChange: handleYubiKeySetupConfirm
|
|
2150
|
-
}
|
|
2151
|
-
)
|
|
2152
|
-
] });
|
|
2153
|
-
}
|
|
2154
|
-
if (step === "yubikey-configuring") {
|
|
2155
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
2156
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", gap: 1, children: [
|
|
2157
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {}),
|
|
2158
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: "Configuring YubiKey slot 2 for challenge-response..." })
|
|
2159
|
-
] }),
|
|
2160
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Touch your YubiKey when it flashes." })
|
|
2161
|
-
] });
|
|
2162
|
-
}
|
|
2163
|
-
if (step === "generating") {
|
|
2164
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { flexDirection: "column", children: /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", gap: 1, children: [
|
|
2165
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {}),
|
|
2166
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: "Generating RSA-2048 keypair..." })
|
|
2167
|
-
] }) });
|
|
2168
|
-
}
|
|
2169
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ink.Box, {});
|
|
2170
|
-
}
|
|
2171
|
-
async function runKeygenInteractive(options) {
|
|
2172
|
-
return new Promise((resolve2, reject) => {
|
|
2173
|
-
const props = {
|
|
2174
|
-
onComplete: (result) => {
|
|
2175
|
-
unmount();
|
|
2176
|
-
resolve2(result);
|
|
2177
|
-
},
|
|
2178
|
-
onCancel: () => {
|
|
2179
|
-
unmount();
|
|
2180
|
-
reject(new Error("Keygen cancelled"));
|
|
2181
|
-
},
|
|
2182
|
-
onError: (error2) => {
|
|
2183
|
-
unmount();
|
|
2184
|
-
reject(error2);
|
|
2185
|
-
}
|
|
2186
|
-
};
|
|
2187
|
-
if (options.publicKeyPath !== void 0) {
|
|
2188
|
-
props.publicKeyPath = options.publicKeyPath;
|
|
2189
|
-
}
|
|
2190
|
-
if (options.force !== void 0) {
|
|
2191
|
-
props.force = options.force;
|
|
2192
|
-
}
|
|
2193
|
-
const { unmount } = ink.render(/* @__PURE__ */ jsxRuntime.jsx(KeygenInteractive, { ...props }));
|
|
2194
|
-
});
|
|
2195
|
-
}
|
|
2196
|
-
|
|
2197
|
-
// src/commands/keygen.ts
|
|
2198
|
-
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(
|
|
2199
|
-
"--provider <type>",
|
|
2200
|
-
"Key provider: filesystem, 1password, or macos-keychain (skips interactive)"
|
|
2201
|
-
).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) => {
|
|
2202
|
-
await runKeygen(options);
|
|
2203
|
-
});
|
|
2204
|
-
async function runKeygen(options) {
|
|
2205
|
-
try {
|
|
2206
|
-
const useInteractive = options.interactive !== false && !options.provider;
|
|
2207
|
-
if (useInteractive) {
|
|
2208
|
-
const interactiveOptions = {};
|
|
2209
|
-
if (options.output !== void 0) {
|
|
2210
|
-
interactiveOptions.publicKeyPath = options.output;
|
|
2211
|
-
}
|
|
2212
|
-
if (options.force !== void 0) {
|
|
2213
|
-
interactiveOptions.force = options.force;
|
|
2214
|
-
}
|
|
2215
|
-
const result = await runKeygenInteractive(interactiveOptions);
|
|
2216
|
-
success("Keypair generated successfully!");
|
|
2217
|
-
log("");
|
|
2218
|
-
log("Private key stored in:");
|
|
2219
|
-
log(` ${result.storageDescription}`);
|
|
2220
|
-
log("");
|
|
2221
|
-
log("Public key (commit to repo):");
|
|
2222
|
-
log(` ${result.publicKeyPath}`);
|
|
2223
|
-
log("");
|
|
2224
|
-
if (result.provider === "1password") {
|
|
2225
|
-
log("Add to your .attest-it/config.yaml:");
|
|
2226
|
-
log("");
|
|
2227
|
-
log("settings:");
|
|
2228
|
-
log(` publicKeyPath: ${result.publicKeyPath}`);
|
|
2229
|
-
log(" keyProvider:");
|
|
2230
|
-
log(" type: 1password");
|
|
2231
|
-
log(" options:");
|
|
2232
|
-
if (result.account) {
|
|
2233
|
-
log(` account: ${result.account}`);
|
|
2234
|
-
}
|
|
2235
|
-
log(` vault: ${result.vault ?? ""}`);
|
|
2236
|
-
log(` itemName: ${result.itemName ?? ""}`);
|
|
2237
|
-
log("");
|
|
2238
|
-
} else if (result.provider === "macos-keychain") {
|
|
2239
|
-
log("Add to your .attest-it/config.yaml:");
|
|
2240
|
-
log("");
|
|
2241
|
-
log("settings:");
|
|
2242
|
-
log(` publicKeyPath: ${result.publicKeyPath}`);
|
|
2243
|
-
log(" keyProvider:");
|
|
2244
|
-
log(" type: macos-keychain");
|
|
2245
|
-
log(" options:");
|
|
2246
|
-
log(` itemName: ${result.itemName ?? ""}`);
|
|
2247
|
-
log("");
|
|
2248
|
-
}
|
|
2249
|
-
log("Next steps:");
|
|
2250
|
-
log(` 1. git add ${result.publicKeyPath}`);
|
|
2251
|
-
if (result.provider === "1password" || result.provider === "macos-keychain") {
|
|
2252
|
-
log(" 2. Update .attest-it/config.yaml with keyProvider settings");
|
|
2253
|
-
} else {
|
|
2254
|
-
log(" 2. Update .attest-it/config.yaml publicKeyPath if needed");
|
|
2255
|
-
}
|
|
2256
|
-
log(" 3. attest-it run --suite <suite-name>");
|
|
2257
|
-
} else {
|
|
2258
|
-
await runNonInteractiveKeygen(options);
|
|
2259
|
-
}
|
|
2260
|
-
} catch (err) {
|
|
2261
|
-
if (err instanceof Error) {
|
|
2262
|
-
error(err.message);
|
|
2263
|
-
} else {
|
|
2264
|
-
error("Unknown error occurred");
|
|
2265
|
-
}
|
|
2266
|
-
process.exit(ExitCode.CONFIG_ERROR);
|
|
2267
|
-
}
|
|
2268
|
-
}
|
|
2269
|
-
async function runNonInteractiveKeygen(options) {
|
|
2270
|
-
log("Checking OpenSSL...");
|
|
2271
|
-
const version = await core.checkOpenSSL();
|
|
2272
|
-
info(`OpenSSL: ${version}`);
|
|
2273
|
-
const publicPath = options.output ?? core.getDefaultPublicKeyPath();
|
|
2274
|
-
if (options.provider === "1password") {
|
|
2275
|
-
if (!options.vault || !options.itemName) {
|
|
2276
|
-
throw new Error("--vault and --item-name are required for 1password provider");
|
|
2277
|
-
}
|
|
2278
|
-
const providerOptions = {
|
|
2279
|
-
vault: options.vault,
|
|
2280
|
-
itemName: options.itemName
|
|
2281
|
-
};
|
|
2282
|
-
if (options.account !== void 0) {
|
|
2283
|
-
providerOptions.account = options.account;
|
|
2284
|
-
}
|
|
2285
|
-
const provider = new core.OnePasswordKeyProvider(providerOptions);
|
|
2286
|
-
log(`Generating keypair with 1Password storage...`);
|
|
2287
|
-
log(`Vault: ${options.vault}`);
|
|
2288
|
-
log(`Item: ${options.itemName}`);
|
|
2289
|
-
const genOptions = { publicKeyPath: publicPath };
|
|
2290
|
-
if (options.force !== void 0) {
|
|
2291
|
-
genOptions.force = options.force;
|
|
2292
|
-
}
|
|
2293
|
-
const result = await provider.generateKeyPair(genOptions);
|
|
2294
|
-
success("Keypair generated successfully!");
|
|
2295
|
-
log("");
|
|
2296
|
-
log("Private key stored in:");
|
|
2297
|
-
log(` ${result.storageDescription}`);
|
|
2298
|
-
log("");
|
|
2299
|
-
log("Public key (commit to repo):");
|
|
2300
|
-
log(` ${result.publicKeyPath}`);
|
|
2301
|
-
} else if (options.provider === "macos-keychain") {
|
|
2302
|
-
if (!options.itemName) {
|
|
2303
|
-
throw new Error("--item-name is required for macos-keychain provider");
|
|
2304
|
-
}
|
|
2305
|
-
const isAvailable = core.MacOSKeychainKeyProvider.isAvailable();
|
|
2306
|
-
if (!isAvailable) {
|
|
2307
|
-
throw new Error("macOS Keychain is not available on this platform");
|
|
2308
|
-
}
|
|
2309
|
-
const provider = new core.MacOSKeychainKeyProvider({
|
|
2310
|
-
itemName: options.itemName
|
|
2311
|
-
});
|
|
2312
|
-
log(`Generating keypair with macOS Keychain storage...`);
|
|
2313
|
-
log(`Item: ${options.itemName}`);
|
|
2314
|
-
const genOptions = { publicKeyPath: publicPath };
|
|
2315
|
-
if (options.force !== void 0) {
|
|
2316
|
-
genOptions.force = options.force;
|
|
2317
|
-
}
|
|
2318
|
-
const result = await provider.generateKeyPair(genOptions);
|
|
2319
|
-
success("Keypair generated successfully!");
|
|
2320
|
-
log("");
|
|
2321
|
-
log("Private key stored in:");
|
|
2322
|
-
log(` ${result.storageDescription}`);
|
|
2323
|
-
log("");
|
|
2324
|
-
log("Public key (commit to repo):");
|
|
2325
|
-
log(` ${result.publicKeyPath}`);
|
|
2326
|
-
} else {
|
|
2327
|
-
const privatePath = options.private ?? core.getDefaultPrivateKeyPath();
|
|
2328
|
-
log(`Private key: ${privatePath}`);
|
|
2329
|
-
log(`Public key: ${publicPath}`);
|
|
2330
|
-
const privateExists = fs__namespace.existsSync(privatePath);
|
|
2331
|
-
const publicExists = fs__namespace.existsSync(publicPath);
|
|
2332
|
-
if ((privateExists || publicExists) && !options.force) {
|
|
2333
|
-
if (privateExists) {
|
|
2334
|
-
warn(`Private key already exists: ${privatePath}`);
|
|
2335
|
-
}
|
|
2336
|
-
if (publicExists) {
|
|
2337
|
-
warn(`Public key already exists: ${publicPath}`);
|
|
2338
|
-
}
|
|
2339
|
-
const shouldOverwrite = await confirmAction({
|
|
2340
|
-
message: "Overwrite existing keys?",
|
|
2341
|
-
default: false
|
|
2342
|
-
});
|
|
2343
|
-
if (!shouldOverwrite) {
|
|
2344
|
-
error("Keygen cancelled");
|
|
2345
|
-
process.exit(ExitCode.CANCELLED);
|
|
2346
|
-
}
|
|
2347
|
-
}
|
|
2348
|
-
log("\nGenerating RSA-2048 keypair...");
|
|
2349
|
-
const result = await core.generateKeyPair({
|
|
2350
|
-
privatePath,
|
|
2351
|
-
publicPath,
|
|
2352
|
-
force: true
|
|
2353
|
-
});
|
|
2354
|
-
await core.setKeyPermissions(result.privatePath);
|
|
2355
|
-
success("Keypair generated successfully!");
|
|
2356
|
-
log("");
|
|
2357
|
-
log("Private key (KEEP SECRET):");
|
|
2358
|
-
log(` ${result.privatePath}`);
|
|
2359
|
-
log("");
|
|
2360
|
-
log("Public key (commit to repo):");
|
|
2361
|
-
log(` ${result.publicPath}`);
|
|
2362
|
-
}
|
|
2363
|
-
log("");
|
|
2364
|
-
info("Important: Back up your private key securely!");
|
|
2365
|
-
log("");
|
|
2366
|
-
log("Next steps:");
|
|
2367
|
-
log(` 1. git add ${publicPath}`);
|
|
2368
|
-
log(" 2. Update .attest-it/config.yaml publicKeyPath if needed");
|
|
2369
|
-
log(" 3. attest-it run --suite <suite-name>");
|
|
2370
|
-
}
|
|
2371
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) => {
|
|
2372
1734
|
await runPrune(options);
|
|
2373
1735
|
});
|
|
@@ -2619,7 +1981,7 @@ async function runSeal(gates, options) {
|
|
|
2619
1981
|
const localConfig = core.loadLocalConfigSync();
|
|
2620
1982
|
if (!localConfig) {
|
|
2621
1983
|
error("No local identity configuration found");
|
|
2622
|
-
error('Run "attest-it
|
|
1984
|
+
error('Run "attest-it identity create" first to set up your identity');
|
|
2623
1985
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
2624
1986
|
}
|
|
2625
1987
|
const identity = core.getActiveIdentity(localConfig);
|
|
@@ -2716,8 +2078,8 @@ async function processSingleGate(gateId, config, identity, identitySlug, sealsFi
|
|
|
2716
2078
|
const keyProvider = createKeyProviderFromIdentity2(identity);
|
|
2717
2079
|
const keyRef = getKeyRefFromIdentity2(identity);
|
|
2718
2080
|
const keyResult = await keyProvider.getPrivateKey(keyRef);
|
|
2719
|
-
const
|
|
2720
|
-
const privateKeyPem = await
|
|
2081
|
+
const fs3 = await import('fs/promises');
|
|
2082
|
+
const privateKeyPem = await fs3.readFile(keyResult.keyPath, "utf8");
|
|
2721
2083
|
await keyResult.cleanup();
|
|
2722
2084
|
const seal = core.createSeal({
|
|
2723
2085
|
gateId,
|
|
@@ -2938,10 +2300,19 @@ async function runCreate() {
|
|
|
2938
2300
|
default: ""
|
|
2939
2301
|
});
|
|
2940
2302
|
info("Checking available key storage providers...");
|
|
2303
|
+
info(
|
|
2304
|
+
"You may see authentication prompts from 1Password, macOS Keychain, or other security tools."
|
|
2305
|
+
);
|
|
2941
2306
|
const opAvailable = await core.OnePasswordKeyProvider.isInstalled();
|
|
2307
|
+
verbose(` 1Password CLI (op): ${opAvailable ? "found" : "not found"}`);
|
|
2942
2308
|
const keychainAvailable = core.MacOSKeychainKeyProvider.isAvailable();
|
|
2309
|
+
verbose(` macOS Keychain: ${keychainAvailable ? "available" : "not available (not macOS)"}`);
|
|
2943
2310
|
const yubikeyInstalled = await core.YubiKeyProvider.isInstalled();
|
|
2311
|
+
verbose(` YubiKey CLI (ykman): ${yubikeyInstalled ? "found" : "not found"}`);
|
|
2944
2312
|
const yubikeyConnected = yubikeyInstalled ? await core.YubiKeyProvider.isConnected() : false;
|
|
2313
|
+
if (yubikeyInstalled) {
|
|
2314
|
+
verbose(` YubiKey device: ${yubikeyConnected ? "connected" : "not connected"}`);
|
|
2315
|
+
}
|
|
2945
2316
|
const configDir = core.getAttestItConfigDir();
|
|
2946
2317
|
const storageChoices = [
|
|
2947
2318
|
{ name: `File system (${path.join(configDir, "keys")})`, value: "file" }
|
|
@@ -2955,24 +2326,24 @@ async function runCreate() {
|
|
|
2955
2326
|
if (yubikeyInstalled) {
|
|
2956
2327
|
const yubikeyLabel = yubikeyConnected ? "YubiKey (encrypted with challenge-response)" : "YubiKey (not connected - insert YubiKey first)";
|
|
2957
2328
|
storageChoices.push({ name: yubikeyLabel, value: "yubikey" });
|
|
2329
|
+
} else {
|
|
2330
|
+
storageChoices.push({
|
|
2331
|
+
name: theme3.muted("YubiKey (install ykman CLI to enable)"),
|
|
2332
|
+
value: "yubikey-disabled",
|
|
2333
|
+
// @ts-expect-error -- @inquirer/prompts supports disabled property but types may not reflect it
|
|
2334
|
+
disabled: true
|
|
2335
|
+
});
|
|
2958
2336
|
}
|
|
2959
2337
|
const keyStorageType = await prompts.select({
|
|
2960
2338
|
message: "Where should the private key be stored?",
|
|
2961
2339
|
choices: storageChoices
|
|
2962
2340
|
});
|
|
2963
|
-
|
|
2964
|
-
log("Generating Ed25519 keypair...");
|
|
2965
|
-
const keyPair = core.generateEd25519KeyPair();
|
|
2966
|
-
let privateKeyRef;
|
|
2967
|
-
let keyStorageDescription;
|
|
2341
|
+
let storageConfig;
|
|
2968
2342
|
switch (keyStorageType) {
|
|
2969
2343
|
case "file": {
|
|
2970
2344
|
const keysDir = path.join(core.getAttestItConfigDir(), "keys");
|
|
2971
|
-
await promises.mkdir(keysDir, { recursive: true });
|
|
2972
2345
|
const keyPath = path.join(keysDir, `${slug}.pem`);
|
|
2973
|
-
|
|
2974
|
-
privateKeyRef = { type: "file", path: keyPath };
|
|
2975
|
-
keyStorageDescription = keyPath;
|
|
2346
|
+
storageConfig = { type: "file", keyPath };
|
|
2976
2347
|
break;
|
|
2977
2348
|
}
|
|
2978
2349
|
case "keychain": {
|
|
@@ -2980,6 +2351,9 @@ async function runCreate() {
|
|
|
2980
2351
|
error("macOS Keychain is not available on this system");
|
|
2981
2352
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
2982
2353
|
}
|
|
2354
|
+
log("");
|
|
2355
|
+
info("Accessing macOS Keychain to list available keychains...");
|
|
2356
|
+
info("You may be prompted to allow access or enter your password.");
|
|
2983
2357
|
const keychains = await core.MacOSKeychainKeyProvider.listKeychains();
|
|
2984
2358
|
if (keychains.length === 0) {
|
|
2985
2359
|
throw new Error("No keychains found on this system");
|
|
@@ -3015,38 +2389,15 @@ async function runCreate() {
|
|
|
3015
2389
|
return true;
|
|
3016
2390
|
}
|
|
3017
2391
|
});
|
|
3018
|
-
|
|
3019
|
-
const { promisify } = await import('util');
|
|
3020
|
-
const execFileAsync = promisify(execFile);
|
|
3021
|
-
const encodedKey = Buffer.from(keyPair.privateKey).toString("base64");
|
|
3022
|
-
try {
|
|
3023
|
-
const addArgs = [
|
|
3024
|
-
"add-generic-password",
|
|
3025
|
-
"-a",
|
|
3026
|
-
"attest-it",
|
|
3027
|
-
"-s",
|
|
3028
|
-
keychainItemName,
|
|
3029
|
-
"-w",
|
|
3030
|
-
encodedKey,
|
|
3031
|
-
"-U",
|
|
3032
|
-
selectedKeychain.path
|
|
3033
|
-
];
|
|
3034
|
-
await execFileAsync("security", addArgs);
|
|
3035
|
-
} catch (err) {
|
|
3036
|
-
throw new Error(
|
|
3037
|
-
`Failed to store key in macOS Keychain: ${err instanceof Error ? err.message : String(err)}`
|
|
3038
|
-
);
|
|
3039
|
-
}
|
|
3040
|
-
privateKeyRef = {
|
|
3041
|
-
type: "keychain",
|
|
3042
|
-
service: keychainItemName,
|
|
3043
|
-
account: "attest-it",
|
|
3044
|
-
keychain: selectedKeychain.path
|
|
3045
|
-
};
|
|
3046
|
-
keyStorageDescription = `macOS Keychain: ${selectedKeychain.name}/${keychainItemName}`;
|
|
2392
|
+
storageConfig = { type: "keychain", selectedKeychain, keychainItemName };
|
|
3047
2393
|
break;
|
|
3048
2394
|
}
|
|
3049
2395
|
case "1password": {
|
|
2396
|
+
log("");
|
|
2397
|
+
info("Accessing 1Password to list your accounts and vaults...");
|
|
2398
|
+
info(
|
|
2399
|
+
"You may see biometric prompts or be asked to unlock 1Password for each configured account."
|
|
2400
|
+
);
|
|
3050
2401
|
const accounts = await core.OnePasswordKeyProvider.listAccounts();
|
|
3051
2402
|
if (accounts.length === 0) {
|
|
3052
2403
|
throw new Error(
|
|
@@ -3067,7 +2418,7 @@ async function runCreate() {
|
|
|
3067
2418
|
"--format=json"
|
|
3068
2419
|
]);
|
|
3069
2420
|
const details = JSON.parse(stdout);
|
|
3070
|
-
const name2 = details !== null && typeof details === "object" && "name" in details && typeof details.name === "string" ? details.name :
|
|
2421
|
+
const name2 = details !== null && typeof details === "object" && "name" in details && typeof details.name === "string" ? details.name : "[Could not read account name]";
|
|
3071
2422
|
return {
|
|
3072
2423
|
url: acc.url,
|
|
3073
2424
|
email: acc.email,
|
|
@@ -3077,7 +2428,7 @@ async function runCreate() {
|
|
|
3077
2428
|
return {
|
|
3078
2429
|
url: acc.url,
|
|
3079
2430
|
email: acc.email,
|
|
3080
|
-
name:
|
|
2431
|
+
name: "[Could not read account name]"
|
|
3081
2432
|
};
|
|
3082
2433
|
}
|
|
3083
2434
|
})
|
|
@@ -3119,43 +2470,21 @@ async function runCreate() {
|
|
|
3119
2470
|
return true;
|
|
3120
2471
|
}
|
|
3121
2472
|
});
|
|
3122
|
-
const
|
|
3123
|
-
const
|
|
3124
|
-
|
|
3125
|
-
const tempPrivatePath = path.join(tempDir, "private.pem");
|
|
3126
|
-
try {
|
|
3127
|
-
await promises.writeFile(tempPrivatePath, keyPair.privateKey, { mode: 384 });
|
|
3128
|
-
const { execFile: execFile2 } = await import('child_process');
|
|
3129
|
-
const { promisify: promisify2 } = await import('util');
|
|
3130
|
-
const execFileAsync2 = promisify2(execFile2);
|
|
3131
|
-
const opArgs = [
|
|
3132
|
-
"document",
|
|
3133
|
-
"create",
|
|
3134
|
-
tempPrivatePath,
|
|
3135
|
-
"--title",
|
|
3136
|
-
item,
|
|
3137
|
-
"--vault",
|
|
3138
|
-
selectedVault
|
|
3139
|
-
];
|
|
3140
|
-
if (selectedAccount) {
|
|
3141
|
-
opArgs.push("--account", selectedAccount);
|
|
3142
|
-
}
|
|
3143
|
-
await execFileAsync2("op", opArgs);
|
|
3144
|
-
} finally {
|
|
3145
|
-
const { rm } = await import('fs/promises');
|
|
3146
|
-
await rm(tempDir, { recursive: true, force: true }).catch(() => {
|
|
3147
|
-
});
|
|
3148
|
-
}
|
|
3149
|
-
privateKeyRef = {
|
|
2473
|
+
const selectedAccountDetails = accountDetails.find((acc) => acc.url === selectedAccount);
|
|
2474
|
+
const accountDisplayName = selectedAccountDetails?.name ?? selectedAccount;
|
|
2475
|
+
storageConfig = {
|
|
3150
2476
|
type: "1password",
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
2477
|
+
selectedAccount,
|
|
2478
|
+
accountDisplayName,
|
|
2479
|
+
selectedVault,
|
|
2480
|
+
item
|
|
3154
2481
|
};
|
|
3155
|
-
keyStorageDescription = `1Password (${selectedVault}/${item})`;
|
|
3156
2482
|
break;
|
|
3157
2483
|
}
|
|
3158
2484
|
case "yubikey": {
|
|
2485
|
+
log("");
|
|
2486
|
+
info("Accessing YubiKey to detect connected devices...");
|
|
2487
|
+
info("Your private key will be encrypted using HMAC challenge-response from the YubiKey.");
|
|
3159
2488
|
if (!await core.YubiKeyProvider.isConnected()) {
|
|
3160
2489
|
error("No YubiKey detected. Please insert your YubiKey and try again.");
|
|
3161
2490
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
@@ -3207,25 +2536,115 @@ async function runCreate() {
|
|
|
3207
2536
|
}
|
|
3208
2537
|
});
|
|
3209
2538
|
const keysDir = path.join(core.getAttestItConfigDir(), "keys");
|
|
3210
|
-
await promises.mkdir(keysDir, { recursive: true });
|
|
3211
2539
|
const encryptedKeyPath = path.join(keysDir, encryptedKeyName);
|
|
2540
|
+
storageConfig = { type: "yubikey", selectedSerial, slot, encryptedKeyPath };
|
|
2541
|
+
break;
|
|
2542
|
+
}
|
|
2543
|
+
default:
|
|
2544
|
+
throw new Error(`Unknown key storage type: ${keyStorageType}`);
|
|
2545
|
+
}
|
|
2546
|
+
log("");
|
|
2547
|
+
log("Generating Ed25519 keypair...");
|
|
2548
|
+
const keyPair = core.generateEd25519KeyPair();
|
|
2549
|
+
let privateKeyRef;
|
|
2550
|
+
let keyStorageDescription;
|
|
2551
|
+
switch (storageConfig.type) {
|
|
2552
|
+
case "file": {
|
|
2553
|
+
log("");
|
|
2554
|
+
info("Creating private key file on disk...");
|
|
2555
|
+
const keysDir = path.join(core.getAttestItConfigDir(), "keys");
|
|
2556
|
+
await promises.mkdir(keysDir, { recursive: true });
|
|
2557
|
+
await promises.writeFile(storageConfig.keyPath, keyPair.privateKey, { mode: 384 });
|
|
2558
|
+
privateKeyRef = { type: "file", path: storageConfig.keyPath };
|
|
2559
|
+
keyStorageDescription = storageConfig.keyPath;
|
|
2560
|
+
break;
|
|
2561
|
+
}
|
|
2562
|
+
case "keychain": {
|
|
2563
|
+
const { execFile } = await import('child_process');
|
|
2564
|
+
const { promisify } = await import('util');
|
|
2565
|
+
const execFileAsync = promisify(execFile);
|
|
2566
|
+
const encodedKey = Buffer.from(keyPair.privateKey).toString("base64");
|
|
2567
|
+
try {
|
|
2568
|
+
const addArgs = [
|
|
2569
|
+
"add-generic-password",
|
|
2570
|
+
"-a",
|
|
2571
|
+
"attest-it",
|
|
2572
|
+
"-s",
|
|
2573
|
+
storageConfig.keychainItemName,
|
|
2574
|
+
"-w",
|
|
2575
|
+
encodedKey,
|
|
2576
|
+
"-U",
|
|
2577
|
+
storageConfig.selectedKeychain.path
|
|
2578
|
+
];
|
|
2579
|
+
await execFileAsync("security", addArgs);
|
|
2580
|
+
} catch (err) {
|
|
2581
|
+
throw new Error(
|
|
2582
|
+
`Failed to store key in macOS Keychain: ${err instanceof Error ? err.message : String(err)}`
|
|
2583
|
+
);
|
|
2584
|
+
}
|
|
2585
|
+
privateKeyRef = {
|
|
2586
|
+
type: "keychain",
|
|
2587
|
+
service: storageConfig.keychainItemName,
|
|
2588
|
+
account: "attest-it",
|
|
2589
|
+
keychain: storageConfig.selectedKeychain.path
|
|
2590
|
+
};
|
|
2591
|
+
keyStorageDescription = `macOS Keychain: ${storageConfig.selectedKeychain.name}/${storageConfig.keychainItemName}`;
|
|
2592
|
+
break;
|
|
2593
|
+
}
|
|
2594
|
+
case "1password": {
|
|
2595
|
+
const { tmpdir } = await import('os');
|
|
2596
|
+
const tempDir = path.join(tmpdir(), `attest-it-${String(Date.now())}`);
|
|
2597
|
+
await promises.mkdir(tempDir, { recursive: true });
|
|
2598
|
+
const tempPrivatePath = path.join(tempDir, "private.pem");
|
|
2599
|
+
try {
|
|
2600
|
+
await promises.writeFile(tempPrivatePath, keyPair.privateKey, { mode: 384 });
|
|
2601
|
+
const { execFile } = await import('child_process');
|
|
2602
|
+
const { promisify } = await import('util');
|
|
2603
|
+
const execFileAsync = promisify(execFile);
|
|
2604
|
+
const opArgs = [
|
|
2605
|
+
"document",
|
|
2606
|
+
"create",
|
|
2607
|
+
tempPrivatePath,
|
|
2608
|
+
"--title",
|
|
2609
|
+
storageConfig.item,
|
|
2610
|
+
"--vault",
|
|
2611
|
+
storageConfig.selectedVault,
|
|
2612
|
+
"--account",
|
|
2613
|
+
storageConfig.selectedAccount
|
|
2614
|
+
];
|
|
2615
|
+
await execFileAsync("op", opArgs);
|
|
2616
|
+
} finally {
|
|
2617
|
+
const { rm } = await import('fs/promises');
|
|
2618
|
+
await rm(tempDir, { recursive: true, force: true }).catch(() => {
|
|
2619
|
+
});
|
|
2620
|
+
}
|
|
2621
|
+
privateKeyRef = {
|
|
2622
|
+
type: "1password",
|
|
2623
|
+
vault: storageConfig.selectedVault,
|
|
2624
|
+
item: storageConfig.item,
|
|
2625
|
+
account: storageConfig.selectedAccount
|
|
2626
|
+
};
|
|
2627
|
+
keyStorageDescription = `1Password (${storageConfig.accountDisplayName}/${storageConfig.selectedVault}/${storageConfig.item})`;
|
|
2628
|
+
break;
|
|
2629
|
+
}
|
|
2630
|
+
case "yubikey": {
|
|
2631
|
+
const keysDir = path.join(core.getAttestItConfigDir(), "keys");
|
|
2632
|
+
await promises.mkdir(keysDir, { recursive: true });
|
|
3212
2633
|
const result = await core.YubiKeyProvider.encryptPrivateKey({
|
|
3213
2634
|
privateKey: keyPair.privateKey,
|
|
3214
|
-
encryptedKeyPath,
|
|
3215
|
-
slot,
|
|
3216
|
-
serial: selectedSerial
|
|
2635
|
+
encryptedKeyPath: storageConfig.encryptedKeyPath,
|
|
2636
|
+
slot: storageConfig.slot,
|
|
2637
|
+
serial: storageConfig.selectedSerial
|
|
3217
2638
|
});
|
|
3218
2639
|
privateKeyRef = {
|
|
3219
2640
|
type: "yubikey",
|
|
3220
2641
|
encryptedKeyPath: result.encryptedKeyPath,
|
|
3221
|
-
slot,
|
|
3222
|
-
serial: selectedSerial
|
|
2642
|
+
slot: storageConfig.slot,
|
|
2643
|
+
serial: storageConfig.selectedSerial
|
|
3223
2644
|
};
|
|
3224
2645
|
keyStorageDescription = result.storageDescription;
|
|
3225
2646
|
break;
|
|
3226
2647
|
}
|
|
3227
|
-
default:
|
|
3228
|
-
throw new Error(`Unknown key storage type: ${keyStorageType}`);
|
|
3229
2648
|
}
|
|
3230
2649
|
const identity = {
|
|
3231
2650
|
name,
|
|
@@ -3269,9 +2688,9 @@ async function runCreate() {
|
|
|
3269
2688
|
log("");
|
|
3270
2689
|
log(theme3.blue.bold()("Public key saved to:"));
|
|
3271
2690
|
log(` ${publicKeyResult.homePath}`);
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
2691
|
+
log("");
|
|
2692
|
+
log("To add yourself to a project, run:");
|
|
2693
|
+
log(theme3.blue(" attest-it team join"));
|
|
3275
2694
|
log("");
|
|
3276
2695
|
if (!existingConfig) {
|
|
3277
2696
|
success(`Set as active identity`);
|
|
@@ -3392,155 +2811,6 @@ async function runShow(slug) {
|
|
|
3392
2811
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
3393
2812
|
}
|
|
3394
2813
|
}
|
|
3395
|
-
var editCommand = new commander.Command("edit").description("Edit identity or rotate keypair").argument("<slug>", "Identity slug to edit").action(async (slug) => {
|
|
3396
|
-
await runEdit(slug);
|
|
3397
|
-
});
|
|
3398
|
-
async function runEdit(slug) {
|
|
3399
|
-
try {
|
|
3400
|
-
const config = await core.loadLocalConfig();
|
|
3401
|
-
if (!config) {
|
|
3402
|
-
error("No identities configured");
|
|
3403
|
-
process.exit(ExitCode.CONFIG_ERROR);
|
|
3404
|
-
}
|
|
3405
|
-
const identity = config.identities[slug];
|
|
3406
|
-
if (!identity) {
|
|
3407
|
-
error(`Identity "${slug}" not found`);
|
|
3408
|
-
process.exit(ExitCode.CONFIG_ERROR);
|
|
3409
|
-
}
|
|
3410
|
-
const theme3 = getTheme2();
|
|
3411
|
-
log("");
|
|
3412
|
-
log(theme3.blue.bold()(`Edit Identity: ${slug}`));
|
|
3413
|
-
log("");
|
|
3414
|
-
const name = await prompts.input({
|
|
3415
|
-
message: "Display name:",
|
|
3416
|
-
default: identity.name,
|
|
3417
|
-
validate: (value) => {
|
|
3418
|
-
if (!value || value.trim().length === 0) {
|
|
3419
|
-
return "Name cannot be empty";
|
|
3420
|
-
}
|
|
3421
|
-
return true;
|
|
3422
|
-
}
|
|
3423
|
-
});
|
|
3424
|
-
const email = await prompts.input({
|
|
3425
|
-
message: "Email (optional):",
|
|
3426
|
-
default: identity.email ?? ""
|
|
3427
|
-
});
|
|
3428
|
-
const github = await prompts.input({
|
|
3429
|
-
message: "GitHub username (optional):",
|
|
3430
|
-
default: identity.github ?? ""
|
|
3431
|
-
});
|
|
3432
|
-
const rotateKey = await prompts.confirm({
|
|
3433
|
-
message: "Rotate keypair (generate new keys)?",
|
|
3434
|
-
default: false
|
|
3435
|
-
});
|
|
3436
|
-
let publicKey = identity.publicKey;
|
|
3437
|
-
const privateKeyRef = identity.privateKey;
|
|
3438
|
-
if (rotateKey) {
|
|
3439
|
-
log("");
|
|
3440
|
-
log("Generating new Ed25519 keypair...");
|
|
3441
|
-
const keyPair = core.generateEd25519KeyPair();
|
|
3442
|
-
publicKey = keyPair.publicKey;
|
|
3443
|
-
switch (identity.privateKey.type) {
|
|
3444
|
-
case "file": {
|
|
3445
|
-
await promises.writeFile(identity.privateKey.path, keyPair.privateKey, { mode: 384 });
|
|
3446
|
-
log(` Updated private key at: ${identity.privateKey.path}`);
|
|
3447
|
-
break;
|
|
3448
|
-
}
|
|
3449
|
-
case "keychain": {
|
|
3450
|
-
const { execFile } = await import('child_process');
|
|
3451
|
-
const { promisify } = await import('util');
|
|
3452
|
-
const execFileAsync = promisify(execFile);
|
|
3453
|
-
const encodedKey = Buffer.from(keyPair.privateKey).toString("base64");
|
|
3454
|
-
try {
|
|
3455
|
-
await execFileAsync("security", [
|
|
3456
|
-
"delete-generic-password",
|
|
3457
|
-
"-s",
|
|
3458
|
-
identity.privateKey.service,
|
|
3459
|
-
"-a",
|
|
3460
|
-
identity.privateKey.account
|
|
3461
|
-
]);
|
|
3462
|
-
await execFileAsync("security", [
|
|
3463
|
-
"add-generic-password",
|
|
3464
|
-
"-s",
|
|
3465
|
-
identity.privateKey.service,
|
|
3466
|
-
"-a",
|
|
3467
|
-
identity.privateKey.account,
|
|
3468
|
-
"-w",
|
|
3469
|
-
encodedKey,
|
|
3470
|
-
"-U"
|
|
3471
|
-
]);
|
|
3472
|
-
log(` Updated private key in macOS Keychain`);
|
|
3473
|
-
} catch (err) {
|
|
3474
|
-
throw new Error(
|
|
3475
|
-
`Failed to update key in macOS Keychain: ${err instanceof Error ? err.message : String(err)}`
|
|
3476
|
-
);
|
|
3477
|
-
}
|
|
3478
|
-
break;
|
|
3479
|
-
}
|
|
3480
|
-
case "1password": {
|
|
3481
|
-
const { execFile } = await import('child_process');
|
|
3482
|
-
const { promisify } = await import('util');
|
|
3483
|
-
const execFileAsync = promisify(execFile);
|
|
3484
|
-
try {
|
|
3485
|
-
const opArgs = [
|
|
3486
|
-
"item",
|
|
3487
|
-
"edit",
|
|
3488
|
-
identity.privateKey.item,
|
|
3489
|
-
"--vault",
|
|
3490
|
-
identity.privateKey.vault,
|
|
3491
|
-
`privateKey[password]=${keyPair.privateKey}`
|
|
3492
|
-
];
|
|
3493
|
-
if (identity.privateKey.account) {
|
|
3494
|
-
opArgs.push("--account", identity.privateKey.account);
|
|
3495
|
-
}
|
|
3496
|
-
await execFileAsync("op", opArgs);
|
|
3497
|
-
log(` Updated private key in 1Password`);
|
|
3498
|
-
} catch (err) {
|
|
3499
|
-
throw new Error(
|
|
3500
|
-
`Failed to update key in 1Password: ${err instanceof Error ? err.message : String(err)}`
|
|
3501
|
-
);
|
|
3502
|
-
}
|
|
3503
|
-
break;
|
|
3504
|
-
}
|
|
3505
|
-
}
|
|
3506
|
-
}
|
|
3507
|
-
const updatedIdentity = {
|
|
3508
|
-
name,
|
|
3509
|
-
publicKey,
|
|
3510
|
-
privateKey: privateKeyRef,
|
|
3511
|
-
...email && { email },
|
|
3512
|
-
...github && { github }
|
|
3513
|
-
};
|
|
3514
|
-
const newConfig = {
|
|
3515
|
-
...config,
|
|
3516
|
-
identities: {
|
|
3517
|
-
...config.identities,
|
|
3518
|
-
[slug]: updatedIdentity
|
|
3519
|
-
}
|
|
3520
|
-
};
|
|
3521
|
-
await core.saveLocalConfig(newConfig);
|
|
3522
|
-
log("");
|
|
3523
|
-
success("Identity updated successfully");
|
|
3524
|
-
log("");
|
|
3525
|
-
if (rotateKey) {
|
|
3526
|
-
log(" New Public Key: " + publicKey.slice(0, 32) + "...");
|
|
3527
|
-
log("");
|
|
3528
|
-
log(
|
|
3529
|
-
theme3.yellow(
|
|
3530
|
-
" Warning: If this identity is used in team configurations,\n you must update those configurations with the new public key."
|
|
3531
|
-
)
|
|
3532
|
-
);
|
|
3533
|
-
log("");
|
|
3534
|
-
}
|
|
3535
|
-
} catch (err) {
|
|
3536
|
-
if (err instanceof Error) {
|
|
3537
|
-
error(err.message);
|
|
3538
|
-
} else {
|
|
3539
|
-
error("Unknown error occurred");
|
|
3540
|
-
}
|
|
3541
|
-
process.exit(ExitCode.CONFIG_ERROR);
|
|
3542
|
-
}
|
|
3543
|
-
}
|
|
3544
2814
|
|
|
3545
2815
|
// src/utils/format-key-location.ts
|
|
3546
2816
|
function formatKeyLocation(privateKey) {
|
|
@@ -3759,7 +3029,7 @@ async function runExport(slug) {
|
|
|
3759
3029
|
}
|
|
3760
3030
|
|
|
3761
3031
|
// src/commands/identity/index.ts
|
|
3762
|
-
var identityCommand = new commander.Command("identity").description("Manage local identities and keypairs").addCommand(listCommand).addCommand(createCommand).addCommand(useCommand).addCommand(showCommand).addCommand(
|
|
3032
|
+
var identityCommand = new commander.Command("identity").description("Manage local identities and keypairs").addCommand(listCommand).addCommand(createCommand).addCommand(useCommand).addCommand(showCommand).addCommand(removeCommand).addCommand(exportCommand);
|
|
3763
3033
|
var whoamiCommand = new commander.Command("whoami").description("Show the current active identity").action(async () => {
|
|
3764
3034
|
await runWhoami();
|
|
3765
3035
|
});
|
|
@@ -3860,6 +3130,49 @@ async function runList2() {
|
|
|
3860
3130
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
3861
3131
|
}
|
|
3862
3132
|
}
|
|
3133
|
+
async function promptForGateAuthorization(gates) {
|
|
3134
|
+
if (!gates || Object.keys(gates).length === 0) {
|
|
3135
|
+
return [];
|
|
3136
|
+
}
|
|
3137
|
+
const gateChoices = Object.entries(gates).map(([gateId, gate]) => ({
|
|
3138
|
+
name: `${gateId} - ${gate.name}`,
|
|
3139
|
+
value: gateId
|
|
3140
|
+
}));
|
|
3141
|
+
const authorizedGates = await prompts.checkbox({
|
|
3142
|
+
message: "Select gates to authorize (use space to select):",
|
|
3143
|
+
choices: gateChoices
|
|
3144
|
+
});
|
|
3145
|
+
return authorizedGates;
|
|
3146
|
+
}
|
|
3147
|
+
function addTeamMemberToConfig(config, memberSlug, memberData, authorizedGates) {
|
|
3148
|
+
const existingTeam = config.team ?? {};
|
|
3149
|
+
const updatedConfig = {
|
|
3150
|
+
...config,
|
|
3151
|
+
team: {
|
|
3152
|
+
...existingTeam,
|
|
3153
|
+
[memberSlug]: {
|
|
3154
|
+
name: memberData.name,
|
|
3155
|
+
publicKey: memberData.publicKey,
|
|
3156
|
+
publicKeyAlgorithm: memberData.publicKeyAlgorithm ?? "ed25519",
|
|
3157
|
+
...memberData.email ? { email: memberData.email } : {},
|
|
3158
|
+
...memberData.github ? { github: memberData.github } : {}
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
};
|
|
3162
|
+
if (authorizedGates.length > 0 && updatedConfig.gates) {
|
|
3163
|
+
for (const gateId of authorizedGates) {
|
|
3164
|
+
const gate = updatedConfig.gates[gateId];
|
|
3165
|
+
if (gate) {
|
|
3166
|
+
if (!gate.authorizedSigners.includes(memberSlug)) {
|
|
3167
|
+
gate.authorizedSigners.push(memberSlug);
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
return updatedConfig;
|
|
3173
|
+
}
|
|
3174
|
+
|
|
3175
|
+
// src/commands/team/add.ts
|
|
3863
3176
|
var addCommand = new commander.Command("add").description("Add a new team member").action(async () => {
|
|
3864
3177
|
await runAdd();
|
|
3865
3178
|
});
|
|
@@ -3931,45 +3244,22 @@ async function runAdd() {
|
|
|
3931
3244
|
message: "Public key:",
|
|
3932
3245
|
validate: validatePublicKey
|
|
3933
3246
|
});
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
const gateChoices = Object.entries(attestItConfig.gates).map(([gateId, gate]) => ({
|
|
3938
|
-
name: `${gateId} - ${gate.name}`,
|
|
3939
|
-
value: gateId
|
|
3940
|
-
}));
|
|
3941
|
-
authorizedGates = await prompts.checkbox({
|
|
3942
|
-
message: "Select gates to authorize (use space to select):",
|
|
3943
|
-
choices: gateChoices
|
|
3944
|
-
});
|
|
3945
|
-
}
|
|
3946
|
-
const teamMember = {
|
|
3247
|
+
log("");
|
|
3248
|
+
const authorizedGates = await promptForGateAuthorization(attestItConfig.gates);
|
|
3249
|
+
const memberData = {
|
|
3947
3250
|
name,
|
|
3948
|
-
publicKey: publicKey.trim()
|
|
3251
|
+
publicKey: publicKey.trim(),
|
|
3252
|
+
publicKeyAlgorithm: "ed25519"
|
|
3949
3253
|
};
|
|
3950
|
-
|
|
3951
|
-
|
|
3254
|
+
const trimmedEmail = email.trim();
|
|
3255
|
+
const trimmedGithub = github.trim();
|
|
3256
|
+
if (trimmedEmail && trimmedEmail.length > 0) {
|
|
3257
|
+
memberData.email = trimmedEmail;
|
|
3952
3258
|
}
|
|
3953
|
-
if (
|
|
3954
|
-
|
|
3955
|
-
}
|
|
3956
|
-
const updatedConfig = {
|
|
3957
|
-
...config,
|
|
3958
|
-
team: {
|
|
3959
|
-
...existingTeam,
|
|
3960
|
-
[slug]: teamMember
|
|
3961
|
-
}
|
|
3962
|
-
};
|
|
3963
|
-
if (authorizedGates.length > 0 && updatedConfig.gates) {
|
|
3964
|
-
for (const gateId of authorizedGates) {
|
|
3965
|
-
const gate = updatedConfig.gates[gateId];
|
|
3966
|
-
if (gate) {
|
|
3967
|
-
if (!gate.authorizedSigners.includes(slug)) {
|
|
3968
|
-
gate.authorizedSigners.push(slug);
|
|
3969
|
-
}
|
|
3970
|
-
}
|
|
3971
|
-
}
|
|
3259
|
+
if (trimmedGithub && trimmedGithub.length > 0) {
|
|
3260
|
+
memberData.github = trimmedGithub;
|
|
3972
3261
|
}
|
|
3262
|
+
const updatedConfig = addTeamMemberToConfig(config, slug, memberData, authorizedGates);
|
|
3973
3263
|
const configPath = core.findConfigPath();
|
|
3974
3264
|
if (!configPath) {
|
|
3975
3265
|
error("Configuration file not found");
|
|
@@ -3992,127 +3282,73 @@ async function runAdd() {
|
|
|
3992
3282
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
3993
3283
|
}
|
|
3994
3284
|
}
|
|
3995
|
-
var
|
|
3996
|
-
await
|
|
3285
|
+
var joinCommand = new commander.Command("join").description("Add yourself to the project team using your active identity").action(async () => {
|
|
3286
|
+
await runJoin();
|
|
3997
3287
|
});
|
|
3998
|
-
function
|
|
3999
|
-
if (!value || value.trim().length === 0) {
|
|
4000
|
-
return "Public key cannot be empty";
|
|
4001
|
-
}
|
|
4002
|
-
const base64Regex = /^[A-Za-z0-9+/]+=*$/;
|
|
4003
|
-
if (!base64Regex.test(value)) {
|
|
4004
|
-
return "Public key must be valid Base64";
|
|
4005
|
-
}
|
|
4006
|
-
if (value.length !== 44) {
|
|
4007
|
-
return "Public key must be 44 characters (32 bytes in Base64)";
|
|
4008
|
-
}
|
|
4009
|
-
try {
|
|
4010
|
-
const decoded = Buffer.from(value, "base64");
|
|
4011
|
-
if (decoded.length !== 32) {
|
|
4012
|
-
return "Public key must decode to 32 bytes";
|
|
4013
|
-
}
|
|
4014
|
-
} catch {
|
|
4015
|
-
return "Invalid Base64 encoding";
|
|
4016
|
-
}
|
|
4017
|
-
return true;
|
|
4018
|
-
}
|
|
4019
|
-
async function runEdit2(slug) {
|
|
3288
|
+
async function runJoin() {
|
|
4020
3289
|
try {
|
|
4021
3290
|
const theme3 = getTheme2();
|
|
4022
|
-
const config = await core.loadConfig();
|
|
4023
|
-
const attestItConfig = core.toAttestItConfig(config);
|
|
4024
|
-
const existingMember = attestItConfig.team?.[slug];
|
|
4025
|
-
if (!existingMember) {
|
|
4026
|
-
error(`Team member "${slug}" not found`);
|
|
4027
|
-
process.exit(ExitCode.CONFIG_ERROR);
|
|
4028
|
-
}
|
|
4029
3291
|
log("");
|
|
4030
|
-
log(theme3.blue.bold()(
|
|
3292
|
+
log(theme3.blue.bold()("Join Project Team"));
|
|
4031
3293
|
log("");
|
|
4032
|
-
|
|
3294
|
+
const localConfig = await core.loadLocalConfig();
|
|
3295
|
+
if (!localConfig) {
|
|
3296
|
+
error('No identity found. Run "attest-it identity create" first.');
|
|
3297
|
+
process.exit(ExitCode.CONFIG_ERROR);
|
|
3298
|
+
}
|
|
3299
|
+
const activeIdentity = core.getActiveIdentity(localConfig);
|
|
3300
|
+
if (!activeIdentity) {
|
|
3301
|
+
error('No active identity. Run "attest-it identity use <slug>" to select one.');
|
|
3302
|
+
process.exit(ExitCode.CONFIG_ERROR);
|
|
3303
|
+
}
|
|
3304
|
+
const activeSlug = localConfig.activeIdentity;
|
|
3305
|
+
info(`Using identity: ${activeSlug}`);
|
|
3306
|
+
log(` Name: ${activeIdentity.name}`);
|
|
3307
|
+
log(` Public Key: ${activeIdentity.publicKey.slice(0, 32)}...`);
|
|
4033
3308
|
log("");
|
|
4034
|
-
const
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
});
|
|
4044
|
-
const email = await prompts.input({
|
|
4045
|
-
message: "Email (optional):",
|
|
4046
|
-
default: existingMember.email ?? ""
|
|
4047
|
-
});
|
|
4048
|
-
const github = await prompts.input({
|
|
4049
|
-
message: "GitHub username (optional):",
|
|
4050
|
-
default: existingMember.github ?? ""
|
|
4051
|
-
});
|
|
4052
|
-
const updateKey = await prompts.confirm({
|
|
4053
|
-
message: "Update public key?",
|
|
4054
|
-
default: false
|
|
4055
|
-
});
|
|
4056
|
-
let publicKey = existingMember.publicKey;
|
|
4057
|
-
if (updateKey) {
|
|
4058
|
-
log("");
|
|
4059
|
-
log("Paste the new public key:");
|
|
4060
|
-
publicKey = await prompts.input({
|
|
4061
|
-
message: "Public key:",
|
|
4062
|
-
default: existingMember.publicKey,
|
|
4063
|
-
validate: validatePublicKey2
|
|
4064
|
-
});
|
|
3309
|
+
const config = await core.loadConfig();
|
|
3310
|
+
const attestItConfig = core.toAttestItConfig(config);
|
|
3311
|
+
const existingTeam = attestItConfig.team ?? {};
|
|
3312
|
+
const existingMemberWithKey = Object.entries(existingTeam).find(
|
|
3313
|
+
([, member]) => member.publicKey === activeIdentity.publicKey
|
|
3314
|
+
);
|
|
3315
|
+
if (existingMemberWithKey) {
|
|
3316
|
+
error(`You're already a team member as "${existingMemberWithKey[0]}"`);
|
|
3317
|
+
process.exit(ExitCode.CONFIG_ERROR);
|
|
4065
3318
|
}
|
|
4066
|
-
|
|
4067
|
-
if (
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
3319
|
+
let slug = activeSlug;
|
|
3320
|
+
if (existingTeam[slug]) {
|
|
3321
|
+
log(`Slug "${slug}" is already taken by another team member.`);
|
|
3322
|
+
slug = await prompts.input({
|
|
3323
|
+
message: "Choose a different slug:",
|
|
3324
|
+
validate: (value) => {
|
|
3325
|
+
if (!value || value.trim().length === 0) {
|
|
3326
|
+
return "Slug cannot be empty";
|
|
3327
|
+
}
|
|
3328
|
+
if (!/^[a-z0-9-]+$/.test(value)) {
|
|
3329
|
+
return "Slug must contain only lowercase letters, numbers, and hyphens";
|
|
3330
|
+
}
|
|
3331
|
+
if (existingTeam[value]) {
|
|
3332
|
+
return `Slug "${value}" is already taken`;
|
|
3333
|
+
}
|
|
3334
|
+
return true;
|
|
4071
3335
|
}
|
|
4072
|
-
}
|
|
4073
|
-
}
|
|
4074
|
-
let selectedGates = currentGates;
|
|
4075
|
-
if (attestItConfig.gates && Object.keys(attestItConfig.gates).length > 0) {
|
|
4076
|
-
log("");
|
|
4077
|
-
const gateChoices = Object.entries(attestItConfig.gates).map(([gateId, gate]) => ({
|
|
4078
|
-
name: `${gateId} - ${gate.name}`,
|
|
4079
|
-
value: gateId,
|
|
4080
|
-
checked: currentGates.includes(gateId)
|
|
4081
|
-
}));
|
|
4082
|
-
selectedGates = await prompts.checkbox({
|
|
4083
|
-
message: "Select gates to authorize (use space to select):",
|
|
4084
|
-
choices: gateChoices
|
|
4085
3336
|
});
|
|
4086
3337
|
}
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
3338
|
+
log("");
|
|
3339
|
+
const authorizedGates = await promptForGateAuthorization(attestItConfig.gates);
|
|
3340
|
+
const memberData = {
|
|
3341
|
+
name: activeIdentity.name,
|
|
3342
|
+
publicKey: activeIdentity.publicKey,
|
|
3343
|
+
publicKeyAlgorithm: "ed25519"
|
|
4090
3344
|
};
|
|
4091
|
-
if (email
|
|
4092
|
-
|
|
4093
|
-
}
|
|
4094
|
-
if (github && github.trim().length > 0) {
|
|
4095
|
-
updatedMember.github = github.trim();
|
|
3345
|
+
if (activeIdentity.email) {
|
|
3346
|
+
memberData.email = activeIdentity.email;
|
|
4096
3347
|
}
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
team: {
|
|
4100
|
-
...attestItConfig.team,
|
|
4101
|
-
[slug]: updatedMember
|
|
4102
|
-
}
|
|
4103
|
-
};
|
|
4104
|
-
if (updatedConfig.gates) {
|
|
4105
|
-
for (const [gateId, gate] of Object.entries(updatedConfig.gates)) {
|
|
4106
|
-
if (currentGates.includes(gateId) && !selectedGates.includes(gateId)) {
|
|
4107
|
-
gate.authorizedSigners = gate.authorizedSigners.filter((s) => s !== slug);
|
|
4108
|
-
}
|
|
4109
|
-
if (!currentGates.includes(gateId) && selectedGates.includes(gateId)) {
|
|
4110
|
-
if (!gate.authorizedSigners.includes(slug)) {
|
|
4111
|
-
gate.authorizedSigners.push(slug);
|
|
4112
|
-
}
|
|
4113
|
-
}
|
|
4114
|
-
}
|
|
3348
|
+
if (activeIdentity.github) {
|
|
3349
|
+
memberData.github = activeIdentity.github;
|
|
4115
3350
|
}
|
|
3351
|
+
const updatedConfig = addTeamMemberToConfig(config, slug, memberData, authorizedGates);
|
|
4116
3352
|
const configPath = core.findConfigPath();
|
|
4117
3353
|
if (!configPath) {
|
|
4118
3354
|
error("Configuration file not found");
|
|
@@ -4121,11 +3357,9 @@ async function runEdit2(slug) {
|
|
|
4121
3357
|
const yamlContent = yaml.stringify(updatedConfig);
|
|
4122
3358
|
await promises.writeFile(configPath, yamlContent, "utf8");
|
|
4123
3359
|
log("");
|
|
4124
|
-
success(`Team member "${slug}"
|
|
4125
|
-
if (
|
|
4126
|
-
log(`Authorized for gates: ${
|
|
4127
|
-
} else {
|
|
4128
|
-
log("Not authorized for any gates");
|
|
3360
|
+
success(`Team member "${slug}" added successfully`);
|
|
3361
|
+
if (authorizedGates.length > 0) {
|
|
3362
|
+
log(`Authorized for gates: ${authorizedGates.join(", ")}`);
|
|
4129
3363
|
}
|
|
4130
3364
|
log("");
|
|
4131
3365
|
} catch (err) {
|
|
@@ -4240,7 +3474,7 @@ async function runRemove2(slug, options) {
|
|
|
4240
3474
|
}
|
|
4241
3475
|
|
|
4242
3476
|
// src/commands/team/index.ts
|
|
4243
|
-
var teamCommand = new commander.Command("team").description("Manage team members and authorizations").addCommand(listCommand2).addCommand(addCommand).addCommand(
|
|
3477
|
+
var teamCommand = new commander.Command("team").description("Manage team members and authorizations").addCommand(listCommand2).addCommand(addCommand).addCommand(joinCommand).addCommand(removeCommand2);
|
|
4244
3478
|
var PROGRAM_NAME2 = "attest-it";
|
|
4245
3479
|
var PROGRAM_ALIAS2 = "attest";
|
|
4246
3480
|
var PROGRAM_NAMES = [PROGRAM_NAME2, PROGRAM_ALIAS2];
|
|
@@ -4261,7 +3495,6 @@ async function getCompletions(env) {
|
|
|
4261
3495
|
{ name: "run", description: "Run test suites interactively" },
|
|
4262
3496
|
{ name: "verify", description: "Verify all seals are valid" },
|
|
4263
3497
|
{ name: "seal", description: "Create a seal for a gate" },
|
|
4264
|
-
{ name: "keygen", description: "Generate a new keypair" },
|
|
4265
3498
|
{ name: "prune", description: "Remove stale attestations" },
|
|
4266
3499
|
{ name: "identity", description: "Manage identities" },
|
|
4267
3500
|
{ name: "team", description: "Manage team members" },
|
|
@@ -4366,7 +3599,6 @@ async function getCompletions(env) {
|
|
|
4366
3599
|
"run",
|
|
4367
3600
|
"verify",
|
|
4368
3601
|
"seal",
|
|
4369
|
-
"keygen",
|
|
4370
3602
|
"prune",
|
|
4371
3603
|
"identity",
|
|
4372
3604
|
"team",
|
|
@@ -4501,43 +3733,12 @@ function createCompletionServerCommand() {
|
|
|
4501
3733
|
}
|
|
4502
3734
|
});
|
|
4503
3735
|
}
|
|
4504
|
-
function hasVersion(data) {
|
|
4505
|
-
return typeof data === "object" && data !== null && "version" in data && // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
4506
|
-
typeof data.version === "string";
|
|
4507
|
-
}
|
|
4508
|
-
var cachedVersion;
|
|
4509
|
-
function getPackageVersion() {
|
|
4510
|
-
if (cachedVersion !== void 0) {
|
|
4511
|
-
return cachedVersion;
|
|
4512
|
-
}
|
|
4513
|
-
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)));
|
|
4514
|
-
const __dirname = path.dirname(__filename);
|
|
4515
|
-
const possiblePaths = [path.join(__dirname, "../package.json"), path.join(__dirname, "../../package.json")];
|
|
4516
|
-
for (const packageJsonPath of possiblePaths) {
|
|
4517
|
-
try {
|
|
4518
|
-
const content = fs.readFileSync(packageJsonPath, "utf-8");
|
|
4519
|
-
const packageJsonData = JSON.parse(content);
|
|
4520
|
-
if (!hasVersion(packageJsonData)) {
|
|
4521
|
-
throw new Error(`Invalid package.json at ${packageJsonPath}: missing version field`);
|
|
4522
|
-
}
|
|
4523
|
-
cachedVersion = packageJsonData.version;
|
|
4524
|
-
return cachedVersion;
|
|
4525
|
-
} catch (error2) {
|
|
4526
|
-
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") {
|
|
4527
|
-
continue;
|
|
4528
|
-
}
|
|
4529
|
-
throw error2;
|
|
4530
|
-
}
|
|
4531
|
-
}
|
|
4532
|
-
throw new Error("Could not find package.json");
|
|
4533
|
-
}
|
|
4534
3736
|
var program = new commander.Command();
|
|
4535
3737
|
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");
|
|
4536
3738
|
program.option("-V, --version", "output the version number");
|
|
4537
3739
|
program.addCommand(initCommand);
|
|
4538
3740
|
program.addCommand(statusCommand);
|
|
4539
3741
|
program.addCommand(runCommand);
|
|
4540
|
-
program.addCommand(keygenCommand);
|
|
4541
3742
|
program.addCommand(pruneCommand);
|
|
4542
3743
|
program.addCommand(verifyCommand);
|
|
4543
3744
|
program.addCommand(sealCommand);
|