Database
 sql >> база данни >  >> RDS >> Database

Обработка на потвърждение по имейл по време на регистрация в Flask

Този урок подробно описва как да потвърдите имейл адресите по време на регистрация на потребител.

Актуализирано на 30.04.2015 г. :Добавена поддръжка на Python 3.

По отношение на работния процес, след като потребител регистрира нов акаунт, се изпраща имейл за потвърждение. Потребителският акаунт е маркиран като „непотвърден“, докато потребителят не „потвърди“ акаунта чрез инструкциите в имейла. Това е прост работен процес, който повечето уеб приложения следват.

Едно важно нещо, което трябва да вземете предвид, е какво е позволено да правят непотвърдени потребители. С други думи, имат ли пълен достъп до вашето приложение, ограничен/ограничен достъп или изобщо нямат достъп? За приложението в този урок непотвърдените потребители могат да влязат, но те незабавно се пренасочват към страница, която им напомня, че трябва да потвърдят акаунта си, преди да имат достъп до приложението.

Преди да започнем, по-голямата част от функционалността, която ще добавим, е част от разширенията Flask-User и Flask-Security - което повдига въпроса защо просто не използвате разширенията? Е, на първо място, това е възможност за учене. Освен това и двете разширения имат ограничения, като поддържаните бази данни. Ами ако искате да използвате RethinkDB, например?

Да започнем.


Основна регистрация на флакона

Ще започнем с шаблон на Flask, който включва основна регистрация на потребител. Вземете кода от хранилището. След като създадете и активирате virtualenv, изпълнете следните команди, за да започнете бързо:

$ 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

Вижте readme за повече информация.

Когато приложението работи, отидете на http://localhost:5000/register и регистрирайте нов потребител. Забележете, че след регистрация приложението автоматично ви влиза и ви пренасочва към главната страница. Огледайте се наоколо, след това преминете през кода – по-специално „потребителския“ план.

Убийте сървъра, когато приключите.



Актуализирайте текущото приложение


Модели

Първо, нека добавим confirmed поле на нашия User модел в 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

Забележете как това поле е по подразбиране на „False“. Добавихме и confirmed_on поле, което е [datetime ] (https://realpython.com/python-datetime/). Искам да включа и това поле, за да анализирам разликата между registered_on и confirmed_on дати с помощта на кохортен анализ.

Нека започнем изцяло отначало с нашата база данни и миграции. Така че, продължете и изтрийте базата данни, dev.sqlite , както и папката „migrations“.



Команда за управление

След това в manage.py , актуализирайте create_admin команда, за да вземете предвид новите полета на базата данни:

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

Не забравяйте да импортирате datetime . Сега продължете и изпълнете отново следните команди:

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


register() функция за преглед

И накрая, преди да можем да регистрираме потребител отново, трябва да направим бърза промяна в register() функция за преглед в project/user/views.py

Промяна:

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

До:

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

Има смисъл? Помислете защо бихме искали да зададем по подразбиране confirmed до False .

Добре. Стартирайте приложението отново. Отидете до http://localhost:5000/register и регистрирайте нов потребител отново. Ако отворите вашата база данни SQLite в браузъра SQLite, трябва да видите:

И така, новият потребител, който регистрирах, [email protected] , не се потвърждава. Нека променим това.




Добавяне на потвърждение по имейл


Генериране на токен за потвърждение

Потвърждението по имейл трябва да съдържа уникален URL адрес, върху който потребителят просто трябва да щракне, за да потвърди акаунта си. В идеалния случай URL адресът трябва да изглежда така - http://yourapp.com/confirm/<id> . Ключът тук е id . Ще кодираме потребителския имейл (заедно с времева марка) в id използвайки неговия опасен пакет.

Създайте файл, наречен project/token.py и добавете следния код:

# 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

И така, в generate_confirmation_token() функция използваме URLSafeTimedSerializer за генериране на токен, използвайки имейл адреса, получен по време на регистрацията на потребител. действителното имейлът е кодиран в токена. След това, за да потвърдите маркера, в confirm_token() функция, можем да използваме loads() метод, който приема токена и срока на валидност - валиден за един час (3600 секунди) - като аргументи. Докато токенът не е изтекъл, той ще върне имейл.

Не забравяйте да добавите SECURITY_PASSWORD_SALT към конфигурацията на приложението ви (BaseConfig() ):

SECURITY_PASSWORD_SALT = 'my_precious_two'


Актуализиране на register() функция за преглед

Сега нека актуализираме register() функция за преглед отново от 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)

Също така, не забравяйте да актуализирате импортираните:

from project.token import generate_confirmation_token, confirm_token


Обработка на потвърждението по имейл

След това нека добавим нов изглед за обработка на потвърждението по имейл:

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

Добавете това към project/user/views.py . Също така не забравяйте да актуализирате импортираните:

import datetime

Тук ние наричаме confirm_token() функция, предаваща токена. Ако е успешен, актуализираме потребителя, променяйки email_confirmed атрибут на True и задаване на datetime за кога е извършено потвърждението. Освен това, в случай че потребителят вече е минал през процеса на потвърждение - и е потвърден - тогава ние предупреждаваме потребителя за това.



Създайте шаблона за имейл

След това нека добавим основен шаблон за имейл:

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

Запазете това като activate.html в „проект/шаблони/потребител”. Това отнема една променлива, наречена confirm_url , който ще бъде създаден в register() функция за преглед.



Изпратете имейл

Нека създадем основна функция за изпращане на имейли с малко помощ от Flask-Mail, която вече е инсталирана и настроена в project/__init__.py .

Създайте файл, наречен 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)

Запазете това в папката „проект“.

Така че просто трябва да предадем списък с получатели, тема и шаблон. След малко ще се справим с настройките за конфигурация на пощата.



Актуализиране на register() функция за преглед в 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)
        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)

Добавете и следния импорт:

from project.email import send_email

Тук събираме всичко. Тази функция основно действа като контролер (пряко или непряко) за целия процес:

  • Управление на първоначалната регистрация,
  • Генерирайте токен и URL за потвърждение,
  • Изпратете имейл за потвърждение,
  • Потвърждение на Flash,
  • Влезте в потребителя и
  • Пренасочване на потребителя.

Забелязахте ли _external=True аргумент? Това добавя пълния абсолютен URL адрес, който включва името на хоста и порта (http://localhost:5000, в нашия случай.)

Преди да можем да тестваме това, трябва да настроим настройките си за поща.



Поща

Започнете с актуализиране на BaseConfig() в 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]'

Вижте официалната документация на Flask-Mail за повече информация.

Ако вече имате акаунт в GMAIL, можете да го използвате или да регистрирате тест GMAIL акаунт. След това задайте временно променливите на средата в текущата сесия на обвивката:

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

Ако вашият GMAIL акаунт има удостоверяване в 2 стъпки, Google ще блокира опита.

Сега нека тестваме!




Първи тест

Стартирайте приложението и отидете до http://localhost:5000/register. След това се регистрирайте с имейл адрес, до който имате достъп. Ако всичко е минало добре, трябва да имате имейл във входящата си поща, който изглежда така:

Щракнете върху URL адреса и трябва да бъдете отведени до http://localhost:5000/. Уверете се, че потребителят е в базата данни, полето „потвърдено“ е True и има datetime свързано с confirmed_on поле.

Хубаво!



Разрешения за работа

Ако си спомняте, в началото на този урок решихме, че „непотвърдени потребители могат да влизат, но те трябва незабавно да бъдат пренасочени към страница – нека извикаме маршрута /unconfirmed - напомняне на потребителите, че трябва да потвърдят акаунта си, преди да имат достъп до приложението.”

И така, трябва да...

  1. Добавете /unconfirmed маршрут
  2. Добавете unconfirmed.html шаблон
  3. Актуализирайте register() функция за преглед
  4. Създайте декоратор
  5. Актуализиране на navigation.html шаблон

Добавете /unconfirmed маршрут

Добавете следния маршрут към 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')

Виждали сте подобен код преди, така че да продължим напред.



Добавете unconfirmed.html шаблон

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

Запазете това като unconfirmed.html в „проект/шаблони/потребител”. Отново всичко това трябва да е просто. Засега просто добавихме фиктивен URL адрес за повторно изпращане на имейла за потвърждение. Ще разгледаме това по-нататък.



Актуализирайте register() функция за преглед

Сега просто променете:

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

До:

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

И така, след като имейлът за потвърждение бъде изпратен, потребителят вече е пренасочен към /unconfirmed маршрут.



Създайте декоратор

# 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

Тук имаме основна функция, за да проверим дали даден потребител не е потвърден. Ако не е потвърдено, потребителят се пренасочва към /unconfirmed маршрут. Запазете това като decorators.py в директорията „project“.

Сега украсете profile() функция за преглед:

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

Не забравяйте да импортирате декоратора:

from project.decorators import check_confirmed


Актуализиране на navigation.html шаблон

Накрая актуализирайте следната част от navigation.html шаблон-

Промяна:

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

Време е да тествате отново!




Втори тест

Стартирайте приложението и се регистрирайте отново с имейл адрес, до който имате достъп. (Чувствайте се свободни първо да изтриете стария потребител, който сте регистрирали преди, от базата данни, за да го използвате отново.) Сега трябва да бъдете пренасочени към http://localhost:5000/unconfirmed след регистрация.

Уверете се, че сте тествали маршрута http://localhost:5000/profile. Това трябва да ви пренасочи към http://localhost:5000/unconfirmed.

Продължете и потвърдете имейла и ще имате достъп до всички страници. Бум!



Повторно изпращане на имейл

И накрая, нека накараме връзката за повторно изпращане да работи. Добавете следната функция за изглед към 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'))

Сега актуализирайте unconfirmed.html шаблон:

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


Трети тест

Знаеш тренировката. Този път не забравяйте да изпратите отново нов имейл за потвърждение и да тествате връзката. Би трябвало да работи.

И накрая, какво ще стане, ако си изпратите няколко връзки за потвърждение? Всеки валиден ли е? Тествайте го. Регистрирайте нов потребител и след това изпратете няколко нови имейла за потвърждение. Опитайте да потвърдите с първия имейл. Проработи ли? Би трябвало. Това добре ли е? Смятате ли, че тези други имейли трябва да изтекат, ако бъде изпратен нов?

Направете малко проучване за това. И тествайте други уеб приложения, които използвате. Как се справят с подобно поведение?



Актуализиране на тестовия пакет

Добре. Така че това е всичко за основната функционалност. Какво ще кажете да актуализираме текущия тестов пакет, тъй като той е повреден.

Изпълнете тестовете:

$ python manage.py test

Трябва да видите следната грешка:

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

За да коригираме това, просто трябва да актуализираме setUp() метод в 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()

Сега стартирайте тестовете отново. Всичко трябва да мине!



Заключение

Очевидно можем да направим още много:

  1. Имейли с богат и обикновен текст – трябва да изпращаме и двете.
  2. Имейл за нулиране на паролата – Те трябва да бъдат изпратени на потребители, които са забравили паролите си.
  3. Управление на потребителите – Трябва да позволим на потребителите да актуализират своите имейли и пароли и когато имейл се промени, той трябва да бъде потвърден отново.
  4. Тестване – Трябва да напишем още тестове, за да покрием новите функции.

Изтеглете целия изходен код от хранилището на Github. Коментирайте по-долу с въпроси. Вижте част 2.

Весели празници!



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Основи на табличните изрази, част 9 – Изгледи, сравнени с производни таблици и CTE

  2. Използване на JShell в Java 9 в NetBeans 9.0, част 4

  3. SQL освен

  4. Съхранена процедура за получаване на информация за таблици на базата данни

  5. Как да свържете база данни с Amazon VPC