import User from '#models/user'; import BackupCode from '#models/backup_code'; import hash from '@adonisjs/core/services/hash'; export interface ISecureRandom { CHAR_UPPER: string; CHAR_LOWER: string; CHAR_DIGITS: string; CHAR_SYMBOLS: string; CHAR_ALPHANUMERIC: string; CHAR_HUMAN_READABLE: string; /** * Generate a random string of specified length. * @param int $length The length of the generated string * @param string $characters An optional list of characters to use if no character list is * specified all valid base64 characters are used. * @return string * @since 8.0.0 */ generate(length: number, characters?: string): string; } export class SecureRandom implements ISecureRandom { CHAR_UPPER: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; CHAR_LOWER: string = 'abcdefghijklmnopqrstuvwxyz'; CHAR_DIGITS: string = '0123456789'; CHAR_SYMBOLS: string = '!"#$%&\\\'()*+,-./:;<=>?@[]^_`{|}~'; CHAR_ALPHANUMERIC: string = this.CHAR_UPPER + this.CHAR_LOWER + this.CHAR_DIGITS; CHAR_HUMAN_READABLE: string = 'abcdefgijkmnopqrstwxyzABCDEFGHJKLMNPQRSTWXYZ23456789'; public generate(length: number, characters: string = this.CHAR_ALPHANUMERIC): string { if (length <= 0) { throw new Error('Invalid length specified: ' + length + ' must be bigger than 0'); } const maxCharIndex: number = characters.length - 1; let randomString: string = ''; while (length > 0) { const randomNumber: number = Math.floor(Math.random() * (maxCharIndex + 1)); randomString += characters[randomNumber]; length--; } return randomString; } } class BackupCodeStorage { private static CODE_LENGTH: number = 16; // private mapper: BackupCodeMapper; // private hasher: IHasher; private random: ISecureRandom; // private eventDispatcher: IEventDispatcher; // constructor(mapper: BackupCodeMapper, random: ISecureRandom, hasher: IHasher, eventDispatcher: IEventDispatcher) { // this.mapper = mapper; // this.hasher = hasher; // this.random = random; // this.eventDispatcher = eventDispatcher; // } constructor(random: ISecureRandom) { // this.mapper = mapper; // this.hasher = hasher; this.random = random; // this.eventDispatcher = eventDispatcher; } public async createCodes(user: User, number: number = 10): Promise { let results: string[] = []; // this.mapper.deleteCodes(user); await BackupCode.deleteCodes(user); // user.twoFactorRecoveryCodes = [""]; // const uid = user.getUID(); for (let i = 1; i <= Math.min(number, 20); i++) { const code = this.random.generate(BackupCodeStorage.CODE_LENGTH, this.random.CHAR_HUMAN_READABLE); // const code = crypto // .randomBytes(Math.ceil(BackupCodeStorage.CODE_LENGTH / 2)) // .toString('hex') // .slice(0, BackupCodeStorage.CODE_LENGTH); const dbCode = new BackupCode(); // dbCode.setUserId(uid); // dbCode.setCode(this.hasher.hash(code)); dbCode.code = await hash.make(code); // dbCode.setUsed(0); dbCode.used = false; // this.mapper.insert(dbCode); // await dbCode.save(); await dbCode.related('user').associate(user); // speichert schon ab results.push(code); } // this.eventDispatcher.dispatchTyped(new CodesGenerated(user)); return results; } public async hasBackupCodes(user: User): Promise { const codes = await user.getBackupCodes(); return codes.length > 0; } public async getBackupCodesState(user: User) { // const codes = this.mapper.getBackupCodes(user); // const codes = await user.related('backupcodes').query().exec(); const codes: BackupCode[] = await user.getBackupCodes(); const total = codes.length; let used: number = 0; codes.forEach((code) => { if (code.used === true) { used++; } }); return { enabled: total > 0, total: total, used: used, }; } // public validateCode(user: User, code: string): boolean { // const dbCodes = await user.getBackupCodes(); // for (const dbCode of dbCodes) { // if (parseInt(dbCode.getUsed()) === 0 && this.hasher.verify(code, dbCode.getCode())) { // dbCode.setUsed(1); // this.mapper.update(dbCode); // return true; // } // } // return false; // } // public deleteCodes(user: User): void { // this.mapper.deleteCodes(user); // } } export default BackupCodeStorage;