Upload password.ts
Browse files- src/password.ts +32 -0
src/password.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { timingSafeEqual, randomBytes, scryptSync } from 'node:crypto';
|
| 2 |
+
|
| 3 |
+
const PREFIX = 'scrypt';
|
| 4 |
+
const KEY_LENGTH = 64;
|
| 5 |
+
|
| 6 |
+
function hashWithSalt(password: string, salt: string): string {
|
| 7 |
+
return scryptSync(password, salt, KEY_LENGTH).toString('base64');
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
export function isPasswordHash(value: string): boolean {
|
| 11 |
+
return value.startsWith(`${PREFIX}$`);
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
export function hashPassword(password: string): string {
|
| 15 |
+
const salt = randomBytes(16).toString('base64');
|
| 16 |
+
const hash = hashWithSalt(password, salt);
|
| 17 |
+
return `${PREFIX}$${salt}$${hash}`;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
export function verifyPassword(password: string, stored: string): boolean {
|
| 21 |
+
if (!stored) return true;
|
| 22 |
+
if (!isPasswordHash(stored)) return password === stored;
|
| 23 |
+
|
| 24 |
+
const [, salt, expected] = stored.split('$');
|
| 25 |
+
if (!salt || !expected) return false;
|
| 26 |
+
|
| 27 |
+
const actual = hashWithSalt(password, salt);
|
| 28 |
+
const expectedBytes = Buffer.from(expected, 'base64');
|
| 29 |
+
const actualBytes = Buffer.from(actual, 'base64');
|
| 30 |
+
if (expectedBytes.length !== actualBytes.length) return false;
|
| 31 |
+
return timingSafeEqual(expectedBytes, actualBytes);
|
| 32 |
+
}
|