chrome-tools 1.1.7 → 1.1.8
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/src/modules/passwords.js +96 -29
- package/package.json +1 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
1
3
|
import { join } from "node:path";
|
|
2
|
-
import { resolveTargetProfiles } from "../core/chrome.js";
|
|
4
|
+
import { discoverProfiles, resolveTargetProfiles, } from "../core/chrome.js";
|
|
3
5
|
import { deriveEncryptionKey, decryptValue } from "../core/platform/index.js";
|
|
4
6
|
import { chromeTimeToDate } from "../core/crypto.js";
|
|
5
7
|
import { queryDatabase } from "../core/db.js";
|
|
@@ -14,6 +16,44 @@ const PASSWORD_STORES = [
|
|
|
14
16
|
{ file: "Login Data", source: "device" },
|
|
15
17
|
{ file: "Login Data For Account", source: "account" },
|
|
16
18
|
];
|
|
19
|
+
/** Where a CDP launcher keeps the User Data directory it drives Chrome with. */
|
|
20
|
+
const AUTOMATION_DIR = join(homedir(), ".chrome_cdp_profile");
|
|
21
|
+
/** …and where it records a relocated one. */
|
|
22
|
+
const AUTOMATION_CONFIG = join(homedir(), ".config", "chrome-cdp-manager", "config.json");
|
|
23
|
+
/** `CHROME_AUTOMATION_DIR` values meaning "do not look there at all". */
|
|
24
|
+
const AUTOMATION_OFF = ["0", "off", "none", "false"];
|
|
25
|
+
/**
|
|
26
|
+
* The automation profile tree, on a machine that has one.
|
|
27
|
+
*
|
|
28
|
+
* Most machines do not: no CDP launcher is installed, the directory was never
|
|
29
|
+
* created, or it exists but holds no profile yet. All three answer undefined,
|
|
30
|
+
* which leaves the search exactly as it was before this existed — the everyday
|
|
31
|
+
* profile, alone. Nothing here is required for a plain Chrome install.
|
|
32
|
+
*
|
|
33
|
+
* `CHROME_AUTOMATION_DIR` names the tree explicitly, and `off` (or `0`, `none`,
|
|
34
|
+
* `false`) skips it even where one exists.
|
|
35
|
+
*/
|
|
36
|
+
function automationChromeDir() {
|
|
37
|
+
const override = process.env.CHROME_AUTOMATION_DIR?.trim();
|
|
38
|
+
if (override && AUTOMATION_OFF.includes(override.toLowerCase()))
|
|
39
|
+
return undefined;
|
|
40
|
+
const dir = override || configuredAutomationDir() || AUTOMATION_DIR;
|
|
41
|
+
// Holding a profile is what makes it a tree worth reading — an absent or
|
|
42
|
+
// empty directory would otherwise add a root every lookup searches for
|
|
43
|
+
// nothing, and turn a missing `--profile` into a spurious refusal.
|
|
44
|
+
return discoverProfiles(dir).length > 0 ? dir : undefined;
|
|
45
|
+
}
|
|
46
|
+
/** The profile directory a CDP launcher was configured with, if it says. */
|
|
47
|
+
function configuredAutomationDir() {
|
|
48
|
+
try {
|
|
49
|
+
const stored = JSON.parse(readFileSync(AUTOMATION_CONFIG, "utf-8"));
|
|
50
|
+
return typeof stored.profileDir === "string" && stored.profileDir ? stored.profileDir : undefined;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// No launcher config, or an unreadable one: fall back to its default path.
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
17
57
|
/**
|
|
18
58
|
* Extracts and decrypts saved passwords from Chrome's password databases.
|
|
19
59
|
* Reads both the device-local and account-synced stores, labelling each entry
|
|
@@ -24,41 +64,68 @@ const PASSWORD_STORES = [
|
|
|
24
64
|
*/
|
|
25
65
|
export function getPasswords({ chromeDir, profilePath, profile, search, browser, decryptionKey, } = {}) {
|
|
26
66
|
const userDataDir = profilePath || chromeDir;
|
|
27
|
-
|
|
28
|
-
//
|
|
29
|
-
|
|
67
|
+
// The automation User Data directory first, then the everyday one. A Chrome
|
|
68
|
+
// driven over CDP keeps its own profile tree, so a login saved while signing
|
|
69
|
+
// in to that window is stored there and nowhere else — looking only at the
|
|
70
|
+
// everyday tree could not find it. Naming a directory or a browser answers
|
|
71
|
+
// "which tree?" outright, and is used as given.
|
|
72
|
+
const roots = [];
|
|
73
|
+
const automation = userDataDir || browser ? undefined : automationChromeDir();
|
|
74
|
+
if (automation)
|
|
75
|
+
roots.push(automation);
|
|
76
|
+
roots.push(userDataDir);
|
|
30
77
|
const results = [];
|
|
31
78
|
const term = search?.toLowerCase();
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
79
|
+
// A named profile missing from one tree is not a failure while another is
|
|
80
|
+
// still to come — but a profile in none of them is still an error, so the
|
|
81
|
+
// first refusal is kept and raised if no tree resolved at all.
|
|
82
|
+
let refusal;
|
|
83
|
+
let resolved = false;
|
|
84
|
+
for (const root of roots) {
|
|
85
|
+
let profiles;
|
|
86
|
+
try {
|
|
87
|
+
profiles = resolveTargetProfiles({ chromeDir: root, profile, browser });
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
refusal ??= error;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
resolved = true;
|
|
94
|
+
// Per root: on Windows the key comes from that tree's own Local State.
|
|
95
|
+
let derivedKey = decryptionKey;
|
|
96
|
+
for (const p of profiles) {
|
|
97
|
+
for (const store of PASSWORD_STORES) {
|
|
98
|
+
const dbPath = join(p.path, store.file);
|
|
99
|
+
try {
|
|
100
|
+
const rows = queryDatabase(dbPath, `SELECT origin_url, username_value, password_value, date_last_used
|
|
101
|
+
FROM logins ORDER BY origin_url`);
|
|
102
|
+
for (const row of rows) {
|
|
103
|
+
const entry = {
|
|
104
|
+
profile: p.name,
|
|
105
|
+
source: store.source,
|
|
106
|
+
url: row.origin_url,
|
|
107
|
+
username: row.username_value,
|
|
108
|
+
password: row.password_value?.length > 0
|
|
109
|
+
? decryptValue(row.password_value, derivedKey ??= deriveEncryptionKey(root, browser))
|
|
110
|
+
: "",
|
|
111
|
+
lastUsed: chromeTimeToDate(row.date_last_used || 0),
|
|
112
|
+
};
|
|
113
|
+
if (term) {
|
|
114
|
+
if (!(entry.url && entry.url.toLowerCase().includes(term)) &&
|
|
115
|
+
!(entry.username && entry.username.toLowerCase().includes(term))) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
53
118
|
}
|
|
119
|
+
results.push(entry);
|
|
54
120
|
}
|
|
55
|
-
results.push(entry);
|
|
56
121
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
122
|
+
catch {
|
|
123
|
+
// Skip missing or unreadable stores and continue with the others.
|
|
124
|
+
}
|
|
60
125
|
}
|
|
61
126
|
}
|
|
62
127
|
}
|
|
128
|
+
if (!resolved && refusal)
|
|
129
|
+
throw refusal;
|
|
63
130
|
return results;
|
|
64
131
|
}
|