Browse Source

Restore ability to activate account through form submit

Alec Nikolas Reiter 6 years ago
parent
commit
909c4300e3

+ 55 - 34
flaskbb/auth/forms.py

@@ -11,17 +11,28 @@
 import logging
 import logging
 
 
 from flask_babelplus import lazy_gettext as _
 from flask_babelplus import lazy_gettext as _
-from wtforms import (BooleanField, HiddenField, PasswordField, SelectField,
-                     StringField, SubmitField)
-from wtforms.validators import (DataRequired, Email, EqualTo, InputRequired,
-                                regexp)
+from wtforms import (
+    BooleanField,
+    HiddenField,
+    PasswordField,
+    SelectField,
+    StringField,
+    SubmitField,
+)
+from wtforms.validators import (
+    DataRequired,
+    Email,
+    EqualTo,
+    InputRequired,
+    regexp,
+)
 
 
 from flaskbb.utils.fields import RecaptchaField
 from flaskbb.utils.fields import RecaptchaField
 from flaskbb.utils.forms import FlaskBBForm
 from flaskbb.utils.forms import FlaskBBForm
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
-USERNAME_RE = r'^[\w.+-]+$'
+USERNAME_RE = r"^[\w.+-]+$"
 is_valid_username = regexp(
 is_valid_username = regexp(
     USERNAME_RE, message=_("You can only use letters, numbers or dashes.")
     USERNAME_RE, message=_("You can only use letters, numbers or dashes.")
 )
 )
@@ -34,12 +45,12 @@ class LoginForm(FlaskBBForm):
             DataRequired(
             DataRequired(
                 message=_("Please enter your username or email address.")
                 message=_("Please enter your username or email address.")
             )
             )
-        ]
+        ],
     )
     )
 
 
     password = PasswordField(
     password = PasswordField(
         _("Password"),
         _("Password"),
-        validators=[DataRequired(message=_("Please enter your password."))]
+        validators=[DataRequired(message=_("Please enter your password."))],
     )
     )
 
 
     remember_me = BooleanField(_("Remember me"), default=False)
     remember_me = BooleanField(_("Remember me"), default=False)
@@ -57,36 +68,36 @@ class RegisterForm(FlaskBBForm):
         _("Username"),
         _("Username"),
         validators=[
         validators=[
             DataRequired(message=_("A valid username is required")),
             DataRequired(message=_("A valid username is required")),
-            is_valid_username
-        ]
+            is_valid_username,
+        ],
     )
     )
 
 
     email = StringField(
     email = StringField(
         _("Email address"),
         _("Email address"),
         validators=[
         validators=[
             DataRequired(message=_("A valid email address is required.")),
             DataRequired(message=_("A valid email address is required.")),
-            Email(message=_("Invalid email address."))
-        ]
+            Email(message=_("Invalid email address.")),
+        ],
     )
     )
 
 
     password = PasswordField(
     password = PasswordField(
-        _('Password'),
+        _("Password"),
         validators=[
         validators=[
             InputRequired(),
             InputRequired(),
-            EqualTo('confirm_password', message=_('Passwords must match.'))
-        ]
+            EqualTo("confirm_password", message=_("Passwords must match.")),
+        ],
     )
     )
 
 
-    confirm_password = PasswordField(_('Confirm password'))
+    confirm_password = PasswordField(_("Confirm password"))
 
 
     recaptcha = RecaptchaField(_("Captcha"))
     recaptcha = RecaptchaField(_("Captcha"))
 
 
-    language = SelectField(_('Language'))
+    language = SelectField(_("Language"))
 
 
     accept_tos = BooleanField(
     accept_tos = BooleanField(
         _("I accept the Terms of Service"),
         _("I accept the Terms of Service"),
         validators=[DataRequired(message=_("Please accept the TOS."))],
         validators=[DataRequired(message=_("Please accept the TOS."))],
-        default=True
+        default=True,
     )
     )
 
 
     submit = SubmitField(_("Register"))
     submit = SubmitField(_("Register"))
@@ -94,8 +105,8 @@ class RegisterForm(FlaskBBForm):
 
 
 class ReauthForm(FlaskBBForm):
 class ReauthForm(FlaskBBForm):
     password = PasswordField(
     password = PasswordField(
-        _('Password'),
-        validators=[DataRequired(message=_("Please enter your password."))]
+        _("Password"),
+        validators=[DataRequired(message=_("Please enter your password."))],
     )
     )
 
 
     submit = SubmitField(_("Refresh Login"))
     submit = SubmitField(_("Refresh Login"))
@@ -103,11 +114,11 @@ class ReauthForm(FlaskBBForm):
 
 
 class ForgotPasswordForm(FlaskBBForm):
 class ForgotPasswordForm(FlaskBBForm):
     email = StringField(
     email = StringField(
-        _('Email address'),
+        _("Email address"),
         validators=[
         validators=[
             DataRequired(message=_("A valid email address is required.")),
             DataRequired(message=_("A valid email address is required.")),
-            Email()
-        ]
+            Email(),
+        ],
     )
     )
 
 
     recaptcha = RecaptchaField(_("Captcha"))
     recaptcha = RecaptchaField(_("Captcha"))
@@ -116,25 +127,25 @@ class ForgotPasswordForm(FlaskBBForm):
 
 
 
 
 class ResetPasswordForm(FlaskBBForm):
 class ResetPasswordForm(FlaskBBForm):
-    token = HiddenField('Token')
+    token = HiddenField("Token")
 
 
     email = StringField(
     email = StringField(
-        _('Email address'),
+        _("Email address"),
         validators=[
         validators=[
             DataRequired(message=_("A valid email address is required.")),
             DataRequired(message=_("A valid email address is required.")),
-            Email()
-        ]
+            Email(),
+        ],
     )
     )
 
 
     password = PasswordField(
     password = PasswordField(
-        _('Password'),
+        _("Password"),
         validators=[
         validators=[
             InputRequired(),
             InputRequired(),
-            EqualTo('confirm_password', message=_('Passwords must match.'))
-        ]
+            EqualTo("confirm_password", message=_("Passwords must match.")),
+        ],
     )
     )
 
 
-    confirm_password = PasswordField(_('Confirm password'))
+    confirm_password = PasswordField(_("Confirm password"))
 
 
     submit = SubmitField(_("Reset password"))
     submit = SubmitField(_("Reset password"))
 
 
@@ -144,16 +155,26 @@ class RequestActivationForm(FlaskBBForm):
         _("Username"),
         _("Username"),
         validators=[
         validators=[
             DataRequired(message=_("A valid username is required.")),
             DataRequired(message=_("A valid username is required.")),
-            is_valid_username
-        ]
+            is_valid_username,
+        ],
     )
     )
 
 
     email = StringField(
     email = StringField(
         _("Email address"),
         _("Email address"),
         validators=[
         validators=[
             DataRequired(message=_("A valid email address is required.")),
             DataRequired(message=_("A valid email address is required.")),
-            Email(message=_("Invalid email address."))
-        ]
+            Email(message=_("Invalid email address.")),
+        ],
     )
     )
 
 
     submit = SubmitField(_("Send Confirmation Mail"))
     submit = SubmitField(_("Send Confirmation Mail"))
+
+
+class AccountActivationForm(FlaskBBForm):
+    token = StringField(
+        _("Email confirmation token"),
+        validators=[
+            DataRequired(_("Please enter the token we have sent to you."))
+        ],
+    )
+    submit = SubmitField(_("Confirm Email"))

+ 102 - 20
flaskbb/auth/views.py

@@ -15,28 +15,51 @@ from datetime import datetime
 from flask import Blueprint, current_app, flash, g, redirect, request, url_for
 from flask import Blueprint, current_app, flash, g, redirect, request, url_for
 from flask.views import MethodView
 from flask.views import MethodView
 from flask_babelplus import gettext as _
 from flask_babelplus import gettext as _
-from flask_login import (confirm_login, current_user, login_fresh,
-                         login_required, login_user, logout_user)
-from flaskbb.auth.forms import (ForgotPasswordForm, LoginForm,
-                                LoginRecaptchaForm, ReauthForm, RegisterForm,
-                                RequestActivationForm, ResetPasswordForm)
+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.extensions import db, limiter
 from flaskbb.extensions import db, limiter
-from flaskbb.utils.helpers import (anonymous_required, enforce_recaptcha,
-                                   format_timedelta, get_available_languages,
-                                   redirect_or_next, register_view,
-                                   registration_enabled, render_template,
-                                   requires_unactivated)
+from flaskbb.utils.helpers import (
+    anonymous_required,
+    enforce_recaptcha,
+    format_timedelta,
+    get_available_languages,
+    redirect_or_next,
+    register_view,
+    registration_enabled,
+    render_template,
+    requires_unactivated,
+)
 from flaskbb.utils.settings import flaskbb_config
 from flaskbb.utils.settings import flaskbb_config
 
 
 from ..core.auth.authentication import StopAuthentication
 from ..core.auth.authentication import StopAuthentication
 from ..core.auth.registration import UserRegistrationInfo
 from ..core.auth.registration import UserRegistrationInfo
-from ..core.exceptions import StopValidation, ValidationError, PersistenceError
+from ..core.exceptions import PersistenceError, StopValidation, ValidationError
 from ..core.tokens import TokenError
 from ..core.tokens import TokenError
 from .plugins import impl
 from .plugins import impl
-from .services import (account_activator_factory,
-                       authentication_manager_factory,
-                       reauthentication_manager_factory,
-                       registration_service_factory, reset_service_factory)
+from .services import (
+    account_activator_factory,
+    authentication_manager_factory,
+    reauthentication_manager_factory,
+    registration_service_factory,
+    reset_service_factory,
+)
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
@@ -281,21 +304,22 @@ class RequestActivationToken(MethodView):
         )
         )
 
 
 
 
-class ActivateAccount(MethodView):
+class AutoActivateAccount(MethodView):
     decorators = [requires_unactivated]
     decorators = [requires_unactivated]
 
 
     def __init__(self, account_activator_factory):
     def __init__(self, account_activator_factory):
         self.account_activator_factory = account_activator_factory
         self.account_activator_factory = account_activator_factory
 
 
-    def get(self, token=None):
+    def get(self, token):
         activator = self.account_activator_factory()
         activator = self.account_activator_factory()
+
         try:
         try:
             activator.activate_account(token)
             activator.activate_account(token)
         except TokenError as e:
         except TokenError as e:
             flash(e.reason, 'danger')
             flash(e.reason, 'danger')
         except ValidationError as e:
         except ValidationError as e:
             flash(e.reason, 'danger')
             flash(e.reason, 'danger')
-            return redirect('forum.index')
+            return redirect(url_for('forum.index'))
 
 
         else:
         else:
             try:
             try:
@@ -317,7 +341,56 @@ class ActivateAccount(MethodView):
             )
             )
             return redirect(url_for("forum.index"))
             return redirect(url_for("forum.index"))
 
 
-        return render_template("auth/account_activation.html")
+        return redirect(url_for('auth.activate_account'))
+
+
+class ActivateAccount(MethodView):
+    decorators = [requires_unactivated]
+    form = AccountActivationForm
+
+    def __init__(self, account_activator_factory):
+        self.account_activator_factory = account_activator_factory
+
+    def get(self):
+        return render_template(
+            "auth/account_activation.html",
+            form=self.form()
+        )
+
+    def post(self):
+        form = self.form()
+        if form.validate_on_submit():
+            token = form.token.data
+            activator = self.account_activator_factory()
+            try:
+                activator.activate_account(token)
+            except TokenError as e:
+                form.populate_errors([('token', e.reason)])
+            except ValidationError as e:
+                flash(e.reason, 'danger')
+                return redirect(url_for('forum.index'))
+
+            else:
+                try:
+                    db.session.commit()
+                except Exception:  # noqa
+                    logger.exception("Database error while activating account")
+                    db.session.rollback()
+                    flash(
+                        _(
+                            "Could not activate account due to an unrecoverable error"  # noqa
+                        ), "danger"
+                    )
+
+                    return redirect('auth.request_activation_token')
+
+                flash(
+                    _("Your account has been activated and you can now login."),
+                    "success"
+                )
+                return redirect(url_for("forum.index"))
+
+        return render_template("auth/account_activation.html", form=form)
 
 
 
 
 @impl(tryfirst=True)
 @impl(tryfirst=True)
@@ -416,11 +489,20 @@ def flaskbb_load_blueprints(app):
     )
     )
     register_view(
     register_view(
         auth,
         auth,
-        routes=['/activate/confirm', '/activate/confirm/<token>'],
+        routes=['/activate/confirm'],
         view_func=ActivateAccount.as_view(
         view_func=ActivateAccount.as_view(
             'activate_account',
             'activate_account',
             account_activator_factory=account_activator_factory
             account_activator_factory=account_activator_factory
         )
         )
     )
     )
 
 
+    register_view(
+        auth,
+        routes=['/activate/confirm/<token>'],
+        view_func=AutoActivateAccount.as_view(
+            'autoactivate_account',
+            account_activator_factory=account_activator_factory
+        )
+    )
+
     app.register_blueprint(auth, url_prefix=app.config['AUTH_URL_PREFIX'])
     app.register_blueprint(auth, url_prefix=app.config['AUTH_URL_PREFIX'])

+ 1 - 1
flaskbb/templates/email/activate_account.html

@@ -1,4 +1,4 @@
-{% set link = url_for('auth.activate_account', token=token, _external=True) %}
+{% set link = url_for('auth.autoactivate_account', token=token, _external=True) %}
 
 
 <p>{% trans %}Dear {{ username }},{% endtrans %}</p>
 <p>{% trans %}Dear {{ username }},{% endtrans %}</p>
 
 

+ 1 - 1
flaskbb/templates/email/activate_account.txt

@@ -1,4 +1,4 @@
-{% set link = url_for('auth.activate_account', token=token, _external=True) %}
+{% set link = url_for('auth.autoactivate_account', token=token, _external=True) %}
 
 
 {% trans link=link %}Dear {{ username }},
 {% trans link=link %}Dear {{ username }},