v0.1.4 2490b00

password.ts RawHistoryFolder

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 */
6function 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 */
15function 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 */
32async 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}
56
57/**
58 * Securely hashes a password using PBKDF2 with a random salt.
59 * @param password The plain text password to hash.
60 * @returns A promise that resolves to the hashed password in the format 'salt:hash'.
61 */
62export async function hashPassword(password: string): Promise<string> {
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}`;
68}
69
70/**
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.
75 */
76export async function verifyPassword(password: string, hash: string): Promise<boolean> {
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;
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 8

↑↓ moveenter openesc close