bitty-tui 0.0.16 → 0.0.17
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.js +38 -71
- package/dist/hooks/bw.d.ts +6 -0
- package/dist/hooks/bw.js +29 -2
- package/dist/login/LoginView.js +13 -1
- package/package.json +1 -1
package/dist/clients/bw.js
CHANGED
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
* - Cipher-specific key if available
|
|
18
18
|
* 4. Decrypt cipher fields using the chosen key
|
|
19
19
|
*/
|
|
20
|
-
import https from "node:https";
|
|
21
20
|
import crypto from "node:crypto";
|
|
22
21
|
import * as argon2 from "argon2";
|
|
23
22
|
export class FetchError extends Error {
|
|
@@ -32,54 +31,14 @@ export class FetchError extends Error {
|
|
|
32
31
|
return JSON.parse(this.data);
|
|
33
32
|
}
|
|
34
33
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
};
|
|
44
|
-
const req = https.request(requestOptions, (res) => {
|
|
45
|
-
let data = "";
|
|
46
|
-
const onData = (chunk) => {
|
|
47
|
-
data += chunk;
|
|
48
|
-
};
|
|
49
|
-
const onEnd = () => {
|
|
50
|
-
cleanup();
|
|
51
|
-
if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {
|
|
52
|
-
reject(new FetchError(res.statusCode, data, `HTTP error: ${res.statusCode} ${res.statusMessage}`));
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
resolve({
|
|
56
|
-
status: res.statusCode,
|
|
57
|
-
json: () => Promise.resolve(JSON.parse(data)),
|
|
58
|
-
text: () => Promise.resolve(data),
|
|
59
|
-
});
|
|
60
|
-
};
|
|
61
|
-
const onError = (error) => {
|
|
62
|
-
cleanup();
|
|
63
|
-
reject(error);
|
|
64
|
-
};
|
|
65
|
-
const cleanup = () => {
|
|
66
|
-
res.removeListener("data", onData);
|
|
67
|
-
res.removeListener("end", onEnd);
|
|
68
|
-
res.removeListener("error", onError);
|
|
69
|
-
};
|
|
70
|
-
res.on("data", onData);
|
|
71
|
-
res.on("end", onEnd);
|
|
72
|
-
res.on("error", onError);
|
|
73
|
-
});
|
|
74
|
-
req.on("error", (error) => {
|
|
75
|
-
reject(error);
|
|
76
|
-
});
|
|
77
|
-
if (body) {
|
|
78
|
-
req.write(typeof body === "string" ? body : JSON.stringify(body));
|
|
79
|
-
}
|
|
80
|
-
req.end();
|
|
81
|
-
});
|
|
82
|
-
}
|
|
34
|
+
const fetchApi = async (...args) => {
|
|
35
|
+
const response = await fetch(...args);
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
const data = await response.text();
|
|
38
|
+
throw new FetchError(response.status, data);
|
|
39
|
+
}
|
|
40
|
+
return response;
|
|
41
|
+
};
|
|
83
42
|
export var CipherType;
|
|
84
43
|
(function (CipherType) {
|
|
85
44
|
CipherType[CipherType["Login"] = 1] = "Login";
|
|
@@ -409,13 +368,12 @@ export class Client {
|
|
|
409
368
|
async login(email, password, skipPrelogin = false, opts) {
|
|
410
369
|
let keys = this.keys;
|
|
411
370
|
if (!skipPrelogin) {
|
|
412
|
-
const prelogin = await
|
|
371
|
+
const prelogin = await fetchApi(`${this.identityUrl}/accounts/prelogin`, {
|
|
413
372
|
method: "POST",
|
|
414
373
|
headers: {
|
|
415
374
|
"Content-Type": "application/json",
|
|
416
375
|
},
|
|
417
|
-
|
|
418
|
-
email,
|
|
376
|
+
body: JSON.stringify({ email }),
|
|
419
377
|
}).then((r) => r.json());
|
|
420
378
|
keys = await mcbw.deriveMasterKey(email, password, prelogin);
|
|
421
379
|
this.keys = keys;
|
|
@@ -432,12 +390,17 @@ export class Client {
|
|
|
432
390
|
for (const [key, value] of Object.entries(opts || {})) {
|
|
433
391
|
bodyParams.append(key, value);
|
|
434
392
|
}
|
|
435
|
-
const identityReq = await
|
|
393
|
+
const identityReq = await fetchApi(`${this.identityUrl}/connect/token`, {
|
|
436
394
|
method: "POST",
|
|
437
395
|
headers: {
|
|
438
|
-
|
|
396
|
+
accept: "*/*",
|
|
397
|
+
"accept-language": "en-US",
|
|
398
|
+
"bitwarden-client-name": "web",
|
|
399
|
+
"bitwarden-client-version": "2025.9.0",
|
|
400
|
+
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
|
439
401
|
},
|
|
440
|
-
|
|
402
|
+
body: bodyParams.toString(),
|
|
403
|
+
}).then((r) => r.json());
|
|
441
404
|
this.token = identityReq.access_token;
|
|
442
405
|
this.refreshToken = identityReq.refresh_token;
|
|
443
406
|
this.tokenExpiration = Date.now() + identityReq.expires_in * 1000;
|
|
@@ -453,19 +416,20 @@ export class Client {
|
|
|
453
416
|
this.orgKeys = {};
|
|
454
417
|
}
|
|
455
418
|
async sendEmailMfaCode(email) {
|
|
456
|
-
|
|
419
|
+
fetchApi(`${this.apiUrl}/two-factor/send-email-login`, {
|
|
457
420
|
method: "POST",
|
|
458
421
|
headers: {
|
|
459
422
|
"Content-Type": "application/json",
|
|
460
423
|
},
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
424
|
+
body: JSON.stringify({
|
|
425
|
+
email: email,
|
|
426
|
+
masterPasswordHash: this.keys.masterPasswordHash,
|
|
427
|
+
ssoEmail2FaSessionToken: "",
|
|
428
|
+
deviceIdentifier: DEVICE_IDENTIFIER,
|
|
429
|
+
authRequestAccessCode: "",
|
|
430
|
+
authRequestId: "",
|
|
431
|
+
}),
|
|
432
|
+
});
|
|
469
433
|
}
|
|
470
434
|
// Check and refresh token if needed
|
|
471
435
|
async checkToken() {
|
|
@@ -473,12 +437,13 @@ export class Client {
|
|
|
473
437
|
if (!this.refreshToken) {
|
|
474
438
|
throw new Error("No refresh token available. Please login first.");
|
|
475
439
|
}
|
|
476
|
-
const identityReq = await
|
|
440
|
+
const identityReq = await fetchApi(`${this.identityUrl}/connect/token`, {
|
|
477
441
|
method: "POST",
|
|
478
442
|
headers: {
|
|
479
443
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
480
444
|
},
|
|
481
|
-
|
|
445
|
+
body: `refresh_token=${this.refreshToken}&grant_type=refresh_token&client_id=web&scope=api%20offline_access`,
|
|
446
|
+
}).then((r) => r.json());
|
|
482
447
|
this.token = identityReq.access_token;
|
|
483
448
|
this.refreshToken = identityReq.refresh_token;
|
|
484
449
|
this.tokenExpiration = Date.now() + identityReq.expires_in * 1000;
|
|
@@ -491,7 +456,7 @@ export class Client {
|
|
|
491
456
|
*/
|
|
492
457
|
async syncRefresh() {
|
|
493
458
|
await this.checkToken();
|
|
494
|
-
this.syncCache = await
|
|
459
|
+
this.syncCache = await fetchApi(`${this.apiUrl}/sync?excludeDomains=true`, {
|
|
495
460
|
method: "GET",
|
|
496
461
|
headers: {
|
|
497
462
|
Authorization: `Bearer ${this.token}`,
|
|
@@ -637,13 +602,14 @@ export class Client {
|
|
|
637
602
|
}
|
|
638
603
|
async createSecret(obj) {
|
|
639
604
|
const key = this.getDecryptionKey(obj);
|
|
640
|
-
const s = await
|
|
605
|
+
const s = await fetchApi(`${this.apiUrl}/ciphers`, {
|
|
641
606
|
method: "POST",
|
|
642
607
|
headers: {
|
|
643
608
|
Authorization: `Bearer ${this.token}`,
|
|
644
609
|
"Content-Type": "application/json",
|
|
645
610
|
},
|
|
646
|
-
|
|
611
|
+
body: JSON.stringify(this.encryptCipher(obj, key)),
|
|
612
|
+
});
|
|
647
613
|
return s.json();
|
|
648
614
|
}
|
|
649
615
|
objectDiff(obj1, obj2) {
|
|
@@ -683,13 +649,14 @@ export class Client {
|
|
|
683
649
|
const key = this.getDecryptionKey(patch);
|
|
684
650
|
const data = this.patchObject(original, this.encryptCipher(obj, key));
|
|
685
651
|
data.data = undefined;
|
|
686
|
-
const s = await
|
|
652
|
+
const s = await fetchApi(`${this.apiUrl}/ciphers/${id}`, {
|
|
687
653
|
method: "PUT",
|
|
688
654
|
headers: {
|
|
689
655
|
Authorization: `Bearer ${this.token}`,
|
|
690
656
|
"Content-Type": "application/json",
|
|
691
657
|
},
|
|
692
|
-
|
|
658
|
+
body: JSON.stringify(data),
|
|
659
|
+
});
|
|
693
660
|
this.decryptedSyncCache = null;
|
|
694
661
|
this.syncCache = null;
|
|
695
662
|
return s.json();
|
package/dist/hooks/bw.d.ts
CHANGED
|
@@ -5,6 +5,12 @@ interface BwConfig {
|
|
|
5
5
|
refreshToken: string;
|
|
6
6
|
}
|
|
7
7
|
export declare const bwClient: Client;
|
|
8
|
+
export interface LoginHints {
|
|
9
|
+
email?: string;
|
|
10
|
+
baseUrl?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function loadLoginHints(): Promise<LoginHints>;
|
|
13
|
+
export declare function saveLoginHints(hints: LoginHints): Promise<void>;
|
|
8
14
|
export declare function loadConfig(): Promise<boolean>;
|
|
9
15
|
export declare function saveConfig(config: BwConfig): Promise<void>;
|
|
10
16
|
export declare function clearConfig(): Promise<void>;
|
package/dist/hooks/bw.js
CHANGED
|
@@ -4,7 +4,34 @@ import path from "path";
|
|
|
4
4
|
import { CipherType, Client } from "../clients/bw.js";
|
|
5
5
|
import { useCallback, useEffect, useState } from "react";
|
|
6
6
|
export const bwClient = new Client();
|
|
7
|
-
const
|
|
7
|
+
const configDir = path.join(os.homedir(), ".config", "bitty");
|
|
8
|
+
const configPath = path.join(configDir, "config.json");
|
|
9
|
+
export async function loadLoginHints() {
|
|
10
|
+
try {
|
|
11
|
+
if (fs.existsSync(configPath)) {
|
|
12
|
+
const content = await fs.promises.readFile(configPath, "utf-8");
|
|
13
|
+
const config = JSON.parse(Buffer.from(content, "base64").toString("utf-8"));
|
|
14
|
+
return { email: config.email, baseUrl: config.baseUrl };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch { }
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
export async function saveLoginHints(hints) {
|
|
21
|
+
let config = {};
|
|
22
|
+
try {
|
|
23
|
+
if (fs.existsSync(configPath)) {
|
|
24
|
+
const content = await fs.promises.readFile(configPath, "utf-8");
|
|
25
|
+
config = JSON.parse(Buffer.from(content, "base64").toString("utf-8"));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch { }
|
|
29
|
+
config.email = hints.email;
|
|
30
|
+
config.baseUrl = hints.baseUrl;
|
|
31
|
+
const encoded = Buffer.from(JSON.stringify(config)).toString("base64");
|
|
32
|
+
await fs.promises.mkdir(configDir, { recursive: true });
|
|
33
|
+
await fs.promises.writeFile(configPath, encoded);
|
|
34
|
+
}
|
|
8
35
|
export async function loadConfig() {
|
|
9
36
|
try {
|
|
10
37
|
if (fs.existsSync(configPath)) {
|
|
@@ -69,7 +96,7 @@ export async function saveConfig(config) {
|
|
|
69
96
|
...config,
|
|
70
97
|
keys,
|
|
71
98
|
})).toString("base64");
|
|
72
|
-
await fs.promises.mkdir(
|
|
99
|
+
await fs.promises.mkdir(configDir, { recursive: true });
|
|
73
100
|
await fs.promises.writeFile(configPath, encConfig);
|
|
74
101
|
}
|
|
75
102
|
export async function clearConfig() {
|
package/dist/login/LoginView.js
CHANGED
|
@@ -4,7 +4,7 @@ import { useEffect, useState } from "react";
|
|
|
4
4
|
import { TextInput } from "../components/TextInput.js";
|
|
5
5
|
import { Button } from "../components/Button.js";
|
|
6
6
|
import { primary } from "../theme/style.js";
|
|
7
|
-
import { bwClient, loadConfig, saveConfig } from "../hooks/bw.js";
|
|
7
|
+
import { bwClient, loadConfig, loadLoginHints, saveConfig, saveLoginHints } from "../hooks/bw.js";
|
|
8
8
|
import { useStatusMessage } from "../hooks/status-message.js";
|
|
9
9
|
import { Checkbox } from "../components/Checkbox.js";
|
|
10
10
|
import { FetchError, TwoFactorProvider } from "../clients/bw.js";
|
|
@@ -94,6 +94,12 @@ export function LoginView({ onLogin }) {
|
|
|
94
94
|
refreshToken: bwClient.refreshToken,
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
|
+
else {
|
|
98
|
+
saveLoginHints({
|
|
99
|
+
email: email?.trim() || undefined,
|
|
100
|
+
baseUrl: url?.trim() || undefined,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
97
103
|
}
|
|
98
104
|
catch (e) {
|
|
99
105
|
showStatusMessage("Login failed, please check your credentials.", "error");
|
|
@@ -105,7 +111,13 @@ export function LoginView({ onLogin }) {
|
|
|
105
111
|
const loggedIn = await loadConfig();
|
|
106
112
|
if (loggedIn) {
|
|
107
113
|
onLogin();
|
|
114
|
+
return;
|
|
108
115
|
}
|
|
116
|
+
const hints = await loadLoginHints();
|
|
117
|
+
if (hints.baseUrl)
|
|
118
|
+
setUrl(hints.baseUrl);
|
|
119
|
+
if (hints.email)
|
|
120
|
+
setEmail(hints.email);
|
|
109
121
|
}
|
|
110
122
|
catch (e) {
|
|
111
123
|
showStatusMessage("Failed to load config file", "error");
|