$expire, 'path' => $path, 'domain' => $domain, 'secure' => $secure, 'httponly' => true, 'samesite' => 'None' ] ); } /** * Clear the MFA setup for a user. */ public static function clearCookie(): void { $userId = Helper::getUserId(); $expire = time() - 3600; $path = defined('COOKIEPATH') ? COOKIEPATH : '/'; $domain = defined('COOKIE_DOMAIN') ? COOKIE_DOMAIN : ''; $secure = is_ssl(); setcookie( self::MFA_COOKIE_KEY, '', [ 'expires' => $expire, 'path' => $path, 'domain' => $domain, 'secure' => $secure, 'httponly' => true, 'samesite' => 'None' ] ); // Clear user metadata for MFA Wordpress::deleteUserMeta($userId, self::MFA_KEY); Wordpress::deleteUserMeta($userId, self::MFA_SETUP_COMPLETED); } public static function isSetupCompleted(): bool { $userId = Helper::getUserId(); return Wordpress::getUserMeta($userId, self::MFA_SETUP_COMPLETED, true) ?? false; } /** * Generate a QR Code URL for Google Authenticator. * * @return array * @throws Exception */ public static function getQRcode(): array { $authenticator = self::getAuthenticator(); $domain = Wordpress::getSiteDomain(); $label = "JetBackup [$domain]"; $userId = Helper::getUserId(); $secret = Wordpress::getUserMeta($userId, self::MFA_KEY, true); if (!$secret || $secret == '') $secret = self::createSecret(); $setupCompleted = self::isSetupCompleted(); return [ 'code' => $setupCompleted ? '' : $authenticator->getQRCodeGoogleUrl($secret, $label), 'isFirstTime' => !$setupCompleted, ]; } /** * Verify the provided MFA code. * * @param int $code * * @return bool */ public static function verifyCode(int $code): bool { $userId = Helper::getUserId(); $secret = Wordpress::getUserMeta($userId, self::MFA_KEY, true); if (!$secret) return false; $attempts = (int) Wordpress::getUserMeta($userId, self::MFA_MAX_ATTEMPTS_KEY, true); if ($attempts > 0) usleep(min(pow($attempts, 2) * 100000, 3000000)); // up to 3s $isValid = self::getAuthenticator()->verifyCode($secret, $code, 3, null); if ($isValid) { Wordpress::updateUserMeta($userId, self::MFA_SETUP_COMPLETED, 'true', ''); Wordpress::deleteUserMeta($userId, self::MFA_MAX_ATTEMPTS_KEY); return true; } // Failed attempt $attempts++; Wordpress::updateUserMeta($userId, self::MFA_MAX_ATTEMPTS_KEY, $attempts, ''); // If max attempts reached, logout if ($attempts >= self::MFA_MAX_ATTEMPTS) { Wordpress::wpLogout(); Wordpress::wpRedirect(Wordpress::wpLoginURL('', true)); exit; } return false; } }