paint-brush
Evita lo spam via email creando un modulo sicuro in Pythondi@tom2
338 letture
338 letture

Evita lo spam via email creando un modulo sicuro in Python

di Rutkat11m2024/09/04
Read on Terminal Reader

Troppo lungo; Leggere

Un indirizzo email valido è un gateway per stabilire una comunicazione diretta, generare lead, ottenere vendite, inviti privati a community online, ecc. Non dobbiamo affidarci all'uso di servizi di terze parti come Auth0, Facebook o Google per avere accesso alla tua app e ai tuoi servizi. Utilizzeremo moduli Python esistenti che semplificano la pulizia dell'input utente, la generazione di un collegamento di verifica e la comunicazione con il database.
featured image - Evita lo spam via email creando un modulo sicuro in Python
Rutkat HackerNoon profile picture

Un indirizzo email valido è un gateway per stabilire una comunicazione diretta, generare lead, ottenere vendite, inviti privati a community online, ecc. Non darlo per scontato perché i social media stanno cambiando. Grazie alle evoluzioni della tecnologia, l'email è ancora il modo collaudato e vero per connettersi. Manterremo le cose semplici e non scriveremo codice da zero perché Python ha moduli esistenti per aiutarti ad accelerare la codifica.


Alcuni clienti mi hanno chiesto di creare moduli di iscrizione via e-mail per promuovere i loro prodotti, ma nessuno di quei clienti voleva pagare commissioni mensili per un servizio di terze parti già pronto all'uso, quindi ho fatto risparmiare loro denaro creando moduli di contatto personalizzati che possono usare per sempre. Posso aiutarti a fare lo stesso, che sia per la tua startup, per i tuoi clienti, per scopi di marketing o, meglio ancora, per ridurre lo spam.


Questo è per chiunque voglia imparare a programmare in Python ed è particolarmente utile per i principianti che potrebbero non considerare le funzionalità di sicurezza come il filtraggio dell'input utente, la convalida degli indirizzi e-mail e il double opt-in e-mail. In questo tutorial, trattiamo i passaggi 1-3:


  1. Filtraggio dell'input dell'utente per un indirizzo email valido
  2. Registrazione con doppio opt-in
  3. Prevenzione bot/spam


Non dobbiamo affidarci a servizi di terze parti come Auth0, Facebook o Google per accedere alla tua app e ai tuoi servizi che possono chiuderti in qualsiasi momento o condividere i tuoi dati. Tieni i dati della tua app tuoi!


Per iniziare, dovresti avere un po' di esperienza in Python perché useremo il framework Flask con un database MySQL . Sarà più divertente (forse) che usare WordPress, il CMS più popolare. Dovresti pagare per un plugin WordPress per avere le stesse capacità di un'estensione gratuita di Flask. In precedenza ho creato su Wordpress (PHP) e preferisco Python Flask per le app web, anche se Wordpress è molto capace di creare app web.


Utilizzeremo moduli Python esistenti che semplificano la pulizia dell'input dell'utente, la generazione di un collegamento di verifica e la comunicazione con il database.


Ogni frammento di codice verrà spiegato e includerà alcuni commenti nel codice. Nel caso in cui non abbiate creato la registrazione utente o non ne conosceste il funzionamento interno, vi descriverò i dettagli, e poi potrete vedere il codice finale alla fine (non saltate avanti).


Ecco un riepilogo delle funzionalità che implementeremo come indicato nel primo paragrafo:


  1. Un indirizzo email valido può essere verificato analizzando la stringa di input dell'utente tramite un'espressione regolare o un'estensione Flask. Non saranno consentiti hack di tipo testo casuale o iniezione SQL.


  2. Il metodo double opt-in richiede che il destinatario dia il permesso di inviargli un'email ricevendo un link di convalida nella sua casella di posta. Questo viene utilizzato principalmente per impedire a qualcun altro di usare il tuo indirizzo email. Questo impedisce anche agli utenti di prova di registrarsi e abbandonare i propri account.


  3. La prevenzione dei bot può essere effettuata tramite un campo nascosto che non viene mostrato all'utente, ma che viene solitamente compilato automaticamente dai bot che cercano moduli di iscrizione vulnerabili. Tuttavia, non è affidabile quanto un "captcha" di un servizio di terze parti.


Iniziamo a programmare. Crea una directory di lavoro:

 mkdir signup cd signup


Crea il tuo ambiente Python usando python3 -m venv signup o conda create -n double-opt-contact python3 . Io preferisco conda e se vuoi saperne di più, puoi leggere il mio articolo sugli ambienti Python.


Installare le seguenti dipendenze:
pip flask flask-mail secure SQLAlchemy Flask-WTF Flask-SQLAlchemy mysql-connector-python bleach

In alternativa, è possibile avere le stesse dipendenze elencate in un file requirements.txt ed eseguire pip install -r requirements.txt


Crea il file app.py con le seguenti dipendenze incluse:


 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


Inizializzare l'oggetto app con il percorso della cartella modello predefinita:

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


Inserisci i dati di configurazione del tuo server utilizzando queste righe:

 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


Alla fine dovresti avere le informazioni di configurazione in un file .env .


Avremo bisogno di un database MySQL per memorizzare gli utenti, che può essere creato manualmente o tramite codice Python. Come parte del processo di apprendimento, puoi inserire il seguente codice tramite la riga di comando o tramite il metodo Python with app.app_context() db_create_all() .


Il campo convalidato è per una stringa token che consente la tecnica del doppio opt-in.

 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 sezione successiva usa la struttura ORM di SQLAlchemy per interrogare il database per te. Nota che il nome della classe deve corrispondere al nome della tabella del tuo database, altrimenti riceverai un errore. Il db.model rappresenta le impostazioni della tua tabella che includono il nome della colonna, il suo tipo, la lunghezza, la chiave e il valore null:


 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)


Se non hai ancora creato manualmente la tabella del database MySQL, puoi farlo con questo codice Flask direttamente dopo il blocco di codice class User :

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


Ora, inseriamo il codice back-end che è composto da 2 pagine/route (indice, iscrizione), il messaggio e-mail e la conferma. La pagina di iscrizione include i metodi GET/POST che consentono l'invio del modulo. L'oggetto bleach è un'estensione Python che pulisce l'input dell'utente per garantire la sicurezza e mitigare gli script dannosi. Quindi lo sserializer genera un token monouso per inviare tramite e-mail il collegamento di verifica.


 @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')


Prima di aggiungere il modulo di iscrizione HTML, completiamo il backend aggiungendo il percorso per convalidare la funzionalità di double opt-in. Questo percorso utilizza la variabile s creata in precedenza che genera il token segreto sensibile al tempo. Vedere la documentazione per i dettagli .


Max-age indica i secondi prima della scadenza del link, quindi in questo caso l'utente ha 20 minuti per confermare il proprio indirizzo email.


 @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>'


Ora, per l'onnipresente istruzione principale che dice a Python di eseguire lo script se il file viene eseguito direttamente (anziché come un modulo importato):

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


Prima di completare questo codice back-end, abbiamo ancora bisogno dell'HTML front-end per l'input dell'utente. Lo faremo con il modello Jinja integrato di Flask. Crea un file denominato templates/signup.html che dovrebbe corrispondere al nome del percorso creato in precedenza in app.py Per impostazione predefinita, Jinja utilizza la directory /templates per i file html. Puoi modificare questa impostazione, ma per questo tutorial utilizzeremo la directory /templates dell'app.

 <!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>


Il tuo codice dovrebbe funzionare da questo punto quando esegui il comando flask con il debug abilitato. Questo ti consentirà di vedere eventuali errori nella riga di comando e nella finestra del browser:


 flask --app app.py --debug run


Apri il tuo browser sul dominio mostrato nella riga di comando (localhost) e la pagina indice dovrebbe essere renderizzata. Prova a inviare il modulo usando un indirizzo email valido per ricevere il link di verifica. Una volta ottenuto il link, dovrebbe apparire come http://localhost:5000/confirm_email/InRvbUByYXRldG91cmd1aWRlcy5jb20i.ZteEvQ.7o1_L0uM9Wl8uii7KhJdiWAH , puoi seguirlo e ottenere l'indirizzo email convalidato usando il percorso di convalida mostrato qui:


 @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>'


Questa route accetta la stringa token precedentemente inviata e la controlla per vedere se corrisponde alla voce del database corrispondente. In caso affermativo, aggiorna il campo validated a True e puoi stare tranquillo sapendo che il tuo modulo di iscrizione non è stato abbandonato.


Questo è un passaggio importante che tutte le aziende di successo usano nei loro sistemi di registrazione e ora ce l'hai anche tu. Ma aspetta, cosa succederebbe se ricevessimo attacchi di bot che inviano indirizzi email casuali senza convalidarli? Allora avresti un database sporco pieno di voci inutili. Impediamolo!


Per prevenire gli attacchi dei bot o almeno mitigare quelli avanzati, puoi creare una tua soluzione che richiede molto tempo, incluso un limitatore IP che richiede un database in memoria come Redis, oppure puoi utilizzare un servizio di terze parti come captcha o hCaptcha di Google.


Nel nostro tutorial aggiungeremo piano gratuito di hcaptcha . Al momento in cui scrivo, il captcha di Google non è gratuito, mentre hcaptcha lo è. Per farlo funzionare sul tuo sito, devi registrarti con loro per ottenere la chiave API da captcha.


Abbiamo bisogno di nuovi requisiti, quindi installali:
pip install flask-hcaptcha requests


Le richieste sono necessarie per inviare l'indirizzo email a hcaptcha per la convalida. Ottieni la chiave e integra il file javascript di hcaptcha con il tuo modulo di iscrizione HTML. Aggiungi il file all'intestazione della tua pagina HTML e la chiave del tuo sito al tuo modulo:


 <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 chiave del sito in questo codice è un esempio; avrai bisogno della tua dal piano gratuito. Questa chiave del sito convalida il tuo modulo e ispeziona il visitatore del sito con un elenco completo di bot spam noti da hcaptcha.


Successivamente, modifica il file app.py per includere la chiave segreta di hcaptcha (non la chiave del sito) nell'oggetto app.config e pubblica la risposta su hcaptcha prima di salvarla nel tuo database.


 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 volta fatto questo, vedrai l'icona hcaptcha nel tuo modulo di iscrizione, e dovrebbe essere abilitata per prevenire qualsiasi spam. Ora hai un modulo più robusto per la tua nuova app.


Nel caso in cui si riscontrino errori o si verifichi un errore di battitura nel codice, è possibile controllare il codice completato su il mio github.com


Commenta se ne vuoi di più.