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.
- package/dist/clients/bw.d.ts +1 -0
- package/dist/clients/bw.js +16 -3
- package/dist/dashboard/DashboardView.js +1 -0
- package/dist/hooks/bw.js +0 -4
- package/dist/login/LoginView.js +52 -6
- package/package.json +1 -1
- package/readme.md +10 -4
package/dist/clients/bw.d.ts
CHANGED
package/dist/clients/bw.js
CHANGED
|
@@ -101,9 +101,12 @@ export var KeyType;
|
|
|
101
101
|
export const TwoFactorProvider = {
|
|
102
102
|
"0": "Authenticator",
|
|
103
103
|
"1": "Email",
|
|
104
|
-
"2": "
|
|
104
|
+
"2": "Duo",
|
|
105
105
|
"3": "Yubikey",
|
|
106
|
-
"4": "
|
|
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(
|
|
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)
|
package/dist/login/LoginView.js
CHANGED
|
@@ -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
|
-
|
|
61
|
+
const providers = data.TwoFactorProviders;
|
|
62
|
+
if (providers.length === 1) {
|
|
52
63
|
setMfaParams({
|
|
53
|
-
twoFactorProvider:
|
|
64
|
+
twoFactorProvider: providers[0],
|
|
54
65
|
});
|
|
55
66
|
}
|
|
56
|
-
else if (
|
|
57
|
-
setAskMfa(
|
|
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
|
|
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:
|
|
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
package/readme.md
CHANGED
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
## Install
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
|
|
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
|
-
-
|
|
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/)
|