English Русский 中文 Deutsch 日本語 Português
preview
Creación de un Panel de administración de operaciones en MQL5 (Parte V): Autenticación de dos factores (2FA)

Creación de un Panel de administración de operaciones en MQL5 (Parte V): Autenticación de dos factores (2FA)

MetaTrader 5Ejemplos |
103 1
Clemence Benjamin
Clemence Benjamin

Contenido:


Introducción

Anteriormente, exploramos la implementación de la autenticación mediante contraseña en el Panel de administración, un primer paso crucial para proteger las comunicaciones entre administradores y operadores. Esta forma básica de seguridad, si bien es esencial para la protección inicial, puede potencialmente exponer estos sistemas a vulnerabilidades debido a su dependencia de un único factor de verificación. La incorporación de la autenticación de dos factores (2FA) se convierte en una actualización vital, que proporciona un marco de seguridad más sólido para nuestra aplicación.

Este enfoque mitiga significativamente el riesgo de acceso no autorizado, ya que exige acceso al método de verificación secundario junto con el conocimiento de la contraseña. 2FA garantiza que todas las comunicaciones provengan de fuentes legítimas, protegiendo así contra las posibles consecuencias de datos comprometidos, desinformación y manipulación del mercado.

Al integrar 2FA en nuestro Panel de administración, podemos brindar a los usuarios un mayor nivel de confianza y seguridad. Este sistema de autenticación dual actúa como un poderoso elemento disuasorio contra posibles infracciones, ofreciendo tranquilidad tanto a los administradores como a los comerciantes mientras operan en un panorama financiero dinámico. Hoy discutiremos un concepto que he transformado con éxito en una solución práctica. Esta iniciativa ha reforzado la seguridad de nuestro Panel de Administración en respuesta a las vulnerabilidades asociadas con la seguridad del código de acceso implementadas en el artículo anterior de la serie.


¿Qué es la autenticación de dos factores?

La autenticación de dos factores (2FA) es un mecanismo de seguridad que requiere dos formas diferentes de verificación antes de otorgar acceso a una cuenta o sistema. Es un subconjunto de la autenticación multifactor (MFA), que puede implicar dos o más factores de verificación. El objetivo principal de 2FA es agregar otra capa de seguridad más allá de un nombre de usuario y una contraseña, haciendo más difícil que usuarios no autorizados obtengan acceso.

Según algunas fuentes, la 2FA normalmente implica dos de las siguientes tres categorías de factores de autenticación:

  1. Algo que sabes (factor de conocimiento): Normalmente se trata de una contraseña o un PIN que el usuario debe introducir para acceder a su cuenta. Sirve como primera línea de defensa.
  2. Algo que usted tiene (factor de posesión): Se trata de un dispositivo físico o token que el usuario posee, como un token de seguridad, una tarjeta inteligente o un teléfono móvil. Muchos sistemas utilizan aplicaciones como Google Authenticator o Authy para generar contraseñas de un solo uso (OTP) que suelen ser válidas durante un breve periodo de tiempo (normalmente 15 minutos).
  3. Algo que eres (factor biométrico): Este aspecto puede incluir el reconocimiento de huellas dactilares, el reconocimiento facial u otros datos biométricos. Aunque no siempre se utiliza junto con los dos primeros factores para la autenticación de dos factores (2FA), la biometría puede proporcionar una capa adicional de seguridad.


Implementación de la autenticación de dos factores (2FA) en el panel de administración mediante MQL5

Al integrar la autenticación de dos factores (2FA), estamos yendo más allá de la autenticación de un solo factor (SFA) tradicional, que normalmente se basa únicamente en nombres de usuario y contraseñas. Este cambio es crucial porque, aunque las contraseñas siguen siendo la forma más común de seguridad inicial, son intrínsecamente vulnerables a diversos tipos de ataques, incluidos los de ingeniería social, fuerza bruta y diccionario. El enfoque multifactorial de la autenticación de dos factores (2FA) mitiga eficazmente estos riesgos al exigir a los usuarios que proporcionen dos formas distintas de autenticación que pertenecen a categorías diferentes, lo que aumenta la confianza en que el acceso se concede realmente a usuarios legítimos.

Para implementar la autenticación de dos factores (2FA) en nuestro proyecto del Panel de administración, utilicé la biblioteca Dialog, que nos permite crear múltiples capas de ventanas controladas por una lógica específica. Al inicio de esta serie, integramos la comunicación por Telegram, centrada principalmente en la transmisión de mensajes desde el Panel de administración a los canales o grupos de usuarios en Telegram. Sin embargo, su potencial va más allá; nuestro objetivo es utilizarlo también para la entrega de OTP.

En este proyecto, ajustaremos nuestro código para generar un código aleatorio de seis dígitos, que el programa almacenará de forma segura y posteriormente enviará al Telegram del administrador para su verificación. He observado que muchas empresas utilizan Telegram para la verificación, aunque con enfoques diferentes a los que implementaremos aquí. Por ejemplo, en la imagen siguiente, puede ver el Bot de verificación MQL5, que sirve como ejemplo de dicho uso.

Bot de verificación MQL5

Bot de verificación MQL5


Implementación de elementos GUI para la entrada de contraseñas y la validación del código 2FA

Al implementar el cuadro de diálogo de introducción de contraseña, comenzamos definiendo la función ShowAuthenticationPrompt(), que encapsula todos los pasos necesarios para crear una interfaz de usuario para la introducción de contraseñas. El proceso comienza con la instanciación del cuadro de diálogo de autenticación mediante el método Create(), especificando sus dimensiones y posición en el gráfico. Para la entrada del usuario, creamos un passwordInputBox para capturar de forma segura la contraseña del usuario. A continuación, se añade una etiqueta passwordPromptLabel para proporcionar instrucciones claras al usuario y orientarle sobre la finalidad del cuadro de diálogo. Para gestionar los comentarios de los usuarios, especialmente en el caso de entradas incorrectas, se ha implementado una etiqueta feedbackLabel, en la que los mensajes de error se mostrarán en texto rojo, lo que mejora la capacidad del usuario para comprender qué ha fallado. A continuación, configuramos dos botones:

  • Un botón de inicio de sesión (loginButton) en el que los usuarios pueden hacer clic para enviar su contraseña para la autenticación y 
  • un botón «cerrar» que les permite salir del cuadro de diálogo si deciden no continuar. 

Por último, llamamos a show() en el cuadro de diálogo de autenticación para presentarlo al usuario e invocamos ChartRedraw() para garantizar que todos los componentes se muestren correctamente en la pantalla. Este enfoque sistemático garantiza una interfaz segura y fácil de usar para introducir la contraseña en el panel de administración. Vea el siguiente fragmento de código.

Creación del cuadro de diálogo de introducción de contraseña

// 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;
}


Creación del cuadro de diálogo de validación del código 2FA

Para crear el cuadro de diálogo de validación del código 2FA, definimos la función ShowTwoFactorAuthPrompt(), que gestiona todos los componentes necesarios para verificar el código de autenticación de dos factores del usuario.

Comenzamos creando el cuadro de diálogo twoFactorAuth , utilizando de nuevo el método Create() para establecer sus propiedades. El primer componente añadido es twoFACodeInput, un campo de entrada diseñado para capturar de forma segura el código 2FA enviado al Telegram del usuario.

Para guiar al usuario, implementamos una etiqueta twoFAPromptLabel que le indica claramente que introduzca el código 2FA que ha recibido. Para mejorar aún más la experiencia del usuario, incluimos una etiqueta twoFAFeedbackLabel para mostrar comentarios en tiempo real; esta etiqueta mostrará mensajes en rojo si el código introducido no coincide con el valor esperado, informando así al usuario de las entradas incorrectas.

Para el envío, se crea un botón twoFALoginButton, que permite al usuario verificar su código, mientras que se proporciona un botón close2FAButton para salir del cuadro de diálogo si es necesario. Una vez configurados los componentes, invocamos Show() en el cuadro de diálogo twoFactorAuth para que sea visible para el usuario y llamamos a ChartRedraw() para actualizar la interfaz.

Este enfoque estructurado garantiza un método seguro para validar los códigos 2FA y una interacción optimizada con el usuario en el Panel de administración. A continuación se muestra un fragmento de código para una mejor comprensión.

// 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 generación de código de verificación

Después de introducir correctamente la contraseña para acceder al panel de administración, establecimos una segunda capa de protección que implica la generación de un código OTP. Este código se almacena de forma segura y se reenvía a un identificador de chat único codificado de forma fija vinculado a la aplicación Telegram, ya sea en el móvil o en el ordenador, donde el propietario legítimo puede recuperarlo para introducirlo en el mensaje. Si el código introducido coincide, la aplicación concede acceso a todas las funciones del Panel de administración para operaciones y comunicación.

En cuanto al algoritmo de generación de código, explicaré los distintos componentes en los fragmentos de código que aparecen a continuación:

La línea de código que aparece a continuación sirve como declaración de variable esencial para gestionar la autenticación de dos factores (2FA) dentro de nuestro programa.

string twoFACode = "";

Esta línea inicializa la variable twoFACode como una cadena vacía, que se utilizará para almacenar un código de autenticación de dos factores de 6 dígitos generado aleatoriamente. A lo largo del proceso de autenticación, esta variable desempeña un papel fundamental al contener el código real que se envía al usuario a través de Telegram después de que este introduzca correctamente la contraseña para acceder al Panel de administración.

Cuando el usuario supera la comprobación inicial de la contraseña, la variable twoFACode se rellena con un nuevo valor generado por la función GenerateRandom6DigitCode(), que produce una cadena numérica de 6 dígitos. Este valor se envía al Telegram del usuario a través de la función SendMessageToTelegram().

Más adelante, cuando se le solicita al usuario que introduzca su código 2FA, el programa compara la entrada del usuario con el valor almacenado en twoFACode. Si la entrada del usuario coincide con el valor almacenado en twoFACode, se concede acceso al panel de administración; de lo contrario, se muestra un mensaje de error.

1. Autenticación por contraseña:

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("");
    }
}

Una contraseña codificada («2024») sirve como mecanismo de control de acceso para el panel de administración. Cuando el usuario envía la contraseña introducida a través del cuadro de entrada designado, el código comprueba si la entrada coincide con la contraseña codificada. Si coincide, se genera un código aleatorio de 6 dígitos utilizando la función GenerateRandom6DigitCode() y se envía al Telegram del usuario a través de la función SendMessageToTelegram(). Esto indica que la autenticación se ha realizado correctamente y solicita a la aplicación que pase a la fase de autenticación de dos factores. Si la contraseña es incorrecta, un mensaje de error le pide al usuario que vuelva a intentarlo.

2. Generación y envío de códigos de autenticación de dos factores:

// 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 sección gestiona la autenticación de dos factores (2FA) generando un código único de 6 dígitos y validando la entrada del usuario con este código. La función GenerateRandom6DigitCode() genera un número de 6 dígitos utilizando la función MathRand(), lo que garantiza que se mantenga en el formato requerido incluso cuando hay ceros a la izquierda. Una vez validada la contraseña inicial, este código de 6 dígitos se envía a través de Telegram al chat especificado por el usuario para mejorar la seguridad. En la siguiente función, OnTwoFALoginButtonClick(), se comprueba la entrada del usuario con el código generado. Si el código introducido coincide con el enviado a Telegram, se concede acceso al panel de administración; de lo contrario, se notifica al usuario que el código es incorrecto y se le pide que vuelva a intentarlo.


Comprensión de la función MathRand()

Esta función en MQL5 se utiliza para generar un número entero pseudoaleatorio. En el caso de nuestro proyecto, la función MathRand() genera un número entero aleatorio dentro del rango de 0 a MathRandMax(), que normalmente se define como 0 a 32767

Para generar un número aleatorio de 6 dígitos, puedes limitar el resultado aplicando el operador módulo (%). Este operador calcula el resto de una división, lo que le permite restringir el rango de números aleatorios para que se ajusten a valores válidos de 6 dígitos, que van desde 000000 (0) hasta 999999.

En concreto, al utilizar la expresión MathRand() % 1000000 se obtendrá un resultado entre 0 y 999999, lo que garantiza que se cubran todas las combinaciones posibles de 6 dígitos, incluidas aquellas con ceros a la izquierda.


API de Telegram para la verificación 2FA

La función SendMessageToTelegram() es fundamental para garantizar que nuestros códigos 2FA y otros mensajes se envíen de forma segura al chat de Telegram del usuario. Esta función construye una solicitud HTTP POST a la API de Telegram Bot, incluyendo el token del bot y el ID del chat de destino. El mensaje está formateado en JSON para cumplir con los requisitos de la API.

La función WebRequest() ejecuta la solicitud con un tiempo de espera especificado y comprueba el código de respuesta para confirmar si el mensaje se ha enviado correctamente (código HTTP 200).

Si falla la entrega del mensaje, la función registra un error, incluyendo el código de respuesta, el mensaje de error y cualquier contenido relevante de la respuesta, lo que ayuda a identificar posibles problemas en el envío de mensajes para su posterior diagnóstico.

Para obtener más información sobre la API de Telegram, puede visitar su sitio web y leer algunos artículos anteriores en los que lo explicamos.

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;
    }
}

Como parte de la API de Telegram, contamos con las siguientes constantes que hemos incorporado a nuestro proyecto para facilitar el proceso de autenticación de dos factores (2FA) a través de Telegram. 

// Constants for 2FA
const string Hardcoded2FAChatId = "REPLACE WITH YOUR CHAT ID";
const string Hardcoded2FABotToken = "REPLACE WITH YOUR ACTUAL BOT TOKEN";

En esta parte del código, la variable Hardcoded2FAChatId representa el identificador único del chat de Telegram al que se enviarán los mensajes de autenticación, mientras que Hardcoded2FABotToken contiene el token del bot de Telegram utilizado para enviar mensajes.

El token del bot es esencial para autenticar las solicitudes realizadas a la API de Telegram, lo que garantiza que solo un bot legítimo con los permisos correctos pueda enviar mensajes al chat especificado. Al codificar estas constantes, el programa agiliza el proceso de envío de códigos 2FA, ya que se utiliza el mismo ID de chat y token de bot cada vez sin necesidad de que el usuario introduzca datos ni realice configuraciones.

Sin embargo, es importante tener en cuenta que codificar información confidencial como tokens de bot puede suponer un riesgo para la seguridad si el código queda expuesto, por lo que se deben considerar métodos alternativos de almacenamiento seguro para entornos de producción.

Aquí se presenta el código completo, y podemos observar que se está ampliando significativamente a medida que se implementan nuevas funciones.

//+------------------------------------------------------------------+
//|                                             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;
    }
}

//+------------------------------------------------------------------+


Pruebas y resultados

Por último, hemos implementado con éxito una sólida seguridad de contraseñas y la autenticación de dos factores (2FA) para el panel de administración. A continuación se muestran imágenes que ilustran las respuestas.

Intento de contraseña incorrecta

Intento de contraseña incorrecta

Intento de código de verificación erróneo.

Intento de código de verificación incorrecto

La siguiente imagen ilustra los procesos completos de inicio de sesión y verificación. También logramos sincronizar la aplicación Telegram con el procedimiento de inicio de sesión para capturar la entrega del código de verificación.

Prueba de inicio de sesión completa, contraseña y 2FA.

Contraseña de inicio de sesión completa y prueba 2FA

Entrega de código 2FA en Telegram

Entrega del código 2FA en Telegram para el intento de inicio de sesión mencionado anteriormente.


Conclusión

La incorporación de funciones de autenticación de dos factores (2FA) en este proyecto MQL5 mejoró significativamente la seguridad del Panel de administración al añadir una capa crucial de verificación para el acceso de los usuarios. El uso de Telegram para la entrega de código garantizó que los usuarios recibieran notificaciones en tiempo real. La implementación incluye la gestión de errores para entradas incorrectas, solicitando a los usuarios que vuelvan a introducir sus contraseñas o códigos 2FA, lo que minimiza el acceso no autorizado y mantiene a los usuarios informados de cualquier error.

Sin embargo, es importante reconocer que los riesgos persisten, especialmente si la aplicación Telegram está instalada y conectada en el mismo ordenador en riesgo. Un infiltrado puede aprovechar las vulnerabilidades de estas configuraciones, especialmente si el dispositivo está comprometido o si usuarios no autorizados obtienen acceso al teléfono que utiliza el administrador del sistema para Telegram. Por lo tanto, mantener prácticas de seguridad estrictas, como cerrar sesión en la aplicación cuando no se utiliza y garantizar la seguridad de los dispositivos, se convierte en algo fundamental para proteger los datos y las comunicaciones confidenciales.

Espero que hayas obtenido información valiosa sobre la implementación de MQL5 para la autenticación de dos factores (2FA), especialmente en el contexto de la generación de código en tiempo real y la mensajería segura. Comprender estos conceptos mejorará la seguridad de sus aplicaciones y también pondrá de relieve la importancia de las medidas proactivas para proteger sus aplicaciones contra posibles amenazas. Adjunto se encuentra el trabajo final a continuación. ¡Feliz desarrollo! Comerciantes.

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/16142

Archivos adjuntos |
Admin__Panel.mq5 (21.85 KB)
joaopedrodev
joaopedrodev | 23 ago 2025 en 13:32
Sorprendente. El contenido del artículo es muy interesante. ¡Añadir 2FA a MQL5 ha sido brillante!

Gracias por esta contribución.
Redes neuronales en el trading: Mejora de la eficiencia del Transformer mediante la reducción de la nitidez (Final) Redes neuronales en el trading: Mejora de la eficiencia del Transformer mediante la reducción de la nitidez (Final)
El SAMformer ofrece una solución a los problemas clave del Transformer en la previsión de series temporales a largo plazo, incluida la complejidad del entrenamiento y la escasa generalización a muestras pequeñas. Su arquitectura poco profunda y la optimización con control de nitidez garantizan que se eviten los malos mínimos locales. En este artículo, proseguiremos la aplicación de enfoques utilizando MQL5 y evaluaremos su valor práctico.
Cómo crear un panel interactivo MQL5 utilizando la clase Controls (Parte 2): Añadir capacidad de respuesta a los botones Cómo crear un panel interactivo MQL5 utilizando la clase Controls (Parte 2): Añadir capacidad de respuesta a los botones
En este artículo, nos centramos en transformar nuestro panel de control MQL5 estático en una herramienta interactiva habilitando la capacidad de respuesta de los botones. Exploramos cómo automatizar la funcionalidad de los componentes de la interfaz gráfica de usuario (GUI), asegurándonos de que reaccionen adecuadamente a los clics de los usuarios. Al final del artículo, establecemos una interfaz dinámica que mejora la participación del usuario y la experiencia comercial.
Redes neuronales en el trading: Transformer parámetro-eficiente con atención segmentada (PSformer) Redes neuronales en el trading: Transformer parámetro-eficiente con atención segmentada (PSformer)
Hoy proponemos al lector un primer contacto con el nuevo framework PSformer, que adapta la arquitectura del Transformer vainilla para resolver problemas de previsión de series temporales multidimensionales. El framework se basa en dos innovaciones clave: el mecanismo de compartición de parámetros (PS) y la atención a los segmentos espaciotemporales (SegAtt).
Instalación de MetaTrader 5 y otras aplicaciones MetaQuotes en HarmonyOS NEXT Instalación de MetaTrader 5 y otras aplicaciones MetaQuotes en HarmonyOS NEXT
Las aplicaciones de MetaQuotes, incluidas las plataformas MetaTrader 5 y MetaTrader 4, pueden instalarse en dispositivos con sistema operativo HarmonyOS NEXT usando el componente DroiTong. Este artículo ofrece una guía paso a paso para instalar aplicaciones en su teléfono o portátil.