############################################################################## # # pgAdmin 4 - PostgreSQL Tools # # Copyright (C) 2013 - 2023, The pgAdmin Development Team # This software is released under the PostgreSQL Licence # ############################################################################## """Multi-factor Authentication implementation for Time-based One-Time Password (TOTP) applications""" import base64 from io import BytesIO from typing import Union from flask import url_for, session, flash from flask_babel import gettext as _ from flask_login import current_user import pyotp import qrcode import config from pgadmin.model import UserMFA from .registry import BaseMFAuth from .utils import ValidationException, fetch_auth_option, mfa_add _TOTP_AUTH_METHOD = "authenticator" _TOTP_AUTHENTICATOR = _("Authenticator App") class TOTPAuthenticator(BaseMFAuth): """ Authenction class for TOTP based authentication. Base Class: BaseMFAuth """ @classmethod def __create_topt_for_currentuser(cls) -> pyotp.TOTP: """ Create the TOPT object using the secret stored for the current user in the configuration database. Assumption: Configuration database is not modified by anybody manually, and removed the secrete for the current user. Raises: ValidationException: Raises when user is not registered for this authenction method. Returns: pyotp.TOTP: TOTP object for the current user (if registered) """ options, found = fetch_auth_option(_TOTP_AUTH_METHOD) if found is False: raise ValidationException(_( "User has not registered the Time-based One-Time Password " "(TOTP) Authenticator for authentication." )) if options is None or options == '': raise ValidationException(_( "User does not have valid HASH to generate the OTP." )) return pyotp.TOTP(options) @property def name(self) -> str: """ Name of the authetication method for internal presentation. Returns: str: Short name for this authentication method """ return _TOTP_AUTH_METHOD @property def label(self) -> str: """ Label for the UI for this authentication method. Returns: str: User presentable string for this auth method """ return _(_TOTP_AUTHENTICATOR) @property def icon(self) -> str: """ Property for the icon url string for this auth method, to be used on the authentication or registration page. Returns: str: url for the icon representation for this auth method """ return url_for("mfa.static", filename="images/totp_lock.svg") def validate(self, **kwargs): """ Validate the code sent using the HTTP request. Raises: ValidationException: Raises when code is not valid """ code = kwargs.get('code', None) totp = TOTPAuthenticator.__create_topt_for_currentuser() if totp.verify(code) is False: raise ValidationException("Invalid Code") def validation_view(self) -> str: """ Generate the portion of the view to render on the authentication page Returns: str: Authentication view as a string """ return ( "