Criação de um painel de administração de trading em MQL5 (Parte V): Autenticação de dois fatores (2FA)
Conteúdo:
- Introdução
- O que é autenticação de dois fatores (2FA)?
- Implementação da autenticação de dois fatores (2FA) no painel de administração usando MQL5
- Implementação dos elementos da interface gráfica para entrada de senha e verificação do código 2FA
- Algoritmo de geração do código de verificação
- Função MathRand()
- API do Telegram para verificação via 2FA
- Testes e resultados
- Considerações finais
Introdução
No artigo anterior exploramos a implementação da autenticação por senha no painel de administração, o que representa um primeiro passo importante na proteção da comunicação entre administradores e traders. Embora essa forma básica de segurança seja necessária para uma proteção inicial, ela pode tornar o sistema vulnerável por depender de um único fator de verificação. A autenticação de dois fatores (two-factor authentication, 2FA) oferece uma estrutura de segurança muito mais robusta para nossa aplicação.
Essa abordagem reduz significativamente o risco de acesso não autorizado, pois exige um método adicional de verificação além do conhecimento da senha. A autenticação de dois fatores garante que todas as mensagens sejam originadas de fontes legítimas, protegendo assim contra possíveis consequências de vazamento de dados, desinformação e manipulação de mercado.
Ao integrar o 2FA ao nosso painel de administração, conseguimos proporcionar aos usuários um nível superior de confiança e segurança. Essa sistema de autenticação dupla funciona como uma poderosa barreira contra violações potenciais, oferecendo tanto a administradores quanto a traders mais tranquilidade ao operarem em um ambiente financeiro dinâmico. Hoje, vamos discutir um conceito que consegui transformar com sucesso em uma solução funcional. Essa iniciativa reforçou a segurança do nosso painel de administração como resposta às vulnerabilidades associadas à proteção por senha, implementada no artigo anterior desta série.
O que é autenticação de dois fatores (2FA)?
Autenticação de dois fatores (2FA) é um mecanismo de segurança que exige duas formas distintas de verificação antes de conceder acesso a uma conta ou sistema. Trata-se de um subconjunto da autenticação multifator (MFA), que pode incluir dois ou mais fatores de verificação. O principal objetivo do 2FA é adicionar uma camada extra de segurança além do simples nome de usuário e senha, dificultando o acesso não autorizado por parte de usuários externos.
De acordo com algumas fontes, o 2FA normalmente envolve dois dos seguintes três tipos de fatores de autenticação:
- Algo que você sabe (fator de conhecimento): geralmente, uma senha ou um código PIN que o usuário deve inserir para acessar sua conta. Ele funciona como a primeira linha de defesa.
- Algo que você possui (fator de posse): aqui entram dispositivos físicos ou tokens que o usuário possui, como um token de segurança, cartão inteligente ou telefone celular. Muitos sistemas utilizam aplicativos como Google Authenticator ou Authy para gerar senhas de uso único (one-time passwords, OTP), que são válidas por um curto período de tempo (geralmente 15 minutos).
- Quem você é (fator biométrico): esse aspecto pode incluir reconhecimento de impressões digitais, reconhecimento facial ou outros dados biométricos. Embora a biometria nem sempre seja usada junto aos dois primeiros fatores na autenticação de dois fatores, ela pode fornecer uma camada adicional de segurança.
Implementação da autenticação de dois fatores (2FA) no painel de administração usando MQL5
Com a integração do 2FA, avançamos além da autenticação tradicional de um único fator (single-factor authentication, SFA), que normalmente se baseia apenas em nomes de usuário e senhas. Essa mudança é essencial, pois embora as senhas ainda sejam a forma mais comum de segurança inicial, elas são inerentemente vulneráveis a diversos tipos de ataques, incluindo engenharia social, ataques por tentativa e erro e ataques por dicionário. A abordagem multifatorial do 2FA reduz esses riscos de forma eficaz, exigindo que os usuários forneçam duas formas distintas de autenticação, pertencentes a diferentes categorias, aumentando assim a certeza de que o acesso está sendo concedido a usuários legítimos.
Para implementar o 2FA em nosso projeto do painel de administração, utilizei a biblioteca Dialog, que nos permite criar múltiplas camadas de janelas controladas por uma lógica específica. No início desta série, integramos a comunicação com o Telegram, focando principalmente no envio de mensagens do painel de administração para canais ou grupos de usuários no Telegram. No entanto, o potencial dessa integração é muito mais amplo: pretendemos também usá-la para envio de senhas de uso único.
Neste projeto, configuraremos nosso código para gerar um código aleatório de seis dígitos, que será armazenado de forma segura pelo programa e posteriormente enviado para o Telegram do administrador para verificação. Observei que muitas empresas utilizam o Telegram para verificação, embora com abordagens diferentes da que estamos implementando aqui. Por exemplo, na imagem abaixo é possível ver um bot de verificação (MQL5 Verification Bot), que serve como exemplo desse tipo de uso.

Bot de verificação MQL5
Implementação dos elementos da interface gráfica para entrada de senha e verificação do código 2FA
Na implementação da janela de entrada de senha, começamos definindo a função ShowAuthenticationPrompt(), que encapsula todas as etapas necessárias para criar a interface de usuário para entrada da senha. O processo inicia com a criação da janela de autenticação utilizando o método Create(), no qual são definidos seu tamanho e posição no gráfico. Para entrada dos dados do usuário, criamos o passwordInputBox para receber a senha do usuário de forma segura. Em seguida, adicionamos o passwordPromptLabel, que fornece instruções claras ao usuário sobre o propósito do diálogo. Para lidar com os retornos dos usuários, especialmente em casos de entrada incorreta, foi implementado o feedbackLabel, onde mensagens de erro são exibidas em vermelho, ajudando o usuário a entender melhor o que deu errado. Depois disso, configuramos dois botões:
- loginButton, para que o usuário envie sua senha para autenticação, e
- closeAuthButton, para que ele possa sair da janela de diálogo.
Por fim, chamamos show() na janela de autenticação para apresentá-la ao usuário e invocamos ChartRedraw() para garantir que todos os componentes sejam exibidos corretamente na tela. Essa abordagem sistemática garante uma interface segura e amigável para entrada de senha no painel de administração. Vejamos o trecho de código a seguir.
Criação da janela de entrada de senha
// 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; }
Criação da janela de verificação do código 2FA
Para criar a janela de verificação do código 2FA, definimos a função ShowTwoFactorAuthPrompt(), que gerencia todos os componentes necessários para verificar o código de autenticação de dois fatores do usuário.
Começamos criando o diálogo twoFactorAuth, novamente usando o método Create() para configurar suas propriedades. O primeiro componente adicionado é o twoFACodeInput, um campo de entrada destinado à captura segura do código 2FA enviado ao Telegram do usuário.
Para orientar o usuário, implementamos o twoFAPromptLabel, no qual é claramente solicitado que ele insira o código 2FA recebido. Para aprimorar ainda mais a experiência do usuário, incluímos o twoFAFeedbackLabel, que exibe mensagens de retorno em tempo real. Essa etiqueta mostrará mensagens em vermelho caso o código inserido não corresponda ao valor esperado, informando assim o usuário sobre entradas incorretas.
Para o envio do código, é criado o twoFALoginButton, permitindo que o usuário valide seu código, enquanto o close2FAButton possibilita sair do diálogo. Após a configuração dos componentes, chamamos Show() na janela twoFactorAuth para torná-la visível ao usuário, e ChartRedraw() para atualizar a interface.
Essa abordagem estruturada proporciona um método seguro de verificação de códigos 2FA e uma interação otimizada para o usuário dentro do painel de administração. Abaixo está um trecho de código para melhor compreensão.
// 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(); }
Algoritmo de geração do código de verificação
Após a inserção bem-sucedida da senha para acesso ao painel de administração, configuramos um segundo nível de proteção que envolve a geração do código OTP. Esse código é armazenado com segurança e enviado a um identificador de chat exclusivo, codificado diretamente no sistema, vinculado ao aplicativo Telegram, tanto no dispositivo móvel quanto no computador, onde o usuário legítimo poderá acessá-lo para digitação posterior na linha de comando. Se o código digitado corresponder, o aplicativo concede acesso total a todas as funções do painel de administração para operações e comunicação.
Sobre o algoritmo de geração do código, explicarei os diferentes componentes nos trechos de código apresentados a seguir:
A linha de código mostrada abaixo serve para declarar uma variável importante para o controle da autenticação de dois fatores (2FA) no nosso programa.
string twoFACode = "";
Essa linha inicializa a variável twoFACode como uma string vazia, que será utilizada para armazenar o código aleatório de 6 dígitos gerado para a autenticação de dois fatores. Durante todo o processo de autenticação, essa variável desempenha um papel essencial ao guardar o código efetivo que será enviado ao usuário via Telegram, logo após ele inserir corretamente a senha de acesso ao painel de administração.
Quando o usuário passa pela verificação inicial da senha, a variável twoFACode é preenchida com um novo valor gerado pela função GenerateRandom6DigitCode(), que cria uma string numérica de seis dígitos. Esse valor é então enviado ao Telegram do usuário com a ajuda da função SendMessageToTelegram().
Mais tarde, quando for solicitado ao usuário que insira seu 2FA, o programa compara os dados digitados pelo usuário com o valor armazenado na variável twoFACode. Se os dados inseridos corresponderem ao valor contido em twoFACode, o acesso ao painel de administração é concedido. Caso contrário, uma mensagem de erro é exibida.
1. Autenticação por senha:
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(""); } }
Uma senha fixa ("2024") funciona como mecanismo de controle de acesso ao painel de administração. Quando o usuário insere a senha por meio do campo apropriado, o código verifica se a senha inserida corresponde à definida. Em caso afirmativo, a função GenerateRandom6DigitCode() é utilizada para gerar um código aleatório de 6 dígitos, que é enviado ao Telegram do usuário pela função SendMessageToTelegram(). Isso indica que a autenticação foi bem-sucedida e aciona o aplicativo para passar à fase de autenticação de dois fatores. Se a senha estiver incorreta, será exibida uma mensagem de erro convidando o usuário a tentar novamente.
2. Geração e envio do código de autenticação de dois fatores:
// 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(""); } }
Esta parte gerencia a autenticação de dois fatores (2FA) gerando um código exclusivo de 6 dígitos e verificando se os dados inseridos pelo usuário correspondem a esse código. A função GenerateRandom6DigitCode() gera um número de 6 dígitos usando a função MathRand(), garantindo que o formato necessário seja mantido, mesmo quando há zeros à esquerda. Após a verificação da senha inicial, esse código de 6 dígitos é enviado via Telegram para o chat do usuário especificado, reforçando a segurança. Na função OnTwoFALoginButtonClick(), os dados inseridos pelo usuário são comparados com o código gerado. Se o código digitado corresponder ao que foi enviado via Telegram, o acesso ao painel de administração é concedido. Caso contrário, o usuário é informado de que o código está incorreto e convidado a tentar novamente.
Função MathRand()
Essa função no MQL5 é usada para gerar um número inteiro pseudoaleatório. No nosso caso, a função MathRand() gera um número inteiro aleatório dentro do intervalo de 0 até MathRandMax(). Normalmente, esse intervalo vai de 0 até 32767.
Para gerar um número aleatório de 6 dígitos, podemos limitar a saída aplicando o operador de divisão modular (%). Esse operador calcula o resto da divisão, permitindo restringir o intervalo dos números aleatórios para que correspondam aos valores válidos de 6 dígitos, que abrangem o intervalo de 000000 (0) até 999999.
Em particular, o uso da expressão MathRand() % 1000000 resultará em um número entre 0 e 999999, garantindo que todas as combinações possíveis de 6 dígitos sejam contempladas, incluindo aquelas com zeros à esquerda.
API do Telegram para verificação via 2FA
A função SendMessageToTelegram() é crucial para garantir o envio seguro dos nossos códigos 2FA e de outras mensagens ao chat do usuário no Telegram. Essa função constrói uma requisição HTTP POST para a API do Telegram Bot, incluindo o token do bot e o identificador do chat apropriado. A mensagem é formatada em JSON conforme os requisitos da API.
A função WebRequest() executa a requisição com um tempo limite definido e também verifica o código de resposta para confirmar se a mensagem foi enviada com sucesso (código HTTP 200).
Se o envio da mensagem falhar, a função registra um erro, incluindo o código de resposta, a mensagem de erro e qualquer conteúdo relevante da resposta, o que ajuda a identificar potenciais problemas no envio para posterior diagnóstico.
Detalhes sobre o API do Telegram podem ser encontrados no site oficial, assim como em artigos anteriores.
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; } }
Dentro da API do Telegram, incluímos no projeto as seguintes constantes para facilitar o processo de autenticação de dois fatores (2FA) por meio do Telegram.
// Constants for 2FA const string Hardcoded2FAChatId = "REPLACE WITH YOUR CHAT ID"; const string Hardcoded2FABotToken = "REPLACE WITH YOUR ACTUAL BOT TOKEN";
Nesta parte do código, a variável Hardcoded2FAChatId representa um identificador único do chat no Telegram, para o qual as mensagens de autenticação serão enviadas, enquanto Hardcoded2FABotToken contém o token do bot do Telegram responsável pelo envio dessas mensagens.
O token do bot é necessário para autenticar as requisições feitas à API do Telegram, garantindo que apenas o bot com as permissões corretas possa enviar mensagens ao chat indicado. Com o uso dessas constantes codificadas diretamente no sistema, o programa otimiza o envio do código 2FA, utilizando sempre o mesmo identificador de chat e token do bot, sem necessidade de entrada ou configuração adicional por parte do usuário.
No entanto, é importante observar que o uso de dados sensíveis codificados diretamente, como tokens de bot, pode representar um risco de segurança caso o código seja exposto, portanto, para ambientes de produção, recomenda-se considerar métodos alternativos de armazenamento seguro dessas informações.
Abaixo está o código final. Como podemos ver, seu volume cresceu significativamente com a adição das novas funcionalidades.
//+------------------------------------------------------------------+ //| 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; } } //+------------------------------------------------------------------+
Testes e resultados
Por fim, conseguimos implementar com sucesso uma proteção robusta por senha e autenticação de dois fatores (2FA) para o painel de administração. Os resultados estão ilustrados a seguir.
Senha incorreta
Código de verificação incorreto
A próxima imagem mostra o processo completo de login e verificação. Também conseguimos sincronizar o Telegram com o procedimento de login, registrando a entrega do código de verificação.

Login completo no sistema, verificação de senha e 2FA

Entrega do código 2FA no Telegram para tentativa de login.
Considerações finais
A implementação das funcionalidades de autenticação de dois fatores (2FA) aumentou significativamente a segurança do painel de administração, adicionando uma camada essencial de verificação de acesso dos usuários. O uso do Telegram para envio dos códigos garantiu que os usuários recebessem notificações em tempo real. A implementação contempla o tratamento de erros no caso de entradas incorretas, permitindo que os usuários tentem novamente inserir senhas ou códigos 2FA, o que minimiza o risco de acessos não autorizados e, ao mesmo tempo, informa os usuários sobre qualquer falha.
No entanto, os riscos ainda existem, especialmente se o aplicativo Telegram estiver instalado e acessado no mesmo computador comprometido. Um invasor pode explorar vulnerabilidades na configuração, especialmente se o dispositivo estiver infectado ou se usuários não autorizados obtiverem acesso ao telefone usado pelo administrador do sistema para operar no Telegram. Por isso, seguir regras rígidas de segurança, como sair do aplicativo quando ele não estiver em uso e proteger os dispositivos, torna-se uma condição fundamental para garantir a confidencialidade dos dados.
Espero que você tenha obtido informações valiosas sobre a implementação de 2FA com MQL5, especialmente no contexto da geração de código em tempo real e da troca segura de mensagens. Compreender esses conceitos aumentará a segurança dos seus aplicativos e reforçará a importância das medidas preventivas para proteger seus sistemas contra possíveis ameaças. Abaixo está o código final. Boa programação!
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/16142
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Técnicas do MQL5 Wizard que você deve conhecer (Parte 44): Indicador técnico Average True Range (ATR)
Seleção de características e redução de dimensionalidade com Análise de Componentes Principais (PCA)
Desenvolvendo um EA multimoeda (Parte 21): Preparação para um experimento importante e otimização do código
Criando um painel MQL5 interativo usando a classe Controls (Parte 2): Adicionando responsividade aos botões
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso