
Создание торговой панели администратора на MQL5 (Часть V): Двухфакторная аутентификация (2FA)
Содержание:
- Введение
- Что такое двухфакторная аутентификация (2FA)?
- Реализация двухфакторной аутентификации (2FA) в панели администратора с использованием MQL5
- Реализация элементов графического интерфейса для ввода пароля и проверки кода 2FA
- Алгоритм генерации кода верификации
- Функция MathRand()
- API Telegram для 2FA-проверки
- Тестирование и результаты
- Заключение
Введение
В предыдущей статье мы изучили реализацию аутентификации с помощью пароля в панели администратора, что является первым важным шагом в обеспечении безопасности коммуникаций между администраторами и трейдерами. Эта базовая форма безопасности, хотя и необходимая для первоначальной защиты, может потенциально подвергнуть эти системы уязвимостям из-за ее зависимости от единственного фактора проверки. Внедрение двухфакторной аутентификации (two-factor authentication, 2FA) обеспечивает более надежную структуру безопасности для нашего приложения.
Такой подход существенно снижает риск несанкционированного доступа, поскольку требует доступа к вторичному методу проверки наряду со знанием пароля. Двухфакторная аутентификация гарантирует, что все сообщения приходят из законных источников, тем самым защищая от потенциальных последствий компрометации данных, дезинформации и манипулирования рынком.
Интегрировав 2FA в нашу панель администратора, мы можем предоставить пользователям более высокий уровень доверия и безопасности. Эта система двойной аутентификации выступает в качестве мощного средства защиты от потенциальных нарушений, обеспечивая как администраторам, так и трейдерам спокойствие при работе в динамичной финансовой среде. Сегодня мы обсудим концепцию, которую мне удалось успешно превратить в работающее решение. Эта инициатива усилила безопасность нашей панели администратора в ответ на уязвимости, связанные с защитой паролем, реализованной в предыдущей статье серии.
Что такое двухфакторная аутентификация (2FA)?
Двухфакторная аутентификация (2FA) — это механизм безопасности, требующий двух различных форм проверки перед предоставлением доступа к учетной записи или системе. Это подмножество многофакторной аутентификации (MFA), которое может включать два или более факторов проверки. Основная цель 2FA — добавить еще один уровень безопасности, выходящий за рамки просто имени пользователя и пароля, что затруднит несанкционированный доступ пользователей.
По данным некоторых источников, 2FA обычно включает в себя две из следующих трех категорий факторов аутентификации:
- Что-то, что вы знаете (фактор знания): обычно это пароль или PIN-код, который пользователь должен ввести для доступа к своей учетной записи. Он служит первой линией обороны.
- Что-то, что у вас есть (фактор владения): сюда входит физическое устройство или токен, которым владеет пользователь, например токен безопасности, смарт-карта или мобильный телефон. Многие системы используют такие приложения, как Google Authenticator или Authy, для генерации одноразовых паролей (one-time passwords, OTP), которые действительны в течение короткого периода времени (обычно 15 минут).
- То, кем вы являетесь (биометрический фактор): этот аспект может включать распознавание отпечатков пальцев, распознавание лиц или другие биометрические данные. Хотя биометрия не всегда используется совместно с первыми двумя факторами двухфакторной аутентификации, она может обеспечить дополнительный уровень безопасности.
Реализация двухфакторной аутентификации (2FA) в панели администратора с использованием MQL5
Благодаря интеграции 2FA мы выходим за рамки традиционной однофакторной аутентификации (single-factor authentication, SFA), которая обычно основана исключительно на именах пользователей и паролях. Этот сдвиг имеет решающее значение, поскольку, хотя пароли остаются наиболее распространенной формой первоначальной безопасности, они по своей природе уязвимы для различных типов атак, включая социальную инженерию, атаки методом подбора и атаки перебором по словарю. Многофакторный подход 2FA эффективно снижает эти риски, требуя от пользователей предоставления двух различных форм аутентификации, относящихся к разным категориям, тем самым повышая уверенность в том, что доступ действительно предоставляется законным пользователям.
Для внедрения 2FA в наш проект панели администратора я использовал библиотеку Dialog, которая позволяет нам создавать несколько слоев окон, управляемых определенной логикой. В начале этой серии мы интегрировали коммуникацию Telegram, в первую очередь сосредоточившись на передаче сообщений из панели администратора в пользовательские каналы или группы Telegram. Однако ее потенциал выходит гораздо шире: мы стремимся использовать ее также для доставки одноразовых паролей.
В этом проекте мы настроим наш код для генерации случайного шестизначного кода, который программа будет надежно хранить и впоследствии отправлять в Telegram администратора для проверки. Я заметил, что многие компании используют Telegram для проверки, хотя и с подходами, отличающимися от тех, которые мы реализуем здесь. Например, на изображении ниже вы можете увидеть бота верификации (MQL5 Verification Bot), который служит примером такого использования.
Бот верификации MQL5
Реализация элементов графического интерфейса для ввода пароля и проверки кода 2FA
При реализации диалогового окна ввода пароля мы начинаем с определения функции ShowAuthenticationPrompt(), которая инкапсулирует все шаги, необходимые для создания пользовательского интерфейса для ввода пароля. Процесс начинается с создания диалогового окна аутентификации с использованием метода Create(), указывающего его размеры и положение на графике. Для ввода данных пользователем мы создаем passwordInputBox для безопасного приема пароля пользователя. Затем добавляется passwordPromptLabel для предоставления пользователю четких инструкций, указывающих ему цель диалога. Для обработки отзывов пользователей, особенно в случае неверных вводов, реализован feedbackLabel, где сообщения об ошибках будут отображаться красным, что позволяет пользователю лучше понять, что пошло не так. Далее мы настраиваем две кнопки:
- loginButton для отправки пользователем пароля для аутентификации и
- closeAuthButton для выхода из диалогового окна.
Наконец вызываем show() в диалоговом окне аутентификации, чтобы представить его пользователю и вызвать ChartRedraw(), чтобы гарантировать правильное отображение всех компонентов на экране. Такой системный подход обеспечивает безопасный и удобный интерфейс для ввода пароля в панели администратора. Рассмотрим следующий фрагмент кода.
Создание диалогового окна ввода пароля
// Show authentication input dialog bool ShowAuthenticationPrompt() { if (!authentication.Create(ChartID(), "Authentication", 0, 100, 100, 500, 300)) { Print("Failed to create authentication dialog"); return false; } // Create password input box if (!passwordInputBox.Create(ChartID(), "PasswordInputBox", 0, 20, 70, 260, 95)) { Print("Failed to create password input box"); return false; } authentication.Add(passwordInputBox); // Create password prompt label if (!passwordPromptLabel.Create(ChartID(), "PasswordPromptLabel", 0, 20, 20, 260, 40)) { Print("Failed to create password prompt label"); return false; } passwordPromptLabel.Text("Enter password: Access Admin Panel"); authentication.Add(passwordPromptLabel); // Create feedback label for wrong attempts if (!feedbackLabel.Create(ChartID(), "FeedbackLabel", 0, 20, 140, 380, 40)) { Print("Failed to create feedback label"); return false; } feedbackLabel.Text(""); feedbackLabel.Color(clrRed); // Set color for feedback authentication.Add(feedbackLabel); // Create login button if (!loginButton.Create(ChartID(), "LoginButton", 0, 20, 120, 100, 40)) { Print("Failed to create login button"); return false; } loginButton.Text("Login"); authentication.Add(loginButton); // Create close button for authentication dialog if (!closeAuthButton.Create(ChartID(), "CloseAuthButton", 0, 120, 120, 200, 40)) { Print("Failed to create close button for authentication"); return false; } closeAuthButton.Text("Close"); authentication.Add(closeAuthButton); authentication.Show(); ChartRedraw(); return true; }
Создание диалогового окна проверки кода 2FA
Чтобы создать диалоговое окно проверки кода 2FA, мы определяем функцию ShowTwoFactorAuthPrompt(), которая обрабатывает все компоненты, необходимые для проверки кода двухфакторной аутентификации пользователя.
Начнем с создания диалога twoFactorAuth, опять же используя метод Create() для установки его свойств. Первый добавленный компонент - twoFACodeInput, поле ввода, предназначенное для безопасного захвата кода 2FA, отправленного в Telegram пользователя.
Чтобы направлять пользователя, мы реализуем twoFAPromptLabel, в котором ему четко указано ввести полученный 2FA-код. Для дальнейшего улучшения пользовательского опыта мы включаем twoFAFeedbackLabel для отображения обратной связи в реальном времени. Эта метка будет отображать сообщения красным цветом, если введенный код не соответствует ожидаемому значению, тем самым информируя пользователя о некорректных вводах.
Для ввода создается twoFALoginButton, позволяя пользователю проверить свой код, в то время как close2FAButton позволяет выйти из диалога. После настройки компонентов вызываем Show() в диалоге twoFactorAuth, чтобы сделать его видимым для пользователя, и ChartRedraw() для обновления интерфейса.
Такой структурированный подход обеспечивает безопасный метод проверки кодов 2FA и оптимизированное взаимодействие пользователя в панели администратора. Ниже приведен фрагмент кода для лучшего понимания.
// Show two-factor authentication input dialog void ShowTwoFactorAuthPrompt() { if (!twoFactorAuth.Create(ChartID(), "Two-Factor Authentication", 0, 100, 100, 500, 300)) { Print("Failed to create 2FA dialog"); return; } // Create input box for 2FA code if (!twoFACodeInput.Create(ChartID(), "TwoFACodeInput", 0, 20, 70, 260, 95)) { Print("Failed to create 2FA code input box"); return; } twoFactorAuth.Add(twoFACodeInput); // Create prompt label for 2FA if (!twoFAPromptLabel.Create(ChartID(), "TwoFAPromptLabel", 0, 20, 20, 380, 40)) { Print("Failed to create 2FA prompt label"); return; } twoFAPromptLabel.Text("Enter the 2FA code sent to your Telegram:"); twoFactorAuth.Add(twoFAPromptLabel); // Create feedback label for wrong attempts if (!twoFAFeedbackLabel.Create(ChartID(), "TwoFAFeedbackLabel", 0, 20, 140, 380, 40)) { Print("Failed to create 2FA feedback label"); return; } twoFAFeedbackLabel.Text(""); twoFAFeedbackLabel.Color(clrRed); // Set color for feedback twoFactorAuth.Add(twoFAFeedbackLabel); // Create login button for 2FA code submission if (!twoFALoginButton.Create(ChartID(), "TwoFALoginButton", 0, 20, 120, 100, 40)) { Print("Failed to create 2FA login button"); return; } twoFALoginButton.Text("Verify"); twoFactorAuth.Add(twoFALoginButton); // Create close button for 2FA dialog if (!close2FAButton.Create(ChartID(), "Close2FAButton", 0, 120, 120, 200, 40)) { Print("Failed to create close button for 2FA"); return; } close2FAButton.Text("Close"); twoFactorAuth.Add(close2FAButton); twoFactorAuth.Show(); ChartRedraw(); }
Алгоритм генерации кода верификации
После успешного ввода пароля для доступа к панели администратора мы установили второй уровень защиты, включающий генерацию OTP-кода. Этот код надежно хранится и пересылается на уникальный жестко запрограммированный идентификатор чата, связанный с приложением Telegram, как на мобильном устройстве, так и на компьютере, где законный владелец может получить его для дальнейшего ввода в командной строке. Если введенный код совпадает, приложение предоставляет доступ ко всем функциям панели администратора для работы и общения.
Что касается алгоритма генерации кода, я объясню различные компоненты в приведенных ниже фрагментах кода:
Приведенная ниже строка кода служит для объявления важной переменной для управления двухфакторной аутентификацией (2FA) в нашей программе.
string twoFACode = "";
Эта строка инициализирует переменную twoFACode как пустую строку, которая будет использоваться для хранения случайно сгенерированного 6-значного кода двухфакторной аутентификации. На протяжении всего процесса аутентификации эта переменная играет важную роль, храня фактический код, который отправляется пользователю через Telegram после того, как он успешно введет правильный пароль для доступа к панели администратора.
Когда пользователь проходит первоначальную проверку пароля, переменная twoFACode заполняется новым значением, сгенерированным функцией GenerateRandom6DigitCode(), которая создает шестизначную числовую строку. Это значение затем отправляется в Telegram пользователя с помощью функции SendMessageToTelegram().
Позже, когда пользователю будет предложено ввести свой 2FA-код, программа сравнивает введенные пользователем данные с этим сохраненным значением в twoFACode. Если введенные пользователем данные совпадают со значением, хранящимся в twoFACode, предоставляется доступ к панели администратора. В противном случае выводится сообщение об ошибке.
1. Аутентификация по паролю:
string Password = "2024"; // Hardcoded password // Handle login button click void OnLoginButtonClick() { string enteredPassword = passwordInputBox.Text(); if (enteredPassword == Password) { twoFACode = GenerateRandom6DigitCode(); SendMessageToTelegram("Your 2FA code is: " + twoFACode, Hardcoded2FAChatId, Hardcoded2FABotToken); authentication.Destroy(); ShowTwoFactorAuthPrompt(); Print("Password authentication successful. A 2FA code has been sent to your Telegram."); } else { feedbackLabel.Text("Wrong password. Try again."); passwordInputBox.Text(""); } }
Жестко заданный пароль ("2024") служит механизмом контроля доступа к панели администратора. Когда пользователь вводит пароль через специальное поле, код проверяет, соответствует ли введенный пароль жестко заданному. Если да, с помощью функции GenerateRandom6DigitCode() генерируется случайный 6-значный код, который отправляется в Telegram пользователя с помощью функции SendMessageToTelegram(). Это указывает на успешную аутентификацию и побуждает приложение перейти к фазе двухфакторной аутентификации. Если пароль неверный, появится сообщение об ошибке, предлагающее пользователю повторить попытку.
2. Генерация и доставка кода двухфакторной аутентификации:
// Generate a random 6-digit code for 2FA string GenerateRandom6DigitCode() { int code = MathRand() % 1000000; // Produces a 6-digit number return StringFormat("%06d", code); // Ensures leading zeros } // Handle 2FA login button click void OnTwoFALoginButtonClick() { string enteredCode = twoFACodeInput.Text(); if (enteredCode == twoFACode) { twoFactorAuth.Destroy(); adminPanel.Show(); Print("2FA authentication successful."); } else { twoFAFeedbackLabel.Text("Wrong code. Try again."); twoFACodeInput.Text(""); } }
Этот раздел управляет двухфакторной аутентификацией (2FA) путем генерации уникального 6-значного кода и проверки введенных пользователем данных на соответствие этому коду. Функция GenerateRandom6DigitCode() генерирует 6-значное число, используя функцию MathRand(), гарантирующую сохранение требуемого формата даже при наличии начальных нулей. После проверки первоначального пароля этот 6-значный код отправляется через Telegram в указанный чат пользователя для повышения безопасности. В функции OnTwoFALoginButtonClick() введенные пользователем данные проверяются на соответствие сгенерированному коду. Если введенный код совпадает с отправленным в Telegram, то предоставляется доступ к панели администратора. В противном случае пользователю сообщается о неверном коде и предлагается повторить попытку
Функция MathRand()
Эта функция в MQL5 используется для генерации псевдослучайного целого числа. В нашем случае функция MathRand() генерирует случайное целое число в диапазоне от 0 до MathRandMax(). Обычно это диапазон от 0 до 32767.
Чтобы сгенерировать случайное 6-значное число, можно ограничить вывод, применив оператор деления по модулю (%). Этот оператор вычисляет остаток от деления, позволяя ограничить диапазон случайных чисел, чтобы они соответствовали допустимым 6-значным значениям, которые охватывают диапазон от 000000 (0) до 999999.
В частности, использование выражения MathRand() % 1000000 даст результат между 0 и 999999, гарантируя, что будут охвачены все возможные 6-значные комбинации, включая комбинации с нулями.
API Telegram для 2FA-проверки
Функция SendMessageToTelegram() имеет решающее значение для обеспечения безопасной доставки наших 2FA-кодов и других сообщений в Telegram-чат пользователя. Эта функция создает HTTP-запрос POST к API Telegram Bot, включая токен бота и идентификатор необходимого графика. Сообщение форматируется в формате JSON в соответствии с требованиями API.
Функция WebRequest() выполняет запрос с указанным тайм-аутом, а также проверяет код ответа, чтобы подтвердить, было ли сообщение успешно отправлено (HTTP-код 200).
Если доставка сообщения не удалась, функция регистрирует ошибку, включая код ответа, сообщение об ошибке и любое соответствующее содержимое ответа, что помогает выявить потенциальные проблемы при отправке сообщений для дальнейшей диагностики.
Подробности об API Telegram вы можете узнать на сайте, а также в предыдущих статьях.
bool SendMessageToTelegram(string message, string chatId, string botToken) { string url = "https://api.telegram.org/bot" + botToken + "/sendMessage"; string jsonMessage = "{\"chat_id\":\"" + chatId + "\", \"text\":\"" + message + "\"}"; char postData[]; ArrayResize(postData, StringToCharArray(jsonMessage, postData) - 1); int timeout = 5000; char result[]; string responseHeaders; int responseCode = WebRequest("POST", url, "Content-Type: application/json\r\n", timeout, postData, result, responseHeaders); if (responseCode == 200) { Print("Message sent successfully: ", message); return true; } else { Print("Failed to send message. HTTP code: ", responseCode, " Error code: ", GetLastError()); Print("Response: ", CharArrayToString(result)); return false; } }
В рамках API Telegram мы включили в наш проект следующие константы для упрощения процесса двухфакторной аутентификации (2FA) через Telegram.
// Constants for 2FA const string Hardcoded2FAChatId = "REPLACE WITH YOUR CHAT ID"; const string Hardcoded2FABotToken = "REPLACE WITH YOUR ACTUAL BOT TOKEN";
В этой части кода переменная Hardcoded2FAChatId представляет собой уникальный идентификатор чата Telegram, куда будут отправляться сообщения аутентификации, в то время как Hardcoded2FABotToken содержит токен для бота Telegram, используемого для отправки сообщений.
Токен бота необходим для аутентификации запросов к API Telegram, гарантируя, что отправлять сообщения в указанный чат сможет только бот с соответствующими разрешениями. Благодаря жесткому кодированию этих констант программа оптимизирует процесс отправки 2FA-кода, поскольку каждый раз используется один и тот же идентификатор чата и токен бота без необходимости ввода данных или настройки пользователем.
Однако важно отметить, что жесткое кодирование конфиденциальной информации, такой как токены ботов, может представлять угрозу безопасности, если код будет раскрыт, поэтому для эксплуатационных сред следует рассмотреть альтернативные методы безопасного хранения.
Ниже представлен финальный код. Как видим, его объем значительно вырос по мере внедрения новых функций.
//+------------------------------------------------------------------+ //| Admin Panel.mq5 | //| Copyright 2024, Clemence Benjamin | //| https://www.mql5.com/en/users/billionaire2024/seller | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, Clemence Benjamin" #property link "https://www.mql5.com/en/users/billionaire2024/seller" #property description "A secure and responsive Admin Panel. Send messages to your telegram clients without leaving MT5" #property version "1.20" #include <Trade\Trade.mqh> #include <Controls\Dialog.mqh> #include <Controls\Button.mqh> #include <Controls\Edit.mqh> #include <Controls\Label.mqh> // Input parameters for quick messages input string QuickMessage1 = "Updates"; input string QuickMessage2 = "Close all"; input string QuickMessage3 = "In deep profits"; input string QuickMessage4 = "Hold position"; input string QuickMessage5 = "Swing Entry"; input string QuickMessage6 = "Scalp Entry"; input string QuickMessage7 = "Book profit"; input string QuickMessage8 = "Invalid Signal"; input string InputChatId = "YOUR_CHAT_ID"; input string InputBotToken = "YOUR_BOT_TOKEN"; // Constants for 2FA const string Hardcoded2FAChatId = "ENTER YOUR REAL CHAT ID"; const string Hardcoded2FABotToken = "ENTER YOUR Telegram Bot Token"; // Global variables CDialog adminPanel; CDialog authentication, twoFactorAuth; CButton sendButton, clearButton, changeFontButton, toggleThemeButton; CButton loginButton, closeAuthButton, twoFALoginButton, close2FAButton; CButton quickMessageButtons[8], minimizeButton, maximizeButton, closeButton; CEdit inputBox, passwordInputBox, twoFACodeInput; CLabel charCounter, passwordPromptLabel, feedbackLabel, twoFAPromptLabel, twoFAFeedbackLabel; bool minimized = false; bool darkTheme = false; int MAX_MESSAGE_LENGTH = 4096; string availableFonts[] = { "Arial", "Courier New", "Verdana", "Times New Roman" }; int currentFontIndex = 0; string Password = "2024"; // Hardcoded password string twoFACode = ""; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { if (!ShowAuthenticationPrompt()) { Print("Authorization failed. Exiting..."); return INIT_FAILED; } if (!adminPanel.Create(ChartID(), "Admin Panel", 0, 30, 30, 500, 500)) { Print("Failed to create admin panel dialog"); return INIT_FAILED; } if (!CreateControls()) { Print("Control creation failed"); return INIT_FAILED; } adminPanel.Hide(); Print("Initialization complete"); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Show authentication input dialog | //+------------------------------------------------------------------+ bool ShowAuthenticationPrompt() { if (!authentication.Create(ChartID(), "Authentication", 0, 100, 100, 500, 300)) { Print("Failed to create authentication dialog"); return false; } if (!passwordInputBox.Create(ChartID(), "PasswordInputBox", 0, 20, 70, 260, 95)) { Print("Failed to create password input box"); return false; } authentication.Add(passwordInputBox); if (!passwordPromptLabel.Create(ChartID(), "PasswordPromptLabel", 0, 20, 20, 260, 40)) { Print("Failed to create password prompt label"); return false; } passwordPromptLabel.Text("Enter password: Access Admin Panel"); authentication.Add(passwordPromptLabel); if (!feedbackLabel.Create(ChartID(), "FeedbackLabel", 0, 20, 140, 380, 160)) { Print("Failed to create feedback label"); return false; } feedbackLabel.Text(""); feedbackLabel.Color(clrRed); // Red color for incorrect attempts authentication.Add(feedbackLabel); if (!loginButton.Create(ChartID(), "LoginButton", 0, 20, 120, 100, 140)) { Print("Failed to create login button"); return false; } loginButton.Text("Login"); authentication.Add(loginButton); if (!closeAuthButton.Create(ChartID(), "CloseAuthButton", 0, 120, 120, 200, 140)) { Print("Failed to create close button for authentication"); return false; } closeAuthButton.Text("Close"); authentication.Add(closeAuthButton); authentication.Show(); ChartRedraw(); return true; } //+------------------------------------------------------------------+ //| Show two-factor authentication input dialog | //+------------------------------------------------------------------+ void ShowTwoFactorAuthPrompt() { if (!twoFactorAuth.Create(ChartID(), "Two-Factor Authentication", 0, 100, 100, 500, 300)) { Print("Failed to create 2FA dialog"); return; } if (!twoFACodeInput.Create(ChartID(), "TwoFACodeInput", 0, 20, 70, 260, 95)) { Print("Failed to create 2FA code input box"); return; } twoFactorAuth.Add(twoFACodeInput); if (!twoFAPromptLabel.Create(ChartID(), "TwoFAPromptLabel", 0, 20, 20, 380, 40)) { Print("Failed to create 2FA prompt label"); return; } twoFAPromptLabel.Text("Enter the 2FA code sent to your Telegram:"); twoFactorAuth.Add(twoFAPromptLabel); if (!twoFAFeedbackLabel.Create(ChartID(), "TwoFAFeedbackLabel", 0, 20, 140, 380, 160)) { Print("Failed to create 2FA feedback label"); return; } twoFAFeedbackLabel.Text(""); twoFAFeedbackLabel.Color(clrRed); // Red color for incorrect 2FA attempts twoFactorAuth.Add(twoFAFeedbackLabel); if (!twoFALoginButton.Create(ChartID(), "TwoFALoginButton", 0, 20, 120, 100, 140)) { Print("Failed to create 2FA login button"); return; } twoFALoginButton.Text("Verify"); twoFactorAuth.Add(twoFALoginButton); if (!close2FAButton.Create(ChartID(), "Close2FAButton", 0, 120, 120, 200, 140)) { Print("Failed to create close button for 2FA"); return; } close2FAButton.Text("Close"); twoFactorAuth.Add(close2FAButton); twoFactorAuth.Show(); ChartRedraw(); } //+------------------------------------------------------------------+ //| Handle chart events | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if (id == CHARTEVENT_OBJECT_CLICK) { if (sparam == "LoginButton") { OnLoginButtonClick(); } else if (sparam == "CloseAuthButton") { OnCloseAuthButtonClick(); } else if (sparam == "TwoFALoginButton") { OnTwoFALoginButtonClick(); } else if (sparam == "Close2FAButton") { OnClose2FAButtonClick(); } } switch (id) { case CHARTEVENT_OBJECT_CLICK: if (sparam == "SendButton") OnSendButtonClick(); else if (sparam == "ClearButton") OnClearButtonClick(); else if (sparam == "ChangeFontButton") OnChangeFontButtonClick(); else if (sparam == "ToggleThemeButton") OnToggleThemeButtonClick(); else if (sparam == "MinimizeButton") OnMinimizeButtonClick(); else if (sparam == "MaximizeButton") OnMaximizeButtonClick(); else if (sparam == "CloseButton") OnCloseButtonClick(); else if (StringFind(sparam, "QuickMessageButton") != -1) { long index = StringToInteger(StringSubstr(sparam, 18)); OnQuickMessageButtonClick(index - 1); } break; case CHARTEVENT_OBJECT_ENDEDIT: if (sparam == "InputBox") OnInputChange(); break; } } //+------------------------------------------------------------------+ //| Handle login button click | //+------------------------------------------------------------------+ void OnLoginButtonClick() { string enteredPassword = passwordInputBox.Text(); if (enteredPassword == Password) { twoFACode = GenerateRandom6DigitCode(); SendMessageToTelegram("A login attempt was made on the Admin Panel. Please use this code to verify your identity. " + twoFACode, Hardcoded2FAChatId, Hardcoded2FABotToken); authentication.Destroy(); ShowTwoFactorAuthPrompt(); Print("Password authentication successful. A 2FA code has been sent to your Telegram."); } else { feedbackLabel.Text("Wrong password. Try again."); passwordInputBox.Text(""); } } //+------------------------------------------------------------------+ //| Handle 2FA login button click | //+------------------------------------------------------------------+ void OnTwoFALoginButtonClick() { string enteredCode = twoFACodeInput.Text(); if (enteredCode == twoFACode) { twoFactorAuth.Destroy(); adminPanel.Show(); Print("2FA authentication successful."); } else { twoFAFeedbackLabel.Text("Wrong code. Try again."); twoFACodeInput.Text(""); } } //+------------------------------------------------------------------+ //| Handle close button for authentication | //+------------------------------------------------------------------+ void OnCloseAuthButtonClick() { authentication.Destroy(); ExpertRemove(); // Exit the expert Print("Authentication dialog closed."); } //+------------------------------------------------------------------+ //| Handle close button for 2FA | //+------------------------------------------------------------------+ void OnClose2FAButtonClick() { twoFactorAuth.Destroy(); ExpertRemove(); Print("2FA dialog closed."); } //+------------------------------------------------------------------+ //| Create necessary UI controls | //+------------------------------------------------------------------+ bool CreateControls() { long chart_id = ChartID(); if (!inputBox.Create(chart_id, "InputBox", 0, 5, 25, 460, 95)) { Print("Failed to create input box"); return false; } adminPanel.Add(inputBox); if (!charCounter.Create(chart_id, "CharCounter", 0, 380, 5, 460, 25)) { Print("Failed to create character counter"); return false; } charCounter.Text("0/" + IntegerToString(MAX_MESSAGE_LENGTH)); adminPanel.Add(charCounter); if (!clearButton.Create(chart_id, "ClearButton", 0, 235, 95, 345, 125)) { Print("Failed to create clear button"); return false; } clearButton.Text("Clear"); adminPanel.Add(clearButton); if (!sendButton.Create(chart_id, "SendButton", 0, 350, 95, 460, 125)) { Print("Failed to create send button"); return false; } sendButton.Text("Send"); adminPanel.Add(sendButton); if (!changeFontButton.Create(chart_id, "ChangeFontButton", 0, 95, 95, 230, 115)) { Print("Failed to create change font button"); return false; } changeFontButton.Text("Font<>"); adminPanel.Add(changeFontButton); if (!toggleThemeButton.Create(chart_id, "ToggleThemeButton", 0, 5, 95, 90, 115)) { Print("Failed to create toggle theme button"); return false; } toggleThemeButton.Text("Theme<>"); adminPanel.Add(toggleThemeButton); if (!minimizeButton.Create(chart_id, "MinimizeButton", 0, 375, -22, 405, 0)) { Print("Failed to create minimize button"); return false; } minimizeButton.Text("_"); adminPanel.Add(minimizeButton); if (!maximizeButton.Create(chart_id, "MaximizeButton", 0, 405, -22, 435, 0)) { Print("Failed to create maximize button"); return false; } maximizeButton.Text("[ ]"); adminPanel.Add(maximizeButton); if (!closeButton.Create(chart_id, "CloseButton", 0, 435, -22, 465, 0)) { Print("Failed to create close button"); return false; } closeButton.Text("X"); adminPanel.Add(closeButton); return CreateQuickMessageButtons(); } //+------------------------------------------------------------------+ //| Create quick message buttons | //+------------------------------------------------------------------+ bool CreateQuickMessageButtons() { string quickMessages[] = { QuickMessage1, QuickMessage2, QuickMessage3, QuickMessage4, QuickMessage5, QuickMessage6, QuickMessage7, QuickMessage8 }; int startX = 5, startY = 160, width = 222, height = 65, spacing = 5; for (int i = 0; i < ArraySize(quickMessages); i++) { bool created = quickMessageButtons[i].Create(ChartID(), "QuickMessageButton" + IntegerToString(i + 1), 0, startX + (i % 2) * (width + spacing), startY + (i / 2) * (height + spacing), startX + (i % 2) * (width + spacing) + width, startY + (i / 2) * (height + spacing) + height); if (!created) { Print("Failed to create quick message button ", i + 1); return false; } quickMessageButtons[i].Text(quickMessages[i]); adminPanel.Add(quickMessageButtons[i]); } return true; } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { adminPanel.Destroy(); Print("Deinitialization complete"); } //+------------------------------------------------------------------+ //| Handle custom message send button click | //+------------------------------------------------------------------+ void OnSendButtonClick() { string message = inputBox.Text(); if (StringLen(message) > 0) { if (SendMessageToTelegram(message, InputChatId, InputBotToken)) Print("Custom message sent: ", message); else Print("Failed to send custom message."); } else { Print("No message entered."); } } //+------------------------------------------------------------------+ //| Handle clear button click | //+------------------------------------------------------------------+ void OnClearButtonClick() { inputBox.Text(""); OnInputChange(); Print("Input box cleared."); } //+------------------------------------------------------------------+ //| Handle quick message button click | //+------------------------------------------------------------------+ void OnQuickMessageButtonClick(long index) { string quickMessages[] = { QuickMessage1, QuickMessage2, QuickMessage3, QuickMessage4, QuickMessage5, QuickMessage6, QuickMessage7, QuickMessage8 }; string message = quickMessages[(int)index]; if (SendMessageToTelegram(message, InputChatId, InputBotToken)) Print("Quick message sent: ", message); else Print("Failed to send quick message."); } //+------------------------------------------------------------------+ //| Update character counter | //+------------------------------------------------------------------+ void OnInputChange() { int currentLength = StringLen(inputBox.Text()); charCounter.Text(IntegerToString(currentLength) + "/" + IntegerToString(MAX_MESSAGE_LENGTH)); ChartRedraw(); } //+------------------------------------------------------------------+ //| Handle toggle theme button click | //+------------------------------------------------------------------+ void OnToggleThemeButtonClick() { darkTheme = !darkTheme; UpdateThemeColors(); Print("Theme toggled: ", darkTheme ? "Dark" : "Light"); } //+------------------------------------------------------------------+ //| Update theme colors for the panel | //+------------------------------------------------------------------+ void UpdateThemeColors() { color textColor = darkTheme ? clrWhite : clrBlack; color buttonBgColor = darkTheme ? clrDarkSlateGray : clrGainsboro; color borderColor = darkTheme ? clrSlateGray : clrGray; color bgColor = darkTheme ? clrDarkBlue : clrWhite; UpdateButtonTheme(clearButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(sendButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(toggleThemeButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(changeFontButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(minimizeButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(maximizeButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(closeButton, textColor, buttonBgColor, borderColor); for (int i = 0; i < ArraySize(quickMessageButtons); i++) { UpdateButtonTheme(quickMessageButtons[i], textColor, buttonBgColor, borderColor); } ChartRedraw(); } //+------------------------------------------------------------------+ //| Apply theme settings to a button | //+------------------------------------------------------------------+ void UpdateButtonTheme(CButton &button, color textColor, color bgColor, color borderColor) { button.SetTextColor(textColor); button.SetBackgroundColor(bgColor); button.SetBorderColor(borderColor); } //+------------------------------------------------------------------+ //| Handle change font button click | //+------------------------------------------------------------------+ void OnChangeFontButtonClick() { currentFontIndex = (currentFontIndex + 1) % ArraySize(availableFonts); SetFontForAll(availableFonts[currentFontIndex]); Print("Font changed to: ", availableFonts[currentFontIndex]); ChartRedraw(); } //+------------------------------------------------------------------+ //| Set font for all input boxes and buttons | //+------------------------------------------------------------------+ void SetFontForAll(string fontName) { inputBox.Font(fontName); clearButton.Font(fontName); sendButton.Font(fontName); toggleThemeButton.Font(fontName); changeFontButton.Font(fontName); minimizeButton.Font(fontName); maximizeButton.Font(fontName); closeButton.Font(fontName); for (int i = 0; i < ArraySize(quickMessageButtons); i++) { quickMessageButtons[i].Font(fontName); } } //+------------------------------------------------------------------+ //| Generate a random 6-digit code for 2FA | //+------------------------------------------------------------------+ string GenerateRandom6DigitCode() { int code = MathRand() % 1000000; // Produces a 6-digit number return StringFormat("%06d", code); // Ensures leading zeros } //+------------------------------------------------------------------+ //| Handle minimize button click | //+------------------------------------------------------------------+ void OnMinimizeButtonClick() { minimized = true; adminPanel.Hide(); minimizeButton.Hide(); maximizeButton.Show(); closeButton.Show(); Print("Panel minimized."); } //+------------------------------------------------------------------+ //| Handle maximize button click | //+------------------------------------------------------------------+ void OnMaximizeButtonClick() { if (minimized) { adminPanel.Show(); minimizeButton.Show(); maximizeButton.Hide(); closeButton.Hide(); minimized = false; Print("Panel maximized."); } } //+------------------------------------------------------------------+ //| Handle close button click for admin panel | //+------------------------------------------------------------------+ void OnCloseButtonClick() { ExpertRemove(); Print("Admin panel closed."); } //+------------------------------------------------------------------+ //| Send the message to Telegram | //+------------------------------------------------------------------+ bool SendMessageToTelegram(string message, string chatId, string botToken) { string url = "https://api.telegram.org/bot" + botToken + "/sendMessage"; string jsonMessage = "{\"chat_id\":\"" + chatId + "\", \"text\":\"" + message + "\"}"; char postData[]; ArrayResize(postData, StringToCharArray(jsonMessage, postData) - 1); int timeout = 5000; char result[]; string responseHeaders; int responseCode = WebRequest("POST", url, "Content-Type: application/json\r\n", timeout, postData, result, responseHeaders); if (responseCode == 200) { Print("Message sent successfully: ", message); return true; } else { Print("Failed to send message. HTTP code: ", responseCode, " Error code: ", GetLastError()); Print("Response: ", CharArrayToString(result)); return false; } } //+------------------------------------------------------------------+
Тестирование и результаты
Наконец, мы успешно реализовали надежную защиту с помощью пароля и двухфакторной аутентификации (2FA) для панели администратора. Результаты показаны ниже.
Неверный пароль
Неверный код верификации
На следующем изображении показан полный процесс входа в систему и верификации. Нам также удалось синхронизировать Telegram с процедурой входа в систему, чтобы зафиксировать доставку кода верификации.
Полный вход в систему, проверка пароля и 2FA
Доставка 2FA-кода в Telegram для попытки входа.
Заключение
Внедрение функций двухфакторной аутентификации (2FA) значительно повысило безопасность панели администратора, добавив важный уровень проверки доступа пользователей. Использование Telegram для доставки кодов гарантировало получение пользователем уведомлений в реальном времени. Реализация включает обработку ошибок при неправильном вводе данных, предлагая пользователям повторить попытку ввода паролей или 2FA-кодов, что сводит к минимуму несанкционированный доступ и одновременно информирует пользователей о любых ошибках.
Однако риски сохраняются, особенно если приложение Telegram установлено и вход в него осуществляется на том же подверженном риску компьютере. Злоумышленник может воспользоваться уязвимостями в настройках, особенно если устройство взломано или если неавторизованные пользователи получат доступ к телефону, используемому системным администратором для работе в Telegram. Поэтому соблюдение строгих правил безопасности, таких как выход из приложения, когда оно не используется, и обеспечение безопасности устройств, становится важнейшим условием защиты конфиденциальных данных.
Надеюсь, вы получили ценную информацию об MQL5-реализации для 2FA, особенно в контексте генерации кода в реальном времени и безопасного обмена сообщениями. Понимание этих концепций повысит безопасность ваших приложений, а также подчеркнет важность превентивных мер по защите ваших приложений от потенциальных угроз. Ниже прилагается окончательный код. Удачной разработки!
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/16142
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.






- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования