diff options
Diffstat (limited to 'src/web')
-rw-r--r-- | src/web/controllers/user.py | 8 | ||||
-rw-r--r-- | src/web/forms.py | 9 | ||||
-rw-r--r-- | src/web/lib/user_utils.py | 23 | ||||
-rw-r--r-- | src/web/models/__init__.py | 2 | ||||
-rw-r--r-- | src/web/models/user.py | 3 | ||||
-rw-r--r-- | src/web/notifications.py | 6 | ||||
-rw-r--r-- | src/web/templates/admin/dashboard.html | 4 | ||||
-rw-r--r-- | src/web/views/admin.py | 11 | ||||
-rw-r--r-- | src/web/views/api/common.py | 3 | ||||
-rw-r--r-- | src/web/views/user.py | 12 |
10 files changed, 48 insertions, 33 deletions
diff --git a/src/web/controllers/user.py b/src/web/controllers/user.py index d8bf1fa1..ae169b05 100644 --- a/src/web/controllers/user.py +++ b/src/web/controllers/user.py @@ -9,14 +9,6 @@ class UserController(AbstractController): _db_cls = User _user_id_key = 'id' - def unset_activation_key(self, obj_id): - self.update({'id': obj_id}, {'activation_key': ""}) - - def set_activation_key(self, obj_id): - key = str(random.getrandbits(256)).encode("utf-8") - key = hashlib.sha512(key).hexdigest()[:86] - self.update({'id': obj_id}, {'activation_key': key}) - def _handle_password(self, attrs): if attrs.get('password'): attrs['pwdhash'] = generate_password_hash(attrs.pop('password')) diff --git a/src/web/forms.py b/src/web/forms.py index 172f31a8..b17d2f7a 100644 --- a/src/web/forms.py +++ b/src/web/forms.py @@ -99,10 +99,9 @@ class SigninForm(RedirectForm): return False user = User.query.filter(User.email == self.email.data).first() - if user and user.check_password(self.password.data) \ - and user.activation_key == "": + if user and user.check_password(self.password.data) and user.enabled: return True - elif user and user.activation_key != "": + elif user and not user.enabled: flash(lazy_gettext('Account not confirmed'), 'danger') return False else: @@ -207,9 +206,9 @@ class RecoverPasswordForm(Form): return False user = User.query.filter(User.email == self.email.data).first() - if user and user.activation_key == "": + if user and user.enabled: return True - elif user and user.activation_key != "": + elif user and not user.enabled: flash(lazy_gettext('Account not confirmed.'), 'danger') return False else: diff --git a/src/web/lib/user_utils.py b/src/web/lib/user_utils.py new file mode 100644 index 00000000..78468379 --- /dev/null +++ b/src/web/lib/user_utils.py @@ -0,0 +1,23 @@ + + +from itsdangerous import URLSafeTimedSerializer + +from bootstrap import application + + +def generate_confirmation_token(email): + serializer = URLSafeTimedSerializer(app.config['SECRET_KEY']) + return serializer.dumps(email, salt=app.config['SECURITY_PASSWORD_SALT']) + + +def confirm_token(token, expiration=3600): + serializer = URLSafeTimedSerializer(app.config['SECRET_KEY']) + try: + email = serializer.loads( + token, + salt=app.config['SECURITY_PASSWORD_SALT'], + max_age=expiration + ) + except: + return False + return email diff --git a/src/web/models/__init__.py b/src/web/models/__init__.py index e6615ab4..d9489dbb 100644 --- a/src/web/models/__init__.py +++ b/src/web/models/__init__.py @@ -96,7 +96,7 @@ def db_create(db): "root@jarr.localhost"), pwdhash=generate_password_hash( os.environ.get("ADMIN_PASSWORD", "password")), - activation_key="") + enabled=True) user1.roles.extend([role_admin, role_user]) db.session.add(user1) diff --git a/src/web/models/user.py b/src/web/models/user.py index cdbfb457..1a276f7e 100644 --- a/src/web/models/user.py +++ b/src/web/models/user.py @@ -45,8 +45,7 @@ class User(db.Model, UserMixin): email = db.Column(db.String(254), index=True, unique=True) pwdhash = db.Column(db.String()) roles = db.relationship('Role', backref='user', lazy='dynamic') - activation_key = db.Column(db.String(128), default=hashlib.sha512( - str(random.getrandbits(256)).encode("utf-8")).hexdigest()[:86]) + enabled = db.Column(db.Boolean(), default=False) date_created = db.Column(db.DateTime(), default=datetime.now) last_seen = db.Column(db.DateTime(), default=datetime.now) feeds = db.relationship('Feed', backref='subscriber', lazy='dynamic', diff --git a/src/web/notifications.py b/src/web/notifications.py index c0d4fb1c..309da2a3 100644 --- a/src/web/notifications.py +++ b/src/web/notifications.py @@ -21,6 +21,7 @@ import conf from web import emails +from web.lib.user_utils import generate_confirmation_token def information_message(subject, plaintext): @@ -30,7 +31,7 @@ def information_message(subject, plaintext): from web.models import User users = User.query.all() # Only send email for activated accounts. - user_emails = [user.email for user in users if user.activation_key == ""] + user_emails = [user.email for user in users if user.enabled] # Postmark has a limit of twenty recipients per message in total. for i in xrange(0, len(user_emails), 19): emails.send(to=conf.NOTIFICATION_EMAIL, @@ -41,8 +42,9 @@ def new_account_notification(user): """ Account creation notification. """ + token = generate_confirmation_token(user.email) plaintext = """Hello,\n\nYour account has been created. Click on the following link to confirm it:\n%s\n\nSee you,""" % \ - (conf.PLATFORM_URL + 'user/confirm_account/' + user.activation_key) + (conf.PLATFORM_URL + 'user/confirm_account/' + token) emails.send(to=user.email, bcc=conf.NOTIFICATION_EMAIL, subject="[jarr] Account creation", plaintext=plaintext) diff --git a/src/web/templates/admin/dashboard.html b/src/web/templates/admin/dashboard.html index 22e82349..57b20bb5 100644 --- a/src/web/templates/admin/dashboard.html +++ b/src/web/templates/admin/dashboard.html @@ -18,7 +18,7 @@ </thead> <tbody> {% for user in users|sort(attribute="last_seen")|reverse %} - <tr {% if user.activation_key != "" %}class="warning"{% endif %}> + <tr {% if not user.enabled %}class="warning"{% endif %}> <td>{{ loop.index }}</td> <td>{{ user.nickname }}{% if user.id == current_user.id %} (It's you!){% endif %}</td> <td><a href="mailto:{{ user.email }}">{{ user.email }}</a></td> @@ -28,7 +28,7 @@ <a href="{{ url_for("admin.user_form", user_id=user.id) }}"><i class="glyphicon glyphicon-edit" title="{{ _('Edit this user') }}"></i></a> {% if user.id != current_user.id %} <a href="{{ url_for("admin.toggle_user", user_id=user.id) }}"> - {% if user.activation_key == "" %} + {% if user.enabled %} <i class="glyphicon glyphicon-ban-circle" title="{{ _("Disable this account") }}"></i> {% else %} <i class="glyphicon glyphicon-ok-circle" title="{{ _("Enable this account") }}"></i> diff --git a/src/web/views/admin.py b/src/web/views/admin.py index 30758f63..832c134d 100644 --- a/src/web/views/admin.py +++ b/src/web/views/admin.py @@ -83,13 +83,13 @@ def process_user_form(user_id=None): flash(gettext('User %(nick)s successfully updated', nick=user.nickname), 'success') else: - # Create a new user + # Create a new user (by the admin) user = user_contr.create(nickname=form.nickname.data, email=form.email.data, password=form.password.data, roles=[role_user], refresh_rate=form.refresh_rate.data, - activation_key="") + enabled=True) flash(gettext('User %(nick)s successfully created', nick=user.nickname), 'success') return redirect(url_for('admin.user_form', user_id=user.id)) @@ -144,12 +144,11 @@ def toggle_user(user_id=None): flash(gettext('This user does not exist.'), 'danger') return redirect(url_for('admin.dashboard')) - if user.activation_key != "": - + if not user.enabled: # Send the confirmation email try: notifications.new_account_activation(user) - user_contr.unset_activation_key(user.id) + user_contr.update({'id': user.id}, {'enabled': True}) message = gettext('Account of the user %(nick)s successfully ' 'activated.', nick=user.nickname) except Exception as error: @@ -158,7 +157,7 @@ def toggle_user(user_id=None): return redirect(url_for('admin.dashboard')) else: - user_contr.set_activation_key(user.id) + user_contr.update({'id': user.id}, {'enabled': False}) message = gettext('Account of the user %(nick)s successfully disabled', nick=user.nickname) flash(message, 'success') diff --git a/src/web/views/api/common.py b/src/web/views/api/common.py index 3476cad9..c155a254 100644 --- a/src/web/views/api/common.py +++ b/src/web/views/api/common.py @@ -54,8 +54,7 @@ def authenticate(func): if auth is not None: user = User.query.filter( User.nickname == auth.username).first() - if user and user.check_password(auth.password) \ - and user.activation_key == "": + if user and user.check_password(auth.password) and user.enabled: g.user = user logged_in = True if logged_in: diff --git a/src/web/views/user.py b/src/web/views/user.py index 754d3b9a..0f9fe612 100644 --- a/src/web/views/user.py +++ b/src/web/views/user.py @@ -7,6 +7,7 @@ from flask.ext.login import login_required import conf from web import utils, notifications +from web.lib.user_utils import confirm_token from web.controllers import (UserController, FeedController, ArticleController) from web.forms import ProfileForm, RecoverPasswordForm @@ -102,16 +103,17 @@ def delete_account(): return redirect(url_for('login')) -@user_bp.route('/confirm_account/<string:activation_key>', methods=['GET']) -def confirm_account(activation_key=None): +@user_bp.route('/confirm_account/<string:token>', methods=['GET']) +def confirm_account(token=None): """ Confirm the account of a user. """ user_contr = UserController() - if activation_key != "": - user = user_contr.read(activation_key=activation_key).first() + if token != "": + email = confirm_token(token, expiration=3600) + user = user_contr.read(email=email).first() if user is not None: - user_contr.update({'id': user.id}, {'activation_key': ''}) + user_contr.update({'id': user.id}, {'enabled': True}) flash(gettext('Your account has been confirmed.'), 'success') else: flash(gettext('Impossible to confirm this account.'), 'danger') |