bitty-tui 0.0.13 → 0.0.15

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/cli.js CHANGED
@@ -5,9 +5,13 @@ import App from "./app.js";
5
5
  import { StatusMessageProvider } from "./hooks/status-message.js";
6
6
  import { readPackageUpSync } from "read-package-up";
7
7
  import { art } from "./theme/art.js";
8
+ import path from "node:path";
9
+ import { fileURLToPath } from "node:url";
8
10
  const args = process.argv.slice(2);
9
11
  if (args.includes("--version") || args.includes("-v")) {
10
- const pkg = readPackageUpSync();
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+ const pkg = readPackageUpSync({ cwd: __dirname });
11
15
  console.log(pkg?.packageJson.version ?? "unknown");
12
16
  process.exit(0);
13
17
  }
@@ -239,4 +239,5 @@ export declare class Client {
239
239
  type: number;
240
240
  }[] | undefined;
241
241
  };
242
+ logout(): void;
242
243
  }
@@ -101,9 +101,12 @@ export var KeyType;
101
101
  export const TwoFactorProvider = {
102
102
  "0": "Authenticator",
103
103
  "1": "Email",
104
- "2": "Fido2",
104
+ "2": "Duo",
105
105
  "3": "Yubikey",
106
- "4": "Duo",
106
+ "4": "U2f",
107
+ "6": "Org Duo",
108
+ "7": "Fido2",
109
+ "8": "Recovery Code",
107
110
  };
108
111
  export var KdfType;
109
112
  (function (KdfType) {
@@ -449,7 +452,7 @@ export class Client {
449
452
  this.orgKeys = {};
450
453
  }
451
454
  async sendEmailMfaCode(email) {
452
- fetch("https://vault.bitwarden.eu/api/two-factor/send-email-login", {
455
+ fetch(`${this.apiUrl}/two-factor/send-email-login`, {
453
456
  method: "POST",
454
457
  headers: {
455
458
  "Content-Type": "application/json",
@@ -798,4 +801,13 @@ export class Client {
798
801
  }
799
802
  return ret;
800
803
  }
804
+ logout() {
805
+ this.token = null;
806
+ this.refreshToken = null;
807
+ this.tokenExpiration = null;
808
+ this.keys = {};
809
+ this.orgKeys = {};
810
+ this.decryptedSyncCache = null;
811
+ this.syncCache = null;
812
+ }
801
813
  }
@@ -42,6 +42,7 @@ export function DashboardView({ onLogout }) {
42
42
  }, [listSelected, filteredCiphers]);
43
43
  const selectedCipher = detailMode === "new" ? editedCipher : filteredCiphers[listIndex];
44
44
  const logout = async () => {
45
+ bwClient.logout();
45
46
  await clearConfig();
46
47
  onLogout();
47
48
  };
@@ -16,10 +16,17 @@ export function LoginView({ onLogin }) {
16
16
  const [password, setPassword] = useState("");
17
17
  const [mfaParams, setMfaParams] = useState(null);
18
18
  const [askMfa, setAskMfa] = useState(null);
19
+ const [mfaProviderData, setMfaProviderData] = useState(null);
19
20
  const [rememberMe, setRememberMe] = useState(false);
21
+ const [resendTimeout, setResendTimeout] = useState(0);
20
22
  const { stdout } = useStdout();
21
23
  const { focusNext, focusPrevious } = useFocusManager();
22
24
  const { statusMessage, statusMessageColor, showStatusMessage } = useStatusMessage();
25
+ const getProviderLabel = (provider) => TwoFactorProvider[String(provider)] ?? `Provider ${provider}`;
26
+ const getProviderData = (provider) => mfaProviderData?.[String(provider)] ?? null;
27
+ const isEmailProvider = (provider) => String(provider) === "1";
28
+ const isDuoProvider = (provider) => String(provider) === "2" || String(provider) === "6";
29
+ const isWebAuthnProvider = (provider) => String(provider) === "7";
23
30
  useInput(async (_, key) => {
24
31
  if (key.upArrow) {
25
32
  focusPrevious();
@@ -47,14 +54,29 @@ export function LoginView({ onLogin }) {
47
54
  catch (e) {
48
55
  if (e instanceof FetchError) {
49
56
  const data = e.json();
57
+ if (data.TwoFactorProviders2) {
58
+ setMfaProviderData(data.TwoFactorProviders2);
59
+ }
50
60
  if (data.TwoFactorProviders) {
51
- if (data.TwoFactorProviders.length === 1) {
61
+ const providers = data.TwoFactorProviders;
62
+ if (providers.length === 1) {
52
63
  setMfaParams({
53
- twoFactorProvider: data.TwoFactorProviders[0],
64
+ twoFactorProvider: providers[0],
54
65
  });
55
66
  }
56
- else if (data.TwoFactorProviders.length > 1) {
57
- setAskMfa(data.TwoFactorProviders);
67
+ else if (providers.length > 1) {
68
+ setAskMfa(providers);
69
+ }
70
+ }
71
+ else if (data.TwoFactorProviders2) {
72
+ const providers = Object.keys(data.TwoFactorProviders2);
73
+ if (providers.length === 1) {
74
+ setMfaParams({
75
+ twoFactorProvider: providers[0],
76
+ });
77
+ }
78
+ else if (providers.length > 1) {
79
+ setAskMfa(providers);
58
80
  }
59
81
  }
60
82
  }
@@ -93,8 +115,20 @@ export function LoginView({ onLogin }) {
93
115
  }
94
116
  })();
95
117
  }, []);
118
+ useEffect(() => {
119
+ let interval;
120
+ if (resendTimeout > 0) {
121
+ interval = setInterval(() => {
122
+ setResendTimeout((t) => t - 1);
123
+ }, 1000);
124
+ }
125
+ return () => {
126
+ if (interval)
127
+ clearInterval(interval);
128
+ };
129
+ }, [resendTimeout]);
96
130
  return (_jsxs(Box, { flexDirection: "column", alignItems: "center", padding: 1, flexGrow: 1, minHeight: stdout.rows - 2, children: [_jsx(Box, { marginBottom: 2, children: _jsx(Text, { color: primary, children: art }) }), loading ? (_jsx(Text, { children: "Loading..." })) : askMfa ? (_jsx(Box, { flexDirection: "column", width: "50%", children: Object.values(askMfa).map((provider) => (_jsx(Button, { autoFocus: true, onClick: () => {
97
- if (provider === "1" &&
131
+ if (isEmailProvider(provider) &&
98
132
  (Object.values(askMfa).length > 1 ||
99
133
  !bwClient.isVaultWarden())) {
100
134
  bwClient.sendEmailMfaCode(email);
@@ -104,7 +138,19 @@ export function LoginView({ onLogin }) {
104
138
  twoFactorProvider: provider,
105
139
  }));
106
140
  setAskMfa(null);
107
- }, children: TwoFactorProvider[provider] }, provider))) })) : mfaParams && mfaParams.twoFactorProvider ? (_jsx(Box, { flexDirection: "column", width: "50%", children: _jsx(TextInput, { autoFocus: true, placeholder: `Enter your ${TwoFactorProvider[mfaParams.twoFactorProvider]} code`, value: mfaParams.twoFactorToken || "", onChange: (value) => setMfaParams((p) => ({ ...p, twoFactorToken: value })), onSubmit: () => handleLogin() }) })) : (_jsxs(Box, { flexDirection: "column", width: "50%", children: [_jsx(TextInput, { placeholder: "Server URL", value: url, onChange: setUrl }), _jsx(TextInput, { autoFocus: true, placeholder: "Email address", value: email, onChange: setEmail }), _jsx(TextInput, { placeholder: "Master password", value: password, onChange: setPassword, onSubmit: () => {
141
+ }, children: getProviderLabel(provider) }, provider))) })) : mfaParams && mfaParams.twoFactorProvider ? (_jsxs(Box, { flexDirection: "column", width: "50%", children: [isDuoProvider(mfaParams.twoFactorProvider) && (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { children: "Open the Duo URL in a browser and approve the request." }), _jsx(Text, { children: "Then paste the `code` and `state` from the final URL as `code|state`." }), getProviderData(mfaParams.twoFactorProvider)?.AuthUrl && (_jsxs(Text, { children: ["Auth URL:", " ", getProviderData(mfaParams.twoFactorProvider).AuthUrl] }))] })), isWebAuthnProvider(mfaParams.twoFactorProvider) && (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: _jsx(Text, { children: "Use your FIDO2/WebAuthn security key to generate a response, then paste it here." }) })), _jsx(TextInput, { autoFocus: true, placeholder: `Enter your ${getProviderLabel(mfaParams.twoFactorProvider)} ${isWebAuthnProvider(mfaParams.twoFactorProvider)
142
+ ? "response"
143
+ : "code"}`, value: mfaParams.twoFactorToken || "", onChange: (value) => setMfaParams((p) => ({ ...p, twoFactorToken: value })), onSubmit: () => handleLogin() }), isEmailProvider(mfaParams.twoFactorProvider) && (_jsx(Button, { marginTop: 1, isActive: resendTimeout === 0, onClick: () => {
144
+ bwClient
145
+ .sendEmailMfaCode(email)
146
+ .then(() => {
147
+ showStatusMessage("Sent new MFA code to your email.", "success");
148
+ })
149
+ .catch(() => {
150
+ showStatusMessage("Failed to resend MFA code.", "error");
151
+ });
152
+ setResendTimeout(30);
153
+ }, children: "Resend Code" }))] })) : (_jsxs(Box, { flexDirection: "column", width: "50%", children: [_jsx(TextInput, { placeholder: "Server URL", value: url, onChange: setUrl }), _jsx(TextInput, { autoFocus: true, placeholder: "Email address", value: email, onChange: setEmail }), _jsx(TextInput, { placeholder: "Master password", value: password, onChange: setPassword, onSubmit: () => {
108
154
  if (email?.length && password?.length) {
109
155
  handleLogin();
110
156
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bitty-tui",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "license": "MIT",
5
5
  "repository": "https://github.com/mceck/bitty",
6
6
  "keywords": [