Database
 sql >> Datenbank >  >> RDS >> Database

Handhabung der E-Mail-Bestätigung während der Registrierung in Flask

Diese Anleitung beschreibt, wie Sie E-Mail-Adressen während der Benutzerregistrierung validieren.

Aktualisiert am 30.04.2015 :Unterstützung für Python 3 hinzugefügt.

In Bezug auf den Workflow wird, nachdem ein Benutzer ein neues Konto registriert hat, eine Bestätigungs-E-Mail gesendet. Das Benutzerkonto wird als „unbestätigt“ markiert, bis der Benutzer das Konto über die Anweisungen in der E-Mail „bestätigt“. Dies ist ein einfacher Arbeitsablauf, dem die meisten Webanwendungen folgen.

Eine wichtige Sache, die berücksichtigt werden muss, ist, was unbestätigte Benutzer tun dürfen. Mit anderen Worten, haben sie vollen Zugriff auf Ihre Anwendung, begrenzten/eingeschränkten Zugriff oder überhaupt keinen Zugriff? Bei der Anwendung in diesem Tutorial können sich unbestätigte Benutzer anmelden, werden jedoch sofort auf eine Seite umgeleitet, die sie daran erinnert, dass sie ihr Konto bestätigen müssen, bevor sie auf die Anwendung zugreifen können.

Bevor wir beginnen, sind die meisten Funktionen, die wir hinzufügen werden, Teil der Flask-User- und Flask-Security-Erweiterungen – was die Frage aufwirft, warum nicht einfach die Erweiterungen verwenden? Nun, in erster Linie ist dies eine Gelegenheit zum Lernen. Außerdem haben beide Erweiterungen Einschränkungen, wie die unterstützten Datenbanken. Was wäre, wenn Sie beispielsweise RethinkDB verwenden möchten?

Fangen wir an.


Flask Basisregistrierung

Wir beginnen mit einem Flask-Boilerplate, das eine grundlegende Benutzerregistrierung enthält. Holen Sie sich den Code aus dem Repository. Nachdem Sie eine virtuelle Umgebung erstellt und aktiviert haben, führen Sie die folgenden Befehle aus, um schnell loszulegen:

$ 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

Weitere Informationen finden Sie in der Readme-Datei.

Navigieren Sie bei laufender App zu http://localhost:5000/register und registrieren Sie einen neuen Benutzer. Beachten Sie, dass die App Sie nach der Registrierung automatisch anmeldet und Sie zur Hauptseite weiterleitet. Sehen Sie sich um und führen Sie dann den Code durch – insbesondere den „Benutzer“-Blueprint.

Beenden Sie den Server, wenn Sie fertig sind.



Aktuelle App aktualisieren


Modelle

Lassen Sie uns zuerst den confirmed hinzufügen Feld an unseren User Modell in 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

Beachten Sie, dass dieses Feld standardmäßig auf „False“ gesetzt ist. Wir haben auch einen confirmed_on hinzugefügt Feld, das ein [datetime ist ] (https://realpython.com/python-datetime/). Ich füge dieses Feld gerne auch ein, um den Unterschied zwischen registered_on zu analysieren und confirmed_on Daten mithilfe der Kohortenanalyse.

Beginnen wir mit unserer Datenbank und den Migrationen komplett neu. Löschen Sie also die Datenbank dev.sqlite , sowie den Ordner „migrations“.



Befehl verwalten

Als nächstes innerhalb von manage.py , aktualisieren Sie create_admin Befehl, um die neuen Datenbankfelder zu berücksichtigen:

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

Achten Sie darauf, datetime zu importieren . Fahren Sie jetzt fort und führen Sie die folgenden Befehle erneut aus:

$ python manage.py create_db
$ python manage.py db init
$ python manage.py db migrate
$ python manage.py create_admin


register() Ansichtsfunktion

Bevor wir einen Benutzer erneut registrieren können, müssen wir schließlich eine schnelle Änderung an register() vornehmen Ansichtsfunktion in project/user/views.py

Änderung:

user = User(
    email=form.email.data,
    password=form.password.data
)

An:

user = User(
    email=form.email.data,
    password=form.password.data,
    confirmed=False
)

Sinn ergeben? Denken Sie darüber nach, warum wir standardmäßig confirmed verwenden möchten zu False .

Okay. Führen Sie die App erneut aus. Navigieren Sie zu http://localhost:5000/register und registrieren Sie erneut einen neuen Benutzer. Wenn Sie Ihre SQLite-Datenbank im SQLite-Browser öffnen, sollten Sie Folgendes sehen:

Also, der neue Benutzer, den ich registriert habe, [email protected] , wird nicht bestätigt. Lass uns das ändern.




E-Mail-Bestätigung hinzufügen


Bestätigungstoken generieren

Die E-Mail-Bestätigung sollte eine eindeutige URL enthalten, auf die ein Benutzer einfach klicken muss, um sein Konto zu bestätigen. Idealerweise sollte die URL ungefähr so ​​aussehen:http://yourapp.com/confirm/<id> . Der Schlüssel hier ist die id . Wir werden die Benutzer-E-Mail (zusammen mit einem Zeitstempel) in der id codieren mit dem itsdangerous-Paket.

Erstellen Sie eine Datei namens project/token.py und fügen Sie den folgenden Code hinzu:

# 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

Also im generate_confirmation_token() Funktion verwenden wir den URLSafeTimedSerializer zur Generierung eines Tokens unter Verwendung der E-Mail-Adresse, die bei der Benutzerregistrierung erhalten wurde. Das eigentliche E-Mail ist im Token verschlüsselt. Bestätigen Sie dann das Token innerhalb von confirm_token() Funktion können wir die loads() verwenden -Methode, die das Token und das Ablaufdatum – gültig für eine Stunde (3.600 Sekunden) – als Argumente akzeptiert. Solange das Token nicht abgelaufen ist, wird eine E-Mail zurückgegeben.

Achten Sie darauf, SECURITY_PASSWORD_SALT hinzuzufügen in die Konfiguration Ihrer App (BaseConfig() ):

SECURITY_PASSWORD_SALT = 'my_precious_two'


Aktualisiere register() Ansichtsfunktion

Jetzt aktualisieren wir das register() View-Funktion erneut aus 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)

Stellen Sie außerdem sicher, dass Sie die Importe aktualisieren:

from project.token import generate_confirmation_token, confirm_token


E-Mail-Bestätigung bearbeiten

Als Nächstes fügen wir eine neue Ansicht hinzu, um die E-Mail-Bestätigung zu verarbeiten:

@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ügen Sie dies zu project/user/views.py hinzu . Achten Sie auch darauf, die Importe zu aktualisieren:

import datetime

Hier rufen wir confirm_token() auf Funktion, wobei das Token übergeben wird. Bei Erfolg aktualisieren wir den Benutzer und ändern den email_confirmed Attribut auf True und Einstellen von datetime für wann die Konfirmation stattfand. Auch für den Fall, dass der Benutzer den Bestätigungsprozess bereits durchlaufen hat – und bestätigt wird – benachrichtigen wir den Benutzer darüber.



E-Mail-Vorlage erstellen

Als Nächstes fügen wir eine Basis-E-Mail-Vorlage hinzu:

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

Speichern Sie diese als activate.html in „Projekt/Vorlagen/Benutzer“. Dies nimmt eine einzelne Variable namens confirm_url , die im register() erstellt wird Ansichtsfunktion.



E-Mail senden

Lassen Sie uns eine grundlegende Funktion zum Versenden von E-Mails mit ein wenig Hilfe von Flask-Mail erstellen, das bereits in project/__init__.py installiert und eingerichtet ist .

Erstellen Sie eine Datei namens 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)

Speichern Sie diese im Ordner „Projekt“.

Wir müssen also lediglich eine Empfängerliste, einen Betreff und eine Vorlage übergeben. Wir werden uns gleich mit den Mail-Konfigurationseinstellungen befassen.



Aktualisiere register() Ansichtsfunktion in project/user/views.py (wieder!)

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

Fügen Sie auch den folgenden Import hinzu:

from project.email import send_email

Hier fügen wir alles zusammen. Diese Funktion fungiert im Wesentlichen als Controller (entweder direkt oder indirekt) für den gesamten Prozess:

  • Bearbeitung der Erstregistrierung
  • Token und Bestätigungs-URL generieren
  • Bestätigungs-E-Mail senden,
  • Flash-Bestätigung,
  • Benutzer anmelden und
  • Benutzer umleiten.

Haben Sie _external=True bemerkt Streit? Dadurch wird die vollständige absolute URL hinzugefügt, die den Hostnamen und den Port enthält (in unserem Fall http://localhost:5000).

Bevor wir dies testen können, müssen wir unsere E-Mail-Einstellungen einrichten.



Mail

Beginnen Sie mit der Aktualisierung der BaseConfig() in 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]'

Weitere Informationen finden Sie in der offiziellen Flask-Mail-Dokumentation.

Wenn Sie bereits ein GMAIL-Konto haben, können Sie dieses verwenden oder ein GMAIL-Testkonto registrieren. Setzen Sie dann die Umgebungsvariablen temporär in der aktuellen Shell-Sitzung:

$ export APP_MAIL_USERNAME="foo"
$ export APP_MAIL_PASSWORD="bar"

Wenn Ihr GMAIL-Konto über eine zweistufige Authentifizierung verfügt, blockiert Google den Versuch.

Jetzt testen!




Erster Test

Starten Sie die App und navigieren Sie zu http://localhost:5000/register. Registrieren Sie sich dann mit einer E-Mail-Adresse, auf die Sie Zugriff haben. Wenn alles gut gegangen ist, sollten Sie eine E-Mail in Ihrem Posteingang haben, die etwa so aussieht:

Klicken Sie auf die URL und Sie sollten zu http://localhost:5000/ weitergeleitet werden. Stellen Sie sicher, dass sich der Benutzer in der Datenbank befindet, das Feld „bestätigt“ ist True , und es gibt ein datetime verbunden mit confirmed_on Feld.

Schön!



Berechtigungen verwalten

Wenn Sie sich erinnern, haben wir zu Beginn dieses Tutorials entschieden, dass „unbestätigte Benutzer sich anmelden können, aber sofort auf eine Seite umgeleitet werden sollten – nennen wir die Route /unconfirmed - Benutzer daran erinnern, dass sie ihr Konto bestätigen müssen, bevor sie auf die Anwendung zugreifen können“.

Also müssen wir-

  1. Fügen Sie /unconfirmed hinzu Strecke
  2. Fügen Sie eine unconfirmed.html hinzu Vorlage
  3. Aktualisiere das register() Ansichtsfunktion
  4. Erstelle einen Decorator
  5. Aktualisieren Sie navigation.html Vorlage

/unconfirmed hinzufügen Strecke

Fügen Sie die folgende Route zu project/user/views.py hinzu :

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

Sie haben schon einmal ähnlichen Code gesehen, also machen wir weiter.



Fügen Sie unconfirmed.html hinzu Vorlage

{% 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 %}

Speichern Sie diese als unconfirmed.html in „Projekt/Vorlagen/Benutzer“. Auch dies sollte alles unkompliziert sein. Im Moment haben wir nur eine Dummy-URL hinzugefügt, um die Bestätigungs-E-Mail erneut zu senden. Darauf gehen wir weiter unten ein.



Aktualisiere das register() Ansichtsfunktion

Jetzt einfach ändern:

return redirect(url_for("main.home"))

An:

return redirect(url_for("user.unconfirmed"))

Nachdem die Bestätigungs-E-Mail gesendet wurde, wird der Benutzer nun zu /unconfirmed umgeleitet Strecke.



Erstellen Sie einen Dekorateur

# 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

Hier haben wir eine grundlegende Funktion, um zu überprüfen, ob ein Benutzer unbestätigt ist. Wenn unbestätigt, wird der Benutzer zu /unconfirmed umgeleitet Route. Speichern Sie dies als decorators.py im „Projekt“-Verzeichnis.

Dekorieren Sie nun das profile() Ansichtsfunktion:

@user_blueprint.route('/profile', methods=['GET', 'POST'])
@login_required
@check_confirmed
def profile():
    # ... snip ...

Achten Sie darauf, den Decorator zu importieren:

from project.decorators import check_confirmed


Aktualisieren Sie navigation.html Vorlage

Aktualisieren Sie abschließend den folgenden Teil der navigation.html Vorlage-

Änderung:

<ul class="nav navbar-nav">
  {% if current_user.is_authenticated() %}
    <li><a href="{{ url_for('user.profile') }}">Profile</a></li>
  {% endif %}
</ul>

An:

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

Zeit zum erneuten Testen!




Zweiter Test

Starten Sie die App und registrieren Sie sich erneut mit einer E-Mail-Adresse, auf die Sie Zugriff haben. (Fühlen Sie sich frei, den alten Benutzer, den Sie zuvor registriert haben, zuerst aus der Datenbank zu löschen, um ihn erneut zu verwenden.) Jetzt sollten Sie nach der Registrierung zu http://localhost:5000/unconfirmed umgeleitet werden.

Stellen Sie sicher, dass Sie die Route http://localhost:5000/profile testen. Dies sollte Sie zu http://localhost:5000/unconfirmed.

weiterleiten

Fahren Sie fort und bestätigen Sie die E-Mail, und Sie haben Zugriff auf alle Seiten. Boom!



E-Mail erneut senden

Lassen Sie uns zum Schluss den Link zum erneuten Senden zum Laufen bringen. Fügen Sie die folgende Ansichtsfunktion zu project/user/views.py hinzu :

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

Aktualisieren Sie nun die unconfirmed.html Vorlage:

{% 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 %}


Dritter Test

Du kennst die Übung. Stellen Sie dieses Mal sicher, dass Sie eine neue Bestätigungs-E-Mail erneut senden und den Link testen. Es sollte funktionieren.

Was passiert schließlich, wenn Sie sich selbst ein paar Bestätigungslinks schicken? Sind beide gültig? Testen Sie es aus. Registrieren Sie einen neuen Benutzer und senden Sie dann einige neue Bestätigungs-E-Mails. Versuchen Sie, mit der ersten E-Mail zu bestätigen. Hat es funktioniert? Es sollte. Ist das okay? Glauben Sie, dass diese anderen E-Mails ablaufen sollten, wenn eine neue gesendet wird?

Recherchieren Sie dazu. Und testen Sie andere Webanwendungen, die Sie verwenden. Wie gehen sie mit einem solchen Verhalten um?



Testsuite aktualisieren

In Ordnung. Das war es also für die Hauptfunktionalität. Wie wäre es, wenn wir die aktuelle Testsuite aktualisieren, da sie, nun ja, kaputt ist.

Führen Sie die Tests aus:

$ python manage.py test

Sie sollten den folgenden Fehler sehen:

TypeError: __init__() takes at least 4 arguments (3 given)

Um dies zu korrigieren, müssen wir nur setUp() aktualisieren -Methode in 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()

Führen Sie nun die Tests erneut durch. Alle sollten bestehen!



Schlussfolgerung

Wir können natürlich noch viel mehr tun:

  1. Rich- vs. Nur-Text-E-Mails – Wir sollten beides versenden.
  2. E-Mail zum Zurücksetzen des Passworts – Diese E-Mail sollte an Benutzer gesendet werden, die ihr Passwort vergessen haben.
  3. Benutzerverwaltung – Wir sollten Benutzern erlauben, ihre E-Mails und Passwörter zu aktualisieren, und wenn eine E-Mail geändert wird, sollte sie erneut bestätigt werden.
  4. Testen – Wir müssen mehr Tests schreiben, um die neuen Funktionen abzudecken.

Laden Sie den gesamten Quellcode aus dem Github-Repository herunter. Kommentieren Sie unten mit Fragen. Schau dir Teil 2 an.

Frohe Feiertage!