@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.
- package/dist/index.js +88 -88
- 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
|
|
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
|
-
|
|
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()) {
|