@secretstash/cli 0.1.3 → 0.1.4

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.
Files changed (2) hide show
  1. package/dist/index.js +88 -88
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -10,6 +10,7 @@ import { dirname as dirname2, join as join2 } from "path";
10
10
  // src/commands/auth.ts
11
11
  import enquirer from "enquirer";
12
12
  import qrcode from "qrcode-terminal";
13
+ import { execFile } from "child_process";
13
14
 
14
15
  // src/config.ts
15
16
  import Conf from "conf";
@@ -385,6 +386,18 @@ var ApiClient = class {
385
386
  requireAuth: false
386
387
  });
387
388
  }
389
+ // Browser-based CLI auth endpoints
390
+ async initBrowserAuth() {
391
+ return this.request("/api/v1/cli/auth/init", {
392
+ method: "POST",
393
+ requireAuth: false
394
+ });
395
+ }
396
+ async checkBrowserAuthStatus(code) {
397
+ return this.request(`/api/v1/cli/auth/status/${code}`, {
398
+ requireAuth: false
399
+ });
400
+ }
388
401
  async verify2FA(tempToken, code) {
389
402
  return this.request("/api/auth/2fa/verify", {
390
403
  method: "POST",
@@ -819,6 +832,79 @@ var ui = {
819
832
  // src/commands/auth.ts
820
833
  var { prompt } = enquirer;
821
834
  var SERVICE_TOKEN_PREFIX = "stk_";
835
+ function openBrowser(url) {
836
+ const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "powershell" : "xdg-open";
837
+ const args = process.platform === "win32" ? ["-Command", "Start-Process", url] : [url];
838
+ execFile(opener, args, (error) => {
839
+ if (error) {
840
+ ui.warning("Could not open browser automatically");
841
+ ui.info(`Please open this URL manually: ${colors.highlight(url)}`);
842
+ }
843
+ });
844
+ }
845
+ async function handleBrowserLogin() {
846
+ ui.heading("SecretStash Login");
847
+ ui.br();
848
+ const initSpinner = ui.spinner("Starting authentication...");
849
+ const initResult = await apiClient.initBrowserAuth();
850
+ if (initResult.error) {
851
+ initSpinner.fail();
852
+ ui.error(initResult.error.message);
853
+ process.exit(1);
854
+ }
855
+ initSpinner.succeed("Authentication session started");
856
+ const { code, authUrl, expiresIn } = initResult.data;
857
+ ui.br();
858
+ ui.box("Browser Authentication", [
859
+ "A browser window will open for you to sign in.",
860
+ "If it doesn't open automatically, visit:",
861
+ "",
862
+ ` ${colors.highlight(authUrl)}`,
863
+ "",
864
+ `Code: ${colors.primary.bold(code)}`,
865
+ "",
866
+ colors.muted(`This code expires in ${Math.floor(expiresIn / 60)} minutes`)
867
+ ]);
868
+ openBrowser(authUrl);
869
+ ui.br();
870
+ const pollSpinner = ui.spinner("Waiting for authentication...");
871
+ const pollInterval = 2e3;
872
+ const maxAttempts = Math.floor(expiresIn * 1e3 / pollInterval);
873
+ let attempts = 0;
874
+ while (attempts < maxAttempts) {
875
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
876
+ attempts++;
877
+ const statusResult = await apiClient.checkBrowserAuthStatus(code);
878
+ if (statusResult.error) {
879
+ if (statusResult.error.code === "EXPIRED" || statusResult.error.code === "NOT_FOUND") {
880
+ pollSpinner.fail();
881
+ ui.error("Authentication session expired. Please try again.");
882
+ process.exit(1);
883
+ }
884
+ continue;
885
+ }
886
+ const { status, accessToken, refreshToken, expiresIn: tokenExpiry, user } = statusResult.data;
887
+ if (status === "completed" && accessToken && refreshToken && user) {
888
+ pollSpinner.succeed("Authentication successful!");
889
+ configManager.setTokens(accessToken, refreshToken, tokenExpiry || 3600);
890
+ configManager.setUser(user.id, user.email);
891
+ ui.br();
892
+ ui.keyValue("Email", user.email);
893
+ ui.keyValue("Config", configManager.getConfigPath());
894
+ ui.br();
895
+ ui.info(`Run ${ui.code("sstash teams")} to see your teams`);
896
+ return;
897
+ }
898
+ if (status === "expired") {
899
+ pollSpinner.fail();
900
+ ui.error("Authentication session expired. Please try again.");
901
+ process.exit(1);
902
+ }
903
+ }
904
+ pollSpinner.fail();
905
+ ui.error("Authentication timed out. Please try again.");
906
+ process.exit(1);
907
+ }
822
908
  async function handleServiceTokenLogin(token, fromEnvVar = false) {
823
909
  ui.heading("SecretStash Service Token Login");
824
910
  if (!token.startsWith(SERVICE_TOKEN_PREFIX)) {
@@ -866,7 +952,7 @@ async function handleServiceTokenLogin(token, fromEnvVar = false) {
866
952
  ]);
867
953
  }
868
954
  function registerAuthCommands(program2) {
869
- program2.command("login").description("Authenticate with SecretStash").option("-e, --email <email>", "Email address").option("-t, --token <token>", "Service token for CI/CD authentication (must start with stk_)").action(async (options) => {
955
+ program2.command("login").description("Authenticate with SecretStash via browser (supports passkeys and 2FA)").option("-t, --token <token>", "Service token for CI/CD authentication (must start with stk_)").action(async (options) => {
870
956
  if (options.token) {
871
957
  await handleServiceTokenLogin(options.token);
872
958
  return;
@@ -875,93 +961,7 @@ function registerAuthCommands(program2) {
875
961
  await handleServiceTokenLogin(process.env.SECRETSTASH_TOKEN, true);
876
962
  return;
877
963
  }
878
- ui.heading("SecretStash Login");
879
- let email = options.email;
880
- if (!email) {
881
- const response = await prompt({
882
- type: "input",
883
- name: "email",
884
- message: "Email:",
885
- validate: (value) => {
886
- if (!value) return "Email is required";
887
- if (!value.includes("@")) return "Invalid email format";
888
- return true;
889
- }
890
- });
891
- email = response.email;
892
- }
893
- const { password } = await prompt({
894
- type: "password",
895
- name: "password",
896
- message: "Password:",
897
- validate: (value) => value ? true : "Password is required"
898
- });
899
- const spinner = ui.spinner("Authenticating...");
900
- const result = await apiClient.login(email, password);
901
- if (result.error) {
902
- spinner.fail();
903
- if (result.error.code === "CLI_2FA_REQUIRED") {
904
- ui.br();
905
- ui.box("Two-Factor Authentication Required", [
906
- "CLI access requires two-factor authentication.",
907
- "",
908
- "To set up 2FA:",
909
- ` 1. Visit ${colors.highlight("https://app.secretstash.dev/settings/security")}`,
910
- " 2. Enable two-factor authentication",
911
- " 3. Return here and try again",
912
- "",
913
- colors.muted("For CI/CD pipelines, use service tokens instead:"),
914
- ` ${colors.highlight("sstash login --token <service-token>")}`
915
- ]);
916
- process.exit(1);
917
- }
918
- ui.error(result.error.message);
919
- process.exit(1);
920
- }
921
- if (result.data?.requires2FA) {
922
- spinner.stop();
923
- ui.info("Two-factor authentication required");
924
- const { code } = await prompt({
925
- type: "input",
926
- name: "code",
927
- message: "2FA Code:",
928
- validate: (value) => {
929
- if (!value) return "2FA code is required";
930
- if (!/^\d{6}$/.test(value)) return "Code must be 6 digits";
931
- return true;
932
- }
933
- });
934
- const spinner2 = ui.spinner("Verifying...");
935
- const twoFAResult = await apiClient.verify2FA(
936
- result.data.tempToken,
937
- code
938
- );
939
- if (twoFAResult.error) {
940
- spinner2.fail();
941
- ui.error(twoFAResult.error.message);
942
- process.exit(1);
943
- }
944
- configManager.setTokens(
945
- twoFAResult.data.accessToken,
946
- twoFAResult.data.refreshToken,
947
- twoFAResult.data.expiresIn
948
- );
949
- configManager.setUser(twoFAResult.data.user.id, twoFAResult.data.user.email);
950
- spinner2.succeed("Logged in successfully");
951
- } else {
952
- configManager.setTokens(
953
- result.data.accessToken,
954
- result.data.refreshToken,
955
- result.data.expiresIn
956
- );
957
- configManager.setUser(result.data.user.id, result.data.user.email);
958
- spinner.succeed("Logged in successfully");
959
- }
960
- ui.br();
961
- ui.keyValue("Email", email);
962
- ui.keyValue("Config", configManager.getConfigPath());
963
- ui.br();
964
- ui.info(`Run ${ui.code("sstash teams")} to see your teams`);
964
+ await handleBrowserLogin();
965
965
  });
966
966
  program2.command("logout").description("Log out from SecretStash").action(async () => {
967
967
  if (!configManager.isAuthenticated()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@secretstash/cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "CLI tool for SecretStash - secure team secrets management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",