- added backup codes for 2 factor authentication
Some checks failed
CI Pipeline / japa-tests (push) Failing after 58s
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
This commit is contained in:
parent
ac473b1e72
commit
005df2e454
32 changed files with 1416 additions and 526 deletions
136
app/services/backup_code_storage.ts
Normal file
136
app/services/backup_code_storage.ts
Normal file
|
@ -0,0 +1,136 @@
|
|||
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;
|
Loading…
Add table
editor.link_modal.header
Reference in a new issue