@attest-it/cli 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1665,17 +1665,28 @@ function getKeyRefFromIdentity(identity) {
1665
1665
  }
1666
1666
  }
1667
1667
  function KeygenInteractive(props) {
1668
- const { onComplete, onError } = props;
1668
+ const { onComplete, onCancel, onError } = props;
1669
1669
  const [step, setStep] = React7.useState("checking-providers");
1670
1670
  const [opAvailable, setOpAvailable] = React7.useState(false);
1671
1671
  const [keychainAvailable, setKeychainAvailable] = React7.useState(false);
1672
+ const [yubiKeyAvailable, setYubiKeyAvailable] = React7.useState(false);
1672
1673
  const [accounts, setAccounts] = React7.useState([]);
1673
1674
  const [vaults, setVaults] = React7.useState([]);
1675
+ const [yubiKeyDevices, setYubiKeyDevices] = React7.useState([]);
1674
1676
  const [_selectedProvider, setSelectedProvider] = React7.useState();
1675
1677
  const [selectedAccount, setSelectedAccount] = React7.useState();
1676
1678
  const [selectedVault, setSelectedVault] = React7.useState();
1677
1679
  const [itemName, setItemName] = React7.useState("attest-it-private-key");
1678
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
+ });
1679
1690
  React7.useEffect(() => {
1680
1691
  const checkProviders = async () => {
1681
1692
  try {
@@ -1690,6 +1701,19 @@ function KeygenInteractive(props) {
1690
1701
  }
1691
1702
  const isKeychainAvailable = core.MacOSKeychainKeyProvider.isAvailable();
1692
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
+ }
1693
1717
  setStep("select-provider");
1694
1718
  };
1695
1719
  void checkProviders();
@@ -1707,6 +1731,27 @@ function KeygenInteractive(props) {
1707
1731
  void fetchVaults();
1708
1732
  }
1709
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
+ };
1710
1755
  const handleProviderSelect = (value) => {
1711
1756
  if (value === "filesystem") {
1712
1757
  setSelectedProvider("filesystem");
@@ -1714,7 +1759,7 @@ function KeygenInteractive(props) {
1714
1759
  } else if (value === "1password") {
1715
1760
  setSelectedProvider("1password");
1716
1761
  if (accounts.length === 1 && accounts[0]) {
1717
- setSelectedAccount(accounts[0].email);
1762
+ setSelectedAccount(accounts[0].user_uuid);
1718
1763
  setStep("select-vault");
1719
1764
  } else {
1720
1765
  setStep("select-account");
@@ -1722,6 +1767,14 @@ function KeygenInteractive(props) {
1722
1767
  } else if (value === "macos-keychain") {
1723
1768
  setSelectedProvider("macos-keychain");
1724
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
+ }
1725
1778
  }
1726
1779
  };
1727
1780
  const handleAccountSelect = (value) => {
@@ -1740,6 +1793,47 @@ function KeygenInteractive(props) {
1740
1793
  setKeychainItemName(value);
1741
1794
  void generateKeys("macos-keychain");
1742
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
+ };
1743
1837
  const generateKeys = async (provider) => {
1744
1838
  setStep("generating");
1745
1839
  try {
@@ -1761,8 +1855,12 @@ function KeygenInteractive(props) {
1761
1855
  if (!selectedVault || !itemName) {
1762
1856
  throw new Error("Vault and item name are required for 1Password");
1763
1857
  }
1858
+ const vault = vaults.find((v) => v.id === selectedVault);
1859
+ if (!vault) {
1860
+ throw new Error("Selected vault not found");
1861
+ }
1764
1862
  const providerOptions = {
1765
- vault: selectedVault,
1863
+ vault: vault.name,
1766
1864
  itemName
1767
1865
  };
1768
1866
  if (selectedAccount !== void 0) {
@@ -1779,14 +1877,14 @@ function KeygenInteractive(props) {
1779
1877
  publicKeyPath: result.publicKeyPath,
1780
1878
  privateKeyRef: result.privateKeyRef,
1781
1879
  storageDescription: result.storageDescription,
1782
- vault: selectedVault,
1880
+ vault: vault.name,
1783
1881
  itemName
1784
1882
  };
1785
1883
  if (selectedAccount !== void 0) {
1786
1884
  completionResult.account = selectedAccount;
1787
1885
  }
1788
1886
  onComplete(completionResult);
1789
- } else {
1887
+ } else if (provider === "macos-keychain") {
1790
1888
  if (!keychainItemName) {
1791
1889
  throw new Error("Item name is required for macOS Keychain");
1792
1890
  }
@@ -1805,6 +1903,33 @@ function KeygenInteractive(props) {
1805
1903
  storageDescription: result.storageDescription,
1806
1904
  itemName: keychainItemName
1807
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);
1808
1933
  }
1809
1934
  setStep("done");
1810
1935
  } catch (err) {
@@ -1830,6 +1955,12 @@ function KeygenInteractive(props) {
1830
1955
  value: "macos-keychain"
1831
1956
  });
1832
1957
  }
1958
+ if (yubiKeyAvailable) {
1959
+ options.push({
1960
+ label: "YubiKey (hardware security key)",
1961
+ value: "yubikey"
1962
+ });
1963
+ }
1833
1964
  if (opAvailable) {
1834
1965
  options.push({
1835
1966
  label: "1Password (requires op CLI)",
@@ -1844,8 +1975,8 @@ function KeygenInteractive(props) {
1844
1975
  }
1845
1976
  if (step === "select-account") {
1846
1977
  const options = accounts.map((account) => ({
1847
- label: account.email,
1848
- value: account.email
1978
+ label: account.name ? `${account.name} (${account.email})` : account.email,
1979
+ value: account.user_uuid
1849
1980
  }));
1850
1981
  return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
1851
1982
  /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Select 1Password account:" }),
@@ -1862,7 +1993,7 @@ function KeygenInteractive(props) {
1862
1993
  }
1863
1994
  const options = vaults.map((vault) => ({
1864
1995
  label: vault.name,
1865
- value: vault.name
1996
+ value: vault.id
1866
1997
  }));
1867
1998
  return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
1868
1999
  /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Select vault for private key storage:" }),
@@ -1886,6 +2017,65 @@ function KeygenInteractive(props) {
1886
2017
  /* @__PURE__ */ jsxRuntime.jsx(ui.TextInput, { defaultValue: keychainItemName, onSubmit: handleKeychainItemNameSubmit })
1887
2018
  ] });
1888
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
+ }
1889
2079
  if (step === "generating") {
1890
2080
  return /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { flexDirection: "column", children: /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", gap: 1, children: [
1891
2081
  /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {}),
@@ -2970,6 +3160,7 @@ async function runCreate() {
2970
3160
  };
2971
3161
  }
2972
3162
  await core.saveLocalConfig(newConfig);
3163
+ const publicKeyResult = await core.savePublicKey(slug, keyPair.publicKey);
2973
3164
  log("");
2974
3165
  success("Identity created successfully");
2975
3166
  log("");
@@ -2984,6 +3175,12 @@ async function runCreate() {
2984
3175
  log(` Public Key: ${keyPair.publicKey.slice(0, 32)}...`);
2985
3176
  log(` Private Key: ${keyStorageDescription}`);
2986
3177
  log("");
3178
+ log(theme3.blue.bold()("Public key saved to:"));
3179
+ log(` ${publicKeyResult.homePath}`);
3180
+ if (publicKeyResult.projectPath) {
3181
+ log(` ${publicKeyResult.projectPath}`);
3182
+ }
3183
+ log("");
2987
3184
  if (!existingConfig) {
2988
3185
  success(`Set as active identity`);
2989
3186
  log("");