Arno Kaimbacher
005df2e454
Some checks failed
CI Pipeline / japa-tests (push) Failing after 58s
- npm updates - coverage validation: elevation ust be positive, depth must be negative - vinejs-provider.js: get enabled extensions from database, not via validOptions.extnames - vue components for backup codes: e.g.: PersonalSettings.vue - validate spaital coverage in leaflet map: draw.component.vue, map.component.vue - add backup code authentication into Login.vue - preset to use no preferred reviewer: Release.vue - 2 new vinejs validation rules: file_scan.ts and file-length.ts
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;
|