AzerothCore-website/scripts/resetPassword.py
Aaron Barbas 9bbeb35c08 Added support link to download game client, link for addons.
Fixed an issue that prevented the password reset tokens from working.
Added email templates for password reset success and new account creation.
Added more dynamic email template support.
2024-10-03 22:00:40 -05:00

212 lines
7.5 KiB
Python

import mysql.connector
import hashlib
import os
import json
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from flask import Flask, request, render_template, jsonify, url_for
app = Flask(__name__)
def get_db_connection():
script_dir = Path(__file__).resolve().parent
config_path = script_dir / "../config.json"
with open(config_path) as config_file:
config = json.load(config_file)
return mysql.connector.connect(
host=config["SERVER_IP"],
user=config["USERNAME"],
password=config["PASSWORD"],
database=config["DATABASE"],
port=config["MYSQL_PORT"]
)
def get_config():
script_dir = Path(__file__).resolve().parent
config_path = script_dir / "../config.json"
with open(config_path) as config_file:
return json.load(config_file)
@app.route('/resetpassword')
def reset_password():
return render_template('resetpassword.html')
@app.route('/reset_password', methods=['POST'])
def handle_reset_password():
data = request.json
email = data.get('email')
if not email:
return jsonify({'success': False, 'message': 'Email is required.'}), 400
conn = get_db_connection()
cursor = conn.cursor(dictionary=True)
try:
cursor.execute("SELECT id FROM account WHERE email = %s", (email,))
account = cursor.fetchone()
if not account:
return jsonify({'success': False, 'message': 'Email not found.'}), 404
token = os.urandom(24).hex()
cursor.execute(
"INSERT INTO password_reset_tokens (account_id, token) VALUES (%s, %s)",
(account['id'], token)
)
conn.commit()
reset_link = url_for('reset_password_token', token=token, _external=True)
disable_link = url_for('disable_token', token=token, email=email, _external=True)
send_email(email, reset_link, disable_link, 'Azerothcore Password Reset Request')
return jsonify({'success': True, 'message': 'Password reset link has been sent to your email.'})
except mysql.connector.Error as err:
return jsonify({'success': False, 'message': str(err)}), 500
finally:
cursor.close()
conn.close()
def send_email(to_email, reset_link, disable_link, subject):
config = get_config()
from_email = config["SMTP_EMAIL_ADDRESS"]
from_password = config["SMTP_EMAIL_PASSWORD"]
msg = MIMEMultipart('alternative')
msg['From'] = from_email
msg['To'] = to_email
msg['Subject'] = subject
text_content = f'Click the link to reset your password: {reset_link}'
html_content = f"""
<html>
<body style="font-family: Arial, sans-serif; background-color: #f4f4f4; padding: 20px;">
<div style="max-width: 600px; margin: 0 auto; background: white; padding: 20px; border-radius: 10px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);">
<h2 style="color: #333;">Password Reset Request</h2>
<p>Click the button below to reset your password:</p>
<a href="{reset_link}" style="display: inline-block; padding: 10px 20px; font-size: 16px; text-transform: uppercase; font-weight: bold; color: white; background-color: #007bff; text-decoration: none; border-radius: 5px;">Reset Password</a>
<p>If you did not request this email, click the button below to disable the token:</p>
<a href="{disable_link}" style="display: inline-block; padding: 10px 20px; font-size: 16px; text-transform: uppercase; font-weight: bold; color: white; background-color: #ff0000; text-decoration: none; border-radius: 5px;">Disable Token</a>
<p style="color: #999; margin-top: 20px;">If you have any questions, feel free to contact our support team.</p>
</div>
</body>
</html>
"""
part1 = MIMEText(text_content, 'plain')
part2 = MIMEText(html_content, 'html')
msg.attach(part1)
msg.attach(part2)
try:
with smtplib.SMTP('smtp.gmail.com', 587) as server:
server.starttls()
server.login(from_email, from_password)
server.sendmail(from_email, to_email, msg.as_string())
return True
except Exception as e:
print(f"Failed to send email: {e}")
return False
@app.route('/disable_token', methods=['GET'])
def disable_token():
token = request.args.get('token')
email = request.args.get('email')
if not token or not email:
return jsonify({'message': 'Invalid request.'}), 400
conn = get_db_connection()
cursor = conn.cursor()
try:
cursor.execute("DELETE FROM password_reset_tokens WHERE token = %s AND email = %s", (token, email))
conn.commit()
if cursor.rowcount == 0:
return jsonify({'message': 'Token not found or already disabled.'}), 404
return jsonify({'message': 'Token disabled successfully.'}), 200
except mysql.connector.Error as err:
return jsonify({'message': str(err)}), 500
finally:
cursor.close()
conn.close()
@app.route('/reset_password/<token>', methods=['GET', 'POST'])
def reset_password_token(token):
if request.method == 'POST':
data = request.form
password = data.get('password')
confirm_password = data.get('confirm_password')
if not password or not confirm_password:
return render_template('resetpassword.html', message='All fields are required.')
if password != confirm_password:
return render_template('resetpassword.html', message='Passwords do not match.')
conn = get_db_connection()
cursor = conn.cursor()
try:
cursor.execute("SELECT account_id FROM password_reset_tokens WHERE token = %s", (token,))
result = cursor.fetchone()
if not result:
return render_template('resetpassword.html', message='Invalid or expired token.')
account_id = result[0]
salt = os.urandom(32)
verifier = calculate_verifier("USERNAME", password, salt) # Update USERNAME appropriately
cursor.execute(
"UPDATE account SET salt = %s, verifier = %s WHERE id = %s",
(salt, verifier, account_id)
)
conn.commit()
cursor.execute("DELETE FROM password_reset_tokens WHERE token = %s", (token,))
conn.commit()
return redirect(url_for('success'))
finally:
cursor.close()
conn.close()
return render_template('resetpassword.html')
@app.route('/success')
def success():
return render_template('success.html')
def calculate_verifier(username, password, salt):
g = 7
N = int("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7", 16)
username = username.upper()
password = password.upper()
h1 = hashlib.sha1(f"{username}:{password}".encode()).digest()
h2 = hashlib.sha1(salt + h1).digest()
h2_int = int.from_bytes(h2, 'little')
verifier_int = pow(g, h2_int, N)
verifier = verifier_int.to_bytes((verifier_int.bit_length() + 7) // 8, 'little')
return verifier
if __name__ == '__main__':
app.run(debug=True)