tethys.backend/app/services/backup_code_storage.ts
Arno Kaimbacher 005df2e454 - added backup codes for 2 factor authentication
- 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
2024-07-08 13:52:20 +02:00

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;