import crypto from 'crypto'; import { PersistedPassword } from '@/entities/types'; import AbstractStrategy from '../AbstractStrategy'; const DEFAULT_SALTED_PASSWORD_LENGTH = 256; const DEFAULT_SALT_LENGTH = 16; const DEFAULT_ITERATIONS = 10000; const DEFAULT_HASH_FUNCTION = 'sha256'; const DEFAULT_ENCODING = 'base64'; const ALGORITHM_VERSION = 1; export default class BasicAuthStrategy extends AbstractStrategy { generateSecret(): string { return crypto.randomBytes(DEFAULT_SALT_LENGTH).toString(DEFAULT_ENCODING); } async generatePersistablePasswordData( password: string ): Promise { return new Promise((resolve, reject) => { const salt = crypto .randomBytes(DEFAULT_SALT_LENGTH) .toString(DEFAULT_ENCODING); crypto.pbkdf2( password, salt, DEFAULT_ITERATIONS, DEFAULT_SALTED_PASSWORD_LENGTH, DEFAULT_HASH_FUNCTION, (error, hash) => { if (error) { reject(error); } else { resolve({ salt, hash: hash.toString(DEFAULT_ENCODING), iterations: DEFAULT_ITERATIONS, length: DEFAULT_SALTED_PASSWORD_LENGTH, hashFunction: DEFAULT_HASH_FUNCTION, saltEncoding: DEFAULT_ENCODING, algorithmVersion: ALGORITHM_VERSION, }); } } ); }); } /** * Verifies the attempted password against the password information saved * in the database. */ async verifyPersistedPassword({ passwordData, passwordAttempt, }: { passwordData: PersistedPassword; passwordAttempt: string; }): Promise { return new Promise((resolve, reject) => { crypto.pbkdf2( passwordAttempt, passwordData.salt, passwordData.iterations, passwordData.length, passwordData.hashFunction, async (error, passwordAttemptHash) => { if (error) { reject(error); } else { const passwordIsCorrect = crypto.timingSafeEqual( Buffer.from(passwordData.hash, passwordData.saltEncoding), passwordAttemptHash ); if (!passwordIsCorrect) { resolve(false); return; } resolve(true); } } ); }); } }