diff --git a/flask_security/core.py b/flask_security/core.py index 95f3206..b6d83e5 100644 --- a/flask_security/core.py +++ b/flask_security/core.py @@ -69,8 +69,8 @@ _default_config = { 'DEFAULT_HTTP_AUTH_REALM': 'Login Required' } -#: Default Flask-Security flash messages -_default_flash_messages = { +#: Default Flask-Security messages +_default_messages = { 'UNAUTHORIZED': ('You do not have permission to view this resource.', 'error'), 'EMAIL_CONFIRMED': ('Your email has been confirmed. You may now log in.', 'success'), 'ALREADY_CONFIRMED': ('Your email has already been confirmed', 'info'), @@ -78,12 +78,14 @@ _default_flash_messages = { 'PASSWORD_RESET_REQUEST': ('Instructions to reset your password have been sent to %(email)s.', 'info'), 'PASSWORD_RESET_EXPIRED': ('You did not reset your password within %(within)s. New instructions have been sent to %(email)s.', 'error'), 'INVALID_RESET_PASSWORD_TOKEN': ('Invalid reset password token', 'error'), + 'CONFIRMATION_REQUIRED': ('Email requires confirmation', 'error'), 'CONFIRMATION_REQUEST': ('A new confirmation code has been sent to %(email)s.', 'info'), 'CONFIRMATION_EXPIRED': ('You did not confirm your email within %(within)s. New instructions to confirm your email have been sent to %(email)s.', 'error'), 'LOGIN_EXPIRED': ('You did not login within %(within)s. New instructions to login to your account have been sent to %(email)s.', 'error'), 'LOGIN_EMAIL_SENT': ('Instructions to log in to your account have been sent to %(email)s', 'success'), 'INVALID_LOGIN_TOKEN': ('Invalid login token', 'error'), - 'DISABLED_ACCOUNT': ('Account is disabled', 'error') + 'DISABLED_ACCOUNT': ('Account is disabled', 'error'), + 'PASSWORDLESS_LOGIN_SUCCESSFUL': ('You have successfuly logged in', 'success') } @@ -233,7 +235,7 @@ class Security(object): for key, value in _default_config.items(): app.config.setdefault('SECURITY_' + key, value) - for key, value in _default_flash_messages.items(): + for key, value in _default_messages.items(): app.config.setdefault('SECURITY_MSG_' + key, value) identity_loaded.connect_via(app)(_on_identity_loaded) diff --git a/flask_security/forms.py b/flask_security/forms.py index c779a47..968fb35 100644 --- a/flask_security/forms.py +++ b/flask_security/forms.py @@ -92,9 +92,7 @@ class LoginForm(Form, EmailFormMixin, PasswordFormMixin): def __init__(self, *args, **kwargs): super(LoginForm, self).__init__(*args, **kwargs) - - if request.method == 'GET': - self.next.data = request.args.get('next', None) + self.next.data = request.args.get('next', None) class RegisterForm(Form, diff --git a/flask_security/views.py b/flask_security/views.py index a77b4dc..1e1a1fd 100644 --- a/flask_security/views.py +++ b/flask_security/views.py @@ -15,6 +15,7 @@ from werkzeug.datastructures import MultiDict from werkzeug.local import LocalProxy from flask_security.confirmable import confirm_by_token, reset_confirmation_token +from flask_security.core import current_user from flask_security.decorators import login_required from flask_security.exceptions import ConfirmationError, BadCredentialsError, \ ResetPasswordError, PasswordlessLoginError @@ -138,16 +139,17 @@ def send_login(): if user.is_active(): send_login_instructions(user, form.next.data) - msg, cat = get_message('LOGIN_EMAIL_SENT', email=user.email) + do_flash(get_message('LOGIN_EMAIL_SENT', email=user.email)) else: - msg, cat = get_message('DISABLED_ACCOUNT') - - do_flash(msg, cat) + do_flash(get_message('DISABLED_ACCOUNT')) return render_template('security/logins/passwordless.html', login_form=form) def token_login(token): + if current_user.is_authenticated(): + return redirect(_security.post_login_view) + try: user, next = login_by_token(token) @@ -161,6 +163,8 @@ def token_login(token): return redirect(request.referrer or _security.login_manager.login_view) + do_flash(get_message('PASSWORDLESS_LOGIN_SUCCESSFUL')) + return redirect(next or _security.post_login_view) @@ -196,10 +200,6 @@ def confirm_email(token): if e.user: reset_confirmation_token(e.user) - msg, cat = get_message('CONFIRMATION_EXPIRED', - within=_security.confirm_email_within, - email=e.user.email) - do_flash(msg, cat) return redirect(get_url(_security.confirm_error_view)) diff --git a/tests/__init__.py b/tests/__init__.py index 9440844..3c005c4 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -71,3 +71,6 @@ class SecurityTest(TestCase): return TestCase.assertIsNotNone(self, obj, msg) return self.assertTrue(obj is not None) + + def get_message(self, key, **kwargs): + return self.app.config['SECURITY_MSG_' + key][0] % kwargs diff --git a/tests/functional_tests.py b/tests/functional_tests.py index 6188041..a4fd1fb 100644 --- a/tests/functional_tests.py +++ b/tests/functional_tests.py @@ -101,7 +101,7 @@ class DefaultSecurityTests(SecurityTest): def test_unauthenticated_role_required(self): r = self._get('/admin', follow_redirects=True) - self.assertIn('You do not have permission to view this resource', r.data) + self.assertIn(self.get_message('UNAUTHORIZED'), r.data) def test_multiple_role_required(self): for user in ("matt@lp.com", "joe@lp.com"): @@ -270,7 +270,7 @@ class ConfirmableTests(SecurityTest): e = 'dude@lp.com' self.register(e) r = self.authenticate(email=e) - self.assertIn('Email requires confirmation', r.data) + self.assertIn(self.get_message('CONFIRMATION_REQUIRED'), r.data) def test_register_sends_confirmation_email(self): e = 'dude@lp.com' @@ -397,7 +397,8 @@ class RecoverableTests(SecurityTest): 'password': 'newpassword', 'password_confirm': 'newpassword' }, follow_redirects=True) - self.assertIn('Invalid reset password token', r.data) + + self.assertIn(self.get_message('INVALID_RESET_PASSWORD_TOKEN'), r.data) def test_reset_password_twice_flashes_invalid_token_msg(self): with capture_reset_password_requests() as requests: @@ -412,7 +413,7 @@ class RecoverableTests(SecurityTest): url = '/reset/' + t r = self.client.post(url, data=data, follow_redirects=True) r = self.client.post(url, data=data, follow_redirects=True) - self.assertIn('Invalid reset password token', r.data) + self.assertIn(self.get_message('INVALID_RESET_PASSWORD_TOKEN'), r.data) class ExpiredResetPasswordTest(SecurityTest): @@ -492,7 +493,7 @@ class PasswordlessTests(SecurityTest): self.assertIn(msg, r.data) r = self.client.get('/auth/' + token, follow_redirects=True) - self.assertIn('Hello ' + e, r.data) + self.assertIn(self.get_message('PASSWORDLESS_LOGIN_SUCCESSFUL'), r.data) r = self.client.get('/profile') self.assertIn('Profile Page', r.data) @@ -502,6 +503,17 @@ class PasswordlessTests(SecurityTest): r = self._get('/auth/bogus', follow_redirects=True) self.assertIn(msg, r.data) + def test_token_login_forwards_to_post_login_view_when_already_authenticated(self): + with capture_passwordless_login_requests() as requests: + self.client.post('/auth', data=dict(email='matt@lp.com'), follow_redirects=True) + token = requests[0]['login_token'] + + r = self.client.get('/auth/' + token, follow_redirects=True) + self.assertIn(self.get_message('PASSWORDLESS_LOGIN_SUCCESSFUL'), r.data) + + r = self.client.get('/auth/' + token, follow_redirects=True) + self.assertNotIn(self.get_message('PASSWORDLESS_LOGIN_SUCCESSFUL'), r.data) + class ExpiredLoginTokenTests(SecurityTest):