mirror of
https://github.com/wassname/flask-security.git
synced 2026-07-13 01:00:42 +08:00
Simplify routing a tiny bit
This commit is contained in:
@@ -31,14 +31,14 @@ def send_confirmation_instructions(user, token):
|
||||
|
||||
:param user: The user to send the instructions to
|
||||
"""
|
||||
url = url_for('flask_security.confirm',
|
||||
token=token)
|
||||
url = url_for('flask_security.confirm_account', token=token)
|
||||
|
||||
confirmation_link = request.url_root[:-1] + url
|
||||
|
||||
ctx = dict(user=user, confirmation_link=confirmation_link)
|
||||
|
||||
send_mail('Please confirm your email', user.email,
|
||||
'confirmation_instructions',
|
||||
dict(user=user, confirmation_link=confirmation_link))
|
||||
'confirmation_instructions', ctx)
|
||||
|
||||
confirm_instructions_sent.send(user, app=app._get_current_object())
|
||||
|
||||
|
||||
+15
-11
@@ -32,10 +32,10 @@ _default_config = {
|
||||
'AUTH_URL': '/auth',
|
||||
'LOGOUT_URL': '/logout',
|
||||
'REGISTER_URL': '/register',
|
||||
'FORGOT_URL': '/forgot',
|
||||
'RESET_URL': '/reset/<token>',
|
||||
'CONFIRM_URL': '/confirm/<token>',
|
||||
'RESET_URL': '/reset',
|
||||
'CONFIRM_URL': '/confirm',
|
||||
'LOGIN_VIEW': '/login',
|
||||
'CONFIRM_ERROR_VIEW': '/confirm',
|
||||
'POST_LOGIN_VIEW': '/',
|
||||
'POST_LOGOUT_VIEW': '/',
|
||||
'POST_FORGOT_VIEW': '/',
|
||||
@@ -141,20 +141,24 @@ def _create_blueprint(app):
|
||||
|
||||
if cv('REGISTERABLE', app=app):
|
||||
bp.route(cv('REGISTER_URL', app=app),
|
||||
methods=['POST'],
|
||||
endpoint='register')(views.register)
|
||||
methods=['GET', 'POST'],
|
||||
endpoint='register')(views.register_user)
|
||||
|
||||
if cv('RECOVERABLE', app=app):
|
||||
bp.route(cv('FORGOT_URL', app=app),
|
||||
methods=['POST'],
|
||||
endpoint='forgot')(views.forgot)
|
||||
bp.route(cv('RESET_URL', app=app),
|
||||
methods=['POST'],
|
||||
endpoint='reset')(views.reset)
|
||||
methods=['GET', 'POST'],
|
||||
endpoint='forgot_password')(views.forgot_password)
|
||||
bp.route(cv('RESET_URL', app=app) + '/<token>',
|
||||
methods=['GET', 'POST'],
|
||||
endpoint='reset_password')(views.reset_password)
|
||||
|
||||
if cv('CONFIRMABLE', app=app):
|
||||
bp.route(cv('CONFIRM_URL', app=app),
|
||||
endpoint='confirm')(views.confirm)
|
||||
methods=['GET', 'POST'],
|
||||
endpoint='send_confirmation')(views.send_confirmation)
|
||||
bp.route(cv('CONFIRM_URL', app=app) + '/<token>',
|
||||
methods=['GET', 'POST'],
|
||||
endpoint='confirm_account')(views.confirm_account)
|
||||
|
||||
return bp
|
||||
|
||||
|
||||
+33
-3
@@ -9,9 +9,23 @@
|
||||
:license: MIT, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from flask import request
|
||||
from flask import request, current_app as app
|
||||
from flask.ext.wtf import Form, TextField, PasswordField, SubmitField, \
|
||||
HiddenField, Required, BooleanField, EqualTo, Email
|
||||
HiddenField, Required, BooleanField, EqualTo, Email, ValidationError
|
||||
from werkzeug.local import LocalProxy
|
||||
|
||||
from .exceptions import UserNotFoundError
|
||||
|
||||
|
||||
# Convenient reference
|
||||
_datastore = LocalProxy(lambda: app.security.datastore)
|
||||
|
||||
|
||||
def valid_user_email(form, field):
|
||||
try:
|
||||
_datastore.find_user(email=field.data)
|
||||
except UserNotFoundError:
|
||||
raise ValidationError('Invalid email address')
|
||||
|
||||
|
||||
class EmailFormMixin():
|
||||
@@ -20,6 +34,13 @@ class EmailFormMixin():
|
||||
Email(message="Invalid email address")])
|
||||
|
||||
|
||||
class UserEmailFormMixin():
|
||||
email = TextField("Email Address",
|
||||
validators=[Required(message="Email not provided"),
|
||||
Email(message="Invalid email address"),
|
||||
valid_user_email])
|
||||
|
||||
|
||||
class PasswordFormMixin():
|
||||
password = PasswordField("Password",
|
||||
validators=[Required(message="Password not provided")])
|
||||
@@ -30,7 +51,16 @@ class PasswordConfirmFormMixin():
|
||||
validators=[EqualTo('password', message="Passwords do not match")])
|
||||
|
||||
|
||||
class ForgotPasswordForm(Form, EmailFormMixin):
|
||||
class ResendConfirmationForm(Form, UserEmailFormMixin):
|
||||
"""The default forgot password form"""
|
||||
|
||||
submit = SubmitField("Resend Confirmation Instructions")
|
||||
|
||||
def to_dict(self):
|
||||
return dict(email=self.email.data)
|
||||
|
||||
|
||||
class ForgotPasswordForm(Form, UserEmailFormMixin):
|
||||
"""The default forgot password form"""
|
||||
|
||||
submit = SubmitField("Recover Password")
|
||||
|
||||
@@ -31,7 +31,7 @@ def send_reset_password_instructions(user, reset_token):
|
||||
|
||||
:param user: The user to send the instructions to
|
||||
"""
|
||||
url = url_for('flask_security.reset',
|
||||
url = url_for('flask_security.reset_password',
|
||||
token=reset_token)
|
||||
|
||||
reset_link = request.url_root[:-1] + url
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% include "../messages.html" %}
|
||||
{% include "security/messages.html" %}
|
||||
<h1>Resend confirmation instructions</h1>
|
||||
<form action="{{ url_for('flask_security.confirm') }}" method="POST" name="reset_confirmation_form">
|
||||
<form action="{{ url_for('flask_security.send_confirmation') }}" method="POST" name="reset_confirmation_form">
|
||||
{{ reset_confirmation_form.hidden_tag() }}
|
||||
{{ reset_confirmation_form.email.label }} {{ reset_confirmation_form.email }}
|
||||
{{ reset_confirmation_form.submit }}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% include "../messages.html" %}
|
||||
<h1>Change password</h1>
|
||||
<form action="{{ url_for('flask_security.reset') }}" method="POST" name="reset_password_form">
|
||||
<form action="{{ url_for('flask_security.reset_password') }}" method="POST" name="reset_password_form">
|
||||
{{ reset_password_form.hidden_tag() }}
|
||||
{{ reset_password_form.password.label }} {{ reset_password_form.password }}<br/>
|
||||
{{ reset_password_form.password_confirm.label }} {{ reset_password_form.password_confirm }}<br/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% include "../messages.html" %}
|
||||
{% include "security/messages.html" %}
|
||||
<h1>Send reset password instructions</h1>
|
||||
<form action="{{ url_for('flask_security.forgot') }}" method="POST" name="forgot_password_form">
|
||||
<form action="{{ url_for('flask_security.forgot_password') }}" method="POST" name="forgot_password_form">
|
||||
{{ forgot_password_form.hidden_tag() }}
|
||||
{{ forgot_password_form.email.label }} {{ forgot_password_form.email }}
|
||||
{{ forgot_password_form.submit }}
|
||||
|
||||
+57
-34
@@ -19,15 +19,16 @@ from werkzeug.datastructures import MultiDict
|
||||
from werkzeug.local import LocalProxy
|
||||
|
||||
from .confirmable import confirm_by_token, reset_confirmation_token
|
||||
from .exceptions import TokenExpiredError, UserNotFoundError, \
|
||||
ConfirmationError, BadCredentialsError, ResetPasswordError
|
||||
from .exceptions import TokenExpiredError, ConfirmationError, \
|
||||
BadCredentialsError, ResetPasswordError
|
||||
from .forms import LoginForm, RegisterForm, ForgotPasswordForm, \
|
||||
ResetPasswordForm
|
||||
ResetPasswordForm, ResendConfirmationForm
|
||||
from .recoverable import reset_by_token, \
|
||||
reset_password_reset_token
|
||||
from .signals import user_registered
|
||||
from .tokens import generate_authentication_token
|
||||
from .utils import get_post_login_redirect, do_flash, get_remember_token
|
||||
from .utils import get_url, get_post_login_redirect, do_flash, \
|
||||
get_remember_token
|
||||
|
||||
|
||||
# Convenient references
|
||||
@@ -142,50 +143,71 @@ def logout():
|
||||
_security.post_logout_view)
|
||||
|
||||
|
||||
def register():
|
||||
def register_user():
|
||||
"""View function which handles a registration request."""
|
||||
|
||||
form = RegisterForm(csrf_enabled=not app.testing)
|
||||
|
||||
# Exit early if the form doesn't validate
|
||||
if form.validate_on_submit():
|
||||
# Create user and send signal
|
||||
user = _datastore.create_user(**form.to_dict())
|
||||
confirm_token = None
|
||||
# Create user
|
||||
u = _datastore.create_user(**form.to_dict())
|
||||
|
||||
# Send confirmation instructions if necessary
|
||||
if _security.confirmable:
|
||||
confirm_token = reset_confirmation_token(user)
|
||||
t = reset_confirmation_token(u) if _security.confirmable else None
|
||||
|
||||
user_registered.send(dict(user=user, confirm_token=confirm_token),
|
||||
app=app._get_current_object())
|
||||
data = dict(user=u, confirm_token=t)
|
||||
user_registered.send(data, app=app._get_current_object())
|
||||
|
||||
_logger.debug('User %s registered' % user)
|
||||
_logger.debug('User %s registered' % u)
|
||||
|
||||
# Login the user if allowed
|
||||
if not _security.confirmable or _security.login_without_confirmation:
|
||||
_do_login(user)
|
||||
_do_login(u)
|
||||
|
||||
return redirect(_security.post_register_view or
|
||||
_security.post_login_view)
|
||||
|
||||
return redirect(request.referrer or _security.register_url)
|
||||
return render_template('security/registrations/new.html',
|
||||
register_user_form=form)
|
||||
|
||||
|
||||
def confirm(token):
|
||||
def send_confirmation():
|
||||
form = ResendConfirmationForm()
|
||||
|
||||
if form.validate_on_submit():
|
||||
user = _datastore.find_user(email=form.email.data)
|
||||
|
||||
reset_confirmation_token(user)
|
||||
|
||||
_logger.debug('%s request confirmation instructions' % user)
|
||||
|
||||
msg = 'A new confirmation code has been sent to ' + user.email
|
||||
do_flash(msg, 'info')
|
||||
|
||||
else:
|
||||
for key, value in form.errors.items():
|
||||
do_flash(value[0], 'error')
|
||||
|
||||
return render_template('security/confirmations/new.html',
|
||||
reset_confirmation_form=form)
|
||||
|
||||
|
||||
def confirm_account(token):
|
||||
"""View function which handles a account confirmation request."""
|
||||
error = False
|
||||
|
||||
try:
|
||||
user = confirm_by_token(token)
|
||||
|
||||
except ConfirmationError, e:
|
||||
error = True
|
||||
|
||||
_logger.debug('Confirmation error: ' + str(e))
|
||||
|
||||
do_flash(str(e), 'error')
|
||||
|
||||
return redirect('/') # TODO: Don't just redirect to root
|
||||
|
||||
except TokenExpiredError, e:
|
||||
error = True
|
||||
|
||||
reset_confirmation_token(e.user)
|
||||
|
||||
@@ -197,7 +219,8 @@ def confirm(token):
|
||||
|
||||
do_flash(msg, 'error')
|
||||
|
||||
return redirect('/') # TODO: Don't redirect to root
|
||||
if error:
|
||||
return redirect(get_url(_security.confirm_error_view))
|
||||
|
||||
_logger.debug('User %s confirmed' % user)
|
||||
|
||||
@@ -207,35 +230,35 @@ def confirm(token):
|
||||
_security.post_login_view)
|
||||
|
||||
|
||||
def forgot():
|
||||
def forgot_password():
|
||||
"""View function that handles a forgotten password request."""
|
||||
|
||||
form = ForgotPasswordForm(csrf_enabled=not app.testing)
|
||||
|
||||
if form.validate_on_submit():
|
||||
try:
|
||||
user = _datastore.find_user(**form.to_dict())
|
||||
user = _datastore.find_user(**form.to_dict())
|
||||
|
||||
reset_password_reset_token(user)
|
||||
reset_password_reset_token(user)
|
||||
|
||||
_logger.debug('%s requested to reset their password' % user)
|
||||
_logger.debug('%s requested to reset their password' % user)
|
||||
|
||||
do_flash('Instructions to reset your password have been '
|
||||
'sent to %s' % user.email, 'success')
|
||||
|
||||
except UserNotFoundError:
|
||||
_logger.debug('A reset password request was made for %s but '
|
||||
'that email does not exist.' % form.email.data)
|
||||
|
||||
do_flash('The email you provided could not be found', 'error')
|
||||
do_flash('Instructions to reset your password have been '
|
||||
'sent to %s' % user.email, 'success')
|
||||
|
||||
return redirect(_security.post_forgot_view)
|
||||
|
||||
else:
|
||||
_logger.debug('A reset password request was made for %s but '
|
||||
'that email does not exist.' % form.email.data)
|
||||
|
||||
for key, value in form.errors.items():
|
||||
do_flash(value[0], 'error')
|
||||
|
||||
return render_template('security/passwords/new.html',
|
||||
forgot_password_form=form)
|
||||
|
||||
|
||||
def reset(token):
|
||||
def reset_password(token):
|
||||
"""View function that handles a reset password request."""
|
||||
|
||||
form = ResetPasswordForm(csrf_enabled=not app.testing)
|
||||
|
||||
@@ -267,18 +267,18 @@ class RecoverableTests(SecurityTest):
|
||||
def test_forgot_post_sends_email(self):
|
||||
with capture_reset_password_requests():
|
||||
with self.app.mail.record_messages() as outbox:
|
||||
self.client.post('/forgot', data=dict(email='joe@lp.com'))
|
||||
self.client.post('/reset', data=dict(email='joe@lp.com'))
|
||||
self.assertEqual(len(outbox), 1)
|
||||
|
||||
def test_forgot_password_invalid_email(self):
|
||||
r = self.client.post('/forgot',
|
||||
r = self.client.post('/reset',
|
||||
data=dict(email='larry@lp.com'),
|
||||
follow_redirects=True)
|
||||
self.assertIn('The email you provided could not be found', r.data)
|
||||
self.assertIn('Invalid email address', r.data)
|
||||
|
||||
def test_reset_password_with_valid_token(self):
|
||||
with capture_reset_password_requests() as requests:
|
||||
r = self.client.post('/forgot',
|
||||
r = self.client.post('/reset',
|
||||
data=dict(email='joe@lp.com'),
|
||||
follow_redirects=True)
|
||||
t = requests[0]['token']
|
||||
@@ -293,7 +293,7 @@ class RecoverableTests(SecurityTest):
|
||||
|
||||
def test_reset_password_twice_flashes_invalid_token_msg(self):
|
||||
with capture_reset_password_requests() as requests:
|
||||
self.client.post('/forgot', data=dict(email='joe@lp.com'))
|
||||
self.client.post('/reset', data=dict(email='joe@lp.com'))
|
||||
t = requests[0]['token']
|
||||
|
||||
data = {
|
||||
@@ -316,7 +316,7 @@ class ExpiredResetPasswordTest(SecurityTest):
|
||||
|
||||
def test_reset_password_with_expired_token(self):
|
||||
with capture_reset_password_requests() as requests:
|
||||
r = self.client.post('/forgot',
|
||||
r = self.client.post('/reset',
|
||||
data=dict(email='joe@lp.com'),
|
||||
follow_redirects=True)
|
||||
t = requests[0]['token']
|
||||
|
||||
Reference in New Issue
Block a user