diff --git a/docs/source/index.rst b/docs/source/index.rst index 2d35ad2a..b40623bb 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -102,6 +102,7 @@ Documentation Revision History ---------------- +* v0.6.4 Moved custom params from __init__() to init_app(). Added send_reset_password_email(). * v0.6.3 Fix for Python 3.4 and signals. Added UserMixin.has_role() and @roles_accepted(). * v0.6.2 Added support for invitation-only registrations. * v0.6.1 Added Chinese (Simplified) and French translations`. diff --git a/flask_user/__init__.py b/flask_user/__init__.py index b66f46ce..62016081 100644 --- a/flask_user/__init__.py +++ b/flask_user/__init__.py @@ -5,7 +5,7 @@ :license: Simplified BSD License, see LICENSE.txt for more details.""" from passlib.context import CryptContext -from flask import Blueprint, current_app +from flask import Blueprint, current_app, url_for from flask_login import LoginManager, UserMixin as LoginUserMixin, make_secure_token from flask_user.db_adapters import DBAdapter from .db_adapters import SQLAlchemyAdapter @@ -16,6 +16,7 @@ from . import tokens from . import translations from . import views +from . import signals from .translations import get_translations # Enable the following: from flask.ext.user import current_user @@ -26,7 +27,7 @@ # Enable the following: from flask.ext.user import user_logged_in from .signals import * -__version__ = '0.6.3' +__version__ = '0.6.4' def _flask_user_context_processor(): """ Make 'user_manager' available to Jinja2 templates""" @@ -35,7 +36,16 @@ def _flask_user_context_processor(): class UserManager(object): """ This is the Flask-User object that manages the User management process.""" - def __init__(self, db_adapter, app=None, + def __init__(self, db_adapter=None, app=None, **kwargs): + """ Create the UserManager object """ + self.db_adapter = db_adapter + self.app = app + + if db_adapter is not None and app is not None: + self.init_app(app, db_adapter, **kwargs) + + + def init_app(self, app, db_adapter=None, # Forms add_email_form=forms.AddEmailForm, change_password_form=forms.ChangePasswordForm, @@ -73,8 +83,10 @@ def __init__(self, db_adapter, app=None, token_manager=tokens.TokenManager(), legacy_check_password_hash=None ): - """ Initialize the UserManager with custom or built-in attributes""" - self.db_adapter = db_adapter + """ Initialize the UserManager object """ + self.app = app + if db_adapter is not None: + self.db_adapter = db_adapter # Forms self.add_email_form = add_email_form self.change_password_form = change_password_form @@ -112,11 +124,6 @@ def __init__(self, db_adapter, app=None, self.send_email_function = send_email_function self.legacy_check_password_hash = legacy_check_password_hash - self.app = app - if app: - self.init_app(app) - - def init_app(self, app): """ Initialize app.user_manager.""" # Bind Flask-USER to app app.user_manager = self @@ -338,6 +345,24 @@ def username_is_available(self, new_username): # See if new_username is available return self.find_user_by_username(new_username)==None + def send_reset_password_email(self, email): + # Find user by email + user, user_email = self.find_user_by_email(email) + if user: + # Generate reset password link + token = self.generate_token(int(user.get_id())) + reset_password_link = url_for('user.reset_password', token=token, _external=True) + + # Send forgot password email + emails.send_forgot_password_email(user, user_email, reset_password_link) + + # Store token + if hasattr(user, 'reset_password_token'): + self.db_adapter.update_object(user, reset_password_token=token) + self.db_adapter.commit() + + # Send forgot_password signal + signals.user_forgot_password.send(current_app._get_current_object(), user=user) class UserMixin(LoginUserMixin): diff --git a/flask_user/emails.py b/flask_user/emails.py index 9a744ec0..acbd3af8 100644 --- a/flask_user/emails.py +++ b/flask_user/emails.py @@ -54,16 +54,16 @@ class SendEmailError(Exception): except smtplib.SMTPAuthenticationError: raise SendEmailError('SMTP Authentication error: Check your MAIL_USERNAME and MAIL_PASSWORD settings.') -def _get_primary_email(user): +def get_primary_user_email(user): user_manager = current_app.user_manager db_adapter = user_manager.db_adapter if db_adapter.UserEmailClass: user_email = db_adapter.find_first_object(db_adapter.UserEmailClass, user_id=int(user.get_id()), is_primary=True) - return user_email.email if user_email else None + return user_email else: - return user.email + return user def send_confirm_email_email(user, user_email, confirm_email_link): @@ -113,7 +113,9 @@ def send_password_changed_email(user): if not user_manager.send_password_changed_email: return # Retrieve email address from User or UserEmail object - email = _get_primary_email(user) + user_email = get_primary_user_email(user) + assert(user_email) + email = user_email.email assert(email) # Render subject, html message and text message @@ -152,7 +154,9 @@ def send_username_changed_email(user): # pragma: no cover if not user_manager.send_username_changed_email: return # Retrieve email address from User or UserEmail object - email = _get_primary_email(user) + user_email = get_primary_user_email(user) + assert(user_email) + email = user_email.email assert(email) # Render subject, html message and text message diff --git a/flask_user/views.py b/flask_user/views.py index e621f35e..e25f076a 100644 --- a/flask_user/views.py +++ b/flask_user/views.py @@ -33,14 +33,13 @@ def confirm_email(token): flash(_('Invalid confirmation token.'), 'error') return redirect(url_for('user.login')) - # Confirm email by setting User.active=True and User.confirmed_at=utcnow() + # Confirm email by setting User.confirmed_at=utcnow() or UserEmail.confirmed_at=utcnow() + user = None if db_adapter.UserEmailClass: user_email = user_manager.get_user_email_by_id(object_id) if user_email: user_email.confirmed_at = datetime.utcnow() user = user_email.user - else: - user = None else: user_email = None user = user_manager.get_user_by_id(object_id) @@ -192,24 +191,7 @@ def forgot_password(): # Process valid POST if request.method=='POST' and form.validate(): email = form.email.data - - # Find user by email - user, user_email = user_manager.find_user_by_email(email) - if user: - # Generate reset password link - token = user_manager.generate_token(int(user.get_id())) - reset_password_link = url_for('user.reset_password', token=token, _external=True) - - # Send forgot password email - emails.send_forgot_password_email(user, user_email, reset_password_link) - - # Store token - if hasattr(user, 'reset_password_token'): - db_adapter.update_object(user, reset_password_token=token) - db_adapter.commit() - - # Send forgot_password signal - signals.user_forgot_password.send(current_app._get_current_object(), user=user) + user_manager.send_reset_password_email(email) # Prepare one-time system message flash(_("A reset password email has been sent to '%(email)s'. Open that email and follow the instructions to reset your password.", email=email), 'success') @@ -556,6 +538,9 @@ def reset_password(token): user_manager = current_app.user_manager db_adapter = user_manager.db_adapter + if current_user.is_authenticated(): + logout_user() + is_valid, has_expired, user_id = user_manager.verify_token( token, user_manager.reset_password_expiration) @@ -579,6 +564,10 @@ def reset_password(token): flash(_('Your reset password token is invalid.'), 'error') return redirect(_endpoint_url(user_manager.login_endpoint)) + # Mark email as confirmed + user_email = emails.get_primary_user_email(user) + user_email.confirmed_at = datetime.utcnow() + # Initialize form form = user_manager.reset_password_form(request.form) @@ -599,7 +588,7 @@ def reset_password(token): emails.send_password_changed_email(user) # Prepare one-time system message - flash(_("Your password has been reset successfully. Please sign in with your new password"), 'success') + flash(_("Your password has been reset successfully."), 'success') # Auto-login after reset password or redirect to login page next = request.args.get('next', _endpoint_url(user_manager.after_reset_password_endpoint)) @@ -733,3 +722,5 @@ def _endpoint_url(endpoint): if endpoint: url = url_for(endpoint) return url + + diff --git a/setup.py b/setup.py index 8bd24085..da535b88 100644 --- a/setup.py +++ b/setup.py @@ -90,7 +90,7 @@ setup( name='Flask-User', - version='0.6.3', + version='0.6.4', url='http://github.com/lingthio/Flask-User', license='BSD License', author='Ling Thio',