diff --git a/CTFd/auth.py b/CTFd/auth.py index e6c04b92..c4d930d8 100644 --- a/CTFd/auth.py +++ b/CTFd/auth.py @@ -7,8 +7,6 @@ from flask import ( session, Blueprint, ) -from passlib.hash import bcrypt_sha256 - from CTFd.models import db, Users, Teams from CTFd.utils import get_config, get_app_config @@ -17,6 +15,7 @@ from CTFd.utils import user as current_user from CTFd.utils import config, validators from CTFd.utils import email from CTFd.utils.security.auth import login_user, logout_user +from CTFd.utils.security.passwords import hash_password, check_password from CTFd.utils.logging import log from CTFd.utils.decorators.visibility import check_registration_visibility from CTFd.utils.modes import TEAMS_MODE, USERS_MODE @@ -46,8 +45,8 @@ def confirm(data=None): except (BadSignature, TypeError, base64.binascii.Error): return render_template('confirm.html', errors=['Your confirmation token is invalid']) - team = Users.query.filter_by(email=user_email).first_or_404() - team.verified = True + user = Users.query.filter_by(email=user_email).first_or_404() + user.verified = True log('registrations', format="[{date}] {ip} - successful password reset for {name}") db.session.commit() db.session.close() @@ -59,24 +58,19 @@ def confirm(data=None): if not current_user.authed(): return redirect(url_for('auth.login')) - team = Users.query.filter_by(id=session['id']).first_or_404() + user = Users.query.filter_by(id=session['id']).first_or_404() + if user.verified: + return redirect(url_for('views.settings')) if data is None: if request.method == "POST": # User wants to resend their confirmation email - if team.verified: - return redirect(url_for('views.profile')) - else: - email.verify_email_address(team.email) - log('registrations', format="[{date}] {ip} - {name} initiated a confirmation email resend") - return render_template('confirm.html', team=team, infos=['Your confirmation email has been resent!']) + email.verify_email_address(user.email) + log('registrations', format="[{date}] {ip} - {name} initiated a confirmation email resend") + return render_template('confirm.html', user=user, infos=['Your confirmation email has been resent!']) elif request.method == "GET": # User has been directed to the confirm page - team = Users.query.filter_by(id=session['id']).first_or_404() - if team.verified: - # If user is already verified, redirect to their profile - return redirect(url_for('views.profile')) - return render_template('confirm.html', team=team) + return render_template('confirm.html', user=user) @auth.route('/reset_password', methods=['POST', 'GET']) @@ -94,10 +88,10 @@ def reset_password(data=None): if request.method == "GET": return render_template('reset_password.html', mode='set') if request.method == "POST": - team = Users.query.filter_by(name=name).first_or_404() - team.password = bcrypt_sha256.encrypt(request.form['password'].strip()) + user = Users.query.filter_by(name=name).first_or_404() + user.password = request.form['password'].strip() db.session.commit() - log('logins', format="[{date}] {ip} - successful password reset for {name}") + log('logins', format="[{date}] {ip} - successful password reset for {name}", name=name) db.session.close() return redirect(url_for('auth.login')) @@ -226,7 +220,7 @@ def login(): user = Users.query.filter_by(name=name).first() if user: - if user and bcrypt_sha256.verify(request.form['password'], user.password): + if user and check_password(request.form['password'], user.password): session.regenerate() login_user(user) diff --git a/CTFd/themes/admin/templates/configs/email.html b/CTFd/themes/admin/templates/configs/email.html index 46f80ecc..3b777c0b 100644 --- a/CTFd/themes/admin/templates/configs/email.html +++ b/CTFd/themes/admin/templates/configs/email.html @@ -11,26 +11,37 @@
- - + +
- +
- +
@@ -42,20 +53,32 @@
- + {% if mail_username is defined and mail_username != None %} - + {% endif %} - +
- + {% if mail_password is defined and mail_password != None %} - + {% endif %} - +
Uncheck setting and update to remove username and password
@@ -76,15 +99,23 @@
- +
- +
diff --git a/CTFd/themes/core/templates/confirm.html b/CTFd/themes/core/templates/confirm.html index f2b8967b..3c204175 100644 --- a/CTFd/themes/core/templates/confirm.html +++ b/CTFd/themes/core/templates/confirm.html @@ -28,9 +28,9 @@ aria-hidden="true">×
{% endfor %} - {% if team %} + {% if user %}

- We've sent a confirmation email to {{ team.email }} + We've sent a confirmation email to {{ user.email }}


@@ -40,7 +40,9 @@ {% endif %} - {% if username %} +
+ + {% if name %}

Need to resend the confirmation email? diff --git a/tests/users/test_auth.py b/tests/users/test_auth.py index 64e7e91a..d0e4dd81 100644 --- a/tests/users/test_auth.py +++ b/tests/users/test_auth.py @@ -4,6 +4,7 @@ from CTFd.models import Users from CTFd.utils import set_config, get_config from CTFd.utils.security.signing import serialize +from CTFd.utils.security.passwords import hash_password, check_password from freezegun import freeze_time from tests.helpers import * from mock import patch @@ -133,11 +134,10 @@ def test_user_isnt_admin(): destroy_ctfd(app) -@freeze_time("2019-02-24 03:21:34") def test_expired_confirmation_links(): """Test that expired confirmation links are reported to the user""" app = create_ctfd() - with app.app_context(): + with app.app_context(), freeze_time("2019-02-24 03:21:34"): set_config('verify_emails', True) register_user(app, email="user@user.com") @@ -172,7 +172,6 @@ def test_invalid_confirmation_links(): destroy_ctfd(app) -@freeze_time("2019-02-24 03:21:34") def test_expired_reset_password_link(): """Test that expired reset password links are reported to the user""" app = create_ctfd() @@ -185,7 +184,7 @@ def test_expired_reset_password_link(): register_user(app, name="user1", email="user@user.com") - with app.test_client() as client: + with app.test_client() as client, freeze_time("2019-02-24 03:21:34"): # user@user.com "2012-01-14 03:21:34" forgot_link = 'http://localhost/reset_password/InVzZXJAdXNlci5jb20i.TxD0vg.cAGwAy8cK1T0saEEbrDEBVF2plI' r = client.get(forgot_link) @@ -230,11 +229,10 @@ def test_contact_for_password_reset(): @patch('smtplib.SMTP') -@freeze_time("2012-01-14 03:21:34") def test_user_can_confirm_email(mock_smtp): """Test that a user is capable of confirming their email address""" app = create_ctfd() - with app.app_context(): + with app.app_context(), freeze_time("2012-01-14 03:21:34"): # Set CTFd to only allow confirmed users and send emails set_config('verify_emails', True) set_config('mail_server', 'localhost') @@ -251,6 +249,9 @@ def test_user_can_confirm_email(mock_smtp): client = login_as_user(app, name="user1", password="password") + r = client.get('http://localhost/confirm') + assert "Need to resend the confirmation email?" in r.get_data(as_text=True) + # smtp.sendmail was called mock_smtp.return_value.sendmail.assert_called() @@ -258,6 +259,9 @@ def test_user_can_confirm_email(mock_smtp): data = { "nonce": sess.get('nonce') } + r = client.post('http://localhost/confirm', data=data) + assert "confirmation email has been resent" in r.get_data(as_text=True) + r = client.get('/challenges') assert r.location == "http://localhost/confirm" # We got redirected to /confirm @@ -269,16 +273,18 @@ def test_user_can_confirm_email(mock_smtp): # The team is now verified user = Users.query.filter_by(email='user@user.com').first() assert user.verified is True + + r = client.get('http://localhost/confirm') + assert r.location == "http://localhost/settings" destroy_ctfd(app) @patch('smtplib.SMTP') -@freeze_time("2012-01-14 03:21:34") def test_user_can_reset_password(mock_smtp): """Test that a user is capable of resetting their password""" from email.mime.text import MIMEText app = create_ctfd() - with app.app_context(): + with app.app_context(), freeze_time("2012-01-14 03:21:34"): # Set CTFd to send emails set_config('mail_server', 'localhost') set_config('mail_port', 25) @@ -333,7 +339,7 @@ def test_user_can_reset_password(mock_smtp): # Make sure that the user's password changed user = Users.query.filter_by(email="user@user.com").first() - assert user.password != user_password_saved + assert check_password('passwordtwo', user.password) destroy_ctfd(app)