Ce didacticiel explique comment valider les adresses e-mail lors de l'inscription de l'utilisateur.
Mis à jour le 30/04/2015 :Ajout de la prise en charge de Python 3.
En termes de flux de travail, après qu'un utilisateur enregistre un nouveau compte, un e-mail de confirmation est envoyé. Le compte d'utilisateur est marqué comme "non confirmé" jusqu'à ce que l'utilisateur "confirme" le compte via les instructions de l'e-mail. Il s'agit d'un flux de travail simple suivi par la plupart des applications Web.
Une chose importante à prendre en compte est ce que les utilisateurs non confirmés sont autorisés à faire. En d'autres termes, ont-ils un accès complet à votre application, un accès limité/restreint ou aucun accès ? Pour l'application de ce tutoriel, les utilisateurs non confirmés peuvent se connecter mais ils sont immédiatement redirigés vers une page leur rappelant qu'ils doivent confirmer leur compte avant de pouvoir accéder à l'application.
Avant de commencer, la plupart des fonctionnalités que nous ajouterons font partie des extensions Flask-User et Flask-Security - ce qui pose la question pourquoi ne pas simplement utiliser les extensions ? Eh bien, c'est avant tout une occasion d'apprendre. De plus, ces deux extensions ont des limitations, comme les bases de données prises en charge. Et si vous vouliez utiliser RethinkDB, par exemple ?
Commençons.
Enregistrement de base du flacon
Nous allons commencer avec un passe-partout Flask qui inclut l'enregistrement utilisateur de base. Récupérez le code du référentiel. Une fois que vous avez créé et activé un environnement virtuel, exécutez les commandes suivantes pour démarrer rapidement :
$ 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
Consultez le fichier Lisez-moi pour plus d'informations.
Avec l'application en cours d'exécution, accédez à http://localhost:5000/register et enregistrez un nouvel utilisateur. Notez qu'après l'inscription, l'application vous connecte automatiquement et vous redirige vers la page principale. Jetez un coup d'œil, puis parcourez le code - en particulier le plan "utilisateur".
Tuez le serveur lorsque vous avez terminé.
Mettre à jour l'application actuelle
Modèles
Ajoutons d'abord le confirmed
champ à notre User
modèle dans 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
Remarquez que ce champ est par défaut sur "Faux". Nous avons également ajouté un confirmed_on
champ, qui est un [datetime
] (https://realpython.com/python-datetime/). J'aime également inclure ce champ afin d'analyser la différence entre le registered_on
et confirmed_on
dates à l'aide d'une analyse de cohorte.
Recommençons complètement avec notre base de données et nos migrations. Alors, allez-y et supprimez la base de données, dev.sqlite , ainsi que le dossier "migrations".
Gérer la commande
Ensuite, dans manage.py , mettez à jour le create_admin
commande pour prendre en compte les nouveaux champs de la base de données :
@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()
Assurez-vous d'importer datetime
. Maintenant, continuez et exécutez à nouveau les commandes suivantes :
$ python manage.py create_db
$ python manage.py db init
$ python manage.py db migrate
$ python manage.py create_admin
register()
fonction d'affichage
Enfin, avant de pouvoir enregistrer à nouveau un utilisateur, nous devons apporter une modification rapide au register()
fonction d'affichage dans project/user/views.py …
Modifier :
user = User(
email=form.email.data,
password=form.password.data
)
À :
user = User(
email=form.email.data,
password=form.password.data,
confirmed=False
)
Avoir du sens ? Réfléchissez à la raison pour laquelle nous voudrions par défaut confirmed
à False
.
D'accord. Exécutez à nouveau l'application. Accédez à http://localhost:5000/register et enregistrez à nouveau un nouvel utilisateur. Si vous ouvrez votre base de données SQLite dans le navigateur SQLite, vous devriez voir :
Donc, le nouvel utilisateur que j'ai enregistré, [email protected]
, n'est pas confirmée. Changeons cela.
Ajouter une confirmation par e-mail
Générer un jeton de confirmation
L'e-mail de confirmation doit contenir une URL unique sur laquelle un utilisateur doit simplement cliquer pour confirmer son compte. Idéalement, l'URL devrait ressembler à ceci - http://yourapp.com/confirm/<id>
. La clé ici est le id
. Nous allons encoder l'email de l'utilisateur (avec un horodatage) dans le id
en utilisant le package itsdangerous.
Créez un fichier appelé project/token.py et ajoutez le code suivant :
# 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
Ainsi, dans le generate_confirmation_token()
fonction nous utilisons le URLSafeTimedSerializer
pour générer un jeton à l'aide de l'adresse e-mail obtenue lors de l'inscription de l'utilisateur. Le réel l'e-mail est encodé dans le jeton. Puis pour confirmer le jeton, dans le confirm_token()
fonction, nous pouvons utiliser la fonction loads()
, qui prend le jeton et l'expiration - valable une heure (3 600 secondes) - comme arguments. Tant que le jeton n'a pas expiré, il renverra un e-mail.
Assurez-vous d'ajouter le SECURITY_PASSWORD_SALT
à la configuration de votre application (BaseConfig()
):
SECURITY_PASSWORD_SALT = 'my_precious_two'
Mettre à jour register()
fonction d'affichage
Maintenant, mettons à jour le register()
afficher à nouveau la fonction depuis 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)
Assurez-vous également de mettre à jour les importations :
from project.token import generate_confirmation_token, confirm_token
Gérer la confirmation par e-mail
Ensuite, ajoutons une nouvelle vue pour gérer l'e-mail de confirmation :
@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'))
Ajoutez ceci à project/user/views.py . Assurez-vous également de mettre à jour les importations :
import datetime
Ici, nous appelons le confirm_token()
fonction, en passant le jeton. En cas de succès, nous mettons à jour l'utilisateur, en modifiant le email_confirmed
attribut à True
et définir le datetime
pour le moment où la confirmation a eu lieu. De plus, dans le cas où l'utilisateur a déjà suivi le processus de confirmation - et est confirmé - nous en alertons l'utilisateur.
Créer le modèle d'e-mail
Ensuite, ajoutons un modèle d'e-mail de base :
<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>
Enregistrez-le sous activate.html dans "projet/modèles/utilisateur". Cela prend une seule variable appelée confirm_url
, qui sera créé dans le register()
fonction d'affichage.
Envoyer un e-mail
Créons une fonction de base pour envoyer des e-mails avec un peu d'aide de Flask-Mail, qui est déjà installé et configuré dans project/__init__.py
.
Créez un fichier appelé 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)
Enregistrez-le dans le dossier "projet".
Donc, nous avons simplement besoin de passer une liste de destinataires, un sujet et un modèle. Nous traiterons des paramètres de configuration de la messagerie dans un instant.
Mettre à jour register()
fonction d'affichage dans project/user/views.py (encore !)
@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)
Ajoutez également l'importation suivante :
from project.email import send_email
Ici, nous mettons tout en place. Cette fonction agit essentiellement comme un contrôleur (directement ou indirectement) pour l'ensemble du processus :
- Gérer l'inscription initiale,
- Générer un jeton et une URL de confirmation,
- Envoyer un e-mail de confirmation,
- Confirmation Flash,
- Connectez l'utilisateur, et
- Redirection de l'utilisateur.
Avez-vous remarqué le _external=True
argument? Cela ajoute l'URL absolue complète qui inclut le nom d'hôte et le port (http://localhost:5000, dans notre cas.)
Avant de pouvoir tester cela, nous devons configurer nos paramètres de messagerie.
Commencez par mettre à jour le BaseConfig()
dans 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]'
Consultez la documentation officielle de Flask-Mail pour plus d'informations.
Si vous avez déjà un compte GMAIL, vous pouvez l'utiliser ou enregistrer un compte GMAIL de test. Définissez ensuite temporairement les variables d'environnement dans la session shell en cours :
$ export APP_MAIL_USERNAME="foo"
$ export APP_MAIL_PASSWORD="bar"
Si votre compte GMAIL dispose d'une authentification en deux étapes, Google bloquera la tentative.
Testons maintenant !
Premier essai
Lancez l'application et accédez à http://localhost:5000/register. Inscrivez-vous ensuite avec une adresse e-mail à laquelle vous avez accès. Si tout s'est bien passé, vous devriez avoir un e-mail dans votre boîte de réception qui ressemble à ceci :
Cliquez sur l'URL et vous devriez être redirigé vers http://localhost:5000/. Assurez-vous que l'utilisateur est dans la base de données, le champ "confirmé" est True
, et il y a un datetime
associé au confirmed_on
champ.
Génial !
Gérer les autorisations
Si vous vous souvenez, au début de ce tutoriel, nous avons décidé que "les utilisateurs non confirmés peuvent se connecter mais ils doivent être immédiatement redirigés vers une page - appelons la route /unconfirmed
- rappelant aux utilisateurs qu'ils doivent confirmer leur compte avant de pouvoir accéder à l'application ».
Donc, nous devons-
- Ajouter le
/unconfirmed
itinéraire - Ajouter un unconfirmed.html modèle
- Mettre à jour le
register()
fonction d'affichage - Créer un décorateur
- Mettre à jour navigation.html modèle
Ajouter /unconfirmed
itinéraire
Ajoutez la route suivante à 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')
Vous avez déjà vu un code similaire, alors passons à autre chose.
Ajouter non confirmé.html modèle
{% 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 %}
Enregistrez-le sous unconfirmed.html dans "projet/modèles/utilisateur". Encore une fois, tout cela devrait être simple. Pour l'instant, nous avons simplement ajouté une URL factice pour renvoyer l'e-mail de confirmation. Nous y reviendrons plus loin.
Mettre à jour le register()
fonction d'affichage
Maintenant, changez simplement :
return redirect(url_for("main.home"))
À :
return redirect(url_for("user.unconfirmed"))
Ainsi, après l'envoi de l'e-mail de confirmation, l'utilisateur est maintenant redirigé vers le /unconfirmed
itinéraire.
Créer un décorateur
# 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
Ici, nous avons une fonction de base pour vérifier si un utilisateur n'est pas confirmé. Si non confirmé, l'utilisateur est redirigé vers le /unconfirmed
itinéraire. Enregistrez-le sous decorators.py dans le répertoire "projet".
Maintenant, décorez le profile()
fonction d'affichage :
@user_blueprint.route('/profile', methods=['GET', 'POST'])
@login_required
@check_confirmed
def profile():
# ... snip ...
Assurez-vous d'importer le décorateur :
from project.decorators import check_confirmed
Mettre à jour navigation.html modèle
Enfin, mettez à jour la partie suivante du navigation.html modèle-
Modifier :
<ul class="nav navbar-nav">
{% if current_user.is_authenticated() %}
<li><a href="{{ url_for('user.profile') }}">Profile</a></li>
{% endif %}
</ul>
À :
<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>
Il est temps de tester à nouveau !
Deuxième essai
Lancez l'application et enregistrez-vous à nouveau avec une adresse e-mail à laquelle vous avez accès. (N'hésitez pas à supprimer l'ancien utilisateur que vous avez enregistré auparavant de la base de données pour l'utiliser à nouveau.) Vous devriez maintenant être redirigé vers http://localhost:5000/unconfirmed après l'enregistrement.
Assurez-vous de tester la route http://localhost:5000/profile. Cela devrait vous rediriger vers http://localhost:5000/unconfirmed.
Allez-y et confirmez l'e-mail, et vous aurez accès à toutes les pages. Boum !
Renvoyer l'e-mail
Enfin, faisons fonctionner le lien de renvoi. Ajoutez la fonction d'affichage suivante à 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'))
Maintenant, mettez à jour le unconfirmed.html modèle :
{% 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 %}
Troisième essai
Vous connaissez le refrain. Cette fois, assurez-vous de renvoyer un nouvel e-mail de confirmation et de tester le lien. Cela devrait fonctionner.
Enfin, que se passe-t-il si vous vous envoyez quelques liens de confirmation ? Sont-ils tous valides ? Testez-le. Enregistrez un nouvel utilisateur, puis envoyez quelques nouveaux e-mails de confirmation. Essayez de confirmer avec le premier e-mail. Cela a-t-il fonctionné ? Cela devrait. Est-ce correct? Pensez-vous que ces autres e-mails doivent expirer si un nouveau est envoyé ?
Faites des recherches à ce sujet. Et testez d'autres applications Web que vous utilisez. Comment gèrent-ils un tel comportement ?
Mettre à jour la suite de tests
Très bien. Voilà pour la fonctionnalité principale. Que diriez-vous de mettre à jour la suite de tests actuelle puisqu'elle est, eh bien, cassée.
Exécutez les tests :
$ python manage.py test
Vous devriez voir l'erreur suivante :
TypeError: __init__() takes at least 4 arguments (3 given)
Pour corriger cela, nous avons juste besoin de mettre à jour le setUp()
méthode dans 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()
Maintenant, relancez les tests. Tout devrait passer !
Conclusion
Il y a clairement beaucoup plus que nous pouvons faire :
- E-mails enrichis ou en texte brut :nous devrions envoyer les deux.
- E-mail de réinitialisation du mot de passe - Ceux-ci doivent être envoyés aux utilisateurs qui ont oublié leur mot de passe.
- Gestion des utilisateurs :nous devons autoriser les utilisateurs à mettre à jour leurs e-mails et leurs mots de passe, et lorsqu'un e-mail est modifié, il doit être confirmé à nouveau.
- Tests :nous devons écrire davantage de tests pour couvrir les nouvelles fonctionnalités.
Téléchargez l'intégralité du code source depuis le référentiel Github. Commentez ci-dessous avec des questions. Découvrez la partie 2.
Joyeuses fêtes !