mirror of
https://github.com/Llloooggg/Dyxless.git
synced 2026-03-06 10:46:24 +03:00
Добавлена двухфакторная регистрация через email
This commit is contained in:
@@ -5,48 +5,45 @@ from flask_mail import Mail
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_login import LoginManager
|
||||
|
||||
app = Flask("__name__", template_folder="dyxless/templates")
|
||||
db = SQLAlchemy()
|
||||
mail = Mail()
|
||||
|
||||
with open("dyxless/config.json") as config_file:
|
||||
config_data = json.load(config_file)
|
||||
|
||||
main_settings = config_data["main_settings"]
|
||||
app.config.update(main_settings)
|
||||
|
||||
def create_app():
|
||||
app = Flask("__name__", template_folder="dyxless/templates")
|
||||
db_settings = config_data["db_settings"]
|
||||
app.config.update(db_settings)
|
||||
|
||||
main_settings = config_data["main_settings"]
|
||||
app.config.update(main_settings)
|
||||
mail_settings = config_data["mail_settings"]
|
||||
app.config.update(mail_settings)
|
||||
|
||||
db_settings = config_data["db_settings"]
|
||||
app.config.update(db_settings)
|
||||
db.init_app(app)
|
||||
mail.init_app(app)
|
||||
|
||||
mail_settings = config_data["mail_settings"]
|
||||
app.config.update(mail_settings)
|
||||
login_manager = LoginManager()
|
||||
login_manager.login_view = "auth.login"
|
||||
login_manager.init_app(app)
|
||||
|
||||
db.init_app(app)
|
||||
mail.init_app(app)
|
||||
from .models import User
|
||||
|
||||
login_manager = LoginManager()
|
||||
login_manager.login_view = "auth.login"
|
||||
login_manager.init_app(app)
|
||||
|
||||
from .models import User
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
return User.query.get(int(user_id))
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
return User.query.get(int(user_id))
|
||||
|
||||
from .auth import auth as auth_blueprint
|
||||
from .auth import auth as auth_blueprint
|
||||
|
||||
app.register_blueprint(auth_blueprint)
|
||||
app.register_blueprint(auth_blueprint)
|
||||
|
||||
from .main import main as main_blueprint
|
||||
from .main import main as main_blueprint
|
||||
|
||||
app.register_blueprint(main_blueprint)
|
||||
app.register_blueprint(main_blueprint)
|
||||
|
||||
from .mails import mails as mails_blueprint
|
||||
from .mails import mails as mails_blueprint
|
||||
|
||||
app.register_blueprint(mails_blueprint)
|
||||
|
||||
return app
|
||||
app.register_blueprint(mails_blueprint)
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
from flask import Blueprint, render_template, redirect, url_for, request, flash
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from flask_login import current_user, login_user, logout_user
|
||||
import datetime
|
||||
|
||||
from flask import (
|
||||
Blueprint,
|
||||
render_template,
|
||||
redirect,
|
||||
url_for,
|
||||
request,
|
||||
flash,
|
||||
current_app,
|
||||
)
|
||||
from werkzeug.security import check_password_hash
|
||||
from flask_login import current_user, login_user, logout_user
|
||||
from itsdangerous import URLSafeTimedSerializer
|
||||
|
||||
from .models import User
|
||||
from . import db
|
||||
from .models import User
|
||||
from .mails import send_async_email
|
||||
|
||||
auth = Blueprint("auth", __name__)
|
||||
|
||||
@@ -25,10 +37,20 @@ def login():
|
||||
user = User.query.filter_by(email=email).first()
|
||||
|
||||
if not user or not check_password_hash(user.password, password):
|
||||
flash("Please check your login details and try again.")
|
||||
flash("Пожалуйста, проверьте введенные данные и попробуйте снова")
|
||||
return redirect(url_for("auth.login"))
|
||||
elif not user.is_confirmed:
|
||||
flash(
|
||||
"Аккаунт еще не активирован. Пожалуйста, проверьте вашу почту"
|
||||
)
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
login_user(user, remember=remember)
|
||||
|
||||
user.last_login = datetime.datetime.now()
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return redirect(url_for("main.profile"))
|
||||
|
||||
|
||||
@@ -43,27 +65,82 @@ def signup():
|
||||
|
||||
elif request.method == "POST":
|
||||
email = request.form.get("email")
|
||||
name = request.form.get("name")
|
||||
username = request.form.get("username")
|
||||
password = request.form.get("password")
|
||||
|
||||
user = User.query.filter_by(email=email).first()
|
||||
|
||||
if user:
|
||||
flash("Email address already exists")
|
||||
flash("Указанная почта уже используется")
|
||||
return redirect(url_for("auth.signup"))
|
||||
|
||||
user = User.query.filter_by(email=username).first()
|
||||
|
||||
if user:
|
||||
flash("Указанное имя уже используется")
|
||||
return redirect(url_for("auth.signup"))
|
||||
|
||||
new_user = User(
|
||||
email=email,
|
||||
name=name,
|
||||
password=generate_password_hash(password, method="sha256"),
|
||||
password=password,
|
||||
username=username,
|
||||
)
|
||||
|
||||
db.session.add(new_user)
|
||||
db.session.commit()
|
||||
|
||||
token = generate_confirmation_token(new_user.email)
|
||||
confirm_url = url_for(
|
||||
"auth.confirm_email", token=token, _external=True
|
||||
)
|
||||
|
||||
send_async_email(
|
||||
subject="Подтверждение регистрации",
|
||||
recipients=[new_user.email],
|
||||
html=render_template(
|
||||
"mail/confirmation_mail.html", confirm_url=confirm_url
|
||||
),
|
||||
)
|
||||
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
|
||||
def generate_confirmation_token(email):
|
||||
serializer = URLSafeTimedSerializer(current_app.config["SECRET_KEY"])
|
||||
return serializer.dumps(
|
||||
email, salt=current_app.config["SECURITY_PASSWORD_SALT"]
|
||||
)
|
||||
|
||||
|
||||
def confirm_token(token, expiration=3600):
|
||||
serializer = URLSafeTimedSerializer(current_app.config["SECRET_KEY"])
|
||||
try:
|
||||
email = serializer.loads(
|
||||
token,
|
||||
salt=current_app.config["SECURITY_PASSWORD_SALT"],
|
||||
max_age=expiration,
|
||||
)
|
||||
except:
|
||||
return False
|
||||
return email
|
||||
|
||||
|
||||
@auth.route("/confirm/<token>")
|
||||
def confirm_email(token):
|
||||
try:
|
||||
email = confirm_token(token)
|
||||
except:
|
||||
flash("Ссылка подтверждения невалидна или устарела", "danger")
|
||||
user = User.query.filter_by(email=email).first_or_404()
|
||||
if user.is_confirmed:
|
||||
flash("Аккаунт уже подтвержден", "success")
|
||||
else:
|
||||
user.is_confirmed = True
|
||||
db.session.commit()
|
||||
flash("Ваш аккаунт подвтержден!", "success")
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
|
||||
@auth.route("/logout")
|
||||
def logout():
|
||||
logout_user()
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
{
|
||||
"main_settings": {
|
||||
"SECRET_KEY": "some-very-secret-key",
|
||||
"SQLALCHEMY_TRACK_MODIFICATIONS": false
|
||||
"SECURITY_PASSWORD_SALT": "second-some-very-secret-key",
|
||||
"SQLALCHEMY_TRACK_MODIFICATIONS": false,
|
||||
"APP_EMAIL": "appsender@gmail.com
|
||||
},
|
||||
"db_settings": {
|
||||
"SQLALCHEMY_DATABASE_URI": "sqlite:///db.sqlite"
|
||||
|
||||
@@ -1,25 +1,38 @@
|
||||
from flask import Blueprint
|
||||
from flask import Blueprint, render_template
|
||||
from flask_mail import Message
|
||||
|
||||
from . import mail
|
||||
from . import app, mail
|
||||
from .decorators import async_work
|
||||
|
||||
mails = Blueprint("mails", __name__)
|
||||
|
||||
|
||||
def prepare_msg(subject, sender, recipients, text_body, html_body):
|
||||
def prepare_msg(subject, recipients, body, html, sender):
|
||||
msg = Message(subject, sender=sender, recipients=recipients)
|
||||
msg.body = text_body
|
||||
msg.html = html_body
|
||||
msg.body = body
|
||||
msg.html = html
|
||||
return msg
|
||||
|
||||
|
||||
@async_work
|
||||
def send_async_email(subject, sender, recipients, text_body, html_body):
|
||||
msg = prepare_msg(subject, sender, recipients, text_body, html_body)
|
||||
mail.send(msg)
|
||||
def send_async_email(
|
||||
subject,
|
||||
recipients,
|
||||
body=None,
|
||||
html=None,
|
||||
sender=app.config["APP_EMAIL"],
|
||||
):
|
||||
msg = prepare_msg(subject, recipients, body, html, sender)
|
||||
with app.app_context():
|
||||
mail.send(msg)
|
||||
|
||||
|
||||
def send_mail(subject, sender, recipients, text_body, html_body):
|
||||
msg = prepare_msg(subject, sender, recipients, text_body, html_body)
|
||||
def send_mail(
|
||||
subject,
|
||||
recipients,
|
||||
body=None,
|
||||
html=None,
|
||||
sender=app.config["APP_EMAIL"],
|
||||
):
|
||||
msg = prepare_msg(subject, recipients, body, html, sender)
|
||||
mail.send(msg)
|
||||
|
||||
@@ -1,10 +1,35 @@
|
||||
import datetime
|
||||
|
||||
from flask_login import UserMixin
|
||||
from werkzeug.security import generate_password_hash
|
||||
|
||||
from . import db
|
||||
|
||||
|
||||
class User(UserMixin, db.Model):
|
||||
|
||||
__tablename__ = "users"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
email = db.Column(db.String(100), unique=True)
|
||||
password = db.Column(db.String(100))
|
||||
name = db.Column(db.String(1000))
|
||||
email = db.Column(db.String, unique=True, nullable=False)
|
||||
password = db.Column(db.String, nullable=False)
|
||||
username = db.Column(db.String, unique=True, nullable=False)
|
||||
registered_on = db.Column(db.DateTime, nullable=False)
|
||||
last_login = db.Column(db.DateTime, nullable=True)
|
||||
is_confirmed = db.Column(db.Boolean, nullable=False, default=False)
|
||||
is_admin = db.Column(db.Boolean, nullable=False, default=False)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
email,
|
||||
password,
|
||||
username,
|
||||
is_confirmed=False,
|
||||
is_admin=False,
|
||||
):
|
||||
self.email = email
|
||||
self.password = generate_password_hash(password, method="sha256")
|
||||
self.username = username
|
||||
self.registered_on = datetime.datetime.now()
|
||||
self.is_confirmed = is_confirmed
|
||||
self.is_admin = is_admin
|
||||
|
||||
@@ -19,19 +19,19 @@
|
||||
<div id="navbarMenuHeroA" class="navbar-menu">
|
||||
<div class="navbar-end">
|
||||
<a href="{{ url_for('main.index') }}" class="navbar-item">
|
||||
Home
|
||||
Домой
|
||||
</a>
|
||||
{% if not current_user.is_authenticated %}
|
||||
<a href="{{ url_for('auth.login') }}" class="navbar-item">
|
||||
Login
|
||||
Вход
|
||||
</a>
|
||||
<a href="{{ url_for('auth.signup') }}" class="navbar-item">
|
||||
Sign Up
|
||||
Регистрация
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if current_user.is_authenticated %}
|
||||
<a href="{{ url_for('auth.logout') }}" class="navbar-item">
|
||||
Logout
|
||||
Выйти
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if current_user.is_authenticated %}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
{% block content %}
|
||||
<h1 class="title">
|
||||
Flask Login Example
|
||||
Dyxless
|
||||
</h1>
|
||||
<h2 class="subtitle">
|
||||
Easy authentication and authorization in Flask.
|
||||
Заготовка web-приложения
|
||||
</h2>
|
||||
{% endblock %}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="column is-4 is-offset-4">
|
||||
<h3 class="title">Login</h3>
|
||||
<h3 class="title">Вход</h3>
|
||||
<div class="box">
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
@@ -14,22 +14,22 @@
|
||||
<form method="POST" action="/login">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input class="input is-large" type="email" name="email" placeholder="Your Email" autofocus="">
|
||||
<input class="input is-large" type="email" name="email" placeholder="Email" autofocus="">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input class="input is-large" type="password" name="password" placeholder="Your Password">
|
||||
<input class="input is-large" type="password" name="password" placeholder="Пароль">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox">
|
||||
Remember me
|
||||
Запомнить меня
|
||||
</label>
|
||||
</div>
|
||||
<button class="button is-block is-info is-large is-fullwidth">Login</button>
|
||||
<button class="button is-block is-info is-large is-fullwidth">Войти</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
3
dyxless/templates/mail/confirmation_mail.html
Normal file
3
dyxless/templates/mail/confirmation_mail.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<p>Перейдите по ссылке для подтверждения регистрации</p>
|
||||
<p><a href="{{ confirm_url }}">{{ confirm_url }}</a></p>
|
||||
<br>
|
||||
@@ -2,6 +2,6 @@
|
||||
|
||||
{% block content %}
|
||||
<h1 class="title">
|
||||
Welcome, {{ current_user.name }}!
|
||||
Добро пожаловать, {{ current_user.username }}!
|
||||
</h1>
|
||||
{% endblock %}
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="column is-4 is-offset-4">
|
||||
<h3 class="title">Sign Up</h3>
|
||||
<h3 class="title">Регистрация</h3>
|
||||
<div class="box">
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
<div class="notification is-danger">
|
||||
{{ messages[0] }}. Go to <a href="{{ url_for('auth.login') }}">login page</a>.
|
||||
{{ messages[0] }}<a href="{{ url_for('auth.login') }}">Перейти к странице входа</a>.
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
@@ -20,17 +20,17 @@
|
||||
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input class="input is-large" type="text" name="name" placeholder="Name" autofocus="">
|
||||
<input class="input is-large" type="text" name="username" placeholder="Имя пользователя" autofocus="">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input class="input is-large" type="password" name="password" placeholder="Password">
|
||||
<input class="input is-large" type="password" name="password" placeholder="Пароль">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="button is-block is-info is-large is-fullwidth">Sign Up</button>
|
||||
<button class="button is-block is-info is-large is-fullwidth">Зарегистрироваться</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user