Denne vejledning beskriver, hvordan man validerer e-mailadresser under brugerregistrering.
Opdateret 30/04/2015 :Tilføjet Python 3-understøttelse.
Med hensyn til workflow, efter at en bruger har registreret en ny konto, sendes en bekræftelses-e-mail. Brugerkontoen er markeret som "ubekræftet", indtil brugeren, ja, "bekræfter" kontoen via instruktionerne i e-mailen. Dette er en simpel arbejdsgang, som de fleste webapplikationer følger.
En vigtig ting at tage højde for er, hvad ubekræftede brugere må gøre. Med andre ord, har de fuld adgang til din applikation, begrænset/begrænset adgang eller slet ingen adgang? For applikationen i dette selvstudium kan ubekræftede brugere logge ind, men de bliver straks omdirigeret til en side, der minder dem om, at de skal bekræfte deres konto, før de kan få adgang til applikationen.
Inden vi begynder, er det meste af den funktionalitet, som vi vil tilføje, en del af Flask-User og Flask-Security-udvidelserne - hvilket rejser spørgsmålet, hvorfor ikke bare bruge udvidelserne? Nå, først og fremmest er dette en mulighed for at lære. Begge disse udvidelser har også begrænsninger, som de understøttede databaser. Hvad hvis du for eksempel ville bruge RethinkDB?
Lad os begynde.
Klabe grundlæggende registrering
Vi starter med en Flask-kedelplade, der inkluderer grundlæggende brugerregistrering. Få fat i koden fra depotet. Når du har oprettet og aktiveret en virtualenv, skal du køre følgende kommandoer for hurtigt at komme i gang:
$ pip install -r requirements.txt
$ export APP_SETTINGS="project.config.DevelopmentConfig"
$ python manage.py create_db
$ python manage.py db init
$ python manage.py db migrate
$ python manage.py create_admin
$ python manage.py runserver
Se readme for mere information.
Med appen kørende skal du navigere til http://localhost:5000/register og registrere en ny bruger. Bemærk, at efter registrering logger appen dig automatisk på og omdirigerer dig til hovedsiden. Tag et kig rundt, og kør derefter koden igennem - specifikt "bruger"-planen.
Dræb serveren, når du er færdig.
Opdater den aktuelle app
Modeller
Lad os først tilføje den confirmed
til vores User
model i project/models.py :
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String, unique=True, nullable=False)
password = db.Column(db.String, nullable=False)
registered_on = db.Column(db.DateTime, nullable=False)
admin = db.Column(db.Boolean, nullable=False, default=False)
confirmed = db.Column(db.Boolean, nullable=False, default=False)
confirmed_on = db.Column(db.DateTime, nullable=True)
def __init__(self, email, password, confirmed,
paid=False, admin=False, confirmed_on=None):
self.email = email
self.password = bcrypt.generate_password_hash(password)
self.registered_on = datetime.datetime.now()
self.admin = admin
self.confirmed = confirmed
self.confirmed_on = confirmed_on
Bemærk, hvordan dette felt som standard er "False". Vi har også tilføjet en confirmed_on
felt, som er en [datetime
] (https://realpython.com/python-datetime/). Jeg kan godt lide at inkludere dette felt også for at analysere forskellen mellem registered_on
og confirmed_on
datoer ved hjælp af kohorteanalyse.
Lad os helt starte forfra med vores database og migreringer. Så gå videre og slet databasen, dev.sqlite , samt mappen "migreringer".
Administrer kommando
Dernæst i manage.py , opdater create_admin
kommando for at tage de nye databasefelter i betragtning:
@manager.command
def create_admin():
"""Creates the admin user."""
db.session.add(User(
email="[email protected]",
password="admin",
admin=True,
confirmed=True,
confirmed_on=datetime.datetime.now())
)
db.session.commit()
Sørg for at importere datetime
. Gå nu videre og kør følgende kommandoer igen:
$ python manage.py create_db
$ python manage.py db init
$ python manage.py db migrate
$ python manage.py create_admin
register()
visningsfunktion
Til sidst, før vi kan registrere en bruger igen, skal vi foretage en hurtig ændring af register()
visningsfunktion i project/user/views.py …
Skift:
user = User(
email=form.email.data,
password=form.password.data
)
Til:
user = User(
email=form.email.data,
password=form.password.data,
confirmed=False
)
Giver mening? Tænk over, hvorfor vi ønsker at standard confirmed
til False
.
Okay. Kør appen igen. Naviger til http://localhost:5000/register og tilmeld en ny bruger igen. Hvis du åbner din SQLite-database i SQLite-browseren, bør du se:
Så den nye bruger, som jeg registrerede, [email protected]
, er ikke bekræftet. Lad os ændre det.
Tilføj e-mailbekræftelse
Generer bekræftelsestoken
E-mailbekræftelsen skal indeholde en unik URL, som en bruger blot skal klikke på for at bekræfte sin konto. Ideelt set skal URL'en se sådan ud - http://yourapp.com/confirm/<id>
. Nøglen her er id
. Vi skal kode brugerens e-mail (sammen med et tidsstempel) i id
ved at bruge pakken itsdangerous.
Opret en fil kaldet project/token.py og tilføj følgende kode:
# project/token.py
from itsdangerous import URLSafeTimedSerializer
from project import app
def generate_confirmation_token(email):
serializer = URLSafeTimedSerializer(app.config['SECRET_KEY'])
return serializer.dumps(email, salt=app.config['SECURITY_PASSWORD_SALT'])
def confirm_token(token, expiration=3600):
serializer = URLSafeTimedSerializer(app.config['SECRET_KEY'])
try:
email = serializer.loads(
token,
salt=app.config['SECURITY_PASSWORD_SALT'],
max_age=expiration
)
except:
return False
return email
Så i generate_confirmation_token()
funktion bruger vi URLSafeTimedSerializer
at generere et token ved hjælp af den e-mailadresse, der blev opnået under brugerregistreringen. Den faktiske e-mail er kodet i tokenet. Derefter for at bekræfte tokenet i confirm_token()
funktion, kan vi bruge loads()
metode, som tager token og udløb - gyldig i en time (3.600 sekunder) - som argumenter. Så længe tokenet ikke er udløbet, vil det returnere en e-mail.
Sørg for at tilføje SECURITY_PASSWORD_SALT
til din apps konfiguration (BaseConfig()
):
SECURITY_PASSWORD_SALT = 'my_precious_two'
Opdater register()
visningsfunktion
Lad os nu opdatere register()
se funktionen igen fra project/user/views.py :
@user_blueprint.route('/register', methods=['GET', 'POST'])
def register():
form = RegisterForm(request.form)
if form.validate_on_submit():
user = User(
email=form.email.data,
password=form.password.data,
confirmed=False
)
db.session.add(user)
db.session.commit()
token = generate_confirmation_token(user.email)
Sørg også for at opdatere importen:
from project.token import generate_confirmation_token, confirm_token
Håndter e-mailbekræftelse
Lad os derefter tilføje en ny visning til at håndtere e-mailbekræftelsen:
@user_blueprint.route('/confirm/<token>')
@login_required
def confirm_email(token):
try:
email = confirm_token(token)
except:
flash('The confirmation link is invalid or has expired.', 'danger')
user = User.query.filter_by(email=email).first_or_404()
if user.confirmed:
flash('Account already confirmed. Please login.', 'success')
else:
user.confirmed = True
user.confirmed_on = datetime.datetime.now()
db.session.add(user)
db.session.commit()
flash('You have confirmed your account. Thanks!', 'success')
return redirect(url_for('main.home'))
Føj dette til project/user/views.py . Sørg også for at opdatere importen:
import datetime
Her kalder vi confirm_token()
funktion, passerer tokenet. Hvis det lykkes, opdaterer vi brugeren ved at ændre email_confirmed
attribut til True
og indstille datetime
for hvornår konfirmationen fandt sted. Også, hvis brugeren allerede har gennemgået bekræftelsesprocessen - og er bekræftet - så advarer vi brugeren om dette.
Opret e-mailskabelonen
Lad os derefter tilføje en basis-e-mail-skabelon:
<p>Welcome! Thanks for signing up. Please follow this link to activate your account:</p>
<p><a href="{{ confirm_url }}">{{ confirm_url }}</a></p>
<br>
<p>Cheers!</p>
Gem dette som activate.html i "projekt/skabeloner/bruger". Dette tager en enkelt variabel kaldet confirm_url
, som vil blive oprettet i register()
visningsfunktion.
Send e-mail
Lad os skabe en grundlæggende funktion til at sende e-mails med lidt hjælp fra Flask-Mail, som allerede er installeret og opsat i project/__init__.py
.
Opret en fil kaldet email.py :
# project/email.py
from flask.ext.mail import Message
from project import app, mail
def send_email(to, subject, template):
msg = Message(
subject,
recipients=[to],
html=template,
sender=app.config['MAIL_DEFAULT_SENDER']
)
mail.send(msg)
Gem dette i mappen "projekt".
Så vi skal blot videregive en liste over modtagere, et emne og en skabelon. Vi vil behandle e-mail-konfigurationsindstillingerne om lidt.
Opdater register()
se funktion i project/user/views.py (igen!)
@user_blueprint.route('/register', methods=['GET', 'POST'])
def register():
form = RegisterForm(request.form)
if form.validate_on_submit():
user = User(
email=form.email.data,
password=form.password.data,
confirmed=False
)
db.session.add(user)
db.session.commit()
token = generate_confirmation_token(user.email)
confirm_url = url_for('user.confirm_email', token=token, _external=True)
html = render_template('user/activate.html', confirm_url=confirm_url)
subject = "Please confirm your email"
send_email(user.email, subject, html)
login_user(user)
flash('A confirmation email has been sent via email.', 'success')
return redirect(url_for("main.home"))
return render_template('user/register.html', form=form)
Tilføj også følgende import:
from project.email import send_email
Her sætter vi alt sammen. Denne funktion fungerer grundlæggende som en controller (enten direkte eller indirekte) for hele processen:
- Håndter indledende registrering,
- Generer token og bekræftelses-URL,
- Send bekræftelses-e-mail,
- Flash-bekræftelse,
- Log på brugeren, og
- Omdiriger bruger.
Lagde du mærke til _external=True
argument? Dette tilføjer den fulde absolutte URL, der inkluderer værtsnavnet og porten (http://localhost:5000, i vores tilfælde.)
Før vi kan teste dette, skal vi konfigurere vores mailindstillinger.
Start med at opdatere BaseConfig()
i project/config.py :
class BaseConfig(object):
"""Base configuration."""
# main config
SECRET_KEY = 'my_precious'
SECURITY_PASSWORD_SALT = 'my_precious_two'
DEBUG = False
BCRYPT_LOG_ROUNDS = 13
WTF_CSRF_ENABLED = True
DEBUG_TB_ENABLED = False
DEBUG_TB_INTERCEPT_REDIRECTS = False
# mail settings
MAIL_SERVER = 'smtp.googlemail.com'
MAIL_PORT = 465
MAIL_USE_TLS = False
MAIL_USE_SSL = True
# gmail authentication
MAIL_USERNAME = os.environ['APP_MAIL_USERNAME']
MAIL_PASSWORD = os.environ['APP_MAIL_PASSWORD']
# mail accounts
MAIL_DEFAULT_SENDER = '[email protected]'
Tjek den officielle Flask-Mail-dokumentation for mere information.
Hvis du allerede har en GMAIL-konto, kan du bruge den eller registrere en GMAIL-testkonto. Indstil derefter miljøvariablerne midlertidigt i den aktuelle shell-session:
$ export APP_MAIL_USERNAME="foo"
$ export APP_MAIL_PASSWORD="bar"
Hvis din GMAIL-konto har 2-trins godkendelse, blokerer Google forsøget.
Lad os nu teste!
Første test
Tænd appen, og naviger til http://localhost:5000/register. Så tilmeld dig med en e-mailadresse, som du har adgang til. Hvis alt gik godt, skulle du have en e-mail i din indbakke, der ser sådan ud:
Klik på URL'en, og du skal føres til http://localhost:5000/. Sørg for, at brugeren er i databasen, feltet 'bekræftet' er True
, og der er en datetime
knyttet til confirmed_on
felt.
Dejligt!
Håndter tilladelser
Hvis du husker det, besluttede vi i begyndelsen af denne vejledning, at "ubekræftede brugere kan logge ind, men de skal straks omdirigeres til en side - lad os kalde ruten /unconfirmed
- minde brugerne om, at de skal bekræfte deres konto, før de kan få adgang til applikationen.”
Så vi skal-
- Tilføj
/unconfirmed
rute - Tilføj en unconfirmed.html skabelon
- Opdater
register()
visningsfunktion - Opret en dekoratør
- Opdater navigation.html skabelon
Tilføj /unconfirmed
rute
Tilføj følgende rute til project/user/views.py :
@user_blueprint.route('/unconfirmed')
@login_required
def unconfirmed():
if current_user.confirmed:
return redirect('main.home')
flash('Please confirm your account!', 'warning')
return render_template('user/unconfirmed.html')
Du har set lignende kode før, så lad os gå videre.
Tilføj unconfirmed.html skabelon
{% extends "_base.html" %}
{% block content %}
<h1>Welcome!</h1>
<br>
<p>You have not confirmed your account. Please check your inbox (and your spam folder) - you should have received an email with a confirmation link.</p>
<p>Didn't get the email? <a href="/">Resend</a>.</p>
{% endblock %}
Gem dette som unconfirmed.html i "projekt/skabeloner/bruger". Igen, det hele burde være ligetil. For nu har vi lige tilføjet en dummy-URL til at gensende bekræftelses-e-mailen. Vi behandler dette længere nede.
Opdater register()
visningsfunktion
Nu skal du blot ændre:
return redirect(url_for("main.home"))
Til:
return redirect(url_for("user.unconfirmed"))
Så efter bekræftelses-e-mailen er sendt, bliver brugeren nu omdirigeret til /unconfirmed
rute.
Opret en dekoratør
# project/decorators.py
from functools import wraps
from flask import flash, redirect, url_for
from flask.ext.login import current_user
def check_confirmed(func):
@wraps(func)
def decorated_function(*args, **kwargs):
if current_user.confirmed is False:
flash('Please confirm your account!', 'warning')
return redirect(url_for('user.unconfirmed'))
return func(*args, **kwargs)
return decorated_function
Her har vi en grundlæggende funktion til at tjekke om en bruger er ubekræftet. Hvis ubekræftet, omdirigeres brugeren til /unconfirmed
rute. Gem dette som decorators.py i "projekt"-biblioteket.
Dekorer nu profile()
visningsfunktion:
@user_blueprint.route('/profile', methods=['GET', 'POST'])
@login_required
@check_confirmed
def profile():
# ... snip ...
Sørg for at importere dekoratøren:
from project.decorators import check_confirmed
Opdater navigation.html skabelon
Til sidst skal du opdatere følgende del af navigation.html skabelon-
Skift:
<ul class="nav navbar-nav">
{% if current_user.is_authenticated() %}
<li><a href="{{ url_for('user.profile') }}">Profile</a></li>
{% endif %}
</ul>
Til:
<ul class="nav navbar-nav">
{% if current_user.confirmed and current_user.is_authenticated() %}
<li><a href="{{ url_for('user.profile') }}">Profile</a></li>
{% elif current_user.is_authenticated() %}
<li><a href="{{ url_for('user.unconfirmed') }}">Confirm</a></li>
{% endif %}
</ul>
Tid til at teste igen!
Anden test
Tænd appen, og tilmeld dig igen med en e-mailadresse, som du har adgang til. (Du er velkommen til at slette den gamle bruger, som du har registreret før først fra databasen for at bruge igen.) Nu skulle du blive omdirigeret til http://localhost:5000/unconfirmed efter registrering.
Sørg for at teste http://localhost:5000/profile-ruten. Dette skulle omdirigere dig til http://localhost:5000/unconfirmed.
Gå videre og bekræft e-mailen, og du vil have adgang til alle sider. Bom!
Send e-mail igen
Lad os endelig få gensend-linket til at virke. Tilføj følgende visningsfunktion til project/user/views.py :
@user_blueprint.route('/resend')
@login_required
def resend_confirmation():
token = generate_confirmation_token(current_user.email)
confirm_url = url_for('user.confirm_email', token=token, _external=True)
html = render_template('user/activate.html', confirm_url=confirm_url)
subject = "Please confirm your email"
send_email(current_user.email, subject, html)
flash('A new confirmation email has been sent.', 'success')
return redirect(url_for('user.unconfirmed'))
Opdater nu unconfirmed.html skabelon:
{% extends "_base.html" %}
{% block content %}
<h1>Welcome!</h1>
<br>
<p>You have not confirmed your account. Please check your inbox (and your spam folder) - you should have received an email with a confirmation link.</p>
<p>Didn't get the email? <a href="{{ url_for('user.resend_confirmation') }}">Resend</a>.</p>
{% endblock %}
Tredje test
Du kender øvelsen. Denne gang skal du sørge for at sende en ny bekræftelses-e-mail igen og teste linket. Det burde virke.
Til sidst, hvad sker der, hvis du sender dig selv et par bekræftelseslinks? Er hver gyldig? Test det af. Registrer en ny bruger, og send derefter et par nye bekræftelsesmails. Prøv at bekræfte med den første e-mail. Virkede det? Det burde. Er det okay? Tror du, at de andre e-mails skal udløbe, hvis der sendes en ny?
Lav nogle undersøgelser om dette. Og test andre webapplikationer, som du bruger. Hvordan håndterer de sådan adfærd?
Opdater testsuite
I orden. Så det er det for hovedfunktionaliteten. Hvad med at opdatere den nuværende testpakke, da den er gået i stykker.
Kør testene:
$ python manage.py test
Du skulle se følgende fejl:
TypeError: __init__() takes at least 4 arguments (3 given)
For at rette op på dette skal vi bare opdatere setUp()
metode i project/util.py :
def setUp(self):
db.create_all()
user = User(email="[email protected]", password="admin_user", confirmed=False)
db.session.add(user)
db.session.commit()
Kør nu testene igen. Alle burde bestå!
Konklusion
Der er helt klart meget mere, vi kan gøre:
- Rige versus almindelige e-mails – vi burde sende begge dele.
- Nulstil adgangskode-e-mail - Disse skal sendes til brugere, der har glemt deres adgangskoder.
- Brugeradministration – Vi bør tillade brugere at opdatere deres e-mails og adgangskoder, og når en e-mail ændres, skal den bekræftes igen.
- Test – Vi er nødt til at skrive flere tests for at dække de nye funktioner.
Download hele kildekoden fra Github-lageret. Kommenter nedenfor med spørgsmål. Se del 2.
Glædelig ferie!