paint-brush
Izbjegnite neželjenu poštu izgradnjom sigurnog obrasca u Pythonu po@tom2
231 čitanja

Izbjegnite neželjenu poštu izgradnjom sigurnog obrasca u Pythonu

po Rutkat11m2024/09/04
Read on Terminal Reader

Predugo; Čitati

Važeća adresa e-pošte pristupnik je za uspostavljanje izravne komunikacije, stvaranje potencijalnih kupaca, ostvarivanje prodaje, privatne pozivnice za online zajednice itd. Ne moramo se oslanjati na korištenje usluga treće strane kao što su Auth0, Facebook ili Google da bismo imali pristup svoju aplikaciju i usluge. Koristit ćemo postojeće Python module koji pojednostavljuju čišćenje korisničkog unosa, generiranje veze za provjeru i komunikaciju s bazom podataka.
featured image - Izbjegnite neželjenu poštu izgradnjom sigurnog obrasca u Pythonu
Rutkat HackerNoon profile picture

Valjana adresa e-pošte je pristupnik za uspostavljanje izravne komunikacije, generiranje potencijalnih kupaca, ostvarivanje prodaje, privatne pozivnice za online zajednice itd. Nemojte je uzimati zdravo za gotovo jer se društveni mediji mijenjaju. Kroz evoluciju tehnologije, e-pošta je još uvijek provjeren i pravi način povezivanja. Držat ćemo stvari jednostavnima i nećemo kodirati ispočetka jer Python ima postojeće module koji vam pomažu da ubrzate kodiranje.


Imali su me klijenti koji su tražili da napravim obrasce za prijavu e-poštom kako bi promovirali njihove proizvode, ali nitko od tih kupaca nije želio plaćati mjesečne naknade za gotovu uslugu treće strane, pa sam im uštedio novac izradom prilagođenih obrazaca za kontakt koje mogu koristiti zauvijek. Mogu vam pomoći učiniti isto bilo da se radi o vašem startupu, klijentu, u marketinške svrhe ili najbolje od svega, za ublažavanje neželjene pošte.


Ovo je za svakoga tko želi naučiti kodirati u Pythonu i posebno je korisno za početnike koji možda ne uzimaju u obzir sigurnosne značajke kao što su filtriranje korisničkog unosa, provjera valjanosti adresa e-pošte i dvostruko uključivanje e-pošte. U ovom vodiču pokrivamo korake 1-3:


  1. Filtriranje korisničkog unosa za valjanu adresu e-pošte
  2. Dvostruka prijava
  3. Prevencija botova/spama


Ne moramo se oslanjati na korištenje usluga treće strane kao što su Auth0, Facebook ili Google da bismo imali pristup vašoj aplikaciji i uslugama koje vas mogu ugasiti u bilo kojem trenutku ili podijeliti vaše podatke. Držite podatke svoje aplikacije svojima!


Za početak, trebali biste imati iskustva s Pythonom jer ćemo koristiti okvir Flask s MySQL bazom podataka . Ovo će biti zabavnije (možda) od korištenja WordPressa, najpopularnijeg CMS-a. Morali biste platiti za neki dodatak za WordPress da bi imao iste mogućnosti kao besplatno proširenje Flaska. Prethodno sam gradio na Wordpressu (PHP) i više volim Python Flask za web aplikacije iako je Wordpress vrlo sposoban za izradu web aplikacija.


Koristit ćemo postojeće Python module koji pojednostavljuju čišćenje korisničkog unosa, generiranje veze za provjeru i komunikaciju s bazom podataka.


Svaki isječak koda bit će objašnjen i uključivat će neke komentare u kodu. U slučaju da niste izradili korisničku registraciju ili ne poznajete unutarnje funkcioniranje, opisat ću vam detalje, a zatim možete vidjeti konačni kod na kraju (nemojte preskakati unaprijed).


Ovdje je sažetak značajki koje ćemo implementirati kako je navedeno u prvom odlomku:


  1. Valjana adresa e-pošte može se provjeriti raščlanjivanjem korisničkog niza unosa pomoću regularnog izraza ili proširenja Flask. Nećemo dopustiti hakiranje nasumičnim tekstom ili SQL injekcijom.


  2. Metoda dvostrukog uključivanja zahtijeva od primatelja dopuštenje da mu pošaljete e-poštu primanjem veze za potvrdu u njegovu pristiglu poštu. Ovo se uglavnom koristi kako bi se spriječilo da netko drugi koristi vašu adresu e-pošte. Ovo također sprječava testne korisnike koji se samo prijave i napuste svoje račune.


  3. Prevencija botova može se izvršiti sa skrivenim poljem koje se ne prikazuje korisniku, ali ga botovi obično automatski popunjavaju tražeći ranjive obrasce za prijavu, ali nije tako pouzdano kao "captcha" usluge treće strane.


Počnimo s kodiranjem. Napravite radni imenik:

 mkdir signup cd signup


Stvorite svoje Python okruženje pomoću python3 -m venv signup ili conda create -n double-opt-contact python3 . Više volim conda, a ako želite saznati više, možete pročitati moj članak Python okruženja.


Instalirajte sljedeće ovisnosti:
pip flask flask-mail secure SQLAlchemy Flask-WTF Flask-SQLAlchemy mysql-connector-python bleach

Alternativno, možete imati iste ovisnosti navedene u datoteci requirements.txt i pokrenuti pip install -r requirements.txt


Stvorite datoteku app.py sa sljedećim ovisnostima:


 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


Inicijalizirajte objekt aplikacije sa zadanom lokacijom mape predloška:

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


Unesite vlastite podatke o konfiguraciji poslužitelja pomoću ovih redaka:

 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


U konačnici, trebali biste imati svoje podatke o konfiguraciji u .env datoteci.


Trebat će nam MySQL baza podataka za pohranu korisnika koja se može kreirati ručno ili pomoću Python koda. Kao dio procesa učenja, možete unijeti sljedeći kod pomoću naredbenog retka ili pomoću metode Python with app.app_context() db_create_all() .


Potvrđeno polje je za niz tokena koji dopušta tehniku dvostrukog uključivanja.

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


Sljedeći odjeljak koristi SQLAlchemyjevu ORM strukturu za postavljanje upita bazi podataka umjesto vas. Imajte na umu da naziv klase treba odgovarati nazivu vaše tablice baze podataka, inače ćete dobiti pogrešku. db.model predstavlja vaše postavke tablice koje uključuju naziv stupca, njegovu vrstu, duljinu, ključ i nultu vrijednost:


 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)


Ako već niste ručno stvorili MySQL tablicu baze podataka, možete to učiniti s ovim Flask kodom neposredno nakon bloka class User :

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


Sada unosimo pozadinski kod koji je 2 stranice/rute (indeks, prijava), poruka e-pošte i potvrda. Stranica za prijavu uključuje metode GET/POST koje omogućuju slanje obrasca. Objekt bleach proširenje je Pythona koje čisti unos od korisnika kako bi se osigurala sigurnost i ublažile zlonamjerne skripte. Zatim sserializer generira jednokratni token za slanje veze za potvrdu e-poštom.


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


Prije dodavanja HTML obrasca za prijavu, dovršimo pozadinu dodavanjem rute za provjeru valjanosti značajke dvostrukog uključivanja. Ova ruta koristi varijablu s smo ranije stvorili koja generira vremenski osjetljivi tajni token. Vidjeti dokumenti za detalje .


Maksimalna starost je sekunde prije isteka veze, tako da u ovom slučaju korisnik ima 20 minuta da potvrdi svoju adresu e-pošte.


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


Sada, za sveprisutnu glavnu izjavu koja govori Pythonu da izvrši skriptu ako se datoteka izvodi izravno (za razliku od uvezenog modula):

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


Prije nego što dovršimo ovaj pozadinski kod, još uvijek trebamo prednji HTML za korisnički unos. To ćemo učiniti s ugrađenim Jinja predloškom Flaska. Napravite datoteku pod nazivom templates/signup.html koja bi trebala po nazivu odgovarati ruti koju ste prethodno stvorili u app.py Prema zadanim postavkama, Jinja koristi direktorij /templates za html datoteke. Možete promijeniti ovu postavku, ali za ovaj vodič koristit ćemo /templates direktorij aplikacije.

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


Vaš kod bi trebao raditi od ove točke kada pokrenete naredbu flask s omogućenim otklanjanjem pogrešaka. To će vam omogućiti da vidite sve pogreške u naredbenom retku, kao iu prozoru preglednika:


 flask --app app.py --debug run


Otvorite svoj preglednik na domeni prikazanoj u naredbenom retku (localhost) i trebala bi se prikazati indeksna stranica. Pokušajte poslati obrazac koristeći valjanu adresu e-pošte kako biste primili vezu za potvrdu. Nakon što dobijete vezu, trebala bi izgledati ovako http://localhost:5000/confirm_email/InRvbUByYXRldG91cmd1aWRlcy5jb20i.ZteEvQ.7o1_L0uM9Wl8uii7KhJdiWAH , možete je pratiti i potvrditi adresu e-pošte pomoću rute validatora prikazane ovdje:


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


Ova ruta prihvaća niz tokena koji vam je prethodno poslan i provjerava ga da vidi odgovara li odgovarajućem unosu baze podataka. Ako se dogodi, ažurira validated polje na True i možete biti mirni znajući da vaš obrazac za prijavu nije napušten.


Ovo je važan korak koji sve uspješne tvrtke koriste u svojim sustavima registracije, a sada ga imate i vi. Ali čekajte, što ako dobijemo napade robota koji šalju nasumične adrese e-pošte bez provjere? Tada ćete imati prljavu bazu podataka ispunjenu beskorisnim unosima. Spriječimo to!


Kako biste spriječili napade robota ili barem ublažili one napredne, možete izgraditi vlastito dugotrajno rješenje, uključujući IP limiter koji zahtijeva bazu podataka u memoriji kao što je Redis, ili možete koristiti uslugu treće strane kao što je Googleov captcha ili hCaptcha.


U našem vodiču ćemo dodati hcaptchin besplatni plan . U vrijeme pisanja ovog teksta, googleova captcha nije besplatna, a hcaptcha jest. Da bi ovo funkcioniralo za vašu stranicu, morate se registrirati kod njih kako biste dobili API ključ iz captcha.


Trebamo nove zahtjeve pa ih instalirajte:
pip install flask-hcaptcha requests


Zahtjevi su potrebni za slanje adrese e-pošte na hcaptcha radi provjere. Nabavite ključ i integrirajte hcaptchinu javascript datoteku sa svojim HTML obrascem za prijavu. Dodajte datoteku u zaglavlje svoje HTML stranice i ključ web-lokacije u obrazac:


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


Ključ web mjesta u ovom kodu je primjer; trebat će vam vlastiti iz besplatnog plana. Ovaj ključ web-mjesta potvrđuje vaš obrazac i pregledava posjetitelja web-mjesta s opsežnim popisom neželjenih robota poznatih hcaptcha.


Zatim izmijenite svoju datoteku app.py tako da uključi tajni ključ hcaptcha (ne ključ web-mjesta) u objektu app.config i objavite odgovor na hcaptcha prije nego što ga spremite u vlastitu bazu podataka.


 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)


Nakon što to učinite, imat ćete ikonu hcaptcha koja se prikazuje u vašem obrascu za prijavu i trebala bi biti omogućena kako biste spriječili neželjenu poštu. Sada imate robusniji oblik za svoju novu aplikaciju.


U slučaju da naiđete na pogreške ili imate tipfeler u kodu, možete provjeriti dovršeni kod na moj github.com


Komentirajte ako želite više.