sql >> Database teknologi >  >> RDS >> Database

Håndtering af e-mail-bekræftelse under registrering i Flask

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.



Mail

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-

  1. Tilføj /unconfirmed rute
  2. Tilføj en unconfirmed.html skabelon
  3. Opdater register() visningsfunktion
  4. Opret en dekoratør
  5. 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:

  1. Rige versus almindelige e-mails – vi burde sende begge dele.
  2. Nulstil adgangskode-e-mail - Disse skal sendes til brugere, der har glemt deres adgangskoder.
  3. 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.
  4. 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!



  1. Indlæs Postgres-dump efter docker-compose up

  2. Lave beregninger i MySQL vs PHP

  3. Lagret procedure, der eksporterer data til csv-filer, eksporterer kun til én fil

  4. Hvordan kan man se indekser for en database eller tabel i MySQL?