def signup_get():
return render_template("signup.html")
+def _check_password(password, password_confirm):
+ error = False
+ if password != password_confirm:
+ flash_and_log("Passwords do not match", "error")
+ error = True
+ elif len(password) < 8:
+ flash_and_log("Password must be at least 8 characters", "error")
+ error = True
+ return error
+
+def _hash_password(password):
+ return bcrypt.hashpw(password.encode(), bcrypt.gensalt())
+
@bp.post("/signup")
def signup_post():
username = request.form["username"]
flash_and_log("Username cannot be more than 30 characters", "error")
error = True
- elif password != password_confirm:
- flash_and_log("Passwords do not match", "error")
- error = True
- elif len(password) < 8:
- flash_and_log("Password must be at least 8 characters", "error")
- error = True
+ error = error or _check_password(password, password_confirm)
if db.query("select * from users where username = ?", [username], one=True):
flash_and_log(f"Username '{username}' is already taken", "error")
current_app.logger.info("Failed signup attempt")
return redirect(request.referrer)
- password = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
+ password = _hash_password(password)
timestamp = datetime.now(timezone.utc).isoformat()
user_data = db.query(
return render_template("login.html")
+@bp.get("/change-password")
+def password_reset():
+ return render_template("password-reset.html")
+
+@bp.post("/change-password")
+def password_reset_post():
+ error = False
+
+ username = request.form["username"]
+ old_password = request.form["old_password"]
+ password = request.form["password"]
+ password_confirm = request.form["password_confirm"]
+
+ # Get user from DB
+ user_data = db.query("select * from users where username = ?", [username], one=True)
+
+ # Check username
+ if not user_data:
+ flash("Invalid username/password", "error")
+ error = True
+
+ # Check old password
+ elif not bcrypt.checkpw(old_password.encode(), user_data["password"]):
+ flash("Invalid username/password", "error")
+ error = True
+
+ # Check new password
+ error = error or _check_password(password, password_confirm)
+
+ # Reload page on error
+ if error:
+ current_app.logger.info(f"Failed password reset attempt for {username}")
+ return redirect(request.referrer)
+
+ # OK - Update password
+ password = _hash_password(password)
+ db.query("update users set password = ? where userid = ?", [password, user_data["userid"]])
+ db.commit()
+
+ flash("Password updated. Please sign in to continue.", "success")
+ current_app.logger.info(f"Changed password for {username}")
+
+ # Force logout
+ if "username" in session:
+ session.pop("username")
+ if "userid" in session:
+ session.pop("userid")
+
+ return redirect("/login")
+
@bp.get("/logout")
def logout():
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}Change Password{% endblock %}
+
+{% block body %}
+
+<h1>change password</h1>
+
+<p>
+ Forgot your password? Please email cfull at
+ <a href="mailto:mail@littlesong.place">mail@littlesong.place</a> to reset
+ it.
+</p>
+
+<form method="post" action="/change-password">
+ <div class="signup-form">
+ <label for="username">Username</label><br>
+ <input type="text" name="username" maxlength="30" required></input>
+ </div>
+
+ <div class="signup-form">
+ <label for="old_password">Old Password</label><br>
+ <input type="password" name="old_password" maxlength="100" required></input>
+ </div>
+
+ <div class="signup-form">
+ <label for="password">New Password</label><br>
+ <input type="password" name="password" maxlength="100" required></input>
+ </div>
+
+ <div class="signup-form">
+ <label for="password_confirm">Confirm New Password</label><br>
+ <input type="password" name="password_confirm" maxlength="100" required></input>
+ </div>
+
+ <div class="signup-form">
+ <input type="submit" value="Change Password"></input>
+ </div>
+</form>
+
+{% endblock %}
+