137 lines
4.8 KiB
TypeScript
137 lines
4.8 KiB
TypeScript
|
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<string[]> {
|
||
|
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<boolean> {
|
||
|
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;
|