paint-brush
Evite el spam de correo electrónico creando un formulario seguro en Pythonpor@tom2
338 lecturas
338 lecturas

Evite el spam de correo electrónico creando un formulario seguro en Python

por Rutkat11m2024/09/04
Read on Terminal Reader

Demasiado Largo; Para Leer

Una dirección de correo electrónico válida es una puerta de entrada para establecer una comunicación directa, generar clientes potenciales, conseguir ventas, invitaciones privadas a comunidades en línea, etc. No tenemos que depender del uso de servicios de terceros como Auth0, Facebook o Google para tener acceso a su aplicación y servicios. Vamos a utilizar módulos de Python existentes que simplifican la limpieza de la entrada del usuario, la generación de un enlace de verificación y la comunicación con la base de datos.
featured image - Evite el spam de correo electrónico creando un formulario seguro en Python
Rutkat HackerNoon profile picture

Una dirección de correo electrónico válida es una puerta de entrada para establecer una comunicación directa, generar clientes potenciales, conseguir ventas, invitaciones privadas a comunidades en línea, etc. No lo dé por sentado porque las redes sociales están cambiando. A pesar de las evoluciones en la tecnología, el correo electrónico sigue siendo la forma probada y verdadera de conectarse. Vamos a simplificar las cosas y no codificaremos desde cero porque Python tiene módulos existentes que lo ayudarán a acelerar la codificación.


Algunos clientes me pidieron que creara formularios de registro por correo electrónico para promocionar sus productos, pero ninguno de ellos quería pagar tarifas mensuales por un servicio de terceros listo para usar, así que les ahorré dinero creando formularios de contacto personalizados que pueden usar para siempre. Puedo ayudarte a hacer lo mismo, ya sea para tu empresa emergente, para tus clientes, para fines de marketing o, lo mejor de todo, para mitigar el correo no deseado.


Este tutorial está dirigido a quienes quieran aprender a codificar en Python y es especialmente útil para principiantes que no tengan en cuenta las funciones de seguridad, como el filtrado de la entrada del usuario, la validación de direcciones de correo electrónico y la suscripción doble a correos electrónicos. En este tutorial, cubrimos los pasos 1 a 3:


  1. Filtrar la entrada del usuario para una dirección de correo electrónico válida
  2. Registro con doble opt-in
  3. Prevención de bots y spam


No tenemos que depender de servicios de terceros como Auth0, Facebook o Google para tener acceso a su aplicación y servicios que pueden interrumpir su actividad en cualquier momento o compartir sus datos. ¡Mantenga los datos de su aplicación bajo su control!


Para empezar, deberías tener algo de experiencia en Python, ya que vamos a utilizar el framework Flask con una base de datos MySQL . Esto va a ser más divertido (tal vez) que usar WordPress, el CMS más popular. Tendrías que pagar por algún complemento de WordPress para tener la misma capacidad que una extensión gratuita de Flask. He creado anteriormente en Wordpress (PHP) y prefiero Python Flask para aplicaciones web, aunque Wordpress es muy capaz de crear aplicaciones web.


Utilizaremos módulos Python existentes que simplifican la limpieza de la entrada del usuario, la generación de un enlace de verificación y la comunicación con la base de datos.


Se explicará cada fragmento de código y se incluirán algunos comentarios en el código. En caso de que no hayas creado el registro de usuarios o no conozcas el funcionamiento interno, te describiré los detalles y luego podrás ver el código final al final (no te saltes los pasos).


A continuación se muestra un resumen de las características que implementaremos como se indica en el primer párrafo:


  1. Se puede comprobar si una dirección de correo electrónico es válida analizando la cadena de entrada del usuario mediante una expresión regular o una extensión Flask. No permitiremos texto aleatorio ni ataques de tipo inyección SQL.


  2. El método de doble confirmación requiere que el destinatario dé su permiso para que le envíes un correo electrónico mediante la recepción de un enlace de validación en su bandeja de entrada. Esto se utiliza principalmente para evitar que otra persona utilice tu dirección de correo electrónico. Esto también evita que los usuarios de prueba simplemente se registren y abandonen sus cuentas.


  3. La prevención de bots se puede realizar con un campo oculto que no se muestra al usuario pero que comúnmente es completado automáticamente por bots que rastrean formularios de registro vulnerables, pero no es tan confiable como un "captcha" de un servicio de terceros.


Comencemos a codificar. Crea un directorio de trabajo:

 mkdir signup cd signup


Crea tu entorno Python usando python3 -m venv signup o conda create -n double-opt-contact python3 . Yo prefiero conda y, si quieres aprender más, puedes leer mi artículo sobre entornos Python.


Instalar las siguientes dependencias:
pip flask flask-mail secure SQLAlchemy Flask-WTF Flask-SQLAlchemy mysql-connector-python bleach

Alternativamente, puede tener las mismas dependencias enumeradas en un archivo requirements.txt y ejecutar pip install -r requirements.txt


Cree el archivo app.py con las siguientes dependencias incluidas:


 from flask import Flask, render_template, request, url_for, redirect, flash from flask_mail import Mail, Message from datetime import datetime from flask_sqlalchemy import SQLAlchemy from sqlalchemy.sql import func from itsdangerous import URLSafeTimedSerializer, SignatureExpired import secrets import bleach


Inicialice el objeto de la aplicación con la ubicación de la carpeta de plantilla predeterminada:

 app = Flask(__name__, template_folder='templates')


Introduzca sus propios datos de configuración del servidor utilizando estas líneas:

 secret = secrets.token_urlsafe(32) app.secret_key = secret app.config['SECRET_KEY'] = secret # auto-generated secret key # SQLAlchemy configurations app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+mysqlconnector://admin:user@localhost/tablename' # Email configurations app.config['MAIL_SERVER'] = 'smtp.example.com' app.config['MAIL_PORT'] = 465 #check your port app.config['MAIL_USERNAME'] = '[email protected]' app.config['MAIL_PASSWORD'] = 'your_password' app.config['MAIL_USE_TLS'] = True app.config['MAIL_USE_SSL'] = False db = SQLAlchemy(app) mail = Mail(app) sserialzer = URLSafeTimedSerializer(app.config['SECRET_KEY']) #set secret to the serliazer


En última instancia, debes tener la información de configuración en un archivo .env .


Necesitaremos una base de datos MySQL para almacenar usuarios, que se puede crear de forma manual o mediante código Python. Como parte del proceso de aprendizaje, puedes ingresar el siguiente código mediante la línea de comandos o usando el método with app.app_context() db_create_all() .


El campo validado es para una cadena de token que permite la técnica de doble confirmación.

 CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, email VARCHAR(120) NOT NULL UNIQUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, validated BOOLEAN DEFAULT FALSE );


La siguiente sección utiliza la estructura ORM de SQLAlchemy para consultar la base de datos por usted. Tenga en cuenta que el nombre de la clase debe coincidir con el nombre de la tabla de su base de datos; de lo contrario, obtendrá un error. db.model representa la configuración de su tabla, que incluye el nombre de la columna, su tipo, longitud, clave y valor nulo:


 class User(db.Model): id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(120), unique=True, nullable=False) created_at = db.Column(db.DateTime, server_default=db.func.now()) validated = db.Column(db.Boolean, default=False)


Si aún no ha creado manualmente la tabla de base de datos MySQL, puede hacerlo con este código Flask directamente después del bloque de código class User :

 # Create the database table with app.app_context(): db.create_all()


Ahora, ingresamos el código del back-end que consta de 2 páginas/rutas (índice, registro), el mensaje de correo electrónico y la confirmación. La página de registro incluye los métodos GET/POST que permiten enviar el formulario. El objeto bleach es una extensión de Python que limpia la entrada del usuario para garantizar la seguridad y mitigar los scripts maliciosos. Luego, el sserializer genera un token de un solo uso para enviar por correo electrónico el enlace de verificación.


 @app.route('/') def index(): return '<h1>Index page</h1>' @app.route('/signup', methods=['GET', 'POST']) def signup(): if request.method == 'POST': email = bleach.clean(request.form.get('email')) # Insert user into the database new_user = User(email=email) try: db.session.add(new_user) db.session.commit() except Exception as e: print(f"Error occurred saving to db: {e}") # Send confirmation email token = sserialzer.dumps(email, salt='email-confirm') msg = Message('Confirm your Email', sender='[email protected]', recipients=[email]) link = url_for('confirm_email', token=token, _external=True) msg.body = f'Your link is {link}' try: mail.send(msg) except Exception as e: print(f"Error occurred sending message: {e}") flash("Error occurred sending message!") return render_template('signup.html') flash('A confirmation email has been sent to your email address.', 'success') return redirect(url_for('index')) return render_template('signup.html')


Antes de agregar el formulario de registro HTML, completemos el backend agregando la ruta para validar la función de doble suscripción. Esta ruta usa la variable s que creamos anteriormente, que genera el token secreto y sensible al tiempo. Ver los documentos para más detalles .


La edad máxima son los segundos antes de que el enlace expire, por lo que en este caso, el usuario tiene 20 minutos para confirmar su dirección de correo electrónico.


 @app.route('/confirm_email/<token>') def confirm_email(token): try: email = sserialzer.loads(token, salt='email-confirm', max_age=1200) # Token expires after 1 hour except SignatureExpired: return '<h1>The token is expired!</h1>' # Update field in database user = User.query.filter_by(email=email).first_or_404() user.validated = True db.session.commit() return '<h1>Email address confirmed!</h1>'


Ahora, para la omnipresente declaración principal que le dice a Python que ejecute el script si el archivo se ejecuta directamente (a diferencia de un módulo importado):

 if __name__ == '__main__': app.run()


Antes de completar este código de back-end, todavía necesitamos el HTML de front-end para la entrada del usuario. Vamos a hacer esto con la plantilla Jinja incorporada de Flask. Crea un archivo llamado templates/signup.html que debe coincidir con la ruta que creaste anteriormente en app.py . De forma predeterminada, Jinja usa el directorio /templates para los archivos html. Puedes cambiar esta configuración, pero para este tutorial, vamos a usar el directorio /templates de la aplicación.

 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Email Sign Up</title> </head> <body> <h1>Sign Up</h1> <form action="{{ url_for('signup') }}" method="POST"> <input type="email" name="email" placeholder="Enter your email" required> <input type="submit" value="Sign Up"> </form> {% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} <ul> {% for category, message in messages %} <li>{{ message }}</li> {% endfor %} </ul> {% endif %} {% endwith %} </body> </html>


A partir de este momento, el código debería funcionar cuando se ejecuta el comando flask con la depuración habilitada. Esto le permitirá ver los errores en la línea de comandos y en la ventana del navegador:


 flask --app app.py --debug run


Abra su navegador en el dominio que se muestra en la línea de comandos (localhost) y debería aparecer la página de índice. Intente enviar el formulario utilizando una dirección de correo electrónico válida para recibir el enlace de verificación. Una vez que obtenga el enlace, debería verse como http://localhost:5000/confirm_email/InRvbUByYXRldG91cmd1aWRlcy5jb20i.ZteEvQ.7o1_L0uM9Wl8uii7KhJdiWAH , puede seguirlo y obtener la dirección de correo electrónico validada utilizando la ruta del validador que se muestra aquí:


 @app.route('/confirm_email/<token>') def confirm_email(token): try: email = sserializer.loads(token, salt='email-confirm', max_age=1200) # Token expires after 1 hour except SignatureExpired: return '<h1>Oops, the token expired!</h1>' # Update field in database user = Users.query.filter_by(email=email).first_or_404() user.validated = True try: db.session.commit() except Exception as e: print(f"Error occurred saving to db: {e}") return '<h1>Email address confirmed!</h1>'


Esta ruta acepta la cadena de token que se le envió previamente y la verifica para ver si coincide con la entrada de la base de datos correspondiente. Si es así, actualiza el campo validated a True y usted puede estar tranquilo sabiendo que su formulario de registro no fue abandonado.


Este es un paso importante que todas las empresas exitosas utilizan en sus sistemas de registro y ahora usted también lo tiene. Pero espere, ¿qué pasa si recibimos ataques de bots que envían direcciones de correo electrónico aleatorias sin validarlas? Entonces tendrá una base de datos sucia llena de entradas inútiles. ¡Evitemos eso!


Para evitar ataques de bots o al menos mitigar los más avanzados, puede crear su propia solución, que requiere mucho tiempo y que incluye un limitador de IP que requiere una base de datos en memoria como Redis, o puede utilizar un servicio de terceros como captcha o hCaptcha de Google.


En nuestro tutorial, agregaremos Plan gratuito de hcaptcha Al momento de escribir este artículo, el captcha de Google no es gratuito, mientras que hcaptcha sí lo es. Para que esto funcione en su sitio, debe registrarse con ellos para obtener la clave API del captcha.


Necesitamos nuevos requisitos, así que instálelos:
pip install flask-hcaptcha requests


Se necesitan solicitudes para enviar la dirección de correo electrónico a hcaptcha para su validación. Obtenga la clave e integre el archivo javascript de hcaptcha con su formulario de registro HTML. Agregue el archivo al encabezado de su página HTML y la clave de su sitio a su formulario:


 <head> ... <script src="https://hcaptcha.com/1/api.js" async defer></script> </head> <body> ... <form action="{{ url_for('signup') }}" method="POST"> <input type="email" name="email" placeholder="Enter your email" required> <input type="submit" value="Sign Up"> <div class="h-captcha" data-sitekey="b62gbcc-5cg2-41b2-cd5a-de95dd1eg61h" data-size="compact"></div> </form>


La clave del sitio en este código es un ejemplo; necesitará la suya propia del plan gratuito. Esta clave del sitio valida su formulario e inspecciona al visitante del sitio con una lista completa de robots de spam conocidos por hcaptcha.


A continuación, modifique su archivo app.py para incluir la clave secreta de hcaptcha (no la clave del sitio) en el objeto app.config y publique la respuesta a hcaptcha antes de guardarla en su propia base de datos.


 app.config['HCAPTCHA_SECRET_KEY'] = 'your-secret-hcaptcha-key' ... @app.route("/signup", methods=['GET', 'POST']) def signup(): if request.method == 'POST': email = bleach.clean(request.form.get('email')) hcaptcha_response = request.form.get('h-captcha-response') # Verify hCaptcha response payload = { 'secret': app.config['HCAPTCHA_SECRET_KEY'], 'response': hcaptcha_response } try: response = requests.post('https://hcaptcha.com/siteverify', data=payload, timeout=10) result = response.json() except requests.exceptions.RequestException as e: print(f"Request failed: {e}") if not result.get('success'): flash('CAPTCHA validation failed, please try again.', 'danger') ... # Insert user into the database new_user = Users(email=email)


Una vez hecho esto, el icono hcaptcha aparecerá en el formulario de registro y debería estar habilitado para evitar el spam. Ahora, tienes un formulario más sólido para tu nueva aplicación.


En caso de que encuentres algún error o tengas un error tipográfico en el código, puedes consultar el código completo en mi github.com


Comenta si quieres más.