<?php
/**
 * This package is based on the WordPress feature plugin https://wordpress.org/plugins/two-factor/
 *
 * Class for creating two-factor authorization.
 *
 * @since 7.0.6
 * @noinspection OffsetOperationsInspection
 * @noinspection UnknownInspectionInspection
 * @package RSSSL\Pro\Security\WordPress\Two_Fa
 */

namespace RSSSL\Security\WordPress\Two_Fa;

use Exception;
use RSSSL\Security\WordPress\Two_Fa\Repositories\Rsssl_Two_Fa_User_Repository;
use RSSSL\Security\WordPress\Two_Fa\Services\Rsssl_Two_Fa_Reminder_Service;
use RSSSL\Security\WordPress\Two_Fa\Services\Rsssl_Two_Factor_Reset_Service;
use RSSSL\Security\WordPress\Two_Fa\Providers\Rsssl_Provider_Loader;
use RSSSL\Security\WordPress\Two_Fa\Providers\Rsssl_Two_Factor_Provider;
use RSSSL\Security\WordPress\Two_Fa\Providers\Rsssl_Two_Factor_Provider_Interface;
use RSSSL\Security\WordPress\Two_Fa\Traits\Rsssl_Email_Trait;
use WP_Error;
use WP_Session_Tokens;
use WP_User;

/**
 * Class Rsssl_Two_Factor.
 *
 * The Rsssl_Two_Factor class provides methods for managing two-factor authentication for users.
 *
 * @package Rsssl
 */
class Rsssl_Two_Factor
{
    use Rsssl_Email_Trait;

    /**
     * The user meta key to store the last failed timestamp.
     *
     * @type string
     */
    public const RSSSL_USER_RATE_LIMIT_KEY = '_rsssl_two_factor_last_login_failure';

    /**
     * The user meta key to store the number of failed login attempts.
     *
     * @var string
     */
    public const RSSSL_USER_FAILED_LOGIN_ATTEMPTS_KEY = '_rsssl_two_factor_failed_login_attempts';

    /**
     * The user meta key to store whether the password was reset.
     *
     * @var string
     */
    public const RSSSL_USER_PASSWORD_WAS_RESET_KEY = '_rsssl_two_factor_password_was_reset';

    /**
     * URL query parameter used for our custom actions.
     *
     * @var string
     */
    public const RSSSL_USER_SETTINGS_ACTION_QUERY_VAR = 'rsssl_two_factor_action';

    /**
     * Nonce key for user settings.
     *
     * @var string
     */
    public const RSSSL_USER_SETTINGS_ACTION_NONCE_QUERY_ARG = '_rsssl_two_factor_action_nonce';
    public const RSSSL_USER_META_ONBOARDING_COMPLETE = 'rsssl_two_fa_onboarding_complete';


    /**
     * Namespace for plugin rest api endpoints.
     *
     * @var string
     */
    public const REST_NAMESPACE = 'really-simple-security/v1/two-fa/v2';

    /**
     * Keep track of all the password-based authentication sessions that
     * need to invalidated before the second factor authentication.
     *
     * @var array
     */
    private static array $password_auth_tokens = array();

    /**
     * Set up filters and actions.
     *
     * @param object $compat A compatibility layer for plugins.
     *
     * @since 0.1-dev
     */
    public static function add_hooks(object $compat): void
    {
	    if ( ( defined( 'RSSSL_DISABLE_2FA' ) && RSSSL_DISABLE_2FA )
            || ( defined( 'RSSSL_SAFE_MODE' ) && RSSSL_SAFE_MODE )
        ) {
		    if ( rsssl_admin_logged_in() ) {
			    ( new Rsssl_Two_Factor_Admin() );
		    }
		    ( new Rsssl_Two_Factor_On_Board_Api() );
		    if ( is_user_logged_in() ) {
			    (Rsssl_Two_Factor_Profile_Settings::get_instance());
		    }
		    return;
	    }

        /**
         * Runs the fix for the reset error in 9.1.1
         */
	    if (filter_var(get_option('rsssl_reset_fix', false), FILTER_VALIDATE_BOOLEAN)) {
            $repository = new Rsssl_Two_Fa_User_Repository();
            (new Rsssl_Two_Factor_Reset_Service($repository))->resetFix();
	    }

//		add_action( 'login_enqueue_scripts', array( __CLASS__, 'twofa_scripts' ) );
        add_action('init', array(Rsssl_Provider_Loader::class, 'get_providers'));
        add_action('wp_login', array(__CLASS__, 'rsssl_wp_login'), 10, 2);
        add_filter('wp_login_errors', array(__CLASS__, 'show_expired_onboarding_error'));
        add_filter('wp_login_errors', array(__CLASS__, 'maybe_add_login_error_notice'));
        add_filter('wp_login_errors', array(__CLASS__, 'rsssl_maybe_show_reset_password_notice'));
        add_action('after_password_reset', array(__CLASS__, 'rsssl_clear_password_reset_notice'));
        add_action('login_form_validate_2fa', array(__CLASS__, 'rsssl_login_form_validate_2fa'));
        // Loading the styles.
        add_action('login_enqueue_scripts', array(__CLASS__, 'enqueue_onboarding_styles'));
        if (rsssl_admin_logged_in()) {
            (new Rsssl_Two_Factor_Admin());
        }
	    ( new Rsssl_Two_Factor_On_Board_Api() );
        if(is_user_logged_in()) {
	        Rsssl_Two_Factor_Profile_Settings::get_instance();
        }

        //add_action('rsssl_upgrade',  array(__CLASS__, 'upgrade'));
        self::upgrade();
        // Add the localized script for WP_REST.

        /**
         * Keep track of all the user sessions for which we need to invalidate the
         * authentication cookies set during the initial password check.
         *
         * Is there a better way of doing this?
         */
        add_action('set_auth_cookie', array(__CLASS__, 'rsssl_collect_auth_cookie_tokens'));
        add_action('set_logged_in_cookie', array(__CLASS__, 'rsssl_collect_auth_cookie_tokens'));
        if ( isset( $_GET['rsssl_one_time_login'], $_GET['_wpnonce'] ) ) {
            $nonce = sanitize_text_field(wp_unslash($_GET['_wpnonce']));

            if (wp_verify_nonce($nonce)) {
                add_action('init', array(__CLASS__, 'maybe_skip_auth'));
            }
	        self::maybe_skip_auth();
        }

        add_action('init', array(__CLASS__, 'rsssl_collect_auth_cookie_tokens'));

        // Run only after the core wp_authenticate_username_password() check.
        add_filter('authenticate', array(__CLASS__, 'rsssl_filter_authenticate'));

        // Run as late as possible to prevent other plugins from unintentionally bypassing.
        add_filter('authenticate', array(__CLASS__, 'rsssl_filter_authenticate_block_cookies'), PHP_INT_MAX);
        add_action('admin_init', array(__CLASS__, 'rsssl_enable_dummy_method_for_debug'));
        add_filter('rsssl_two_factor_providers', array(__CLASS__, 'enable_dummy_method_for_debug'));
	    add_action( 'rsssl_daily_cron', array( __CLASS__, 'maybe_send_reminder_email' ) );
        add_action( 'user_register', [__CLASS__, 'set_2fa_activation_date'], 10, 1 );

        // Register object-cache invalidation hooks for the multisite candidate-IDs cache.
        Rsssl_Two_Fa_User_Repository::registerCacheInvalidationHooks();

        $compat->init();
    }

	/**
	 * @return void
	 *
	 * Send a reminder e-mail if Two FA has not been configured within 3 days.
	 */
	public static function maybe_send_reminder_email():void {
        $forcedRoles = rsssl_get_option('two_fa_forced_roles', []);
        if(empty($forcedRoles)) {
            return;
        }
        (new Rsssl_Two_Fa_Reminder_Service())->maybeSendReminderEmails($forcedRoles);
	}

    /**
     * Simple Date setter for Two Factor Forced roles.
     * @param $user_id
     * @return void
     */
    public static function set_2fa_activation_date($user_id): void {
        // Get the user data; if not found, return early.
        $user_data = get_userdata($user_id);
        if (!$user_data) {
            return;
        }
        $user_roles = $user_data->roles;

        // Ensure forced roles is an array (empty if not set).
        $forcedRoles = rsssl_get_option('two_fa_forced_roles') ?: [];

        // If there is no intersection between forced roles and user's roles, do nothing.
        if (!array_intersect($forcedRoles, $user_roles)) {
            return;
        }

        // TODO: I really regret the meta_key name here. It should be rsssl_two_fa_activation_date. Need to fix this in the future.
        update_user_meta($user_id, 'rsssl_two_fa_last_login', gmdate('Y-m-d H:i:s'));
    }

    /**
     * Upgrade the two-factor login configuration.
     *
     * This method updates the configuration of two-factor login if necessary.
     * It checks if the login protection is enabled, if the plugin has been upgraded,
     * and if the enabled roles for email and TOTP need to be updated.
     *
     * @return void
     */
    public static function upgrade(): void
    {
        if (rsssl_get_option('login_protection_enabled') && get_option('rsssl_two_fa_upgrade', false) === false) {
            // The way roles configuration was has now been changed. This means the forced roles and enabled roles need to change.
            $forced_roles = rsssl_get_option('two_fa_forced_roles');
            $optional_roles = rsssl_get_option('two_fa_optional_roles');

            $forced_roles = ($forced_roles !== false) ? $forced_roles : [];
            $optional_roles = ($optional_roles !== false) ? $optional_roles : [];

            // Merge the forced and optional roles into one array with unique values.
            $enabled_roles = array_unique(array_merge($forced_roles, $optional_roles));

            if (empty($optional_roles)) {
                // no roles were set so ending the upgrade.
                return;
            }

            if (function_exists('rsssl_update_option')) {
                // Update the enabled roles for only email.
                rsssl_update_option('two_fa_enabled_roles_email', $enabled_roles);
                rsssl_update_option('two_fa_enabled_roles_totp', ['administrator']);
                // update the forced roles.
                rsssl_update_option('two_fa_forced_roles', $forced_roles);
            }

            // fetching the users that have active 2FA enabled.
            $users = get_users(array('meta_key' => 'rsssl_two_fa_status_email', 'meta_value' => 'active'));
            foreach ($users as $user) {
                Rsssl_Two_Fa_Status::set_active_provider($user->ID, 'email');
            }
            update_option('rsssl_two_fa_upgrade', rsssl_version, false);
        }
    }

    /**
     * Enqueue the two-factor authentication scripts.
     *
     * @return void
     *
     * Allow 2FA bypass if status is open.
     */
    public static function maybe_skip_auth(): void
    {

        if (isset($_GET['rsssl_one_time_login'], $_GET['token'], $_GET['_wpnonce'])) {

            // Unslash and sanitize.
            $rsssl_one_time_login = sanitize_text_field(wp_unslash($_GET['rsssl_one_time_login']));
            $user_id = (int)Rsssl_Two_Factor_Settings::deobfuscate_user_id($rsssl_one_time_login);
            $user = get_user_by('id', $user_id);

            // Verify the nonce.
            $nonce = sanitize_text_field(wp_unslash($_GET['_wpnonce']));
            if (!wp_verify_nonce($nonce, 'one_time_login_' . $user_id)) {
                wp_safe_redirect(wp_login_url() . '?login_error=nonce_invalid');
                exit;
            }

            // Retrieve the stored token from the transient.
            $stored_token = get_transient('skip_two_fa_token_' . $user_id);

            // Check if the token is valid and not expired.
            $token = sanitize_text_field(wp_unslash($_GET['token']));
            if ($user && $stored_token && hash_equals($stored_token, $token)) {

                // Delete the transient to invalidate the token.
                delete_transient('skip_two_fa_token_' . $user_id);

                $status = get_user_meta($user->ID, 'rsssl_two_fa_status_email', true);

                // Only allow skipping for users which have 2FA value open.
                if (isset($_GET['rsssl_two_fa_disable']) && 'open' === $status) {
                    update_user_meta($user_id, 'rsssl_two_fa_status_email', 'disabled');
                }

                if ('open' === Rsssl_Two_Factor_Settings::get_user_status('email', $user_id)) {
                    update_user_meta($user_id, 'rsssl_two_fa_status_email', 'active');
                    update_user_meta($user_id, 'rsssl_two_fa_status_totp', 'disabled');

                }
	            delete_user_meta( $user_id, '_rsssl_factor_email_token' );
                delete_user_meta( $user_id, '_rsssl_two_factor_backup_codes' );
                wp_set_auth_cookie($user_id);
                wp_safe_redirect(admin_url());
                exit;
            }

            // The token is invalid or expired.
            // Redirect to the login page with an error message or handle it as needed.
            wp_safe_redirect(wp_login_url() . '?login_error=token_invalid');
            exit;
        }
    }

    /**
     * Enable the dummy method only during debugging.
     *
     * @param array $methods List of enabled methods.
     *
     * @return array
     */
    public static function enable_dummy_method_for_debug(array $methods): array
    {
        if (!self::is_wp_debug()) {
            unset($methods['Two_Factor_Dummy']);
        }

        return $methods;
    }

    /**
     * Check if the debug mode is enabled.
     *
     * @return boolean
     */
    protected static function is_wp_debug(): bool
    {
        return (defined('WP_DEBUG') && WP_DEBUG);
    }

    /**
     * Check if a user action is valid.
     *
     * @param integer $user_id User ID.
     * @param string $action User action ID.
     *
     * @return boolean
     */
    public static function is_valid_user_action(int $user_id, string $action): bool
    {
        $request_nonce = isset($_REQUEST[self::RSSSL_USER_SETTINGS_ACTION_NONCE_QUERY_ARG]) ? sanitize_text_field(wp_unslash($_REQUEST[self::RSSSL_USER_SETTINGS_ACTION_NONCE_QUERY_ARG])) : '';

        if (!$user_id || !$action || !$request_nonce) {
            return false;
        }

        return wp_verify_nonce(
            $request_nonce,
            sprintf('%d-%s', $user_id, $action)
        );
    }

    /**
     * Get the ID of the user being edited.
     *
     * @return integer
     */
    public static function current_user_being_edited(): int
    {
        // Try to resolve the user ID from the request first.
        if (!empty($_REQUEST['rsssl_user_id']) && !empty($_REQUEST['rsssl-action-nonce'])) {
            if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['rsssl-action-nonce'])), 'rsssl-user-action')) {
                wp_die('Invalid nonce');
            }

            $user_id = (int)$_REQUEST['rsssl_user_id'];
            if (current_user_can('edit_user', $user_id)) {
                return $user_id;
            }
        }
        return get_current_user_id();
    }

    /**
     * Trigger our custom update action if a valid
     * action request is detected and passes the nonce check.
     *
     * @return void
     */
    public static function rsssl_enable_dummy_method_for_debug(): void
    {
        $nonce = isset($_POST['nonce_field']) ? sanitize_text_field(wp_unslash($_POST['nonce_field'])) : '';
        // Verify the nonce.
        if (!wp_verify_nonce($nonce, 'rsssl_user_action')) {
            return;
        }
        $action = isset($_REQUEST[self::RSSSL_USER_SETTINGS_ACTION_QUERY_VAR]) ? sanitize_text_field(wp_unslash($_REQUEST[self::RSSSL_USER_SETTINGS_ACTION_QUERY_VAR])) : '';
        $user_id = self::current_user_being_edited();

        if (self::is_valid_user_action($user_id, $action)) {
            /**
             * This action is triggered when a valid Two Factor settings
             * action is detected, and it passes the nonce validation.
             *
             * @param integer $user_id User ID.
             * @param string $action Settings action.
             */
            do_action('rsssl_two_factor_user_settings_action', $user_id, $action);
        }
    }

    /**
     * Keep track of all the authentication cookies that need to be
     * invalidated before the second factor authentication.
     *
     * @param string $cookie Cookie string.
     *
     * @return void
     */
    public static function rsssl_collect_auth_cookie_tokens(string $cookie): void
    {
        $parsed = wp_parse_auth_cookie($cookie);

        if (!empty($parsed['token'])) {
            self::$password_auth_tokens[] = $parsed['token'];
        }
    }

    /**
     * Get all Two-Factor Auth providers that are both enabled and configured for the specified|current user.
     *
     * @param WP_User $user Optional. User ID, or WP_User object of the user. Defaults to current user.
     *
     * @return array
     */
    public static function get_available_providers_for_user(WP_User $user): array
    {
	    $loader = Rsssl_Provider_Loader::get_loader();
        return $loader::available_providers();
    }

    /**
     * Gets the Two-Factor Auth provider for the specified|current user.
     *
     * @param WP_User $user Optional. User ID, or WP_User object of the user. Defaults to current user.
     *
     * @return string
     * @since 0.1-dev
     */
    public static function get_primary_provider_for_user(WP_User $user): string
    {
        $loader = Rsssl_Provider_Loader::get_loader();
        $available_providers = $loader::get_configured_providers_for_user($user);
        // If there's only one available provider, force that to be the primary.
        if (empty($available_providers)) {
            return '';
        }

        if (1 === count($available_providers)) {
            $provider = key($available_providers);
        } else {
            $provider = Rsssl_Provider_Loader::get_user_enabled_providers($user);
            // Check if already a provider is active.

            // If the provider specified isn't enabled, just grab the first one that is based on the Weight.
            $best_valued_provider = 'totp';
            if (isset($available_providers[$best_valued_provider]) && $available_providers[$best_valued_provider]::is_enabled($user)) {
                $provider = $best_valued_provider;
            } else {
                $provider = key($available_providers);
            }
        }

        return get_class($available_providers[$provider]) ??  '';
    }

    /**
     * Quick boolean check for whether a given user is using two-step.
     * TODO: No longer needed?
     *
     * @param WP_User $user Optional. User ID, or WP_User object of the user. Defaults to current user.
     *
     * @return bool
     * @since 0.1-dev
     */
    public static function is_user_using_two_factor(WP_User $user): bool
    {
        $provider = self::get_primary_provider_for_user($user);

        $enabled_providers_meta = Rsssl_Provider_Loader::get_user_enabled_providers($user);
        // Initialize as empty arrays if they are empty.
        $two_fa_forced_roles = rsssl_get_option('two_fa_forced_roles');
        $two_fa_optional_roles = rsssl_get_option('two_fa_enabled_roles_email');
        $two_fa_optional_roles_totp = rsssl_get_option('two_fa_enabled_roles_totp');

        //ensure an array for all.
        if (!is_array($two_fa_forced_roles)) {
	        $two_fa_forced_roles = [];
        }
        if (!is_array($two_fa_optional_roles)) {
	        $two_fa_optional_roles = [];
        }
        if (!is_array($two_fa_optional_roles_totp)) {
	        $two_fa_optional_roles_totp = [];
        }
        $two_fa_optional_roles = array_unique(array_merge($two_fa_optional_roles, $two_fa_optional_roles_totp));

        foreach ($enabled_providers_meta as $enabled_provider) {
            $status = $enabled_provider::get_status($user);
            if ( ( 'disabled' === $status ) && is_object( $provider ) && get_class( $provider ) === $enabled_provider ) {
                $provider = [];
            }
	        if ('active' === $status ) {
		        return true;
	        }

	        if ('open' === $status) {
		        return true;
	        }
        }

        foreach ($user->roles as $role) {
            // If not forced, and not optional, or disabled, or provider not enabled.
            if (!in_array($role, $two_fa_forced_roles, true)
                && !in_array($role, $two_fa_optional_roles, true)
            ) {
                // Skip 2FA.
                return false;
            }
        }

        return !empty($provider);
    }

    /**
     * Show an expired onboarding error message.
     *
     * @param WP_Error $errors Error object to add the error to.
     *
     * @return WP_Error The updated error object.
     */
    public static function show_expired_onboarding_error(WP_Error $errors): WP_Error
    {
        if ( isset( $_GET['nonce'], $_GET['errors'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['nonce'] ) ), 'rsssl_expired' ) && $_GET['errors'] === 'expired' ) {
            $errors->add('expired', __('Your 2FA grace period expired. Please contact your site administrator to regain access and to configure 2FA.', 'really-simple-ssl'));
        }
        return $errors;
    }

    /**
     * Add login errors for redirects that carry an explicit login error state.
     *
     * @param WP_Error $errors Error object to add the error to.
     *
     * @return WP_Error The updated error object.
     */
    public static function maybe_add_login_error_notice(WP_Error $errors): WP_Error
    {
        if ( ! isset( $_GET['login_error'] ) ) {
            return $errors;
        }

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- strict equality against a hardcoded whitelist value; no sanitization needed.
        $login_error = $_GET['login_error'];

        if ( 'nonce_invalid' === $login_error ) {
            $errors->add(
                'rsssl_two_factor_nonce_invalid',
                __( 'Security nonce invalid or expired.', 'really-simple-ssl' )
            );
        } elseif ( 'token_invalid' === $login_error ) {
            $errors->add(
                'rsssl_two_factor_token_invalid',
                __( 'Security token invalid or expired.', 'really-simple-ssl' )
            );
        }

        return $errors;
    }

    /**
     * Handle the browser-based login.
     *
     * @param string $user_login Username.
     * @param WP_User $user WP_User object of the logged-in user.
     *
     * @throws Exception If the onboarding process fails.
     * @since 0.1-dev
     */
    public static function rsssl_wp_login(string $user_login, WP_User $user): void
    {
        self::maybe_redirect_expired_password_before_two_factor( $user );

        switch (Rsssl_Two_Factor_Settings::get_login_action($user->ID)) {
            case 'onboarding':
                self::is_onboarding_complete($user);
                exit;
            case 'expired':
                // Destroy the current session for the user.
                self::destroy_current_session_for_user($user);
                wp_clear_auth_cookie();
                self::display_expired_onboarding_error();
                exit;
            case 'totp':
            case 'email':
            case 'passkey':
                wp_clear_auth_cookie();
                self::show_two_factor_login($user);
                exit;
            case 'login':
            default:
                break;
        }
    }

    /**
     * Let password expiration win before 2FA can render a login/onboarding screen.
     *
     * Side effects: redirects and exits the request when the user's password is expired.
     */
    private static function maybe_redirect_expired_password_before_two_factor( WP_User $user ): void
    {
        if ( ! function_exists( '\RSSSL\Pro\Security\WordPress\rsssl_maybe_get_expired_password_redirect' ) ) {
            return;
        }

        $redirect_to = isset( $_REQUEST['redirect_to'] )
            ? wp_validate_redirect( wp_unslash( $_REQUEST['redirect_to'] ), admin_url() )
            : admin_url();

        $expired_password_redirect = \RSSSL\Pro\Security\WordPress\rsssl_maybe_get_expired_password_redirect( $redirect_to, $user->ID );
        if ( $expired_password_redirect === $redirect_to ) {
            return;
        }

        wp_safe_redirect( $expired_password_redirect );
        exit;
    }

    /**
     * Destroy the known password-based authentication sessions for the current user.
     *
     * Is there a better way of finding the current session token without
     * having access to the authentication cookies which are just being set
     * on the first password-based authentication request.
     *
     * @param WP_User $user User object.
     *
     * @return void
     */
    public static function destroy_current_session_for_user(WP_User $user): void
    {
        $session_manager = WP_Session_Tokens::get_instance($user->ID);

        foreach (self::$password_auth_tokens as $auth_token) {
            $session_manager->destroy($auth_token);
        }
    }

    /**
     * Prevent login through XML-RPC and REST API for users with at least one
     * two-factor method enabled.
     *
     * @param WP_User|WP_Error $user Valid WP_User only if the previous filters
     *                                have verified and confirmed the
     *                                authentication credentials.
     *
     * @return WP_User|WP_Error
     */
    public static function rsssl_filter_authenticate($user)
    {
        if ($user instanceof WP_User && self::is_api_request() && self::is_user_using_two_factor($user) && !self::is_user_api_login_enabled($user->ID)) {
            return new WP_Error(
                'invalid_application_credentials',
                __('API login for user disabled.', 'really-simple-ssl')
            );
        }

        return $user;
    }

    /**
     * Prevent login cookies being set on login for Two Factor users.
     *
     * This makes it so that Core never sends the auth cookies. `login_form_validate_2fa()` will send them manually once the 2nd factor has been verified.
     *
     * @param WP_User|WP_Error $user Valid WP_User only if the previous filters
     *                                have verified and confirmed the
     *                                authentication credentials.
     *
     * @return WP_User|WP_Error
     */
    public static function rsssl_filter_authenticate_block_cookies($user)
    {
        /*
         * NOTE: The `login_init` action is checked for here to ensure we're within the regular login flow,
         * rather than through an unsupported 3rd-party login process which this plugin doesn't support.
         */
        if ($user instanceof WP_User && self::is_user_using_two_factor($user) && did_action('login_init')) {
            add_filter('send_auth_cookies', '__return_false', PHP_INT_MAX);
        }

        return $user;
    }

    /**
     * If the current user can log in via API requests such as XML-RPC and REST.
     *
     * @param integer $user_id User ID.
     *
     * @return boolean
     */
    public static function is_user_api_login_enabled(int $user_id): bool
    {
        return (bool)apply_filters('rsssl_two_factor_user_api_login_enable', false, $user_id);
    }

    /**
     * Is the current request an XML-RPC or REST request.
     *
     * @return boolean
     */
    public static function is_api_request(): bool
    {
        if (defined('XMLRPC_REQUEST') && XMLRPC_REQUEST) {
            return true;
        }

        if (defined('REST_REQUEST') && REST_REQUEST) {
            return true;
        }

        return false;
    }

    /**
     * Display the login form.
     *
     * @param WP_User $user WP_User object of the logged-in user.
     *
     * @throws Exception If the login nonce creation fails.
     * @since 0.1-dev
     */
    public static function show_two_factor_login(WP_User $user): void
    {
	    $redirect_to = isset($_REQUEST['redirect_to']) ? wp_validate_redirect(wp_unslash($_REQUEST['redirect_to']), admin_url()) : admin_url();
        Rsssl_Two_Factor_Settings::get_login_action($user->ID);
		$login_nonce = Rsssl_Two_Fa_Authentication::create_login_nonce($user->ID)['rsssl_key'];

        self::login_html($user, $login_nonce ,$redirect_to);
    }

    /**
     * Displays a message informing the user that their account has had failed login attempts.
     *
     * @param WP_User $user WP_User object of the logged-in user.
     */
    public static function maybe_show_last_login_failure_notice(WP_User $user): void
    {
        $last_failed_two_factor_login = (int)get_user_meta($user->ID, self::RSSSL_USER_RATE_LIMIT_KEY, true);
        $failed_login_count = (int)get_user_meta(
            $user->ID,
            self::RSSSL_USER_FAILED_LOGIN_ATTEMPTS_KEY,
            true
        );

        if ($last_failed_two_factor_login) {
            echo '<div id="login_notice" class="message"><strong>';
            // translators: %1$s is the number of failed login attempts, %2$s is the time since the last failed login.
            printf(
                esc_html(
                    _n(
                        'Warning: There has been %1$s failed login attempt on your account without providing a valid two-factor token. The last failed login occurred %2$s ago. If this wasn\'t you, you should reset your password.',
                        'Warning: %1$s failed login attempts have been detected on your account without providing a valid two-factor token. The last failed login occurred %2$s ago. If this wasn\'t you, you should reset your password.',
                        $failed_login_count,
                        'really-simple-ssl'
                    )
                ),
                esc_html(number_format_i18n($failed_login_count)),
                esc_html(human_time_diff($last_failed_two_factor_login, time()))
            );
            echo '</strong></div>';
        }
    }

    /**
     * Show the password reset notice if the user's password was reset.
     *
     * They were also sent an email notification in `send_password_reset_email()`, but email sent from a typical
     * web server is not reliable enough to trust completely.
     *
     * @param WP_Error $errors The error object.
     *
     * @return WP_Error
     */
    public static function rsssl_maybe_show_reset_password_notice(WP_Error $errors): WP_Error
    {
        if ('incorrect_password' !== $errors->get_error_code()) {
            return $errors;
        }

        if (!isset($_POST['log'])) {
            return $errors;
        }

        $user_name = sanitize_user(wp_unslash($_POST['log']));
        $attempted_user = get_user_by('login', $user_name);
        if ( $user_name && ! $attempted_user && strpos( $user_name, '@') !== false ) {
            $attempted_user = get_user_by('email', $user_name);
        }

        if (!$attempted_user) {
            return $errors;
        }

        $password_was_reset = get_user_meta($attempted_user->ID, self::RSSSL_USER_PASSWORD_WAS_RESET_KEY, true);

        if (!$password_was_reset) {
            return $errors;
        }

        $errors->remove('incorrect_password');
        $errors->add(
            'rsssl_two_factor_password_reset',
            sprintf(
            /* translators: %s: URL to reset password */
                __(
                    'Your password was reset because of too many failed Two Factor attempts. You will need to <a href="%s">create a new password</a> to regain access. Please check your email for more information.',
                    'really-simple-ssl'
                ),
                esc_url(add_query_arg('action', 'lostpassword', rsssl_wp_login_url()))
            )
        );

        return $errors;
    }

    /**
     * Clear the password reset notice after the user resets their password.
     *
     * @param WP_User $user WP_User object of the logged-in user.
     */
    public static function rsssl_clear_password_reset_notice(WP_User $user): void
    {
        delete_user_meta($user->ID, self::RSSSL_USER_PASSWORD_WAS_RESET_KEY);
    }

    /**
     * Generates the html form for the second step of the authentication process.
     *
     * @param WP_User $user WP_User object of the logged-in user.
     * @param string $login_nonce A string nonce stored in usermeta.
     * @param string $redirect_to The URL to which the user would like to be redirected.
     * @param string $error_msg Optional. Login error message.
     * @param string|object $provider An override to the provider.
     *
     * @throws Exception If the login nonce creation fails.
     * @since 0.1-dev
     */
    public static function login_html(
        WP_User $user,
        string  $login_nonce,
        string  $redirect_to,
        string  $error_msg = '',
                $provider = null
    ): void
    {

        if (empty($provider)) {
            $provider = self::get_primary_provider_for_user($user);
        } elseif (is_string($provider) && method_exists($provider, 'get_instance')) {
            $provider = call_user_func(array($provider, 'get_instance'));
        }

        if (!$provider) {
            return;
        }


        $provider_class = $provider::get_instance();


//        $backup_providers = array_diff_key($available_providers, array($provider => null));
        $interim_login = isset($_REQUEST['interim-login']); // phpcs:ignore WordPress.Security.NonceVerification.Recommended

        $rememberme = (int)self::rememberme();

        if (!function_exists('login_header')) {
            // We really should migrate login_header() out of `wp-login.php` so it can be called from an includes file.
            include_once __DIR__ . '/function-login-header.php';
        }

        // Enqueue two-fa JavaScript assets
        $uri = trailingslashit(rsssl_url) . 'assets/features/two-fa/assets.min.js';
        $uri_file = trailingslashit(rsssl_path) . 'assets/features/two-fa/assets.min.js';
        add_filter('wp_script_attributes', [self::class, 'handle_script_attributes'], 10, 2);
        wp_enqueue_script('rsssl-frontend-settings', $uri, array(), filemtime($uri_file), true);

        wp_localize_script('rsssl-frontend-settings', 'rsssl_validate', array(
            'nonce' => wp_create_nonce('wp_rest'),
            'root' => esc_url_raw(rest_url(self::REST_NAMESPACE)),
            'login_nonce' => $login_nonce,
            'redirect_to' => $redirect_to,
            'user_id' => $user->ID,
            'origin' => 'validation',
            'translatables' => apply_filters('rsssl_two_factor_translatables', []),
        ));

        // Load the login template.
        rsssl_load_template(
            'login.php',
            compact(
                'login_nonce',
                'redirect_to',
                'error_msg',
                'provider',
//                'backup_providers',
                'interim_login',
                'rememberme',
                'provider_class',
                'user'
            ),
            rsssl_path . 'assets/templates/two_fa/'
        );

        if (!function_exists('login_footer')) {
            include_once __DIR__ . '/function-login-footer.php';
        }
        login_footer();
    }

    /**
     * Generate the two-factor login form URL.
     *
     * @param array $params List of query argument pairs to add to the URL.
     * @param string $scheme URL scheme context.
     *
     * @return string
     */
    public static function login_url(array $params = array(), string $scheme = 'login'): string
    {
        $params = urlencode_deep($params);
        return add_query_arg($params, site_url('wp-login.php', $scheme));
    }

    /**
     * Determine the minimum wait between two factor attempts for a user.
     *
     * This implements an increasing backoff, requiring an attacker to wait longer
     * each time to attempt to brute-force the login.
     *
     * @param WP_User $user The user being operated upon.
     *
     * @return int Time delay in seconds between login attempts.
     */
    public static function get_user_time_delay(WP_User $user): int
    {
        /**
         * Filter the minimum time duration between two factor attempts.
         *
         * @param int $rate_limit The number of seconds between two factor attempts.
         */
        $rate_limit = apply_filters('rsssl_two_factor_rate_limit', 1);

        $user_failed_logins = get_user_meta($user->ID, self::RSSSL_USER_FAILED_LOGIN_ATTEMPTS_KEY, true);
        if ($user_failed_logins) {
            $rate_limit = (2 ** $user_failed_logins) * $rate_limit;

            /**
             * Filter the maximum time duration a user may be locked out from retrying two-factor authentications.
             *
             * @param int $max_rate_limit The maximum number of seconds a user might be locked out for. Default 15 minutes.
             */
            $max_rate_limit = apply_filters('rsssl_two_factor_max_rate_limit', 15 * MINUTE_IN_SECONDS);

            $rate_limit = min($max_rate_limit, $rate_limit);
        }

        /**
         * Filters the per-user time duration between two-factor login attempts.
         *
         * @param int $rate_limit The number of seconds between two factor attempts.
         * @param WP_User $user The user attempting to log in.
         */
        return apply_filters('rsssl_two_factor_user_rate_limit', $rate_limit, $user);
    }

    /**
     * Determine if a time delay between user two-factor login attempts should be triggered.
     *
     * @param WP_User $user The User.
     *
     * @return bool True if rate limit is okay, false if not.
     * @since 0.8.0
     */
    public static function is_user_rate_limited(WP_User $user): bool
    {
        $rate_limit = self::get_user_time_delay($user);
        $last_failed = get_user_meta($user->ID, self::RSSSL_USER_RATE_LIMIT_KEY, true);

        $rate_limited = false;
        if ($last_failed && $last_failed + $rate_limit > time()) {
            $rate_limited = true;
        }

        /**
         * Filter whether this login attempt is rate limited or not.
         *
         * This allows for dedicated plugins to rate limit two-factor login attempts
         * based on their own rules.
         *
         * @param bool $rate_limited Whether the user login is rate limited.
         * @param WP_User $user The user attempting to log in.
         */
        return apply_filters('rsssl_two_factor_is_user_rate_limited', $rate_limited, $user);
    }

	/**
	 * Validates the two-factor authentication code. for all providers.
	 *
	 * @return void
	 * @throws Exception
	 */
	public static function rsssl_login_form_validate_2fa(): void {
		[$wp_auth_id, $nonce, $provider_key, $redirect_to] = self::get_request_data();

		if (isset($_SERVER['REQUEST_METHOD']) && 'POST' === strtoupper((sanitize_text_field(wp_unslash($_SERVER['REQUEST_METHOD']))))) {
			$is_post_request = true;
		} else {
			$is_post_request = false;
		}

		if (!$nonce) {
			self::redirect_to_login_error('nonce_invalid', $redirect_to);
		}

		$user = get_userdata($wp_auth_id);
		if (!$user) {
			return;
		}

		// Verify the nonce
		if (true !== Rsssl_Two_Fa_Authentication::verify_login_nonce($user->ID, $nonce)) {
			self::redirect_to_login_error('nonce_invalid', $redirect_to);
		}

		$loader = Rsssl_Provider_Loader::get_loader();
		// Get the provider
		$providers = $loader::get_enabled_providers_for_user($user);
		if ($provider_key && isset($providers[$provider_key])) {
			$provider_class = get_class($providers[$provider_key]);
		} else {
			wp_die(esc_html__('Authentication provider not specified or invalid.', 'really-simple-ssl'), 403);
		}

		/** @var Rsssl_Two_Factor_Provider $provider_instance */
		$provider_instance = $provider_class::get_instance();

		// Check for corrupted/empty TOTP key before attempting authentication
		self::validate_totp_key_exists( $user, $provider_key );

		// Allow the provider to re-send codes, etc.
		if ( ( 'email' === $provider_key ) && true === $provider_instance->pre_process_authentication( $user ) ) {
			// Always generate a new nonce.
			$new_nonce = self::generate_login_nonce_for_user($user->ID);
			self::login_html($user, $new_nonce, $redirect_to, '', $provider_class);
			exit;
		}

		// If the form hasn't been submitted, just display the auth form.
		if (!$is_post_request) {
			self::handle_not_post_request($user, $provider_class);
			exit;
		}
		if (self::is_user_rate_limited($user)) {
			$time_delay = self::get_user_time_delay($user);
			$last_failed = get_user_meta($user->ID, self::RSSSL_USER_RATE_LIMIT_KEY, true);

			$error = new WP_Error(
				'rsssl_two_factor_too_fast',
				sprintf(
				/* translators: %s: time delay between login attempts */
						__(
							'Too many invalid verification codes, you can try again in %s. This limit protects your account against automated attacks.',
							'really-simple-ssl'
						),
						human_time_diff($last_failed + $time_delay)
					)
				);

			do_action('rsssl_wp_login_failed', $user->user_login, $error);

			// Display the login form with an error message
			self::login_html(
				$user,
				$redirect_to,
				esc_html($error->get_error_message()),
				$provider_key
			);
			exit;
		}
		// Validate authentication
		if (!$provider_instance->validate_authentication($user)) {
			// Handle rate limiting and failed attempts
			self::handle_failed_attempt($user, $provider_class, $redirect_to, $nonce);
			exit;
		}

		// Successful authentication
		self::complete_authentication($user, $redirect_to);
	}

	/**
     * Handles the case when a two-factor authentication attempt fails.
     *
     *
     * @return void
     * @throws Exception
     */
    protected static function handle_failed_attempt(WP_User $user, string $provider_class, string $redirect_to, string $login_nonce): void {
		// Store the last time a failed login occurred.
		update_user_meta($user->ID, self::RSSSL_USER_RATE_LIMIT_KEY, time());

		// Store the number of failed login attempts.
		update_user_meta(
			$user->ID,
			self::RSSSL_USER_FAILED_LOGIN_ATTEMPTS_KEY,
			1 + (int)get_user_meta($user->ID, self::RSSSL_USER_FAILED_LOGIN_ATTEMPTS_KEY, true)
		);

		if (self::should_reset_password($user->ID)) {
			self::reset_compromised_password($user);
			self::send_password_reset_emails($user);
			self::show_password_reset_error();
			exit;
		}

		/** @var Rsssl_Two_Factor_Provider_Interface $provider_class */
        $provider_class::get_instance();

		self::login_html(
			$user,
			$login_nonce,
			$redirect_to,
			esc_html__('Invalid verification code.', 'really-simple-ssl'),
			$provider_class
		);
	}

	/**
     * Completes the two-factor authentication process. After a successful authentication, the user is redirected to the appropriate page.
     *
     * @return void
     */
    protected static function complete_authentication(WP_User $user, string $redirect_to): void {
		$rememberme = false;
		if (isset($_REQUEST['rememberme']) && filter_var(wp_unslash($_REQUEST['rememberme']), FILTER_VALIDATE_BOOLEAN)) {
			$rememberme = true;
		}
		// Authenticate the user.
		wp_set_auth_cookie($user->ID, $rememberme);

		do_action('rsssl_two_factor_user_authenticated', $user);

		$redirect_to = apply_filters('login_redirect', $redirect_to, $redirect_to, $user);
		if (function_exists('\RSSSL\Pro\Security\WordPress\rsssl_maybe_get_expired_password_redirect')) {
			$redirect_to = \RSSSL\Pro\Security\WordPress\rsssl_maybe_get_expired_password_redirect($redirect_to, $user->ID);
		}
		// cleaning up the user meta.
	    delete_user_meta( $user->ID, self::RSSSL_USER_FAILED_LOGIN_ATTEMPTS_KEY);
	    delete_user_meta( $user->ID, self::RSSSL_USER_RATE_LIMIT_KEY);
		wp_safe_redirect($redirect_to);
		exit;
	}

    /**
     * Handle the case when the request method is not POST.
     *
     * @param WP_User $user The user object.
     * @param string $provider The provider name.
     *
     * @return void
     * @throws Exception If the login nonce cannot be created.
     */
    private static function handle_not_post_request(WP_User $user, string $provider): void
    {
        $login_nonce = self::generate_login_nonce_for_user($user->ID);

        self::login_html(
            $user,
            $login_nonce,
            isset($_REQUEST['redirect_to']) ? wp_validate_redirect(wp_unslash($_REQUEST['redirect_to']), '') : '',
            '',
            $provider
        );
    }

	/**
	 * Redirect to the login page with a two-factor login error state.
	 *
	 * @param string $login_error Login error query value.
	 * @param string $redirect_to Redirect URL after successful login.
	 *
	 * @return void
	 */
	private static function redirect_to_login_error(string $login_error, string $redirect_to = ''): void {
		$validated_redirect = wp_validate_redirect($redirect_to, admin_url());
		wp_safe_redirect(
			add_query_arg(
				'login_error',
				$login_error,
				wp_login_url($validated_redirect)
			)
		);
		exit;
	}

    /**
     * Get the request data for two-factor authentication.
     *
     * @return array An array containing the sanitized values of wp_auth_id, nonce, and provider.
     */
    private static function get_request_data(): array
    {
        $wp_auth_id = self::sanitize_request_data('rsssl-wp-auth-id', 0, 'absint');
        $nonce = self::sanitize_request_data('rsssl-wp-auth-nonce', '', 'wp_unslash');
        $provider = self::sanitize_request_data('provider', false, 'wp_unslash');
        $redirect_to = self::sanitize_request_data('redirect_to', '', 'wp_unslash');
        return array($wp_auth_id, $nonce, $provider, $redirect_to);
    }

    /**
     * Sanitize request data.
     *
     * @param string $key The key to retrieve from the $_REQUEST array.
     * @param mixed $default_value The default value to return if the key does not exist in the $_REQUEST array.
     * @param callable $sanitize_callback The callback function used to sanitize the value.
     *
     * @return mixed The sanitized value if it exists in the $_REQUEST array, otherwise the default value.
     */
    private static function sanitize_request_data(string $key, $default_value, callable $sanitize_callback)
    {
        return !empty($_REQUEST[$key]) ? $sanitize_callback(sanitize_text_field(wp_unslash($_REQUEST[$key]))) : $default_value;
    }

    /**
     * Checks if a user's password should be reset based on the number of failed login attempts on the 2nd factor.
     *
     * @param int $user_id The ID of the user.
     *
     * @return bool True if the password should be reset, false otherwise.
     */
    public static function should_reset_password(int $user_id): bool
    {
        $failed_attempts = (int)get_user_meta($user_id, self::RSSSL_USER_FAILED_LOGIN_ATTEMPTS_KEY, true);

        /**
         * Filters the maximum number of failed attempts on a 2nd factor before the user's
         * password will be reset. After a reasonable number of attempts, it's safe to assume
         * that the password has been compromised and an attacker is trying to brute force the 2nd
         * factor.
         *
         * ⚠️ `get_user_time_delay()` mitigates brute force attempts, but many 2nd factors --
         * like TOTP and backup codes -- are very weak on their own, so it's not safe to give
         * attackers unlimited attempts. Setting this to a very large number is strongly
         * discouraged.
         *
         * @param int $limit The number of attempts before the password is reset.
         */
        $failed_attempt_limit = apply_filters('rsssl_two_factor_failed_attempt_limit', 30);

        return $failed_attempts >= $failed_attempt_limit;
    }

    /**
     * Reset a compromised password.
     *
     * If we know that the password is compromised, we have the responsibility to reset it and inform the
     * user. `get_user_time_delay()` mitigates brute force attempts, but this acts as an extra layer of defense
     * which guarantees that attackers can't brute force it (unless they compromise the new password).
     *
     * @param WP_User $user The user who failed to log in.
     */
    public static function reset_compromised_password(WP_User $user): void
    {
        // Unhook because `wp_password_change_notification()` wouldn't notify the site admin when
        // their password is compromised.
        remove_action('after_password_reset', 'wp_password_change_notification');
        reset_password($user, wp_generate_password(25));
        update_user_meta($user->ID, self::RSSSL_USER_PASSWORD_WAS_RESET_KEY, true);
        add_action('after_password_reset', 'wp_password_change_notification');

        Rsssl_Two_Fa_Authentication::delete_login_nonce($user->ID);
        delete_user_meta($user->ID, self::RSSSL_USER_RATE_LIMIT_KEY);
        delete_user_meta($user->ID, self::RSSSL_USER_FAILED_LOGIN_ATTEMPTS_KEY);
    }

    /**
     * Notify the user and admin that a password was reset for being compromised.
     *
     * @param WP_User $user The user whose password should be reset.
     */
    public static function send_password_reset_emails(WP_User $user): void
    {
        self::notify_user_password_reset($user);

        /**
         * Filters whether to email the site admin when a user's password has been
         * compromised and reset.
         *
         * @param bool $reset `true` to notify the admin, `false` to not notify them.
         */
        $notify_admin = apply_filters('rsssl_two_factor_notify_admin_user_password_reset', true);
        $admin_email = get_option('admin_email');

        if ($notify_admin && $admin_email !== $user->user_email) {
            self::notify_admin_user_password_reset($user);
        }
    }


    /**
     * Show the password reset error when on the login screen.
     */
    public static function show_password_reset_error(): void
    {
        $error = new WP_Error(
            'too_many_attempts',
            sprintf(
                '<p>%s</p>
				<p style="margin-top: 1em;">%s</p>',
                __(
                    'There have been too many failed two-factor authentication attempts, which often indicates that the password has been compromised. The password has been reset in order to protect the account.',
                    'really-simple-ssl'
                ),
                __(
                    'If you are the owner of this account, please check your email for instructions on regaining access.',
                    'really-simple-ssl'
                )
            )
        );

        login_header(__('Password Reset', 'really-simple-ssl'), '', $error);
        login_footer();
    }

    /**
     * Should the login session persist between sessions.
     *
     * @return boolean
     */
    public static function rememberme(): bool
    {
        $rememberme = false;

        if (!empty($_REQUEST['rememberme'])) {
            $rememberme = true;
        }

        return (bool)apply_filters('rsssl_two_factor_rememberme', $rememberme);
    }

    /**
     * Check if the user has completed the onboarding process.
     *
     * @param WP_User $user The WP_User object representing the user.
     *
     * @return void
     * @throws Exception If the onboarding screen template cannot be loaded.
     */
    private static function is_onboarding_complete(WP_User $user): void
    {
        // If the user has not completed the onboarding process, they should be shown the onboarding screen.
        $onboarding_complete = get_user_meta($user->ID, self::RSSSL_USER_META_ONBOARDING_COMPLETE, true);
        if (!$onboarding_complete) {
            self::onboarding_user_html($user);
        }
    }

    /**
     * Display the expired onboarding error. Manually load our login header and
     * footer functions to ensure they are  available.
     */
    private static function display_expired_onboarding_error(): void
    {
        if (!function_exists('login_header')) {
            include_once __DIR__ . '/function-login-header.php';
        }

        if (!function_exists('login_footer')) {
            include_once __DIR__ . '/function-login-footer.php';
        }

	    rsssl_load_template('expired.php', [
            'message' => esc_html__('Your 2FA grace period expired. Please contact your site administrator to regain access and to configure 2FA.', 'really-simple-ssl'),
        ], rsssl_path . 'assets/templates/two_fa/');
    }

    /**
     * Validate that TOTP key exists for the user when TOTP provider is used.
     * Destroys session and displays error if key is corrupted/missing.
     *
     * @param WP_User $user The user object.
     * @param string $provider_key The provider key being used.
     *
     * @return void
     */
    private static function validate_totp_key_exists( WP_User $user, string $provider_key ): void
    {
        if ( 'totp' !== $provider_key ) {
            return;
        }

        if ( ! class_exists( 'RSSSL\Pro\Security\WordPress\Two_Fa\Providers\Rsssl_Two_Factor_Totp' ) ) {
            return;
        }

        $totp_key = get_user_meta(
            $user->ID,
            \RSSSL\Pro\Security\WordPress\Two_Fa\Providers\Rsssl_Two_Factor_Totp::SECRET_META_KEY,
            true
        );

        if ( empty( $totp_key ) ) {
            // Verify we have a valid user before destroying their session
            if ( ! $user instanceof WP_User || ! $user->exists() ) {
                wp_die( esc_html__( 'Invalid user.', 'really-simple-ssl' ), 403 );
            }

            // TOTP key is missing/corrupted
            self::destroy_current_session_for_user( $user );
            wp_clear_auth_cookie();
            self::display_corrupted_totp_error();
            exit;
        }
    }

    /**
     * Display error when TOTP key is corrupted/missing. Manually load our login header and
     * footer functions to ensure they are available.
     * Follows the same template as the expired onboarding error.
     */
    private static function display_corrupted_totp_error(): void
    {
        if (!function_exists('login_header')) {
            include_once __DIR__ . '/function-login-header.php';
        }

        if (!function_exists('login_footer')) {
            include_once __DIR__ . '/function-login-footer.php';
        }

	    rsssl_load_template('expired.php', [
            'message' => esc_html__('Your Two-Factor Authentication configuration is corrupted. Please contact your site administrator to regain access.', 'really-simple-ssl'),
        ], rsssl_path . 'assets/templates/two_fa/');
    }

    /**
     * Generate the HTML for the onboarding screen for a given user.
     *
     * @param WP_User $user The user object.
     *
     * @return void
     * @throws Exception If the onboarding screen template cannot be loaded.
     */
    private static function onboarding_user_html(WP_User $user): void
    {
        $passkey_onboarding = get_user_meta($user->ID, 'rsssl_two_fa_status_passkey', true) === 'open';
        // Variables needed for the template and scripts
        $onboarding_url = self::login_url(array('action' => 'rsssl_onboarding'), 'login_post');
        $provider_loader = Rsssl_Provider_Loader::get_loader();
        $provider = self::get_primary_provider_for_user($user);
        $redirect_to = isset($_REQUEST['redirect_to']) ? wp_validate_redirect(wp_unslash($_REQUEST['redirect_to']), admin_url()) : admin_url();
        $enabled_providers = $provider_loader::get_user_enabled_providers($user);
        $login_nonce = self::generate_login_nonce_for_user($user->ID);
        $is_forced = Rsssl_Two_Factor_Settings::is_user_forced_to_use_2fa($user->ID);
        $grace_period = Rsssl_Two_Factor_Settings::is_user_in_grace_period($user);
        $is_today = Rsssl_Two_Factor_Settings::is_today($user);
        if ($passkey_onboarding) {
            $is_forced = false;
            //if only passkey is available, set it as the only provider
            if (count($enabled_providers) === 1 && isset($enabled_providers['passkey'])) {
                $provider = 'passkey';
            }
        }
        // Ensure login_header and login_footer functions are available
        if (!function_exists('login_header')) {
            include_once __DIR__ . '/function-login-header.php';
        }

        if (!function_exists('login_footer')) {
            include_once __DIR__ . '/function-login-footer.php';
        }

        //Add the styles for the two-factor authentication.
        add_action('login_enqueue_styles', array(__CLASS__, 'enqueue_onboarding_styles'));

        $uri = trailingslashit(rsssl_url) . 'assets/features/two-fa/assets.min.js';
		$uri_file = trailingslashit(rsssl_path) . 'assets/features/two-fa/assets.min.js';
	    add_filter('wp_script_attributes', [self::class, 'handle_script_attributes'], 10, 2);
        wp_enqueue_script('rsssl-frontend-settings', $uri, array(), filemtime($uri_file), true);

        wp_localize_script('rsssl-frontend-settings', 'rsssl_onboard', array(
            'nonce' => wp_create_nonce('wp_rest'),
            'root' => esc_url_raw(rest_url(self::REST_NAMESPACE)),
            'login_nonce' => $login_nonce,
            'redirect_to' => $redirect_to,
            'user_id' => $user->ID,
            'origin' => 'onboarding',
            'translatables' => apply_filters('rsssl_two_factor_translatables', []),
        ));

        login_header(
            __('Two-Factor Authentication Setup', 'really-simple-ssl'),
            '',
            null
        );

        rsssl_load_template(
            'onboarding.php',
            array(
                'user' => $user,
                'login_nonce' => $login_nonce,
                'url' => $onboarding_url,
                'provider' => $provider,
                'redirect_to' => $redirect_to,
                'available_providers' => $enabled_providers,
                'interim_login' => isset($_REQUEST['interim-login']),
                'rememberme' => (int)self::rememberme(),
                'primary_provider' => $provider,
                'is_forced' => $is_forced,
                'grace_period' => $grace_period,
                'is_today' => $is_today,
                'skip_two_fa_url' => Rsssl_Two_Factor_Settings::rsssl_one_time_login_url($user->ID),
            ),
            rsssl_path . 'assets/templates/two_fa/'
        );

        wp_enqueue_script('rsssl-rest-settings');


        login_footer();

        if (ob_get_level() > 0) {
            ob_flush();
        }
        flush();
        exit; //This was the original exit.
    }

	/**
	 * Handles the script attributes.
	 *
	 *
	 * @param array $attributes
	 * @param string $handle
	 *
	 * @return array
	 */
    public static function handle_script_attributes( array $attributes, string $handle = ''):array
	{
		if ( $handle === 'rsssl-profile-settings' ) {
			$attributes['type'] = 'module';
		}
		return $attributes;
	}


    /**
     * Enqueues the RSSSL profile settings stylesheet.
     *
     * @return void
     */
    public static function enqueue_onboarding_styles(): void
    {
	    $url = trailingslashit(rsssl_url) . 'assets/features/two-fa/styles.css';
	    $file = trailingslashit(rsssl_path) . 'assets/features/two-fa/styles.css';
	    wp_enqueue_style('rsssl-profile-settings', $url, array(), filemtime($file));
    }

	/**
	 * Return the translatable strings for the two-factor authentication.
	 * @return array
	 */
	public static function translatables(): array {
		return self::rsssl_translatables([]);
	}

	/**
     * places all translatable strings.
     *
     *
     * @return array
     */
    public static function rsssl_translatables(array $translatables): array {
		$new_translatables = [
			'download_codes' => esc_html__('Download Backup Codes', 'really-simple-ssl'),
			'keyCopied' => __('Key copied', 'really-simple-ssl'),
			'keyCopiedFailed' => __('Could not copy text: ', 'really-simple-ssl'),
		];
		return array_merge($translatables, $new_translatables);
	}

	/**
	 * Generates a login nonce for a user. and returns the key.
	 *
	 * @param $user_id
	 *
	 * @return string
	 */
	protected static function generate_login_nonce_for_user( $user_id ): string {
		$login_nonce = Rsssl_Two_Fa_Authentication::create_login_nonce( $user_id );
		if ( ! $login_nonce ) {
			$error = new WP_Error();
            $error->add(
                'login_nonce_creation_failed',
                __( 'Failed to create a login nonce.', 'really-simple-ssl' )
            );
		}
		return $login_nonce['rsssl_key'];
	}
}

/**
 * Hook as soon as the file is required. Which is the plugins_loaded hook.
 * @see security/integrations.php
 */
$rsssl_two_factor_compat = new Rsssl_Two_Factor_Compat();
Rsssl_Two_Factor::add_hooks($rsssl_two_factor_compat);
