mirror of
https://github.com/aljazceru/CTFd.git
synced 2025-12-18 14:34:21 +01:00
Strip spaces on registration and have reset password use email address instead of names (#1218)
* Usernames are now properly stripped before being used in registration checks * Reset password function now uses email addresses instead of user names for tokens * Prevent MLC users from resetting their password
This commit is contained in:
53
CTFd/auth.py
53
CTFd/auth.py
@@ -98,7 +98,7 @@ def confirm(data=None):
|
|||||||
def reset_password(data=None):
|
def reset_password(data=None):
|
||||||
if data is not None:
|
if data is not None:
|
||||||
try:
|
try:
|
||||||
name = unserialize(data, max_age=1800)
|
email_address = unserialize(data, max_age=1800)
|
||||||
except (BadTimeSignature, SignatureExpired):
|
except (BadTimeSignature, SignatureExpired):
|
||||||
return render_template(
|
return render_template(
|
||||||
"reset_password.html", errors=["Your link has expired"]
|
"reset_password.html", errors=["Your link has expired"]
|
||||||
@@ -111,20 +111,35 @@ def reset_password(data=None):
|
|||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
return render_template("reset_password.html", mode="set")
|
return render_template("reset_password.html", mode="set")
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
user = Users.query.filter_by(name=name).first_or_404()
|
password = request.form.get("password", "").strip()
|
||||||
user.password = request.form["password"].strip()
|
user = Users.query.filter_by(email=email_address).first_or_404()
|
||||||
|
if user.oauth_id:
|
||||||
|
return render_template(
|
||||||
|
"reset_password.html",
|
||||||
|
errors=[
|
||||||
|
"Your account was registered via an authentication provider and does not have an associated password. Please login via your authentication provider."
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
pass_short = len(password) == 0
|
||||||
|
if pass_short:
|
||||||
|
return render_template(
|
||||||
|
"reset_password.html", errors=["Please pick a longer password"]
|
||||||
|
)
|
||||||
|
|
||||||
|
user.password = password
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
log(
|
log(
|
||||||
"logins",
|
"logins",
|
||||||
format="[{date}] {ip} - successful password reset for {name}",
|
format="[{date}] {ip} - successful password reset for {name}",
|
||||||
name=name,
|
name=user.name,
|
||||||
)
|
)
|
||||||
db.session.close()
|
db.session.close()
|
||||||
return redirect(url_for("auth.login"))
|
return redirect(url_for("auth.login"))
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
email_address = request.form["email"].strip()
|
email_address = request.form["email"].strip()
|
||||||
team = Users.query.filter_by(email=email_address).first()
|
user = Users.query.filter_by(email=email_address).first()
|
||||||
|
|
||||||
get_errors()
|
get_errors()
|
||||||
|
|
||||||
@@ -134,7 +149,7 @@ def reset_password(data=None):
|
|||||||
errors=["Email could not be sent due to server misconfiguration"],
|
errors=["Email could not be sent due to server misconfiguration"],
|
||||||
)
|
)
|
||||||
|
|
||||||
if not team:
|
if not user:
|
||||||
return render_template(
|
return render_template(
|
||||||
"reset_password.html",
|
"reset_password.html",
|
||||||
errors=[
|
errors=[
|
||||||
@@ -142,7 +157,15 @@ def reset_password(data=None):
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
email.forgot_password(email_address, team.name)
|
if user.oauth_id:
|
||||||
|
return render_template(
|
||||||
|
"reset_password.html",
|
||||||
|
errors=[
|
||||||
|
"The email address associated with this account was registered via an authentication provider and does not have an associated password. Please login via your authentication provider."
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
email.forgot_password(email_address)
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"reset_password.html",
|
"reset_password.html",
|
||||||
@@ -159,9 +182,9 @@ def reset_password(data=None):
|
|||||||
def register():
|
def register():
|
||||||
errors = get_errors()
|
errors = get_errors()
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
name = request.form["name"]
|
name = request.form.get("name", "").strip()
|
||||||
email_address = request.form["email"]
|
email_address = request.form.get("email", "").strip().lower()
|
||||||
password = request.form["password"]
|
password = request.form.get("password", "").strip()
|
||||||
|
|
||||||
name_len = len(name) == 0
|
name_len = len(name) == 0
|
||||||
names = Users.query.add_columns("name", "id").filter_by(name=name).first()
|
names = Users.query.add_columns("name", "id").filter_by(name=name).first()
|
||||||
@@ -170,9 +193,9 @@ def register():
|
|||||||
.filter_by(email=email_address)
|
.filter_by(email=email_address)
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
pass_short = len(password.strip()) == 0
|
pass_short = len(password) == 0
|
||||||
pass_long = len(password) > 128
|
pass_long = len(password) > 128
|
||||||
valid_email = validators.validate_email(request.form["email"])
|
valid_email = validators.validate_email(email_address)
|
||||||
team_name_email_check = validators.validate_email(name)
|
team_name_email_check = validators.validate_email(name)
|
||||||
|
|
||||||
if not valid_email:
|
if not valid_email:
|
||||||
@@ -206,11 +229,7 @@ def register():
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
user = Users(
|
user = Users(name=name, email=email_address, password=password)
|
||||||
name=name.strip(),
|
|
||||||
email=email_address.lower(),
|
|
||||||
password=password.strip(),
|
|
||||||
)
|
|
||||||
db.session.add(user)
|
db.session.add(user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ class TeamSchema(ma.ModelSchema):
|
|||||||
name = data.get("name")
|
name = data.get("name")
|
||||||
if name is None:
|
if name is None:
|
||||||
return
|
return
|
||||||
|
name = name.strip()
|
||||||
|
|
||||||
existing_team = Teams.query.filter_by(name=name).first()
|
existing_team = Teams.query.filter_by(name=name).first()
|
||||||
current_team = get_current_team()
|
current_team = get_current_team()
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ class UserSchema(ma.ModelSchema):
|
|||||||
name = data.get("name")
|
name = data.get("name")
|
||||||
if name is None:
|
if name is None:
|
||||||
return
|
return
|
||||||
|
name = name.strip()
|
||||||
|
|
||||||
existing_user = Users.query.filter_by(name=name).first()
|
existing_user = Users.query.filter_by(name=name).first()
|
||||||
current_user = get_current_user()
|
current_user = get_current_user()
|
||||||
@@ -95,6 +96,7 @@ class UserSchema(ma.ModelSchema):
|
|||||||
email = data.get("email")
|
email = data.get("email")
|
||||||
if email is None:
|
if email is None:
|
||||||
return
|
return
|
||||||
|
email = email.strip()
|
||||||
|
|
||||||
existing_user = Users.query.filter_by(email=email).first()
|
existing_user = Users.query.filter_by(email=email).first()
|
||||||
current_user = get_current_user()
|
current_user = get_current_user()
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ def new():
|
|||||||
|
|
||||||
return render_template("teams/new_team.html", infos=infos, errors=errors)
|
return render_template("teams/new_team.html", infos=infos, errors=errors)
|
||||||
elif request.method == "POST":
|
elif request.method == "POST":
|
||||||
teamname = request.form.get("name")
|
teamname = request.form.get("name", "").strip()
|
||||||
passphrase = request.form.get("password", "").strip()
|
passphrase = request.form.get("password", "").strip()
|
||||||
errors = get_errors()
|
errors = get_errors()
|
||||||
|
|
||||||
|
|||||||
@@ -16,17 +16,17 @@ def sendmail(addr, text, subject="Message from {ctf_name}"):
|
|||||||
return False, "No mail settings configured"
|
return False, "No mail settings configured"
|
||||||
|
|
||||||
|
|
||||||
def forgot_password(email, team_name):
|
def forgot_password(email):
|
||||||
token = serialize(team_name)
|
token = serialize(email)
|
||||||
text = """Did you initiate a password reset? Click the following link to reset your password:
|
text = """Did you initiate a password reset? If you didn't initiate this request you can ignore this email.
|
||||||
|
|
||||||
|
Click the following link to reset your password:
|
||||||
{0}/{1}
|
{0}/{1}
|
||||||
|
|
||||||
""".format(
|
""".format(
|
||||||
url_for("auth.reset_password", _external=True), token
|
url_for("auth.reset_password", _external=True), token
|
||||||
)
|
)
|
||||||
|
subject = "Password Reset Request from {ctf_name}"
|
||||||
return sendmail(email, text)
|
return sendmail(addr=email, text=text, subject=subject)
|
||||||
|
|
||||||
|
|
||||||
def verify_email_address(addr):
|
def verify_email_address(addr):
|
||||||
@@ -36,7 +36,8 @@ def verify_email_address(addr):
|
|||||||
url=url_for("auth.confirm", _external=True),
|
url=url_for("auth.confirm", _external=True),
|
||||||
token=token,
|
token=token,
|
||||||
)
|
)
|
||||||
return sendmail(addr, text)
|
subject = "Confirm your account for {ctf_name}"
|
||||||
|
return sendmail(addr=addr, text=text, subject=subject)
|
||||||
|
|
||||||
|
|
||||||
def user_created_notification(addr, name, password):
|
def user_created_notification(addr, name, password):
|
||||||
|
|||||||
@@ -48,6 +48,13 @@ def test_register_duplicate_username():
|
|||||||
password="password",
|
password="password",
|
||||||
raise_for_error=False,
|
raise_for_error=False,
|
||||||
)
|
)
|
||||||
|
register_user(
|
||||||
|
app,
|
||||||
|
name="admin ",
|
||||||
|
email="admin2@ctfd.io",
|
||||||
|
password="password",
|
||||||
|
raise_for_error=False,
|
||||||
|
)
|
||||||
user_count = Users.query.count()
|
user_count = Users.query.count()
|
||||||
assert user_count == 2 # There's the admin user and the first created user
|
assert user_count == 2 # There's the admin user and the first created user
|
||||||
destroy_ctfd(app)
|
destroy_ctfd(app)
|
||||||
@@ -353,11 +360,15 @@ def test_user_can_reset_password(mock_smtp):
|
|||||||
|
|
||||||
# Build the email
|
# Build the email
|
||||||
msg = (
|
msg = (
|
||||||
"""Did you initiate a password reset? Click the following link to reset """
|
"Did you initiate a password reset? If you didn't initiate this request you can ignore this email."
|
||||||
"""your password:\n\nhttp://localhost/reset_password/InVzZXIxIg.TxD0vg.-gvVg-KVy0RWkiclAE6JViv1I0M\n\n"""
|
"\n\nClick the following link to reset your password:\n"
|
||||||
|
"http://localhost/reset_password/InVzZXJAdXNlci5jb20i.TxD0vg.28dY_Gzqb1TH9nrcE_H7W8YFM-U\n"
|
||||||
)
|
)
|
||||||
|
ctf_name = get_config("ctf_name")
|
||||||
email_msg = MIMEText(msg)
|
email_msg = MIMEText(msg)
|
||||||
email_msg["Subject"] = "Message from CTFd"
|
email_msg["Subject"] = "Password Reset Request from {ctf_name}".format(
|
||||||
|
ctf_name=ctf_name
|
||||||
|
)
|
||||||
email_msg["From"] = from_addr
|
email_msg["From"] = from_addr
|
||||||
email_msg["To"] = to_addr
|
email_msg["To"] = to_addr
|
||||||
|
|
||||||
@@ -374,9 +385,11 @@ def test_user_can_reset_password(mock_smtp):
|
|||||||
data = {"nonce": sess.get("nonce"), "password": "passwordtwo"}
|
data = {"nonce": sess.get("nonce"), "password": "passwordtwo"}
|
||||||
|
|
||||||
# Do the password reset
|
# Do the password reset
|
||||||
client.get("/reset_password/InVzZXIxIg.TxD0vg.-gvVg-KVy0RWkiclAE6JViv1I0M")
|
client.get(
|
||||||
|
"/reset_password/InVzZXJAdXNlci5jb20i.TxD0vg.28dY_Gzqb1TH9nrcE_H7W8YFM-U"
|
||||||
|
)
|
||||||
client.post(
|
client.post(
|
||||||
"/reset_password/InVzZXIxIg.TxD0vg.-gvVg-KVy0RWkiclAE6JViv1I0M",
|
"/reset_password/InVzZXJAdXNlci5jb20i.TxD0vg.28dY_Gzqb1TH9nrcE_H7W8YFM-U",
|
||||||
data=data,
|
data=data,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -156,7 +156,6 @@ def test_sendmail_with_mailgun_from_db_config(fake_post_request):
|
|||||||
|
|
||||||
|
|
||||||
@patch("smtplib.SMTP")
|
@patch("smtplib.SMTP")
|
||||||
@freeze_time("2012-01-14 03:21:34")
|
|
||||||
def test_verify_email(mock_smtp):
|
def test_verify_email(mock_smtp):
|
||||||
"""Does verify_email send emails"""
|
"""Does verify_email send emails"""
|
||||||
app = create_ctfd()
|
app = create_ctfd()
|
||||||
@@ -171,6 +170,7 @@ def test_verify_email(mock_smtp):
|
|||||||
from_addr = get_config("mailfrom_addr") or app.config.get("MAILFROM_ADDR")
|
from_addr = get_config("mailfrom_addr") or app.config.get("MAILFROM_ADDR")
|
||||||
to_addr = "user@user.com"
|
to_addr = "user@user.com"
|
||||||
|
|
||||||
|
with freeze_time("2012-01-14 03:21:34"):
|
||||||
verify_email_address(to_addr)
|
verify_email_address(to_addr)
|
||||||
|
|
||||||
# This is currently not actually validated
|
# This is currently not actually validated
|
||||||
@@ -182,7 +182,9 @@ def test_verify_email(mock_smtp):
|
|||||||
|
|
||||||
ctf_name = get_config("ctf_name")
|
ctf_name = get_config("ctf_name")
|
||||||
email_msg = MIMEText(msg)
|
email_msg = MIMEText(msg)
|
||||||
email_msg["Subject"] = "Message from {0}".format(ctf_name)
|
email_msg["Subject"] = "Confirm your account for {ctf_name}".format(
|
||||||
|
ctf_name=ctf_name
|
||||||
|
)
|
||||||
email_msg["From"] = from_addr
|
email_msg["From"] = from_addr
|
||||||
email_msg["To"] = to_addr
|
email_msg["To"] = to_addr
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user