From 9cb1e9650965b330c4adb078c1db47b804cc7dee Mon Sep 17 00:00:00 2001 From: AzuLX Date: Tue, 13 Jan 2026 23:35:42 +0000 Subject: [PATCH 1/4] Replace the shell zip cmd with rustyzipper --- requirements.txt | 1 + review/routes.py | 45 ++++++++------------------------------------- 2 files changed, 9 insertions(+), 37 deletions(-) diff --git a/requirements.txt b/requirements.txt index 253c62a..e7ba5c9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,6 +9,7 @@ bleach==6.1.0 requests==2.31.0 Werkzeug==3.0.1 resend==2.0.0 +rustyzipper==1.0.5 # Testing pytest==7.4.3 diff --git a/review/routes.py b/review/routes.py index 94e887c..12c1556 100644 --- a/review/routes.py +++ b/review/routes.py @@ -27,7 +27,7 @@ import bcrypt import requests -from subprocess import call +from rustyzipper import compress_file, EncryptionMethod from bson.objectid import ObjectId from review.logger import log_reviewer_operation @@ -498,56 +498,27 @@ def create_password_protected_zip(source_path, dest_path_without_ext, filename_i Returns: Tuple of (success: bool, error_message: str or None) """ - # Move to temp location with desired filename + dest_path = dest_path_without_ext + '.zip' temp_path = os.path.join(CRACKMESONE_DIR, filename_in_archive) - shutil.move(source_path, temp_path) try: - # Find zip command (use full path to avoid PATH issues in web server) - zip_cmd = shutil.which("zip") or "/usr/bin/zip" - - # Create password-protected zip - ret = call([ - zip_cmd, "-j", "--password", ARCHIVE_PASSWORD, - dest_path_without_ext, "--", temp_path - ]) - - if ret != 0: - # Move file back on failure - if os.path.exists(temp_path): - try: - shutil.move(temp_path, source_path) - except Exception: - pass - return False, "Failed to create zip archive" - + shutil.move(source_path, temp_path) + compress_file(temp_path, dest_path, password=ARCHIVE_PASSWORD, encryption=EncryptionMethod.ZIPCRYPTO, suppress_warning=True) + os.remove(temp_path) return True, None - except FileNotFoundError: - # zip command not found - move file back - if os.path.exists(temp_path): - try: - shutil.move(temp_path, source_path) - except Exception: - pass - return False, "zip command not found" - except Exception as e: - # Other errors - move file back if os.path.exists(temp_path): try: shutil.move(temp_path, source_path) except Exception: pass - return False, f"Error creating zip: {str(e)}" - - finally: - # Clean up temp file (only if zip succeeded, file was moved to archive) - if os.path.exists(temp_path): + if os.path.exists(dest_path): try: - os.remove(temp_path) + os.remove(dest_path) except Exception: pass + return False, f"Error creating zip: {str(e)}" # ============================================================================= From 8dce2bdeeb6256c0cc94d43f70e8d459b49e783c Mon Sep 17 00:00:00 2001 From: AzuLX Date: Wed, 14 Jan 2026 00:33:07 +0000 Subject: [PATCH 2/4] Added logging in with email address, prevent people as taken emails, added username reminder on emails too --- app/controllers/login.py | 25 +++++++++------ app/controllers/password_reset.py | 48 +++++++++++++++++------------ app/controllers/register.py | 47 ++++++++++++++++++++++------ templates/login/login.html | 4 +-- templates/password_reset/reset.html | 1 + 5 files changed, 84 insertions(+), 41 deletions(-) diff --git a/app/controllers/login.py b/app/controllers/login.py index 457e17e..265918f 100644 --- a/app/controllers/login.py +++ b/app/controllers/login.py @@ -5,7 +5,7 @@ from urllib.parse import urlparse from flask import Blueprint, render_template, request, redirect, flash, session -from app.models.user import user_by_name +from app.models.user import user_by_name, user_by_mail from app.models.errors import ErrNoResult from app.services.passhash import match_string from app.services.limiter import limit @@ -66,9 +66,21 @@ def login_post(): # Track login attempts attempts = session.get('login_attempt', 0) + # Try email lookup first, then username (email takes precedence) + user = None try: - user = user_by_name(name) + try: + user = user_by_mail(name) + except ErrNoResult: + user = user_by_name(name) + except ErrNoResult: + pass # Neither found, user stays None + except Exception as e: + print(f"Login error: {e}") + flash('There was an error. Please try again later.', FLASH_ERROR) + return render_template('login/login.html') + if user: # Check password if match_string(user['password'], password): # Login successful @@ -76,7 +88,7 @@ def login_post(): session['email'] = user['email'] session['name'] = user['name'] flash('Login successful!', FLASH_SUCCESS) - + # Retrieve and clear the redirect from session # Only allow relative URLs to prevent redirecting to external sites redirect_url = session.pop('login_redirect', '/') @@ -87,16 +99,11 @@ def login_post(): attempts += 1 session['login_attempt'] = attempts flash(f'Password is incorrect - Attempt: {attempts}', FLASH_WARNING) - - except ErrNoResult: + else: attempts += 1 session['login_attempt'] = attempts flash(f'Password is incorrect - Attempt: {attempts}', FLASH_WARNING) - except Exception as e: - print(f"Login error: {e}") - flash('There was an error. Please try again later.', FLASH_ERROR) - return render_template('login/login.html') diff --git a/app/controllers/password_reset.py b/app/controllers/password_reset.py index 34d1270..c62a110 100644 --- a/app/controllers/password_reset.py +++ b/app/controllers/password_reset.py @@ -74,10 +74,12 @@ def forgot_password_post(): # Send email subject = "Password Reset Request - crackmes.one" - body = f"""Hello, + body = f"""Hello {user['name']}, You have requested to reset your password on crackmes.one. +Your username is: {user['name']} + Click the link below to reset your password: {reset_url} @@ -113,8 +115,10 @@ def reset_password_get(token): """Display the reset password form.""" try: # Validate token exists and is not expired - get_reset_token(token) - return render_template('password_reset/reset.html', token=token) + token_doc = get_reset_token(token) + # Look up user to get username + user = user_by_mail(token_doc['email']) + return render_template('password_reset/reset.html', token=token, username=user['name']) except ErrNoResult: flash('This password reset link is invalid or has expired.', FLASH_ERROR) return redirect('/forgot-password') @@ -131,46 +135,50 @@ def reset_password_post(token): new_password = request.form.get('new_password', '') new_password_verify = request.form.get('new_password_verify', '') + # Validate token and get user first (so we can show username in error messages) + try: + token_doc = get_reset_token(token) + email = token_doc['email'] + user = user_by_mail(email) + username = user['name'] + except ErrNoResult: + flash('This password reset link is invalid or has expired.', FLASH_ERROR) + return redirect('/forgot-password') + except Exception as e: + print(f"Error validating reset token: {e}") + flash('An error occurred. Please try again.', FLASH_ERROR) + return redirect('/forgot-password') + # Validate passwords if not new_password or not new_password_verify: flash('Please fill in all password fields', FLASH_ERROR) - return render_template('password_reset/reset.html', token=token) + return render_template('password_reset/reset.html', token=token, username=username) if new_password != new_password_verify: flash('Passwords do not match', FLASH_ERROR) - return render_template('password_reset/reset.html', token=token) + return render_template('password_reset/reset.html', token=token, username=username) if len(new_password) < 8: flash('Password must be at least 8 characters long', FLASH_ERROR) - return render_template('password_reset/reset.html', token=token) + return render_template('password_reset/reset.html', token=token, username=username) try: - # Validate token and get associated email - token_doc = get_reset_token(token) - email = token_doc['email'] - - # Find user by email - user = user_by_mail(email) - # Hash new password hashed_password = hash_string(new_password) # Update user's password - update_user_password(user['name'], hashed_password) + update_user_password(username, hashed_password) # Delete the used token delete_reset_token(token) # Notify moderation channel - notify_password_reset_complete(user['name'], email) + notify_password_reset_complete(username, email) - flash('Your password has been reset successfully. You can now log in.', FLASH_SUCCESS) + flash(f'Password reset successfully for {username}. You can now log in.', FLASH_SUCCESS) return redirect('/login') - except ErrNoResult: - flash('This password reset link is invalid or has expired.', FLASH_ERROR) - return redirect('/forgot-password') except Exception as e: print(f"Error resetting password: {e}") flash('An error occurred while resetting your password. Please try again.', FLASH_ERROR) - return render_template('password_reset/reset.html', token=token) + return render_template('password_reset/reset.html', token=token, username=username) diff --git a/app/controllers/register.py b/app/controllers/register.py index 66a4159..7e99617 100644 --- a/app/controllers/register.py +++ b/app/controllers/register.py @@ -77,7 +77,7 @@ def register_post(): flash('An error occurred on the server. Please try again later.', FLASH_ERROR) return redirect('/register') - # Check if email already exists + # Check if email already exists as an email try: user_by_mail(email) flash(f'Account already exists for: {email}', FLASH_ERROR) @@ -89,22 +89,49 @@ def register_post(): flash('An error occurred on the server. Please try again later.', FLASH_ERROR) return render_template('register/register.html', name=name, email=email) - # Check if username already exists + # Check if username already exists as a username try: user_by_name(name) flash(f'Account already exists for: {name}', FLASH_ERROR) return render_template('register/register.html', name=name, email=email) except ErrNoResult: - # Username not taken, create account - try: - user_create(name, email, hashed_password) - flash(f'Account created successfully for: {name}', FLASH_SUCCESS) - return redirect('/login') - except Exception as e: - print(f"User creation error: {e}") - flash('An error occurred on the server. Please try again later.', FLASH_ERROR) + pass # Username not taken, continue except Exception as e: print(f"Database error: {e}") flash('An error occurred on the server. Please try again later.', FLASH_ERROR) + return render_template('register/register.html', name=name, email=email) + + # Cross-check: username must not match an existing user's email + try: + user_by_mail(name) + flash('This username is not available', FLASH_ERROR) + return render_template('register/register.html', name=name, email=email) + except ErrNoResult: + pass # No conflict, continue + except Exception as e: + print(f"Database error: {e}") + flash('An error occurred on the server. Please try again later.', FLASH_ERROR) + return render_template('register/register.html', name=name, email=email) + + # Cross-check: email must not match an existing user's username + try: + user_by_name(email) + flash('This email is not available', FLASH_ERROR) + return render_template('register/register.html', name=name, email=email) + except ErrNoResult: + pass # No conflict, continue + except Exception as e: + print(f"Database error: {e}") + flash('An error occurred on the server. Please try again later.', FLASH_ERROR) + return render_template('register/register.html', name=name, email=email) + + # All checks passed, create account + try: + user_create(name, email, hashed_password) + flash(f'Account created successfully for: {name}', FLASH_SUCCESS) + return redirect('/login') + except Exception as e: + print(f"User creation error: {e}") + flash('An error occurred on the server. Please try again later.', FLASH_ERROR) return render_template('register/register.html', name=name, email=email) diff --git a/templates/login/login.html b/templates/login/login.html index 9f3829a..4b70a89 100644 --- a/templates/login/login.html +++ b/templates/login/login.html @@ -11,10 +11,10 @@
- +
- +
diff --git a/templates/password_reset/reset.html b/templates/password_reset/reset.html index 81cf628..65766dc 100644 --- a/templates/password_reset/reset.html +++ b/templates/password_reset/reset.html @@ -7,6 +7,7 @@
+

Resetting password for: {{ username }}

Enter your new password below.

From 8b4ddd9e492fbf7e8978b0ab96cbe47f09ed2716 Mon Sep 17 00:00:00 2001 From: AzuLX Date: Wed, 14 Jan 2026 00:45:31 +0000 Subject: [PATCH 3/4] Revert: 'Replace the shell zip cmd with rustyzipper' --- requirements.txt | 1 - review/routes.py | 45 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/requirements.txt b/requirements.txt index e7ba5c9..253c62a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,6 @@ bleach==6.1.0 requests==2.31.0 Werkzeug==3.0.1 resend==2.0.0 -rustyzipper==1.0.5 # Testing pytest==7.4.3 diff --git a/review/routes.py b/review/routes.py index 12c1556..94e887c 100644 --- a/review/routes.py +++ b/review/routes.py @@ -27,7 +27,7 @@ import bcrypt import requests -from rustyzipper import compress_file, EncryptionMethod +from subprocess import call from bson.objectid import ObjectId from review.logger import log_reviewer_operation @@ -498,28 +498,57 @@ def create_password_protected_zip(source_path, dest_path_without_ext, filename_i Returns: Tuple of (success: bool, error_message: str or None) """ - dest_path = dest_path_without_ext + '.zip' + # Move to temp location with desired filename temp_path = os.path.join(CRACKMESONE_DIR, filename_in_archive) + shutil.move(source_path, temp_path) try: - shutil.move(source_path, temp_path) - compress_file(temp_path, dest_path, password=ARCHIVE_PASSWORD, encryption=EncryptionMethod.ZIPCRYPTO, suppress_warning=True) - os.remove(temp_path) + # Find zip command (use full path to avoid PATH issues in web server) + zip_cmd = shutil.which("zip") or "/usr/bin/zip" + + # Create password-protected zip + ret = call([ + zip_cmd, "-j", "--password", ARCHIVE_PASSWORD, + dest_path_without_ext, "--", temp_path + ]) + + if ret != 0: + # Move file back on failure + if os.path.exists(temp_path): + try: + shutil.move(temp_path, source_path) + except Exception: + pass + return False, "Failed to create zip archive" + return True, None - except Exception as e: + except FileNotFoundError: + # zip command not found - move file back if os.path.exists(temp_path): try: shutil.move(temp_path, source_path) except Exception: pass - if os.path.exists(dest_path): + return False, "zip command not found" + + except Exception as e: + # Other errors - move file back + if os.path.exists(temp_path): try: - os.remove(dest_path) + shutil.move(temp_path, source_path) except Exception: pass return False, f"Error creating zip: {str(e)}" + finally: + # Clean up temp file (only if zip succeeded, file was moved to archive) + if os.path.exists(temp_path): + try: + os.remove(temp_path) + except Exception: + pass + # ============================================================================= # Pending Submission Operations From 82b2b5121ea832c30ab5bb01fa5a43aeeb1abd94 Mon Sep 17 00:00:00 2001 From: AzuLX Date: Thu, 15 Jan 2026 12:21:31 +0000 Subject: [PATCH 4/4] email body no longer keeps username --- app/controllers/password_reset.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/controllers/password_reset.py b/app/controllers/password_reset.py index c62a110..e445f21 100644 --- a/app/controllers/password_reset.py +++ b/app/controllers/password_reset.py @@ -74,12 +74,10 @@ def forgot_password_post(): # Send email subject = "Password Reset Request - crackmes.one" - body = f"""Hello {user['name']}, + body = f"""Hello, You have requested to reset your password on crackmes.one. -Your username is: {user['name']} - Click the link below to reset your password: {reset_url}