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

Gestione delle e-mail di conferma durante la registrazione in Flask

Questo tutorial descrive in dettaglio come convalidare gli indirizzi e-mail durante la registrazione dell'utente.

Aggiornato il 30/04/2015 :Aggiunto il supporto per Python 3.

In termini di flusso di lavoro, dopo che un utente ha registrato un nuovo account, viene inviata un'e-mail di conferma. L'account utente è contrassegnato come "non confermato" fino a quando l'utente, beh, non "conferma" l'account tramite le istruzioni nell'e-mail. Questo è un flusso di lavoro semplice seguito dalla maggior parte delle applicazioni web.

Una cosa importante da tenere in considerazione è ciò che gli utenti non confermati possono fare. In altre parole, hanno pieno accesso alla tua applicazione, accesso limitato/limitato o nessun accesso? Per l'applicazione in questo tutorial, gli utenti non confermati possono accedere ma vengono immediatamente reindirizzati a una pagina che ricorda loro che devono confermare il proprio account prima di poter accedere all'applicazione.

Prima di iniziare, la maggior parte delle funzionalità che aggiungeremo fa parte delle estensioni Flask-User e Flask-Security, il che pone la domanda:perché non utilizzare semplicemente le estensioni? Bene, prima di tutto, questa è un'opportunità per imparare. Inoltre, entrambe queste estensioni hanno limitazioni, come i database supportati. E se volessi usare RethinkDB, per esempio?

Cominciamo.


Registrazione di base del pallone

Inizieremo con un boilerplate Flask che include la registrazione utente di base. Prendi il codice dal repository. Dopo aver creato e attivato un virtualenv, esegui i seguenti comandi per iniziare rapidamente:

$ 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

Consulta il readme per ulteriori informazioni.

Con l'app in esecuzione, vai a http://localhost:5000/register e registra un nuovo utente. Nota che dopo la registrazione, l'app effettua automaticamente l'accesso e ti reindirizza alla pagina principale. Dai un'occhiata in giro, quindi esegui il codice, in particolare il progetto "utente".

Uccidi il server al termine.



Aggiorna l'app corrente


Modelli

Innanzitutto, aggiungiamo il confirmed campo al nostro User modello 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

Nota come questo campo è impostato su "Falso". Abbiamo anche aggiunto un confirmed_on campo, che è un [datetime ] (https://realpython.com/python-datetime/). Mi piace includere anche questo campo per analizzare la differenza tra il registered_on e confirmed_on date utilizzando l'analisi di coorte.

Ripartiamo completamente con il nostro database e le migrazioni. Quindi, vai avanti ed elimina il database, dev.sqlite , nonché la cartella "migrazioni".



Gestisci comando

Successivamente, all'interno di manage.py , aggiorna il create_admin comando per tenere conto dei nuovi campi del database:

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

Assicurati di importare datetime . Ora vai avanti ed esegui di nuovo i seguenti comandi:

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


register() funzione di visualizzazione

Infine, prima di poter registrare nuovamente un utente, dobbiamo apportare una rapida modifica a register() funzione di visualizzazione in progetto/utente/viste.py

Modifica:

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

A:

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

Ha senso? Pensa al motivo per cui vorremmo impostare confirmed per impostazione predefinita a False .

Bene. Esegui di nuovo l'app. Passare a http://localhost:5000/register e registrare nuovamente un nuovo utente. Se apri il tuo database SQLite nel browser SQLite, dovresti vedere:

Quindi, il nuovo utente che ho registrato, [email protected] , non è confermato. Cambiamo le cose.




Aggiungi e-mail di conferma


Genera token di conferma

L'e-mail di conferma deve contenere un URL univoco su cui un utente deve semplicemente fare clic per confermare il proprio account. Idealmente, l'URL dovrebbe assomigliare a questo:http://yourapp.com/confirm/<id> . La chiave qui è l'id . Codificheremo l'e-mail dell'utente (insieme a un timestamp) nel id usando il suo pacchetto pericoloso.

Crea un file chiamato project/token.py e aggiungi il seguente codice:

# 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

Quindi, nel generate_confirmation_token() funzione usiamo il URLSafeTimedSerializer per generare un token utilizzando l'indirizzo email ottenuto durante la registrazione dell'utente. Il effettivo l'e-mail è codificata nel token. Quindi per confermare il token, all'interno di confirm_token() funzione, possiamo usare loads() metodo, che prende il token e la scadenza - valido per un'ora (3.600 secondi) - come argomenti. Finché il token non è scaduto, restituirà un'e-mail.

Assicurati di aggiungere il SECURITY_PASSWORD_SALT alla configurazione della tua app (BaseConfig() ):

SECURITY_PASSWORD_SALT = 'my_precious_two'


Aggiorna register() funzione di visualizzazione

Ora aggiorniamo il register() visualizza di nuovo la funzione da 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)

Inoltre, assicurati di aggiornare le importazioni:

from project.token import generate_confirmation_token, confirm_token


Gestire le e-mail di conferma

Successivamente, aggiungiamo una nuova vista per gestire l'e-mail di conferma:

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

Aggiungilo a project/user/views.py . Inoltre, assicurati di aggiornare le importazioni:

import datetime

Qui chiamiamo il confirm_token() funzione, passando il token. In caso di successo, aggiorniamo l'utente, modificando il email_confirmed attributo a True e impostando il datetime per quando è avvenuta la conferma. Inoltre, nel caso in cui l'utente abbia già eseguito il processo di conferma - ed è confermato - avviseremo l'utente di ciò.



Crea il modello di email

Successivamente, aggiungiamo un modello di email di 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>

Salvalo come activate.html in "progetto/modelli/utente". Questo prende una singola variabile chiamata confirm_url , che verrà creato nel register() funzione di visualizzazione.



Invia email

Creiamo una funzione di base per l'invio di email con un piccolo aiuto da Flask-Mail, che è già installato e configurato in project/__init__.py .

Crea un file chiamato 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)

Salvalo nella cartella "progetto".

Quindi, dobbiamo semplicemente passare un elenco di destinatari, un oggetto e un modello. Tra poco ci occuperemo delle impostazioni di configurazione della posta.



Aggiorna register() funzione di visualizzazione in project/user/views.py (di nuovo!)

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

Aggiungi anche la seguente importazione:

from project.email import send_email

Qui stiamo mettendo tutto insieme. Questa funzione funge fondamentalmente da controller (direttamente o indirettamente) per l'intero processo:

  • Gestire la registrazione iniziale,
  • Genera token e URL di conferma,
  • Invia email di conferma,
  • Conferma flash,
  • Accedi l'utente e
  • Reindirizza utente.

Hai notato il _external=True discussione? Questo aggiunge l'URL assoluto completo che include il nome host e la porta (http://localhost:5000, nel nostro caso.)

Prima di poterlo testare, dobbiamo configurare le nostre impostazioni di posta.



Posta

Inizia aggiornando BaseConfig() in progetto/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]'

Consulta la documentazione ufficiale di Flask-Mail per maggiori informazioni.

Se hai già un account GMAIL, puoi usarlo o registrare un account GMAIL di prova. Quindi imposta temporaneamente le variabili di ambiente nella sessione della shell corrente:

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

Se il tuo account GMAIL ha l'autenticazione in 2 passaggi, Google bloccherà il tentativo.

Ora proviamo!




Primo test

Avvia l'app e vai a http://localhost:5000/register. Quindi registrati con un indirizzo email a cui hai accesso. Se tutto è andato bene, dovresti avere un'e-mail nella tua casella di posta simile a questa:

Fai clic sull'URL e dovresti essere indirizzato a http://localhost:5000/. Assicurati che l'utente sia nel database, il campo "confermato" è True e c'è un datetime associato al confirmed_on campo.

Bello!



Autorizzazioni di gestione

Se ricordi, all'inizio di questo tutorial, abbiamo deciso che "gli utenti non confermati possono accedere ma devono essere immediatamente reindirizzati a una pagina - chiamiamo il percorso /unconfirmed - ricordando agli utenti che devono confermare il proprio account prima di poter accedere all'applicazione”.

Quindi, dobbiamo-

  1. Aggiungi il /unconfirmed percorso
  2. Aggiungi un unconfirmed.html modello
  3. Aggiorna il register() funzione di visualizzazione
  4. Crea un decoratore
  5. Aggiorna navigation.html modello

Aggiungi /unconfirmed percorso

Aggiungi il seguente percorso a 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')

Hai già visto un codice simile, quindi andiamo avanti.



Aggiungi unconfirmed.html modello

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

Salvalo come unconfirmed.html in "progetto/modelli/utente". Ancora una volta, tutto questo dovrebbe essere semplice. Per ora, abbiamo appena aggiunto un URL fittizio per inviare nuovamente l'e-mail di conferma. Affronteremo questo problema più in basso.



Aggiorna il register() funzione di visualizzazione

Ora cambia semplicemente:

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

A:

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

Quindi, dopo l'invio dell'e-mail di conferma, l'utente viene ora reindirizzato al /unconfirmed percorso.



Crea un decoratore

# 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

Qui abbiamo una funzione di base per verificare se un utente non è confermato. Se non confermato, l'utente viene reindirizzato al /unconfirmed rotta. Salvalo come decorators.py nella directory "progetto".

Ora, decora il profile() funzione di visualizzazione:

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

Assicurati di importare il decoratore:

from project.decorators import check_confirmed


Aggiorna navigation.html modello

Infine, aggiorna la parte seguente di navigation.html modello-

Modifica:

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

A:

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

È ora di riprovare!




Secondo test

Avvia l'app e registrati di nuovo con un indirizzo e-mail a cui hai accesso. (Sentiti libero di eliminare il vecchio utente che hai registrato in precedenza dal database per riutilizzarlo.) Ora dovresti essere reindirizzato a http://localhost:5000/unconfirmed dopo la registrazione.

Assicurati di testare il percorso http://localhost:5000/profile. Questo dovrebbe reindirizzarti a http://localhost:5000/unconfirmed.

Vai avanti e conferma l'e-mail e avrai accesso a tutte le pagine. Boom!



Invia nuovamente l'email

Infine, facciamo in modo che il link di reinvio funzioni. Aggiungi la seguente funzione di visualizzazione a 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'))

Ora aggiorna il unconfirmed.html modello:

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


Terza prova

Conosci il trapano. Questa volta assicurati di inviare nuovamente una nuova e-mail di conferma e testare il collegamento. Dovrebbe funzionare.

Infine, cosa succede se ti invii alcuni link di conferma? Ciascuno è valido? Provalo. Registra un nuovo utente, quindi invia alcune nuove e-mail di conferma. Prova a confermare con la prima email. Ha funzionato? Dovrebbe. Va bene? Pensi che le altre email debbano scadere se ne viene inviata una nuova?

Fai qualche ricerca su questo. E prova altre applicazioni web che usi. Come gestiscono tali comportamenti?



Aggiorna la suite di test

Bene. Quindi è tutto per la funzionalità principale. Che ne dici di aggiornare l'attuale suite di test poiché è, beh, rotta.

Esegui i test:

$ python manage.py test

Dovresti visualizzare il seguente errore:

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

Per correggere questo abbiamo solo bisogno di aggiornare il setUp() metodo 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()

Ora esegui di nuovo i test. Tutto dovrebbe passare!



Conclusione

C'è chiaramente molto di più che possiamo fare:

  1. E-mail ricche e di testo normale:dovremmo inviarle entrambe.
  2. E-mail di reimpostazione della password:devono essere inviate agli utenti che hanno dimenticato le password.
  3. Gestione utenti - Dovremmo consentire agli utenti di aggiornare le loro email e password e, quando un'email viene modificata, dovrebbe essere confermata di nuovo.
  4. Test - Dobbiamo scrivere più test per coprire le nuove funzionalità.

Scarica l'intero codice sorgente dal repository Github. Commenta di seguito con domande. Dai un'occhiata alla parte 2.

Buone vacanze!