tethys.backend/app/Services/TwoFactorAuthProvider.ts
Arno Kaimbacher c70fa4a0d8
Some checks failed
CI Pipeline / japa-tests (push) Failing after 53s
- aded npm packages @types/qrcode, qrcode and node-f2a
- corrected UsersController.ts and RoleController.ts with correct routes for settings
- added migration script and ui and Controller for 2 Factor Authentication
- npm updates
2023-12-29 15:54:49 +01:00

90 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Config from '@ioc:Adonis/Core/Config';
import User from 'App/Models/User';
import { generateSecret } from 'node-2fa/dist/index';
// import cryptoRandomString from 'crypto-random-string';
import QRCode from 'qrcode';
import crypto from 'crypto';
// npm install node-2fa --save
// npm install crypto-random-string --save
// import { cryptoRandomStringAsync } from 'crypto-random-string/index';
// npm install qrcode --save
// npm i --save-dev @types/qrcode
class TwoFactorAuthProvider {
private issuer = Config.get('twoFactorAuthConfig.app.name') || 'TethysCloud';
/**
* generateSecret will generate a user-specific 32-character secret.
* Were providing the name of the app and the users email as parameters for the function.
* This secret key will be used to verify whether the token provided by the user during authentication is valid or not.
*
* Return the default global focus trap stack *
* @param {User} user user for the secrect
* @return {string}
*/
public generateSecret(user: User) {
const secret = generateSecret({
name: this.issuer,
account: user.email,
});
return secret.secret;
}
/**
* We also generated recovery codes which can be used in case were unable to retrieve tokens from 2FA applications.
* We assign the user a list of recovery codes and each code can be used only once during the authentication process.
* The recovery codes are random strings generated using the cryptoRandomString library.
*
* Return recovery codes
* @return {string[]}
*/
public generateRecoveryCodes() {
const recoveryCodeLimit: number = 8;
const codes: string[] = [];
for (let i = 0; i < recoveryCodeLimit; i++) {
const recoveryCode: string = `${this.secureRandomString()}-${this.secureRandomString()}`;
codes.push(recoveryCode);
}
return codes;
}
private secureRandomString() {
// return await cryptoRandomString.async({ length: 10, type: 'hex' });
return this.generateRandomString(10, 'hex');
}
private generateRandomString(length: number, type: 'hex' | 'base64' | 'numeric' = 'hex'): string {
const byteLength = Math.ceil(length * 0.5); // For hex encoding, each byte generates 2 characters
const randomBytes = crypto.randomBytes(byteLength);
switch (type) {
case 'hex':
return randomBytes.toString('hex').slice(0, length);
case 'base64':
return randomBytes.toString('base64').slice(0, length);
case 'numeric':
return randomBytes
.toString('hex')
.replace(/[a-fA-F]/g, '') // Remove non-numeric characters
.slice(0, length);
default:
throw new Error('Invalid type specified');
}
}
public async generateQrCode(user: User) : Promise<{svg: string; url: string; }> {
const issuer = encodeURIComponent(this.issuer); // 'TethysCloud'
// const userName = encodeURIComponent(user.email); // 'rrqx9472%40tethys.at'
const label = `${this.issuer}:${user.email}`;
const algorithm = encodeURIComponent("SHA256");
const query = `?secret=${user.twoFactorSecret}&issuer=${issuer}&algorithm=${algorithm}&digits=6`; // '?secret=FEYCLOSO627CB7SMLX6QQ7BP75L7SJ54&issuer=TethysCloud'
const url = `otpauth://totp/${label}${query}`; // 'otpauth://totp/rrqx9472%40tethys.at?secret=FEYCLOSO627CB7SMLX6QQ7BP75L7SJ54&issuer=TethysCloud'
const svg = await QRCode.toDataURL(url);
return { svg, url };
}
}
export default new TwoFactorAuthProvider();