bitty-tui 0.0.14 → 0.0.16

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.
@@ -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) {
@@ -439,6 +442,7 @@ export class Client {
439
442
  this.refreshToken = identityReq.refresh_token;
440
443
  this.tokenExpiration = Date.now() + identityReq.expires_in * 1000;
441
444
  const { userKey, privateKey } = mcbw.decodeUserKeys(identityReq.Key, identityReq.PrivateKey || "", keys);
445
+ keys.masterKey = undefined; // Clear master key from memory
442
446
  this.keys = {
443
447
  ...keys,
444
448
  userKey,
@@ -449,7 +453,7 @@ export class Client {
449
453
  this.orgKeys = {};
450
454
  }
451
455
  async sendEmailMfaCode(email) {
452
- fetch("https://vault.bitwarden.eu/api/two-factor/send-email-login", {
456
+ fetch(`${this.apiUrl}/two-factor/send-email-login`, {
453
457
  method: "POST",
454
458
  headers: {
455
459
  "Content-Type": "application/json",
@@ -798,4 +802,13 @@ export class Client {
798
802
  }
799
803
  return ret;
800
804
  }
805
+ logout() {
806
+ this.token = null;
807
+ this.refreshToken = null;
808
+ this.tokenExpiration = null;
809
+ this.keys = {};
810
+ this.orgKeys = {};
811
+ this.decryptedSyncCache = null;
812
+ this.syncCache = null;
813
+ }
801
814
  }
@@ -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
  };
package/dist/hooks/bw.js CHANGED
@@ -15,8 +15,6 @@ export async function loadConfig() {
15
15
  }
16
16
  if (config.keys && config.refreshToken) {
17
17
  const keys = {};
18
- if (config.keys.masterKey)
19
- keys.masterKey = Uint8Array.from(config.keys.masterKey);
20
18
  if (config.keys.masterPasswordHash)
21
19
  keys.masterPasswordHash = config.keys.masterPasswordHash;
22
20
  if (config.keys.privateKey)
@@ -50,8 +48,6 @@ export async function loadConfig() {
50
48
  }
51
49
  export async function saveConfig(config) {
52
50
  const keys = {};
53
- if (config.keys.masterKey)
54
- keys.masterKey = Array.from(config.keys.masterKey);
55
51
  if (config.keys.masterPasswordHash)
56
52
  keys.masterPasswordHash = config.keys.masterPasswordHash;
57
53
  if (config.keys.privateKey)
@@ -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.14",
3
+ "version": "0.0.16",
4
4
  "license": "MIT",
5
5
  "repository": "https://github.com/mceck/bitty",
6
6
  "keywords": [
package/readme.md CHANGED
@@ -3,7 +3,13 @@
3
3
  ## Install
4
4
 
5
5
  ```bash
6
- $ npm install -g bitty-tui
6
+ npm install -g bitty-tui
7
+ ```
8
+
9
+ Run with:
10
+
11
+ ```bash
12
+ bitty
7
13
  ```
8
14
 
9
15
  ## Description
@@ -20,13 +26,13 @@ If you check "Remember me" during login, your vault encryption keys will be stor
20
26
  ## TODO
21
27
 
22
28
  - Collections support
23
- - Implement Fido, Duo MFA support
29
+ - Test Fido, Duo MFA support
24
30
  - Handle more fields editing
25
31
  - Handle creating different cipher types
26
32
 
27
33
  ## Acknowledgments
28
-
29
- - [Bitwarden](https://github.com/bitwarden)
34
+ - [Bitwarden whitepaper](https://bitwarden.com/help/bitwarden-security-white-paper)
35
+ - [Bitwarden github](https://github.com/bitwarden)
30
36
  - [BitwardenDecrypt](https://github.com/GurpreetKang/BitwardenDecrypt)
31
37
 
32
38
  This project is not associated with [Bitwarden](https://github.com/bitwarden) or [Bitwarden, Inc.](https://bitwarden.com/)