From adb39a4aa8f1098ee474c1d9deb45c9fb7a65388 Mon Sep 17 00:00:00 2001 From: NguyenND Date: Fri, 26 Jun 2026 13:40:34 -0400 Subject: [PATCH] Jun 26 - Implement registration function for user to register account --- config.py | 9 ++++ models.py | 29 +++++++++++++ routes/admin_settings.py | 21 ++++++++-- routes/auth.py | 50 ++++++++++++++++++++++- templates/admin/settings.html | 23 +++++++++++ templates/login.html | 6 +++ templates/register.html | 77 +++++++++++++++++++++++++++++++++++ 7 files changed, 210 insertions(+), 5 deletions(-) create mode 100644 templates/register.html diff --git a/config.py b/config.py index 0ae8264..84b4cb4 100644 --- a/config.py +++ b/config.py @@ -429,6 +429,15 @@ def initialize_database(): value = IF(value IS NULL OR value = '', VALUES(value), value)""", (k, v), ) + # Seed non-env defaults (INSERT IGNORE so existing values are never overwritten) + default_seeds = [ + ("registration.enabled", "0"), + ] + for k, v in default_seeds: + cursor.execute( + "INSERT IGNORE INTO app_settings (key_name, value) VALUES (%s, %s)", + (k, v), + ) conn.commit() logger.info("app_settings seeded from environment variables (envโ†’DB, blank-only overwrite).") diff --git a/models.py b/models.py index 0774a36..fc8e9a5 100644 --- a/models.py +++ b/models.py @@ -267,6 +267,35 @@ def update_user_profile(user_id: int, full_name: str, email: str): conn.close() +def register_user(username: str, password: str, full_name: str = "", email: str = None): + """Self-service registration. Role is always 'user'. Raises ValueError on duplicates.""" + conn = None + try: + conn = get_connection() + cur = conn.cursor(dictionary=True) + cur.execute("SELECT id FROM users WHERE username=%s", (username,)) + if cur.fetchone(): + raise ValueError("Username is already taken.") + if email: + cur.execute("SELECT id FROM users WHERE email=%s", (email,)) + if cur.fetchone(): + raise ValueError("An account with that email address already exists.") + cur = conn.cursor() + cur.execute( + "INSERT INTO users (username, password, role, full_name, email) VALUES (%s,%s,'user',%s,%s)", + (username, _hash_password(password), full_name or username, email or None), + ) + conn.commit() + new_id = cur.lastrowid + cur.close() + log_action(None, "REGISTER", "users", new_id, + f"New user self-registered: '{username}'.") + return new_id + finally: + if conn: + conn.close() + + def create_user(admin_id, username, password, role, full_name, email=None): conn = None try: diff --git a/routes/admin_settings.py b/routes/admin_settings.py index 27d5b42..68341f6 100644 --- a/routes/admin_settings.py +++ b/routes/admin_settings.py @@ -17,10 +17,12 @@ admin_settings_bp = Blueprint("admin_settings", __name__, url_prefix="/admin/set @admin_settings_bp.route("/") @admin_required def settings(): - email_settings = get_settings_dict("email.") - groq_settings = get_settings_dict("groq.") + email_settings = get_settings_dict("email.") + groq_settings = get_settings_dict("groq.") + registration_settings = get_settings_dict("registration.") return render_template("admin/settings.html", - email=email_settings, groq=groq_settings) + email=email_settings, groq=groq_settings, + registration=registration_settings) @admin_settings_bp.route("/email", methods=["POST"]) @@ -58,6 +60,19 @@ def save_groq(): return redirect(url_for("admin_settings.settings")) +@admin_settings_bp.route("/registration", methods=["POST"]) +@admin_required +def save_registration(): + admin = session["user"] + enabled = "1" if request.form.get("registration_enabled") == "1" else "0" + set_setting("registration.enabled", enabled) + log_action(admin["id"], "UPDATE_REGISTRATION_SETTINGS", "app_settings", None, + f"User self-registration {'enabled' if enabled == '1' else 'disabled'}.") + flash(f"Registration {'enabled' if enabled == '1' else 'disabled'} successfully.", "success") + logger.info(f"Registration setting set to '{enabled}' by admin_id={admin['id']}.") + return redirect(url_for("admin_settings.settings")) + + @admin_settings_bp.route("/test-email", methods=["POST"]) @admin_required def test_email(): diff --git a/routes/auth.py b/routes/auth.py index 3230557..600a179 100644 --- a/routes/auth.py +++ b/routes/auth.py @@ -8,8 +8,9 @@ from models import ( authenticate, check_login_allowed, change_password, log_action, get_user_by_email, create_password_reset_token, get_password_reset_user, consume_password_reset_token, - purge_expired_reset_tokens, update_user_profile, + purge_expired_reset_tokens, update_user_profile, register_user, ) +from config import get_setting from utils.decorators import login_required from utils.email import send_email @@ -59,7 +60,52 @@ def login(): else: error = "Invalid username or password." - return render_template("login.html", error=error) + reg_enabled = get_setting("registration.enabled", "0") == "1" + return render_template("login.html", error=error, reg_enabled=reg_enabled) + + +@auth_bp.route("/register", methods=["GET", "POST"]) +def register(): + if "user" in session: + return redirect(url_for("index")) + + if get_setting("registration.enabled", "0") != "1": + flash("Registration is currently disabled. Contact your administrator.", "warning") + return redirect(url_for("auth.login")) + + if request.method == "GET": + return render_template("register.html") + + username = request.form.get("username", "").strip() + full_name = request.form.get("full_name", "").strip() + email = request.form.get("email", "").strip().lower() + password = request.form.get("password", "") + confirm = request.form.get("confirm_password", "") + + errors = [] + if not username or len(username) < 3: + errors.append("Username must be at least 3 characters.") + if len(password) < 8: + errors.append("Password must be at least 8 characters.") + if password != confirm: + errors.append("Passwords do not match.") + + if errors: + return render_template("register.html", errors=errors, + username=username, full_name=full_name, email=email) + try: + new_id = register_user(username, password, full_name, email or None) + logger.info(f"New user registered: '{username}' id={new_id}.") + flash("Account created successfully. You can now sign in.", "success") + return redirect(url_for("auth.login")) + except ValueError as e: + return render_template("register.html", errors=[str(e)], + username=username, full_name=full_name, email=email) + except Exception as e: + logger.error(f"register error: {e}") + return render_template("register.html", + errors=["An unexpected error occurred. Please try again."], + username=username, full_name=full_name, email=email) @auth_bp.route("/logout") diff --git a/templates/admin/settings.html b/templates/admin/settings.html index 6830069..d8e643c 100644 --- a/templates/admin/settings.html +++ b/templates/admin/settings.html @@ -93,6 +93,29 @@ + +
+

๐Ÿ‘ค User Registration

+
+ + +
+
+ + + +