detached 5d83be3

fix: replace node.crypto

commit
5d83be3decbb952ce4460caa541a23d872118232
author
Kit · · 2025-02-13 07:37 UTC
parent
6fec6e4
signature
SSH UnverifiedSHA256:H91DFuEG4jpk5edzLA4aUKkpv4zv9Q339ipRmkph6Tk The key is unknown
1 file · +72 -21
M src/lib/utils/password.ts +72 -21
@@ -1,32 +1,83 @@
1-import crypto from 'crypto';
1+/* Removed Node's crypto import. Using Web Crypto API instead */
2+
3+/**
4+ * Helper function that converts an ArrayBuffer to a hex string.
5+ */
6+function bufferToHex(buffer: ArrayBuffer): string {
7+ return Array.from(new Uint8Array(buffer))
8+ .map(b => b.toString(16).padStart(2, '0'))
9+ .join('');
10+}
11+
12+/**
13+ * Helper function that converts a hex string to a Uint8Array.
14+ */
15+function hexToBuffer(hex: string): Uint8Array {
16+ if (hex.length % 2 !== 0) {
17+ throw new Error('Invalid hex string');
18+ }
19+ const arr = new Uint8Array(hex.length / 2);
20+ for (let i = 0; i < hex.length; i += 2) {
21+ arr[i / 2] = parseInt(hex.substring(i, i + 2), 16);
22+ }
23+ return arr;
24+}
25+
26+/**
27+ * Derives a password hash using PBKDF2 with the provided salt.
28+ * @param password The plain text password.
29+ * @param salt Uint8Array salt.
30+ * @returns The derived key in hex format.
31+ */
32+async function derivePasswordHash(password: string, salt: Uint8Array): Promise<string> {
33+ const enc = new TextEncoder();
34+ const keyMaterial = enc.encode(password);
35+ console.debug("$state: Key material for password derivation", keyMaterial);
36+ const key = await crypto.subtle.importKey(
37+ "raw",
38+ keyMaterial,
39+ { name: "PBKDF2" },
40+ false,
41+ ["deriveBits"]
42+ );
43+ const derivedBits = await crypto.subtle.deriveBits(
44+ {
45+ name: "PBKDF2",
46+ salt: salt,
47+ iterations: 100000,
48+ hash: "SHA-512"
49+ },
50+ key,
51+ 512
52+ );
53+ console.debug("$state: Derived bits", derivedBits);
54+ return bufferToHex(derivedBits);
55+}
256
357 /**
458 * Securely hashes a password using PBKDF2 with a random salt.
5- * @param password The plain text password to hash
6- * @returns A promise that resolves to the hashed password in the format 'salt:hash'
59+ * @param password The plain text password to hash.
60+ * @returns A promise that resolves to the hashed password in the format 'salt:hash'.
761 */
862 export async function hashPassword(password: string): Promise<string> {
9- return new Promise((resolve, reject) => {
10- const salt = crypto.randomBytes(16).toString('hex');
11- crypto.pbkdf2(password, salt, 100000, 64, 'sha512', (err, derivedKey) => {
12- if (err) return reject(err);
13- resolve(`${salt}:${derivedKey.toString('hex')}`);
14- });
15- });
63+ const salt = crypto.getRandomValues(new Uint8Array(16));
64+ console.debug("$state: Generated salt", salt);
65+ const saltHex = bufferToHex(salt.buffer);
66+ const hash = await derivePasswordHash(password, salt);
67+ return `${saltHex}:${hash}`;
1668 }
1769
1870 /**
19- * Verify a password hash created by hashPassword()
20- * @param password The plain text password to verify
21- * @param hash The hashed password to verify against
22- * @returns A promise that resolves to true if the password matches, false otherwise
71+ * Verify a password hash created by hashPassword().
72+ * @param password The plain text password to verify.
73+ * @param hash The hashed password to verify against in format 'salt:hash'.
74+ * @returns A promise that resolves to true if the password matches, false otherwise.
2375 */
2476 export async function verifyPassword(password: string, hash: string): Promise<boolean> {
25- return new Promise((resolve, reject) => {
26- const [salt, key] = hash.split(':');
27- crypto.pbkdf2(password, salt, 100000, 64, 'sha512', (err, derivedKey) => {
28- if (err) return reject(err);
29- resolve(key === derivedKey.toString('hex'));
30- });
31- });
77+ const [saltHex, hashValue] = hash.split(':');
78+ const salt = hexToBuffer(saltHex);
79+ console.debug("$state: Salt extracted from stored hash", salt);
80+ const derivedHash = await derivePasswordHash(password, salt);
81+ console.debug("$state: Derived hash for verification", derivedHash);
82+ return derivedHash === hashValue;
3283 } ⏎̸
@@ -1,32 +1,83 @@
1-import crypto from 'crypto';1+/* Removed Node's crypto import. Using Web Crypto API instead */
2+
3+/**
4+ * Helper function that converts an ArrayBuffer to a hex string.
5+ */
6+function bufferToHex(buffer: ArrayBuffer): string {
7+ return Array.from(new Uint8Array(buffer))
8+ .map(b => b.toString(16).padStart(2, '0'))
9+ .join('');
10+}
11+
12+/**
13+ * Helper function that converts a hex string to a Uint8Array.
14+ */
15+function hexToBuffer(hex: string): Uint8Array {
16+ if (hex.length % 2 !== 0) {
17+ throw new Error('Invalid hex string');
18+ }
19+ const arr = new Uint8Array(hex.length / 2);
20+ for (let i = 0; i < hex.length; i += 2) {
21+ arr[i / 2] = parseInt(hex.substring(i, i + 2), 16);
22+ }
23+ return arr;
24+}
25+
26+/**
27+ * Derives a password hash using PBKDF2 with the provided salt.
28+ * @param password The plain text password.
29+ * @param salt Uint8Array salt.
30+ * @returns The derived key in hex format.
31+ */
32+async function derivePasswordHash(password: string, salt: Uint8Array): Promise<string> {
33+ const enc = new TextEncoder();
34+ const keyMaterial = enc.encode(password);
35+ console.debug("$state: Key material for password derivation", keyMaterial);
36+ const key = await crypto.subtle.importKey(
37+ "raw",
38+ keyMaterial,
39+ { name: "PBKDF2" },
40+ false,
41+ ["deriveBits"]
42+ );
43+ const derivedBits = await crypto.subtle.deriveBits(
44+ {
45+ name: "PBKDF2",
46+ salt: salt,
47+ iterations: 100000,
48+ hash: "SHA-512"
49+ },
50+ key,
51+ 512
52+ );
53+ console.debug("$state: Derived bits", derivedBits);
54+ return bufferToHex(derivedBits);
55+}
2 56
3 /**57 /**
4 * Securely hashes a password using PBKDF2 with a random salt.58 * Securely hashes a password using PBKDF2 with a random salt.
5- * @param password The plain text password to hash59+ * @param password The plain text password to hash.
6- * @returns A promise that resolves to the hashed password in the format 'salt:hash'60+ * @returns A promise that resolves to the hashed password in the format 'salt:hash'.
7 */61 */
8 export async function hashPassword(password: string): Promise<string> {62 export async function hashPassword(password: string): Promise<string> {
9- return new Promise((resolve, reject) => {63+ const salt = crypto.getRandomValues(new Uint8Array(16));
10- const salt = crypto.randomBytes(16).toString('hex');64+ console.debug("$state: Generated salt", salt);
11- crypto.pbkdf2(password, salt, 100000, 64, 'sha512', (err, derivedKey) => {65+ const saltHex = bufferToHex(salt.buffer);
12- if (err) return reject(err);66+ const hash = await derivePasswordHash(password, salt);
13- resolve(`${salt}:${derivedKey.toString('hex')}`);67+ return `${saltHex}:${hash}`;
14- });
15- });
16 }68 }
17 69
18 /**70 /**
19- * Verify a password hash created by hashPassword()71+ * Verify a password hash created by hashPassword().
20- * @param password The plain text password to verify72+ * @param password The plain text password to verify.
21- * @param hash The hashed password to verify against73+ * @param hash The hashed password to verify against in format 'salt:hash'.
22- * @returns A promise that resolves to true if the password matches, false otherwise74+ * @returns A promise that resolves to true if the password matches, false otherwise.
23 */75 */
24 export async function verifyPassword(password: string, hash: string): Promise<boolean> {76 export async function verifyPassword(password: string, hash: string): Promise<boolean> {
25- return new Promise((resolve, reject) => {77+ const [saltHex, hashValue] = hash.split(':');
26- const [salt, key] = hash.split(':');78+ const salt = hexToBuffer(saltHex);
27- crypto.pbkdf2(password, salt, 100000, 64, 'sha512', (err, derivedKey) => {79+ console.debug("$state: Salt extracted from stored hash", salt);
28- if (err) return reject(err);80+ const derivedHash = await derivePasswordHash(password, salt);
29- resolve(key === derivedKey.toString('hex'));81+ console.debug("$state: Derived hash for verification", derivedHash);
30- });82+ return derivedHash === hashValue;
31- });
32 } ⏎̸83 } ⏎̸

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 3

Tags 9

↑↓ moveenter openesc close