sql >> Databasteknik >  >> RDS >> Database

Hantera e-postbekräftelse under registrering i kolv

Denna handledning beskriver hur du validerar e-postadresser under användarregistrering.

Uppdaterad 2015-04-30 :Lade till stöd för Python 3.

När det gäller arbetsflöde, efter att en användare registrerat ett nytt konto, skickas ett bekräftelsemail. Användarkontot markeras som "obekräftat" tills användaren, ja, "bekräftar" kontot via instruktionerna i e-postmeddelandet. Detta är ett enkelt arbetsflöde som de flesta webbapplikationer följer.

En viktig sak att ta hänsyn till är vad obekräftade användare får göra. Med andra ord, har de full tillgång till din applikation, begränsad/begränsad åtkomst eller ingen tillgång alls? För applikationen i denna handledning kan obekräftade användare logga in men de omdirigeras omedelbart till en sida som påminner dem om att de måste bekräfta sitt konto innan de kan komma åt applikationen.

Innan vi börjar är det mesta av funktionaliteten som vi kommer att lägga till en del av tilläggen Flask-User och Flask-Security - vilket väcker frågan varför inte bara använda tilläggen? Jo, först och främst är det här en möjlighet att lära sig. Dessutom har båda dessa tillägg begränsningar, som de databaser som stöds. Vad händer om du till exempel ville använda RethinkDB?

Låt oss börja.


Grundläggande registrering av kolv

Vi kommer att börja med en kolvplatta som inkluderar grundläggande användarregistrering. Ta koden från förvaret. När du har skapat och aktiverat en virtualenv, kör följande kommandon för att snabbt komma igång:

$ 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

Kolla in readme för mer information.

Med appen igång, navigera till http://localhost:5000/register och registrera en ny användare. Observera att efter registrering loggar appen dig automatiskt in och omdirigerar dig till huvudsidan. Ta en titt runt och kör sedan igenom koden - särskilt "användar"-ritningen.

Döda servern när du är klar.



Uppdatera den aktuella appen


Modeller

Låt oss först lägga till confirmed till vår User modell 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

Lägg märke till hur det här fältet är som standard "False". Vi har också lagt till en confirmed_on fält, som är en [datetime ] (https://realpython.com/python-datetime/). Jag vill inkludera detta fält också för att analysera skillnaden mellan registered_on och confirmed_on datum med hjälp av kohortanalys.

Låt oss helt börja om med vår databas och migrering. Så fortsätt och ta bort databasen, dev.sqlite , samt mappen "migrationer".



Hantera kommando

Därefter inom manage.py , uppdatera create_admin kommando för att ta hänsyn till de nya databasfälten:

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

Se till att importera datetime . Gå nu vidare och kör följande kommandon igen:

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


register() visningsfunktion

Slutligen, innan vi kan registrera en användare igen, måste vi göra en snabb ändring av register() visningsfunktionen i project/user/views.py

Ändra:

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

Till:

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

Vettigt? Fundera på varför vi skulle vilja använda confirmed som standard till False .

Okej. Kör appen igen. Navigera till http://localhost:5000/register och registrera en ny användare igen. Om du öppnar din SQLite-databas i SQLite-webbläsaren bör du se:

Så, den nya användaren som jag registrerade, [email protected] , är inte bekräftad. Låt oss ändra på det.




Lägg till e-postbekräftelse


Generera bekräftelsetoken

E-postbekräftelsen bör innehålla en unik URL som en användare helt enkelt behöver klicka på för att bekräfta sitt konto. Helst bör webbadressen se ut ungefär så här - http://yourapp.com/confirm/<id> . Nyckeln här är id . Vi kommer att koda användarens e-post (tillsammans med en tidsstämpel) i id använder paketet itsdangerous.

Skapa en fil som heter project/token.py och lägg till följande kod:

# 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() funktionen använder vi URLSafeTimedSerializer för att generera en token med den e-postadress som erhölls under användarregistreringen. Den faktiska e-post är kodat i token. Sedan för att bekräfta token, inom confirm_token() funktionen kan vi använda loads() metod, som tar token och utgångsdatum - giltiga i en timme (3 600 sekunder) - som argument. Så länge token inte har gått ut kommer den att returnera ett e-postmeddelande.

Var noga med att lägga till SECURITY_PASSWORD_SALT till appens konfiguration (BaseConfig() ):

SECURITY_PASSWORD_SALT = 'my_precious_two'


Uppdatera register() visningsfunktion

Låt oss nu uppdatera register() visa funktionen igen från 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)

Se också till att uppdatera importerna:

from project.token import generate_confirmation_token, confirm_token


Hantera e-postbekräftelse

Låt oss sedan lägga till en ny vy för att hantera e-postbekrä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'))

Lägg till detta i project/user/views.py . Se också till att uppdatera importerna:

import datetime

Här anropar vi confirm_token() funktion, passerar in token. Om det lyckas uppdaterar vi användaren och ändrar email_confirmed attribut till True och ställ in datetime för när bekräftelsen ägde rum. Dessutom, om användaren redan har gått igenom bekräftelseprocessen - och är bekräftad - varnar vi användaren om detta.



Skapa e-postmallen

Låt oss sedan lägga till en basmall för e-post:

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

Spara detta som activate.html i "projekt/mallar/användare". Detta tar en enda variabel som heter confirm_url , som kommer att skapas i register() visningsfunktion.



Skicka e-post

Låt oss skapa en grundläggande funktion för att skicka e-post med lite hjälp från Flask-Mail, som redan är installerad och konfigurerad i project/__init__.py .

Skapa en fil som heter 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)

Spara detta i mappen "projekt".

Så vi behöver helt enkelt skicka en lista över mottagare, ett ämne och en mall. Vi kommer att ta itu med e-postkonfigurationsinställningarna om en stund.



Uppdatera register() visa 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)

Lägg till följande import också:

from project.email import send_email

Här sätter vi ihop allt. Denna funktion fungerar i princip som en kontrollant (antingen direkt eller indirekt) för hela processen:

  • Hantera den första registreringen,
  • Generera token och bekräftelse-URL,
  • Skicka bekräftelsemail,
  • Flashbekräftelse,
  • Logga in användaren och
  • Omdirigera användare.

Lade du märke till _external=True argument? Detta lägger till den fullständiga absoluta webbadressen som inkluderar värdnamnet och porten (http://localhost:5000, i vårt fall.)

Innan vi kan testa detta måste vi ställa in våra e-postinställningar.



Mail

Börja med att uppdatera 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]'

Kolla in den officiella Flask-Mail-dokumentationen för mer information.

Om du redan har ett GMAIL-konto kan du använda det eller registrera ett GMAIL-testkonto. Ställ sedan in miljövariablerna tillfälligt i den aktuella skalsessionen:

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

Om ditt GMAIL-konto har tvåstegsautentisering kommer Google att blockera försöket.

Nu ska vi testa!




Första testet

Starta appen och navigera till http://localhost:5000/register. Registrera dig sedan med en e-postadress som du har tillgång till. Om allt gick bra bör du ha ett e-postmeddelande i din inkorg som ser ut ungefär så här:

Klicka på URL:en så kommer du till http://localhost:5000/. Se till att användaren finns i databasen, fältet 'bekräftat' är True , och det finns en datetime kopplat till confirmed_on fältet.

Trevligt!



Hantera behörigheter

Om du kommer ihåg så bestämde vi i början av denna handledning att "obekräftade användare kan logga in men de bör omedelbart omdirigeras till en sida - låt oss kalla rutten /unconfirmed - påminna användare om att de måste bekräfta sitt konto innan de kan komma åt applikationen.”

Så vi måste-

  1. Lägg till /unconfirmed rutt
  2. Lägg till en unconfirmed.html mall
  3. Uppdatera register() visningsfunktion
  4. Skapa en dekoratör
  5. Uppdatera navigation.html mall

Lägg till /unconfirmed rutt

Lägg till följande rutt till 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 sett liknande kod förut, så låt oss gå vidare.



Lägg till unconfirmed.html mall

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

Spara detta som unconfirmed.html i "projekt/mallar/användare". Återigen, allt detta borde vara enkelt. För tillfället har vi bara lagt till en dummy-URL för att skicka bekräftelsemailet igen. Vi tar upp detta längre ner.



Uppdatera register() visningsfunktion

Ändra bara:

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

Till:

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

Så efter att bekräftelsemailet har skickats omdirigeras användaren nu till /unconfirmed rutt.



Skapa 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

Här har vi en grundläggande funktion för att kontrollera om en användare är obekräftad. Om den är obekräftad omdirigeras användaren till /unconfirmed rutt. Spara detta som decorators.py i katalogen "projekt".

Dekorera nu profile() visningsfunktion:

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

Se till att importera dekoratören:

from project.decorators import check_confirmed


Uppdatera navigation.html mall

Uppdatera slutligen följande del av navigation.html mall-

Ändra:

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

Till:

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

Dags att testa igen!




Andra testet

Starta appen och registrera dig igen med en e-postadress som du har tillgång till. (Ta bort den gamla användaren som du registrerade innan först från databasen för att använda igen.) Nu bör du omdirigeras till http://localhost:5000/unconfirmed efter registrering.

Se till att testa rutten http://localhost:5000/profile. Detta bör omdirigera dig till http://localhost:5000/unconfirmed.

Gå vidare och bekräfta e-postmeddelandet, så kommer du att ha tillgång till alla sidor. Bom!



Skicka e-post igen

Slutligen, låt oss få återsändningslänken att fungera. Lägg till följande visningsfunktion till 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'))

Uppdatera nu unconfirmed.html mall:

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

Du vet borren. Den här gången se till att skicka ett nytt bekräftelsemail och testa länken. Det borde fungera.

Till sist, vad händer om du skickar några bekräftelselänkar till dig själv? Är var och en giltig? Testa det. Registrera en ny användare och skicka sedan några nya bekräftelsemail. Försök att bekräfta med det första mejlet. Fungerade det? Det borde. Är det här okej? Tycker du att de andra e-postmeddelandena ska upphöra att gälla om ett nytt skickas?

Gör lite forskning om detta. Och testa andra webbapplikationer som du använder. Hur hanterar de sådant beteende?



Uppdatera testsvit

OK. Så det är det för huvudfunktionen. Vad sägs om att vi uppdaterar den nuvarande testsviten eftersom den är trasig.

Kör testerna:

$ python manage.py test

Du bör se följande fel:

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

För att rätta till detta behöver vi bara uppdatera setUp() metod 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 testerna igen. Alla borde passera!



Slutsats

Det finns helt klart mycket mer vi kan göra:

  1. Richa kontra oformaterade e-postmeddelanden – vi borde skicka ut båda.
  2. E-post för återställning av lösenord – Dessa ska skickas till användare som har glömt sina lösenord.
  3. Användarhantering – Vi bör tillåta användare att uppdatera sina e-postadresser och lösenord, och när ett e-postmeddelande ändras ska det bekräftas igen.
  4. Testning – Vi måste skriva fler tester för att täcka de nya funktionerna.

Ladda ner hela källkoden från Github-förvaret. Kommentera nedan med frågor. Kolla in del 2.

Trevlig helg!



  1. sökordsfel saknas i oracle CASE WHEN sql-satsen

  2. Hur man skickar boolesk parameter till Oracle-proceduren C#

  3. Spårning av gilla-meddelanden på Facebook (DB Design)

  4. Installation av Postgres på fönster för användning med Ruby-on-Rails