|
@@ -11,21 +11,24 @@
|
|
|
"""
|
|
|
from datetime import datetime
|
|
|
|
|
|
-from flask import Blueprint, flash, redirect, url_for, request, g
|
|
|
-from flask_login import (current_user, login_user, login_required,
|
|
|
- logout_user, confirm_login, login_fresh)
|
|
|
+from flask import Blueprint, flash, g, redirect, request, url_for
|
|
|
+from flask.views import MethodView
|
|
|
from flask_babelplus import gettext as _
|
|
|
-
|
|
|
-from flaskbb.extensions import limiter
|
|
|
-from flaskbb.utils.helpers import (render_template, redirect_or_next,
|
|
|
- format_timedelta, get_available_languages)
|
|
|
-from flaskbb.email import send_reset_token, send_activation_token
|
|
|
+from flask_login import (confirm_login, current_user, login_fresh,
|
|
|
+ login_required, login_user, logout_user)
|
|
|
+
|
|
|
+from flaskbb.auth.forms import (AccountActivationForm, ForgotPasswordForm,
|
|
|
+ LoginForm, LoginRecaptchaForm, ReauthForm,
|
|
|
+ RegisterForm, RequestActivationForm,
|
|
|
+ ResetPasswordForm)
|
|
|
+from flaskbb.email import send_activation_token, send_reset_token
|
|
|
from flaskbb.exceptions import AuthenticationError
|
|
|
-from flaskbb.auth.forms import (LoginForm, LoginRecaptchaForm, ReauthForm,
|
|
|
- ForgotPasswordForm, ResetPasswordForm,
|
|
|
- RegisterForm, AccountActivationForm,
|
|
|
- RequestActivationForm)
|
|
|
+from flaskbb.extensions import limiter
|
|
|
from flaskbb.user.models import User
|
|
|
+from flaskbb.utils.helpers import (anonymous_required, enforce_recaptcha,
|
|
|
+ format_timedelta, get_available_languages,
|
|
|
+ redirect_or_next, registration_enabled,
|
|
|
+ render_template, requires_unactivated)
|
|
|
from flaskbb.utils.settings import flaskbb_config
|
|
|
from flaskbb.utils.tokens import get_token_status
|
|
|
|
|
@@ -44,16 +47,14 @@ def check_rate_limiting():
|
|
|
def login_rate_limit_error(error):
|
|
|
"""Register a custom error handler for a 'Too Many Requests'
|
|
|
(HTTP CODE 429) error."""
|
|
|
- return render_template("errors/too_many_logins.html",
|
|
|
- timeout=error.description)
|
|
|
+ return render_template("errors/too_many_logins.html", timeout=error.description)
|
|
|
|
|
|
|
|
|
def login_rate_limit():
|
|
|
"""Dynamically load the rate limiting config from the database."""
|
|
|
|
|
|
return "{count}/{timeout}minutes".format(
|
|
|
- count=flaskbb_config["AUTH_REQUESTS"],
|
|
|
- timeout=flaskbb_config["AUTH_TIMEOUT"]
|
|
|
+ count=flaskbb_config["AUTH_REQUESTS"], timeout=flaskbb_config["AUTH_TIMEOUT"]
|
|
|
)
|
|
|
|
|
|
|
|
@@ -72,45 +73,58 @@ def login_rate_limit_message():
|
|
|
limiter.limit(login_rate_limit, error_message=login_rate_limit_message)(auth)
|
|
|
|
|
|
|
|
|
-@auth.route("/login", methods=["GET", "POST"])
|
|
|
-def login():
|
|
|
- """Logs the user in."""
|
|
|
- if current_user is not None and current_user.is_authenticated:
|
|
|
- return redirect_or_next(url_for("forum.index"))
|
|
|
-
|
|
|
- current_limit = getattr(g, 'view_rate_limit', None)
|
|
|
- login_recaptcha = False
|
|
|
- if current_limit is not None:
|
|
|
- window_stats = limiter.limiter.get_window_stats(*current_limit)
|
|
|
- stats_diff = flaskbb_config["AUTH_REQUESTS"] - window_stats[1]
|
|
|
- login_recaptcha = stats_diff >= flaskbb_config["LOGIN_RECAPTCHA"]
|
|
|
+@auth.route("/logout")
|
|
|
+@limiter.exempt
|
|
|
+@login_required
|
|
|
+def logout():
|
|
|
+ """Logs the user out."""
|
|
|
+ logout_user()
|
|
|
+ flash(_("Logged out"), "success")
|
|
|
+ return redirect(url_for("forum.index"))
|
|
|
|
|
|
- form = LoginForm()
|
|
|
- if login_recaptcha and flaskbb_config["RECAPTCHA_ENABLED"]:
|
|
|
- form = LoginRecaptchaForm()
|
|
|
|
|
|
- if form.validate_on_submit():
|
|
|
- try:
|
|
|
- user = User.authenticate(form.login.data, form.password.data)
|
|
|
- if not login_user(user, remember=form.remember_me.data):
|
|
|
- flash(_("In order to use your account you have to activate it "
|
|
|
- "through the link we have sent to your email "
|
|
|
- "address."), "danger")
|
|
|
- return redirect_or_next(url_for("forum.index"))
|
|
|
- except AuthenticationError:
|
|
|
- flash(_("Wrong username or password."), "danger")
|
|
|
+class Login(MethodView):
|
|
|
+ decorators = [anonymous_required]
|
|
|
|
|
|
- return render_template("auth/login.html", form=form,
|
|
|
- login_recaptcha=login_recaptcha)
|
|
|
+ def form(self):
|
|
|
+ if enforce_recaptcha(limiter):
|
|
|
+ return LoginRecaptchaForm()
|
|
|
+ return LoginForm()
|
|
|
|
|
|
+ def get(self):
|
|
|
+ return render_template("auth/login.html", form=self.form())
|
|
|
|
|
|
-@auth.route("/reauth", methods=["GET", "POST"])
|
|
|
-@limiter.exempt
|
|
|
-@login_required
|
|
|
-def reauth():
|
|
|
- """Reauthenticates a user."""
|
|
|
- if not login_fresh():
|
|
|
- form = ReauthForm(request.form)
|
|
|
+ def post(self):
|
|
|
+ form = self.form()
|
|
|
+ if form.validate_on_submit():
|
|
|
+ try:
|
|
|
+ user = User.authenticate(form.login.data, form.password.data)
|
|
|
+ if not login_user(user, remember=form.remember_me.data):
|
|
|
+ flash(
|
|
|
+ _(
|
|
|
+ "In order to use your account you have to activate it "
|
|
|
+ "through the link we have sent to your email "
|
|
|
+ "address."
|
|
|
+ ), "danger"
|
|
|
+ )
|
|
|
+ return redirect_or_next(url_for("forum.index"))
|
|
|
+ except AuthenticationError:
|
|
|
+ flash(_("Wrong username or password."), "danger")
|
|
|
+
|
|
|
+ return render_template("auth/login.html", form=form)
|
|
|
+
|
|
|
+
|
|
|
+class Reauth(MethodView):
|
|
|
+ decorators = [login_required, limiter.exempt]
|
|
|
+ form = ReauthForm
|
|
|
+
|
|
|
+ def get(self):
|
|
|
+ if not login_fresh():
|
|
|
+ return render_template("auth/reauth.html", form=self.form())
|
|
|
+ return redirect_or_next(current_user.url)
|
|
|
+
|
|
|
+ def post(self):
|
|
|
+ form = self.form()
|
|
|
if form.validate_on_submit():
|
|
|
if current_user.check_password(form.password.data):
|
|
|
confirm_login()
|
|
@@ -119,160 +133,201 @@ def reauth():
|
|
|
|
|
|
flash(_("Wrong password."), "danger")
|
|
|
return render_template("auth/reauth.html", form=form)
|
|
|
- return redirect(request.args.get("next") or current_user.url)
|
|
|
|
|
|
|
|
|
-@auth.route("/logout")
|
|
|
-@limiter.exempt
|
|
|
-@login_required
|
|
|
-def logout():
|
|
|
- """Logs the user out."""
|
|
|
- logout_user()
|
|
|
- flash(_("Logged out"), "success")
|
|
|
- return redirect(url_for("forum.index"))
|
|
|
+class Register(MethodView):
|
|
|
+ decorators = [anonymous_required, registration_enabled]
|
|
|
+
|
|
|
+ def form(self):
|
|
|
+ form = RegisterForm()
|
|
|
+
|
|
|
+ form.language.choices = get_available_languages()
|
|
|
+ form.language.default = flaskbb_config['DEFAULT_LANGUAGE']
|
|
|
+ form.process(request.form)
|
|
|
+ return form
|
|
|
+
|
|
|
+ def get(self):
|
|
|
+ return render_template("auth/register.html", form=self.form())
|
|
|
|
|
|
+ def post(self):
|
|
|
+ form = self.form()
|
|
|
+ if form.validate_on_submit():
|
|
|
+ user = form.save()
|
|
|
+
|
|
|
+ if flaskbb_config["ACTIVATE_ACCOUNT"]:
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ user = User.query.filter_by(email=user.email).first()
|
|
|
+ send_activation_token.delay(user)
|
|
|
+ flash(
|
|
|
+ _("An account activation email has been sent to %(email)s", email=user.email),
|
|
|
+ "success"
|
|
|
+ )
|
|
|
+ else:
|
|
|
+ login_user(user)
|
|
|
+ flash(_("Thanks for registering."), "success")
|
|
|
+
|
|
|
+ return redirect_or_next(url_for('forum.index'))
|
|
|
+
|
|
|
+ return render_template("auth/register.html", form=form)
|
|
|
+
|
|
|
+
|
|
|
+class ForgotPassword(MethodView):
|
|
|
+ decorators = [anonymous_required]
|
|
|
+ form = ForgotPasswordForm
|
|
|
+
|
|
|
+ def get(self):
|
|
|
+ return render_template("auth/forgot_password.html", form=self.form())
|
|
|
+
|
|
|
+ def post(self):
|
|
|
+ form = self.form()
|
|
|
+ if form.validate_on_submit():
|
|
|
+ user = User.query.filter_by(email=form.email.data).first()
|
|
|
+
|
|
|
+ if user:
|
|
|
+ send_reset_token.delay(user)
|
|
|
+ flash(_("Email sent! Please check your inbox."), "info")
|
|
|
+ return redirect(url_for("auth.forgot_password"))
|
|
|
+ else:
|
|
|
+ flash(
|
|
|
+ _(
|
|
|
+ "You have entered an username or email address that is "
|
|
|
+ "not linked with your account."
|
|
|
+ ), "danger"
|
|
|
+ )
|
|
|
+ return render_template("auth/forgot_password.html", form=form)
|
|
|
+
|
|
|
+
|
|
|
+class ResetPassword(MethodView):
|
|
|
+ decorators = [anonymous_required]
|
|
|
+ form = ResetPasswordForm
|
|
|
+
|
|
|
+ def get(self, token):
|
|
|
+ form = self.form()
|
|
|
+ form.token.data = token
|
|
|
+ return render_template("auth/reset_password.html", form=form)
|
|
|
+
|
|
|
+ def post(self, token):
|
|
|
+ form = self.form()
|
|
|
+ if form.validate_on_submit():
|
|
|
+ expired, invalid, user = get_token_status(form.token.data, "reset_password")
|
|
|
|
|
|
-@auth.route("/register", methods=["GET", "POST"])
|
|
|
-def register():
|
|
|
- """Register a new user."""
|
|
|
- if current_user is not None and current_user.is_authenticated:
|
|
|
- return redirect_or_next(url_for("forum.index"))
|
|
|
+ if invalid:
|
|
|
+ flash(_("Your password token is invalid."), "danger")
|
|
|
+ return redirect(url_for("auth.forgot_password"))
|
|
|
|
|
|
- if not flaskbb_config["REGISTRATION_ENABLED"]:
|
|
|
- flash(_("The registration has been disabled."), "info")
|
|
|
- return redirect_or_next(url_for("forum.index"))
|
|
|
+ if expired:
|
|
|
+ flash(_("Your password token is expired."), "danger")
|
|
|
+ return redirect(url_for("auth.forgot_password"))
|
|
|
|
|
|
- form = RegisterForm(request.form)
|
|
|
+ if user:
|
|
|
+ user.password = form.password.data
|
|
|
+ user.save()
|
|
|
+ flash(_("Your password has been updated."), "success")
|
|
|
+ return redirect(url_for("auth.login"))
|
|
|
|
|
|
- form.language.choices = get_available_languages()
|
|
|
- form.language.default = flaskbb_config['DEFAULT_LANGUAGE']
|
|
|
- form.process(request.form)
|
|
|
+ form.token.data = token
|
|
|
+ return render_template("auth/reset_password.html", form=form)
|
|
|
|
|
|
- if form.validate_on_submit():
|
|
|
- user = form.save()
|
|
|
|
|
|
- if flaskbb_config["ACTIVATE_ACCOUNT"]:
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- user = User.query.filter_by(email=user.email).first()
|
|
|
+class RequestActivationToken(MethodView):
|
|
|
+ decorators = [requires_unactivated]
|
|
|
+ form = RequestActivationForm
|
|
|
+
|
|
|
+ def get(self):
|
|
|
+ return render_template("auth/request_account_activation.html", form=self.form())
|
|
|
+
|
|
|
+ def post(self):
|
|
|
+ form = self.form()
|
|
|
+ if form.validate_on_submit():
|
|
|
+ user = User.query.filter_by(email=form.email.data).first()
|
|
|
send_activation_token.delay(user)
|
|
|
- flash(_("An account activation email has been sent to %(email)s",
|
|
|
- email=user.email), "success")
|
|
|
- else:
|
|
|
- login_user(user)
|
|
|
- flash(_("Thanks for registering."), "success")
|
|
|
+ flash(
|
|
|
+ _("A new account activation token has been sent to "
|
|
|
+ "your email address."), "success"
|
|
|
+ )
|
|
|
+ return redirect(url_for("auth.activate_account"))
|
|
|
|
|
|
- return redirect_or_next(url_for('forum.index'))
|
|
|
+ return render_template("auth/request_account_activation.html", form=form)
|
|
|
|
|
|
- return render_template("auth/register.html", form=form)
|
|
|
|
|
|
+class ActivateAccount(MethodView):
|
|
|
+ form = AccountActivationForm
|
|
|
+ decorators = [requires_unactivated]
|
|
|
|
|
|
-@auth.route('/reset-password', methods=["GET", "POST"])
|
|
|
-def forgot_password():
|
|
|
- """Sends a reset password token to the user."""
|
|
|
- if not current_user.is_anonymous:
|
|
|
- return redirect(url_for("forum.index"))
|
|
|
+ def get(self, token=None):
|
|
|
+ expired = invalid = user = None
|
|
|
+ if token is not None:
|
|
|
+ expired, invalid, user = get_token_status(token, "activate_account")
|
|
|
|
|
|
- form = ForgotPasswordForm()
|
|
|
- if form.validate_on_submit():
|
|
|
- user = User.query.filter_by(email=form.email.data).first()
|
|
|
+ if invalid:
|
|
|
+ flash(_("Your account activation token is invalid."), "danger")
|
|
|
+ return redirect(url_for("auth.request_activation_token"))
|
|
|
+
|
|
|
+ if expired:
|
|
|
+ flash(_("Your account activation token is expired."), "danger")
|
|
|
+ return redirect(url_for("auth.request_activation_token"))
|
|
|
|
|
|
if user:
|
|
|
- send_reset_token.delay(user)
|
|
|
- flash(_("Email sent! Please check your inbox."), "info")
|
|
|
- return redirect(url_for("auth.forgot_password"))
|
|
|
- else:
|
|
|
- flash(_("You have entered an username or email address that is "
|
|
|
- "not linked with your account."), "danger")
|
|
|
- return render_template("auth/forgot_password.html", form=form)
|
|
|
-
|
|
|
-
|
|
|
-@auth.route("/reset-password/<token>", methods=["GET", "POST"])
|
|
|
-def reset_password(token):
|
|
|
- """Handles the reset password process."""
|
|
|
- if not current_user.is_anonymous:
|
|
|
- return redirect(url_for("forum.index"))
|
|
|
-
|
|
|
- form = ResetPasswordForm()
|
|
|
- if form.validate_on_submit():
|
|
|
- expired, invalid, user = get_token_status(form.token.data,
|
|
|
- "reset_password")
|
|
|
+ user.activated = True
|
|
|
+ user.save()
|
|
|
+
|
|
|
+ if current_user != user:
|
|
|
+ logout_user()
|
|
|
+ login_user(user)
|
|
|
+
|
|
|
+ flash(_("Your account has been activated."), "success")
|
|
|
+ return redirect(url_for("forum.index"))
|
|
|
+
|
|
|
+ return render_template("auth/account_activation.html", form=self.form())
|
|
|
+
|
|
|
+ def post(self, token=None):
|
|
|
+ expired = invalid = user = None
|
|
|
+ form = self.form()
|
|
|
+
|
|
|
+ if token is not None:
|
|
|
+ expired, invalid, user = get_token_status(token, "activate_account")
|
|
|
+
|
|
|
+ elif form.validate_on_submit():
|
|
|
+ expired, invalid, user = get_token_status(form.token.data, "activate_account")
|
|
|
|
|
|
if invalid:
|
|
|
- flash(_("Your password token is invalid."), "danger")
|
|
|
- return redirect(url_for("auth.forgot_password"))
|
|
|
+ flash(_("Your account activation token is invalid."), "danger")
|
|
|
+ return redirect(url_for("auth.request_activation_token"))
|
|
|
|
|
|
if expired:
|
|
|
- flash(_("Your password token is expired."), "danger")
|
|
|
- return redirect(url_for("auth.forgot_password"))
|
|
|
+ flash(_("Your account activation token is expired."), "danger")
|
|
|
+ return redirect(url_for("auth.request_activation_token"))
|
|
|
|
|
|
if user:
|
|
|
- user.password = form.password.data
|
|
|
+ user.activated = True
|
|
|
user.save()
|
|
|
- flash(_("Your password has been updated."), "success")
|
|
|
- return redirect(url_for("auth.login"))
|
|
|
-
|
|
|
- form.token.data = token
|
|
|
- return render_template("auth/reset_password.html", form=form)
|
|
|
-
|
|
|
-
|
|
|
-@auth.route("/activate", methods=["GET", "POST"])
|
|
|
-def request_activation_token():
|
|
|
- """Requests a new account activation token."""
|
|
|
- if current_user.is_active or not flaskbb_config["ACTIVATE_ACCOUNT"]:
|
|
|
- flash(_("This account is already activated."), "info")
|
|
|
- return redirect(url_for('forum.index'))
|
|
|
-
|
|
|
- form = RequestActivationForm()
|
|
|
- if form.validate_on_submit():
|
|
|
- user = User.query.filter_by(email=form.email.data).first()
|
|
|
- send_activation_token.delay(user)
|
|
|
- flash(_("A new account activation token has been sent to "
|
|
|
- "your email address."), "success")
|
|
|
- return redirect(url_for("auth.activate_account"))
|
|
|
-
|
|
|
- return render_template("auth/request_account_activation.html", form=form)
|
|
|
-
|
|
|
-
|
|
|
-@auth.route("/activate/confirm", methods=["GET", "POST"])
|
|
|
-@auth.route("/activate/confirm/<token>", methods=["GET", "POST"])
|
|
|
-def activate_account(token=None):
|
|
|
- """Handles the account activation process."""
|
|
|
- if current_user.is_active or not flaskbb_config["ACTIVATE_ACCOUNT"]:
|
|
|
- flash(_("This account is already activated."), "info")
|
|
|
- return redirect(url_for('forum.index'))
|
|
|
-
|
|
|
- expired = invalid = user = None
|
|
|
- form = None
|
|
|
- if token is not None:
|
|
|
- expired, invalid, user = get_token_status(token, "activate_account")
|
|
|
- else:
|
|
|
- form = AccountActivationForm()
|
|
|
- if form.validate_on_submit():
|
|
|
- expired, invalid, user = get_token_status(form.token.data,
|
|
|
- "activate_account")
|
|
|
|
|
|
- if invalid:
|
|
|
- flash(_("Your account activation token is invalid."), "danger")
|
|
|
- return redirect(url_for("auth.request_activation_token"))
|
|
|
+ if current_user != user:
|
|
|
+ logout_user()
|
|
|
+ login_user(user)
|
|
|
|
|
|
- if expired:
|
|
|
- flash(_("Your account activation token is expired."), "danger")
|
|
|
- return redirect(url_for("auth.request_activation_token"))
|
|
|
+ flash(_("Your account has been activated."), "success")
|
|
|
+ return redirect(url_for("forum.index"))
|
|
|
|
|
|
- if user:
|
|
|
- user.activated = True
|
|
|
- user.save()
|
|
|
+ return render_template("auth/account_activation.html", form=form)
|
|
|
|
|
|
- if current_user != user:
|
|
|
- logout_user()
|
|
|
- login_user(user)
|
|
|
|
|
|
- flash(_("Your account has been activated."), "success")
|
|
|
- return redirect(url_for("forum.index"))
|
|
|
+auth.add_url_rule("/login", view_func=Login.as_view('login'))
|
|
|
+auth.add_url_rule("/reauth", view_func=Reauth.as_view('reauth'))
|
|
|
+auth.add_url_rule("/register", view_func=Register.as_view('register'))
|
|
|
+auth.add_url_rule("/reset-password", view_func=ForgotPassword.as_view('forgot_password'))
|
|
|
+auth.add_url_rule("/reset-password/<token>", view_func=ResetPassword.as_view('reset_password'))
|
|
|
+auth.add_url_rule(
|
|
|
+ "/activate", view_func=RequestActivationToken.as_view('request_activation_token')
|
|
|
+)
|
|
|
|
|
|
- return render_template("auth/account_activation.html", form=form)
|
|
|
+
|
|
|
+_activate = ActivateAccount.as_view('activate_account')
|
|
|
+auth.add_url_rule("/activate/confirm", view_func=_activate)
|
|
|
+auth.add_url_rule("/activate/confirm/<token>", view_func=_activate)
|
|
|
+del _activate
|