import type { HttpContext } from '@adonisjs/core/http'; import User from '#models/user'; import TwoFactorAuthProvider from '#app/services/TwoFactorAuthProvider'; import { StatusCodes } from 'http-status-codes'; import { InvalidArgumentException } from 'node-exceptions'; import { TotpState } from '#contracts/enums'; import BackupCodeStorage, { SecureRandom } from '#services/backup_code_storage'; import BackupCode from '#models/backup_code'; // Here we are generating secret and recovery codes for the user that’s enabling 2FA and storing them to our database. export default class UserController { public async enable({ auth, response, request }: HttpContext) { const user = (await User.find(auth.user?.id)) as User; // await user.load('totp_secret'); // if (!user.totp_secret) { // let totpSecret = new TotpSecret(); // user.related('totp_secret').save(totpSecret); // await user.load('totp_secret'); // } if (!user) { throw new Error('user not available'); } const state: number = request.input('state'); try { switch (state) { case TotpState.STATE_DISABLED: // user.twoFactorSecret = null; // user.twoFactorRecoveryCodes = null; await BackupCode.deleteCodes(user); user.twoFactorSecret = ''; // user.twoFactorRecoveryCodes = ['']; await user.save(); user.state = TotpState.STATE_DISABLED; await user.save(); let storage = new BackupCodeStorage(new SecureRandom()); let backupState = await storage.getBackupCodesState(user); return response.status(StatusCodes.OK).json({ state: TotpState.STATE_DISABLED, backupState: backupState, }); case TotpState.STATE_CREATED: user.twoFactorSecret = TwoFactorAuthProvider.generateSecret(user); user.state = TotpState.STATE_CREATED; await user.save(); let qrcode = await TwoFactorAuthProvider.generateQrCode(user); // throw new InvalidArgumentException('code is missing'); return response.status(StatusCodes.OK).json({ state: user.state, secret: user.twoFactorSecret, url: qrcode.url, svg: qrcode.svg, }); case TotpState.STATE_ENABLED: let code: string = request.input('code'); if (!code) { throw new InvalidArgumentException('code is missing'); } const success = await TwoFactorAuthProvider.enable(user, code); return response.status(StatusCodes.OK).json({ state: success ? TotpState.STATE_ENABLED : TotpState.STATE_CREATED, }); default: throw new InvalidArgumentException('Invalid TOTP state'); } } catch (error) { return response.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ message: 'Invalid TOTP state', }); } } // public async fetchRecoveryCodes({ auth, view }) { // const user = auth?.user; // return view.render('pages/settings', { // twoFactorEnabled: user.isTwoFactorEnabled, // recoveryCodes: user.twoFactorRecoveryCodes, // }); // } /** * @NoAdminRequired * @PasswordConfirmationRequired * * @return JSONResponse */ public async createCodes({ auth, response }: HttpContext) { // $user = $this->userSession->getUser(); const user = (await User.find(auth.user?.id)) as User; // let codes = TwoFactorAuthProvider.generateRecoveryCodes(); let storage = new BackupCodeStorage(new SecureRandom()); // $codes = $this->storage->createCodes($user); const codes = await storage.createCodes(user); let backupState = await storage.getBackupCodesState(user); // return new JSONResponse([ // 'codes' => $codes, // 'state' => $this->storage->getBackupCodesState($user), // ]); return response.status(StatusCodes.OK).json({ codes: codes, // state: success ? TotpState.STATE_ENABLED : TotpState.STATE_CREATED, backupState: backupState, //storage.getBackupCodesState(user), }); } }