/home/ivoiecob/email.hirewise-va.com/modules/StandardResetPassword/Module.php
<?php
/**
 * This code is licensed under AGPLv3 license or Afterlogic Software License
 * if commercial version of the product was purchased.
 * For full statements of the licenses see LICENSE-AFTERLOGIC and LICENSE-AGPL3 files.
 */

namespace Aurora\Modules\StandardResetPassword;

use Aurora\System\Api;
use Aurora\System\Application;
use PHPMailer\PHPMailer\PHPMailer;
use Aurora\Modules\Core\Models\User;

/**
 * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
 * @license https://afterlogic.com/products/common-licensing Afterlogic Software License
 * @copyright Copyright (c) 2023, Afterlogic Corp.
 *
 * @property Settings $oModuleSettings
 *
 * @package Modules
 */
class Module extends \Aurora\System\Module\AbstractWebclientModule
{
    /***** private functions *****/
    /**
     * Initializes Module.
     *
     * @ignore
     */
    public function init()
    {
        $this->extendObject(
            'Aurora\Modules\Core\Classes\User',
            array(
                'RecoveryEmail' => array('string', ''),
                'PasswordResetHash' => array('string', ''),
                'ConfirmRecoveryEmailHash' => array('string', ''),
            )
        );

        $this->aErrors = [
            Enums\ErrorCodes::WrongPassword => $this->i18N('ERROR_WRONG_PASSWORD'),
        ];

        $this->AddEntry('confirm-recovery-email', 'EntryConfirmRecoveryEmail');
    }

    /**
     * @return Module
     */
    public static function getInstance()
    {
        return parent::getInstance();
    }

    /**
     * @return Module
     */
    public static function Decorator()
    {
        return parent::Decorator();
    }

    /**
     * @return Settings
     */
    public function getModuleSettings()
    {
        return $this->oModuleSettings;
    }

    public function EntryConfirmRecoveryEmail()
    {
        Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);
        $sHash = (string) \Aurora\System\Router::getItemByIndex(1, '');
        $oModuleManager = Api::GetModuleManager();
        $sSiteName = $oModuleManager->getModuleConfigValue('Core', 'SiteName');
        $sTheme = $oModuleManager->getModuleConfigValue('CoreWebclient', 'Theme');

        $oUser = null;
        try {
            $oUser = $this->getUserByHash($sHash, 'confirm-recovery-email');
        } catch (\Exception $oEx) {
            Api::LogException($oEx);
        }
        $ConfirmRecoveryEmailHeading = '';
        $ConfirmRecoveryEmailInfo = '';
        if ($oUser instanceof User && $sHash === $oUser->getExtendedProp(self::GetName() . '::ConfirmRecoveryEmailHash')) {
            $ConfirmRecoveryEmailHeading = $this->i18N('HEADING_CONFIRM_EMAIL_RECOVERY_HASH');
            $ConfirmRecoveryEmailInfo = \strtr($this->i18N('INFO_CONFIRM_EMAIL_RECOVERY_HASH'), [
                '%SITE_NAME%' => $sSiteName,
                '%RECOVERY_EMAIL%' => $oUser->getExtendedProp(self::GetName() . '::RecoveryEmail'),
            ]);
            $oMin = \Aurora\Modules\Min\Module::Decorator();
            if ($oMin) {
                $oMin->DeleteMinByHash($sHash);
            }
            $oUser->setExtendedProp(self::GetName() . '::ConfirmRecoveryEmailHash', '');
            $oCoreDecorator = \Aurora\Modules\Core\Module::Decorator();
            $oCoreDecorator->UpdateUserObject($oUser);
        } else {
            $ConfirmRecoveryEmailHeading = $this->i18N('HEADING_CONFIRM_EMAIL_RECOVERY_HASH');
            $ConfirmRecoveryEmailInfo = $this->i18N('ERROR_LINK_NOT_VALID');
        }
        $sConfirmRecoveryEmailTemplate = \file_get_contents($this->GetPath() . '/templates/EntryConfirmRecoveryEmail.html');

        \Aurora\Modules\CoreWebclient\Module::Decorator()->SetHtmlOutputHeaders();
        return \strtr($sConfirmRecoveryEmailTemplate, array(
            '{{SiteName}}' => $sSiteName . ' - ' . $ConfirmRecoveryEmailHeading,
            '{{Theme}}' => $sTheme,
            '{{ConfirmRecoveryEmailHeading}}' => $ConfirmRecoveryEmailHeading,
            '{{ConfirmRecoveryEmailInfo}}' => $ConfirmRecoveryEmailInfo,
            '{{ActionOpenApp}}' => \strtr($this->i18N('ACTION_OPEN_SITENAME'), ['%SITE_NAME%' => $sSiteName]),
            '{{OpenAppUrl}}' => Application::getBaseUrl(),
        ));
    }

    protected function getMinId($iUserId, $sType, $sFunction = '')
    {
        return \implode('|', array(self::GetName(), $iUserId, \md5($iUserId), $sType, $sFunction));
    }

    protected function generateHash($iUserId, $sType, $sFunction = '')
    {
        $mHash = '';
        $oMin = \Aurora\Modules\Min\Module::Decorator();
        if ($oMin) {
            $sMinId = $this->getMinId($iUserId, $sType, $sFunction);
            $mHash = $oMin->GetMinByID($sMinId);

            if ($mHash) {
                $mHash = $oMin->DeleteMinByID($sMinId);
            }

            $iRecoveryLinkLifetimeMinutes = $this->oModuleSettings->RecoveryLinkLifetimeMinutes;
            $iExpiresSeconds = time() + $iRecoveryLinkLifetimeMinutes * 60;
            $mHash = $oMin->CreateMin(
                $sMinId,
                array(
                    'UserId' => $iUserId,
                    'Type' => $sType
                ),
                $iUserId,
                $iExpiresSeconds
            );
        }

        return $mHash;
    }

    protected function getSmtpConfig()
    {
        return [
            'Host' => $this->oModuleSettings->NotificationHost,
            'Port' => $this->oModuleSettings->NotificationPort,
            'UseSsl' => !empty($this->oModuleSettings->NotificationSMTPSecure),
            'SMTPAuth' => (bool) $this->oModuleSettings->NotificationUseAuth,
            'SMTPSecure' => $this->oModuleSettings->NotificationSMTPSecure,
            'Username' => $this->oModuleSettings->NotificationLogin,
            'Password' => \Aurora\System\Utils::DecryptValue($this->oModuleSettings->NotificationPassword),
        ];
    }

    protected function getMailAccountByEmail($sEmail)
    {
        $oAccount = null;
        $oUser = \Aurora\Modules\Core\Module::Decorator()->GetUserByPublicId($sEmail);
        if ($oUser instanceof User) {
            $bPrevState = \Aurora\Api::skipCheckUserRole(true);
            if (class_exists('\Aurora\Modules\Mail\Module')) {
                $oAccount = \Aurora\Modules\Mail\Module::Decorator()->GetAccountByEmail($sEmail, $oUser->Id);
            }
            \Aurora\Api::skipCheckUserRole($bPrevState);
        }
        return $oAccount;
    }

    protected function getMailAccountConfig($sEmail)
    {
        $aConfig = [
            'Host' => '',
            'Port' => '',
            'UseSsl' => false,
            'SMTPSecure' => 'ssl',
            'SMTPAuth' => false,
            'Username' => '',
            'Password' => '',
        ];

        if (class_exists('\Aurora\Modules\Mail\Enums\SmtpAuthType')) {
            $oSendAccount = $this->getMailAccountByEmail($sEmail);
            $oSendServer = $oSendAccount ? $oSendAccount->getServer() : null;
            if ($oSendServer) {
                $aConfig['Host'] = $oSendServer->OutgoingServer;
                $aConfig['Port'] = $oSendServer->OutgoingPort;
                switch ($oSendServer->SmtpAuthType) {
                    case \Aurora\Modules\Mail\Enums\SmtpAuthType::NoAuthentication:
                        break;
                    case \Aurora\Modules\Mail\Enums\SmtpAuthType::UseSpecifiedCredentials:
                        $aConfig['UseSsl'] = $oSendServer->OutgoingUseSsl;
                        $aConfig['SMTPAuth'] = true;
                        $aConfig['Username'] = $oSendServer->SmtpLogin;
                        $aConfig['Password'] = $oSendServer->SmtpPassword;
                        break;
                    case \Aurora\Modules\Mail\Enums\SmtpAuthType::UseUserCredentials:
                        $aConfig['UseSsl'] = $oSendServer->OutgoingUseSsl;
                        $aConfig['SMTPAuth'] = true;
                        $aConfig['Username'] = $oSendAccount->IncomingLogin;
                        $aConfig['Password'] = $oSendAccount->getPassword();
                        break;
                }
            }
        }

        return $aConfig;
    }

    /**
     * Sends notification email.
     * @param string $sRecipientEmail
     * @param string $sSubject
     * @param string $sBody
     * @param bool $bIsHtmlBody
     * @param string $sSiteName
     * @return bool
     * @throws \Exception
     */
    protected function sendMessage($sRecipientEmail, $sSubject, $sBody, $bIsHtmlBody, $sSiteName)
    {
        $bResult = false;

        $oMail = new PHPMailer();

        $sFrom = $this->oModuleSettings->NotificationEmail;
        $sType = \strtolower($this->oModuleSettings->NotificationType);
        switch ($sType) {
            case 'mail':
                $oMail->isMail();
                break;
            case 'smtp':
            case 'account':
                $oMail->isSMTP();
                $aConfig = $sType === 'smtp' ? $this->getSmtpConfig() : $this->getMailAccountConfig($sFrom);
                $oMail->Host = $aConfig['Host'];
                $oMail->Port = $aConfig['Port'];
                $oMail->SMTPAuth = $aConfig['SMTPAuth'];
                if ($aConfig['UseSsl']) {
                    $oMail->SMTPSecure = $aConfig['SMTPSecure'];
                }
                $oMail->Username = $aConfig['Username'];
                $oMail->Password = $aConfig['Password'];
                $oMail->SMTPOptions = array(
                    'ssl' => array(
                        'verify_peer' => false,
                        'verify_peer_name' => false,
                        'allow_self_signed' => true
                    )
                );
                break;
        }

        //$oMail->Timeout = 10; // seconds
        $oMail->setFrom($sFrom);
        $oMail->addAddress($sRecipientEmail);
        $oMail->addReplyTo($sFrom, $sSiteName);

        $oMail->Subject = $sSubject;
        $oMail->Body = $sBody;
        $oMail->isHTML($bIsHtmlBody);

        try {
            $bResult = $oMail->send();
        } catch (\Exception $oEx) {
            Api::LogException($oEx);
            throw new \Exception($oEx->getMessage());
        }
        if (!$bResult && !empty($oMail->ErrorInfo)) {
            Api::Log("Message could not be sent. Mailer Error: {$oMail->ErrorInfo}");
            throw new \Exception($oMail->ErrorInfo);
        }

        return $bResult;
    }

    protected function getHashModuleName()
    {
        return $this->oModuleSettings->HashModuleName;
    }
    /**
     * Sends password reset message.
     * @param string $sRecipientEmail
     * @param string $sHash
     * @return boolean
     */
    protected function sendPasswordResetMessage($sRecipientEmail, $sHash)
    {
        $oModuleManager = Api::GetModuleManager();
        $sSiteName = $oModuleManager->getModuleConfigValue('Core', 'SiteName');

        $sBody = \file_get_contents($this->GetPath() . '/templates/mail/Message.html');
        if (\is_string($sBody)) {
            $sGreeting = $this->i18N('LABEL_MESSAGE_GREETING');
            $sMessage = \strtr($this->i18N('LABEL_RESET_PASSWORD_MESSAGE'), [
                '%SITE_NAME%' => $sSiteName,
                '%RESET_PASSWORD_URL%' => \rtrim(Application::getBaseUrl(), '\\/ ') . '/#' . $this->getHashModuleName() . '/' . $sHash,
            ]);
            $sSignature = \strtr($this->i18N('LABEL_MESSAGE_SIGNATURE'), ['%SITE_NAME%' => $sSiteName]);
            $sBody = \strtr($sBody, array(
                '{{GREETING}}' => $sGreeting,
                '{{MESSAGE}}' => $sMessage,
                '{{SIGNATURE}}' => $sSignature,
            ));
        }
        $bIsHtmlBody = true;
        $sSubject = $this->i18N('LABEL_RESET_PASSWORD_SUBJECT');
        return $this->sendMessage($sRecipientEmail, $sSubject, $sBody, $bIsHtmlBody, $sSiteName);
    }

    /**
     * Sends recovery email confirmation message.
     * @param string $sRecipientEmail
     * @param string $sHash
     * @return bool
     */
    protected function sendRecoveryEmailConfirmationMessage($sRecipientEmail, $sHash)
    {
        $oModuleManager = Api::GetModuleManager();
        $sSiteName = $oModuleManager->getModuleConfigValue('Core', 'SiteName');

        $sBody = \file_get_contents($this->GetPath() . '/templates/mail/Message.html');
        if (\is_string($sBody)) {
            $sGreeting = $this->i18N('LABEL_MESSAGE_GREETING');
            $sMessage = \strtr($this->i18N('LABEL_CONFIRM_EMAIL_MESSAGE'), [
                '%RECOVERY_EMAIL%' => $sRecipientEmail,
                '%SITE_NAME%' => $sSiteName,
                '%RESET_PASSWORD_URL%' => \rtrim(Application::getBaseUrl(), '\\/ ') . '?/confirm-recovery-email/' . $sHash,
            ]);
            $sSignature = \strtr($this->i18N('LABEL_MESSAGE_SIGNATURE'), ['%SITE_NAME%' => $sSiteName]);
            $sBody = \strtr($sBody, array(
                '{{GREETING}}' => $sGreeting,
                '{{MESSAGE}}' => $sMessage,
                '{{SIGNATURE}}' => $sSignature,
            ));
        }
        $bIsHtmlBody = true;
        $sSubject = \strtr($this->i18N('LABEL_CONFIRM_EMAIL_SUBJECT'), ['%RECOVERY_EMAIL%' => $sRecipientEmail]);
        return $this->sendMessage($sRecipientEmail, $sSubject, $sBody, $bIsHtmlBody, $sSiteName);
    }

    /**
     * Returns user with identifier obtained from the hash.
     *
     * @param string $sHash
     * @param string $sType
     * @param boolean $bAdd5Min
     * @return \Aurora\Modules\Core\Models\User
     */
    protected function getUserByHash($sHash, $sType, $bAdd5Min = false)
    {
        $oUser = null;
        $oMin = \Aurora\Modules\Min\Module::Decorator();
        $mHash = $oMin ? $oMin->GetMinByHash($sHash) : null;
        if (!empty($mHash) && isset($mHash['__hash__'], $mHash['UserId'], $mHash['Type']) && $mHash['Type'] === $sType) {
            $iRecoveryLinkLifetimeMinutes = $this->oModuleSettings->RecoveryLinkLifetimeMinutes;
            $bRecoveryLinkPrmament = ($iRecoveryLinkLifetimeMinutes === 0);
            if (!$bRecoveryLinkPrmament) {
                $iExpiresSeconds = $mHash['ExpireDate'];
                if ($bAdd5Min) {
                    $iExpiresSeconds += 5 * 60;
                }
                if ($iExpiresSeconds > time()) {
                    $bRecoveryLinkPrmament = true;
                } else {
                    throw new \Exception($this->i18N('ERROR_LINK_NOT_VALID'));
                }
            }
            if ($bRecoveryLinkPrmament) {
                $iUserId = $mHash['UserId'];
                $bPrevState = \Aurora\Api::skipCheckUserRole(true);
                $oUser = \Aurora\Modules\Core\Module::Decorator()->GetUser($iUserId);
                \Aurora\Api::skipCheckUserRole($bPrevState);
            }
        }
        return $oUser;
    }

    /**
     * Get recovery email address partly replaced with stars.
     * @param \Aurora\Modules\Core\Models\User $oUser
     * @return string
     */
    protected function getStarredRecoveryEmail($oUser)
    {
        $sResult = '';

        if ($oUser instanceof User) {
            $sRecoveryEmail = $oUser->getExtendedProp(self::GetName() . '::RecoveryEmail');
            if (!empty($sRecoveryEmail)) {
                $aRecoveryEmailParts = explode('@', $sRecoveryEmail);
                $iPartsCount = count($aRecoveryEmailParts);
                if ($iPartsCount > 0) {
                    $sResult = substr($aRecoveryEmailParts[0], 0, 3) . '***';
                }
                if ($iPartsCount > 1) {
                    $sResult .= '@' . $aRecoveryEmailParts[$iPartsCount - 1];
                }
            }
        }

        return $sResult;
    }

    private function getAuthenticatedAccount()
    {
        $aUserInfo = Api::getAuthenticatedUserInfo();

        $oAccount = null;

        if (isset($aUserInfo['account']) && isset($aUserInfo['accountType']) && class_exists($aUserInfo['accountType'])) {
            $oAccount = call_user_func_array([$aUserInfo['accountType'], 'find'], [(int)$aUserInfo['account']]);
        }

        return $oAccount;
    }

    private function getAccountById($iUserId, $iAccountId, $sAccountType)
    {
        $oAccount = null;

        $aAccounts = \Aurora\Modules\Core\Module::Decorator()->GetUserAccounts($iUserId);

        foreach ($aAccounts as $oItem) {
            if ($oItem['Id'] === $iAccountId && $oItem['Type'] === $sAccountType && class_exists($oItem['Type'])) {
                $oAccount = call_user_func_array([ $oItem['Type'], 'find'], [(int)$oItem['Id']]);
            }
        }

        return $oAccount;
    }
    /***** private functions *****/

    /***** public functions might be called with web API *****/
    /**
     * Obtains list of module settings for authenticated user.
     *
     * @return array
     */
    public function GetSettings()
    {

        Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);

        $aSettings = [
            'HashModuleName' => $this->oModuleSettings->HashModuleName,
            'CustomLogoUrl' => $this->oModuleSettings->CustomLogoUrl,
            'BottomInfoHtmlText' => $this->oModuleSettings->BottomInfoHtmlText,
        ];

        $oAuthenticatedUser = Api::getAuthenticatedUser();
        if ($oAuthenticatedUser instanceof User) {
            if ($oAuthenticatedUser->isNormalOrTenant()) {
                $iRecoveryAccountId = $oAuthenticatedUser->getExtendedProp(self::GetName() . '::RecoveryAccountId');
                $sRecoveryAccountType = $oAuthenticatedUser->getExtendedProp(self::GetName() . '::RecoveryAccountType');
                $oAccount = $this->getAccountById($oAuthenticatedUser->Id, $iRecoveryAccountId, $sRecoveryAccountType);

                $aSettings['RecoveryEmail'] = $this->getStarredRecoveryEmail($oAuthenticatedUser);
                $aSettings['RecoveryEmailConfirmed'] = empty($oAuthenticatedUser->getExtendedProp(self::GetName() . '::ConfirmRecoveryEmailHash'));
                $aSettings['RecoveryAccount'] = $oAccount ? $oAccount->getLogin() : '';
            }
            if ($oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::SuperAdmin) {
                $aSettings['RecoveryLinkLifetimeMinutes'] = $this->oModuleSettings->RecoveryLinkLifetimeMinutes;
                $aSettings['NotificationEmail'] = $this->oModuleSettings->NotificationEmail;
                $aSettings['NotificationType'] = $this->oModuleSettings->NotificationType;
                $aSettings['NotificationHost'] = $this->oModuleSettings->NotificationHost;
                $aSettings['NotificationPort'] = $this->oModuleSettings->NotificationPort;
                $aSettings['NotificationSMTPSecure'] = $this->oModuleSettings->NotificationSMTPSecure;
                $aSettings['NotificationUseAuth'] = $this->oModuleSettings->NotificationUseAuth;
                $aSettings['NotificationLogin'] = $this->oModuleSettings->NotificationLogin;
                $aSettings['HasNotificationPassword'] = !empty($this->oModuleSettings->NotificationPassword);
            }
        }

        return $aSettings;
    }

    /**
     * Updates per user settings.
     * @param string $RecoveryEmail
     * @param string $Password
     * @return boolean|string
     * @throws \Aurora\System\Exceptions\ApiException
     * @throws \Aurora\Modules\StandardResetPassword\Exceptions\Exception
     */
    public function UpdateSettings($RecoveryEmail = null, $Password = null)
    {
        Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);

        if ($RecoveryEmail === null || $Password === null) {
            throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter);
        }

        $oAuthenticatedUser = Api::getAuthenticatedUser();
        if ($oAuthenticatedUser instanceof User && $oAuthenticatedUser->isNormalOrTenant()) {
            $oAccount = $this->getAuthenticatedAccount();

            if ($Password === null || ($oAccount && $oAccount->getPassword() !== $Password)) {
                throw new \Aurora\Modules\StandardResetPassword\Exceptions\Exception(Enums\ErrorCodes::WrongPassword);
            }

            $sPrevRecoveryEmail = $oAuthenticatedUser->getExtendedProp(self::GetName() . '::RecoveryEmail');
            $sPrevConfirmRecoveryEmail = $oAuthenticatedUser->getExtendedProp(self::GetName() . '::ConfirmRecoveryEmail');
            $sPrevAccountId = $oAuthenticatedUser->getExtendedProp(self::GetName() . '::RecoveryAccountId');
            $sPrevAccountType = $oAuthenticatedUser->getExtendedProp(self::GetName() . '::RecoveryAccountType');

            $sConfirmRecoveryEmailHash = !empty($RecoveryEmail) ? $this->generateHash($oAuthenticatedUser->Id, 'confirm-recovery-email', __FUNCTION__) : '';

            $oAuthenticatedUser->setExtendedProp(self::GetName() . '::ConfirmRecoveryEmailHash', $sConfirmRecoveryEmailHash);
            $oAuthenticatedUser->setExtendedProp(self::GetName() . '::RecoveryEmail', $RecoveryEmail);
            $oAuthenticatedUser->setExtendedProp(self::GetName() . '::RecoveryAccountId', $oAccount->Id);
            $oAuthenticatedUser->setExtendedProp(self::GetName() . '::RecoveryAccountType', $oAccount->getName());

            $bResult = \Aurora\Modules\Core\Module::Decorator()->UpdateUserObject($oAuthenticatedUser);
            if ($bResult) {

                if (!empty($RecoveryEmail)) {
                    $oSentException = null;
                    try {
                        // Send message to confirm recovery email if it's not empty.
                        $bResult = $this->sendRecoveryEmailConfirmationMessage($RecoveryEmail, $sConfirmRecoveryEmailHash);
                    } catch (\Exception $oException) {
                        $bResult = false;
                        $oSentException = $oException;
                    }

                    if (!$bResult) {
                        $oAuthenticatedUser->setExtendedProps([
                            self::GetName() . '::ConfirmRecoveryEmailHash' => $sPrevConfirmRecoveryEmail,
                            self::GetName() . '::RecoveryEmail', $sPrevRecoveryEmail,
                            self::GetName() . '::RecoveryAccountId', $sPrevAccountId,
                            self::GetName() . '::RecoveryAccountType', $sPrevAccountType
                        ]);
                        \Aurora\Modules\Core\Module::Decorator()->UpdateUserObject($oAuthenticatedUser);
                    }

                    if ($oSentException !== null) {
                        throw $oSentException;
                    }
                }
            }

            if ($bResult) {
                return [
                    'RecoveryEmail' => $this->getStarredRecoveryEmail($oAuthenticatedUser),
                    'RecoveryAccount' => $oAccount->getLogin()
                ];
            } else {
                return false;
            }
        }

        throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::AccessDenied);
    }

    /**
         * Updates per user settings.
         * @param int $RecoveryLinkLifetimeMinutes
         * @param string $NotificationEmail
         * @param string $NotificationType
         * @param string $NotificationHost
         * @param int $NotificationPort
         * @param string $NotificationSMTPSecure
         * @param boolean $NotificationUseAuth
         * @param string $NotificationLogin
         * @param string $NotificationPassword
         * @return boolean|string
         * @throws \Aurora\System\Exceptions\ApiException
         * @throws \Aurora\Modules\StandardResetPassword\Exceptions\Exception
         */
    public function UpdateAdminSettings(
        $RecoveryLinkLifetimeMinutes,
        $NotificationEmail,
        $NotificationType,
        $NotificationHost = null,
        $NotificationPort = null,
        $NotificationSMTPSecure = null,
        $NotificationUseAuth = null,
        $NotificationLogin = null,
        $NotificationPassword = null
    ) {
        Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::SuperAdmin);

        $this->setConfig('RecoveryLinkLifetimeMinutes', $RecoveryLinkLifetimeMinutes);
        $this->setConfig('NotificationEmail', $NotificationEmail);
        $this->setConfig('NotificationType', $NotificationType);
        if ($NotificationType === 'smtp') {
            $this->setConfig('NotificationHost', $NotificationHost);
            $this->setConfig('NotificationPort', $NotificationPort);
            $this->setConfig('NotificationSMTPSecure', $NotificationSMTPSecure);
            $this->setConfig('NotificationUseAuth', $NotificationUseAuth);
            if ($NotificationUseAuth) {
                $this->setConfig('NotificationLogin', $NotificationLogin);
                $this->setConfig('NotificationPassword', \Aurora\System\Utils::EncryptValue($NotificationPassword));
            }
        }
        return $this->saveModuleConfig();
    }

    /**
     * Get recovery email address partly replaced with stars.
     * @param string $UserPublicId
     * @return string
     */
    public function GetStarredRecoveryEmailAddress($UserPublicId)
    {
        $sRecoveryEmail = '';
        $oUser = \Aurora\Modules\Core\Module::Decorator()->GetUserByPublicId($UserPublicId);
        if ($oUser) {
            $sRecoveryEmail = $this->getStarredRecoveryEmail($oUser);
            $sConfirmRecoveryEmailHash = $oUser->getExtendedProp(self::GetName() . '::ConfirmRecoveryEmailHash');
            if (!empty($sConfirmRecoveryEmailHash)) { // email is not confirmed
                $sRecoveryEmail = '';
            }
        }
        return $sRecoveryEmail;
    }

    /**
     * Creates a recovery link and sends it to recovery email of the user with specified public ID.
     *
     * @param string $UserPublicId
     * @return boolean
     * @throws \Exception
     */
    public function SendPasswordResetEmail($UserPublicId)
    {
        $oUser = \Aurora\Modules\Core\Module::Decorator()->GetUserByPublicId($UserPublicId);
        if ($oUser instanceof User) {
            $bPrevState = \Aurora\Api::skipCheckUserRole(true);
            $sPasswordResetHash = $this->generateHash($oUser->Id, $this->getHashModuleName(), __FUNCTION__);
            $oUser->setExtendedProp(self::GetName() . '::PasswordResetHash', $sPasswordResetHash);
            \Aurora\Modules\Core\Module::Decorator()->UpdateUserObject($oUser);
            \Aurora\Api::skipCheckUserRole($bPrevState);

            $sRecoveryEmail = $oUser->getExtendedProp(self::GetName() . '::RecoveryEmail');
            $sConfirmRecoveryEmailHash = $oUser->getExtendedProp(self::GetName() . '::ConfirmRecoveryEmailHash');
            if (!empty($sRecoveryEmail) && empty($sConfirmRecoveryEmailHash)) {
                return $this->sendPasswordResetMessage($sRecoveryEmail, $sPasswordResetHash);
            }
        }

        throw new \Exception($this->i18N('ERROR_RECOVERY_EMAIL_NOT_FOUND'));
    }

    /**
     * Returns public id of user obtained from the hash.
     *
     * @param string $Hash Hash with information about user.
     * @return string
     */
    public function GetUserPublicId($Hash)
    {
        Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);

        $oUser = $this->getUserByHash($Hash, $this->getHashModuleName());

        if ($oUser instanceof User) {
            return $oUser->PublicId;
        }
        return '';
    }

    /**
     * Changes password if hash is valid.
     * @param string $Hash
     * @param string $NewPassword
     * @return boolean
     * @throws \Aurora\System\Exceptions\ApiException
     */
    public function ChangePassword($Hash, $NewPassword)
    {
        $bPrevState =  Api::skipCheckUserRole(true);

        $oMin = \Aurora\Modules\Min\Module::Decorator();
        $mResult = false;

        if ($oMin && !empty($Hash) && $NewPassword) {
            $oUser = $this->getUserByHash($Hash, $this->getHashModuleName(), true);
            $oAccount = null;

            if ($oUser) {
                $iAccountId = $oUser->getExtendedProp(self::GetName() . '::RecoveryAccountId');
                $sAccountType = $oUser->getExtendedProp(self::GetName() . '::RecoveryAccountType');

                $oAccount = $this->getAccountById($oUser->Id, $iAccountId, $sAccountType);
            }

            if ($oUser && $oAccount) {
                $aArgs = [
                    'Account' => $oAccount,
                    'CurrentPassword' => $oAccount->getPassword(),
                    'NewPassword' => $NewPassword
                ];
                $aResponse = [
                    'AccountPasswordChanged' => false
                ];

                $this->broadcastEvent('ChangeAccountPassword', $aArgs, $aResponse);

                $mResult = $aResponse['AccountPasswordChanged'];

                if ($mResult) {
                    $oMin->DeleteMinByHash($Hash);
                    Api::UserSession()->DeleteAllAccountSessions($oAccount);
                }
            } else {
                throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter);
            }
        } else {
            throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter);
        }

        Api::skipCheckUserRole($bPrevState);

        return $mResult;
    }
    /***** public functions might be called with web API *****/
}