40 lines
1.3 KiB
TypeScript
40 lines
1.3 KiB
TypeScript
import * as plugins from './plugins.js';
|
|
|
|
const HASH_PREFIX = 'scrypt:v1';
|
|
|
|
export class PasswordHasher {
|
|
public static async hashPassword(passwordArg: string): Promise<string> {
|
|
const salt = plugins.crypto.randomBytes(16).toString('base64url');
|
|
const key = await this.scrypt(passwordArg, salt);
|
|
return `${HASH_PREFIX}:${salt}:${key.toString('base64url')}`;
|
|
}
|
|
|
|
public static async verifyPassword(passwordArg: string, passwordHashArg?: string): Promise<boolean> {
|
|
if (!passwordHashArg) {
|
|
return false;
|
|
}
|
|
const [prefix, version, salt, storedKey] = passwordHashArg.split(':');
|
|
if (`${prefix}:${version}` !== HASH_PREFIX || !salt || !storedKey) {
|
|
return false;
|
|
}
|
|
const candidate = await this.scrypt(passwordArg, salt);
|
|
const stored = Buffer.from(storedKey, 'base64url');
|
|
if (candidate.byteLength !== stored.byteLength) {
|
|
return false;
|
|
}
|
|
return plugins.crypto.timingSafeEqual(candidate, stored);
|
|
}
|
|
|
|
private static async scrypt(passwordArg: string, saltArg: string): Promise<Buffer> {
|
|
return new Promise((resolve, reject) => {
|
|
plugins.crypto.scrypt(passwordArg, saltArg, 64, (error, derivedKey) => {
|
|
if (error) {
|
|
reject(error);
|
|
return;
|
|
}
|
|
resolve(derivedKey as Buffer);
|
|
});
|
|
});
|
|
}
|
|
}
|