diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/AzerothCore.iml b/.idea/AzerothCore.iml
new file mode 100644
index 0000000..f099b8a
--- /dev/null
+++ b/.idea/AzerothCore.iml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..cb50efa
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..f4c9d75
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/AzerothcoreAccountCreation/manage.py b/AzerothcoreAccountCreation/manage.py
deleted file mode 100755
index 580afdf..0000000
--- a/AzerothcoreAccountCreation/manage.py
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/usr/bin/env python
-"""Django's command-line utility for administrative tasks."""
-import os
-import sys
-
-
-def main():
- """Run administrative tasks."""
- os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'AzerothcoreAccountCreation.settings')
- try:
- from django.core.management import execute_from_command_line
- except ImportError as exc:
- raise ImportError(
- "Couldn't import Django. Are you sure it's installed and "
- "available on your PYTHONPATH environment variable? Did you "
- "forget to activate a virtual environment?"
- ) from exc
- execute_from_command_line(sys.argv)
-
-
-if __name__ == '__main__':
- main()
diff --git a/README.md b/README.md
index e69de29..da9c591 100644
--- a/README.md
+++ b/README.md
@@ -0,0 +1,84 @@
+# AzerothCore Account Management
+
+This application allows users to create and manage accounts for the World of Warcraft: Wrath of the Lich King private server. It provides features such as account creation, password reset, and email notifications.
+
+## Features
+
+- **Account Creation**: Create new accounts with username, email, password, and expansion details.
+- **Password Reset**: Reset account passwords through email verification.
+- **Secure Communication**: Utilizes Gmail App Passwords for secure email communication.
+
+## Prerequisites
+
+- **Python 3.8+**
+- **MySQL**: Database for storing user data
+- **Gmail App Passwords**: For sending emails securely
+
+## Installation
+
+1. **Clone the repository:**
+
+ ```bash
+ git clone https://github.com/BeardedInfoSec/AzerothCore.git
+ cd AzerothCore
+ ```
+
+2. **Configure the application:**
+
+ Ensure the `config.json` file in the root directory has the following structure and update it with your details:
+
+ ```json
+ {
+ "USERNAME": "acore",
+ "PASSWORD": "password",
+ "SERVER_IP": "127.0.0.1",
+ "MYSQL_PORT": 3306,
+ "DATABASE": "acore_auth",
+ "SMTP_EMAIL_ADDRESS": "your_email@gmail.com",
+ "SMTP_EMAIL_PASSWORD": "your_app_password"
+ }
+ ```
+
+ **Note**: Ensure you create a [Gmail App Password](https://myaccount.google.com/apppasswords) and enable [2-Step Verification](https://support.google.com/accounts/answer/185833?hl=en) for your Google account.
+
+## Running the Application
+
+1. **Start the Flask application:**
+
+ ```bash
+ python website.py
+ ```
+
+ The application will be available at `http://127.0.0.1:5000/`.
+
+ **Note**: The SQLite database for password reset tokens will be auto-initialized when the website is run.
+
+## Configuration Notes
+
+### HTTP vs. HTTPS
+
+- **HTTP**: Sends web traffic in plain text, making it potentially vulnerable to interception and attacks. It is **not secure**.
+- **HTTPS**: Encrypts web traffic, ensuring data is securely transmitted between the client and server. It is **recommended** for all web applications to protect sensitive data.
+
+To secure your application:
+
+- Open ports 80 (HTTP) and 443 (HTTPS) on your server.
+- Configure your firewall to allow traffic on these ports and point to your server's IP address or domain.
+- Obtain and install an SSL/TLS certificate to enable HTTPS.
+
+## Security Best Practices
+
+- **Disable Debug Mode**: Ensure `debug=False` in your app configuration.
+- **Use Environment Variables**: Store sensitive data in environment variables.
+- **Enable HTTPS**: Secure your application with HTTPS.
+- **Set Secure Headers**: Use libraries like `Flask-Talisman` to set secure headers.
+- **Rate Limiting**: Implement rate limiting to protect against brute force attacks.
+- **Input Validation**: Always validate and sanitize input data.
+
+## Contact
+
+For any issues or questions, please contact [your_email@example.com].
+
+---
+
+This README provides comprehensive instructions for setting up and running your AzerothCore account management application securely.
diff --git a/config.json b/config.json
index 265d9e9..9e49a50 100644
--- a/config.json
+++ b/config.json
@@ -1,7 +1,10 @@
{
"USERNAME" : "acore",
"PASSWORD" : "password",
- "SERVER_IP" : "127.0.01",
- "MYSQL_PORT" : 3306
+ "SERVER_IP" : "127.0.0.1",
+ "MYSQL_PORT" : 3306,
+ "DATABASE" : "acore_auth",
+ "SMTP_EMAIL_ADDRESS" : "gmail_address",
+ "SMTP_EMAIL_PASSWORD" : "gmail_app_password"
}
\ No newline at end of file
diff --git a/main.py b/main.py
deleted file mode 100644
index f7696e9..0000000
--- a/main.py
+++ /dev/null
@@ -1 +0,0 @@
-import flask
diff --git a/scripts/__pycache__/createAccount.cpython-310.pyc b/scripts/__pycache__/createAccount.cpython-310.pyc
new file mode 100644
index 0000000..223dc78
Binary files /dev/null and b/scripts/__pycache__/createAccount.cpython-310.pyc differ
diff --git a/scripts/__pycache__/initialize_db.cpython-310.pyc b/scripts/__pycache__/initialize_db.cpython-310.pyc
new file mode 100644
index 0000000..cb6b41e
Binary files /dev/null and b/scripts/__pycache__/initialize_db.cpython-310.pyc differ
diff --git a/scripts/__pycache__/resetPassword.cpython-310.pyc b/scripts/__pycache__/resetPassword.cpython-310.pyc
new file mode 100644
index 0000000..407d88c
Binary files /dev/null and b/scripts/__pycache__/resetPassword.cpython-310.pyc differ
diff --git a/scripts/createAccount.py b/scripts/createAccount.py
index e69de29..9d33345 100644
--- a/scripts/createAccount.py
+++ b/scripts/createAccount.py
@@ -0,0 +1,85 @@
+import mysql.connector
+import hashlib
+import os
+import json
+from pathlib import Path
+
+def sha1(data):
+ return hashlib.sha1(data).digest()
+
+def generate_salt():
+ return os.urandom(32)
+
+def calculate_verifier(username, password, salt):
+ g = 7
+ N = int("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7", 16)
+
+ username = username.upper()
+ password = password.upper()
+
+ h1 = sha1(f"{username}:{password}".encode())
+ h2 = sha1(salt + h1)
+
+ 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
+
+def create_account(account_name, email, passwd1, passwd2, expansion):
+ if passwd1 != passwd2:
+ return "Passwords do not match."
+
+ script_dir = Path(__file__).resolve().parent
+ config_path = script_dir / "../config.json"
+
+ with open(config_path) as config_file:
+ config = json.load(config_file)
+
+ USERNAME = config["USERNAME"]
+ PASSWORD = config["PASSWORD"]
+ SERVER_IP = config["SERVER_IP"]
+ PORT = config["MYSQL_PORT"]
+ DATABASE = config["DATABASE"]
+
+ conn = None
+ cursor = None
+
+ try:
+ conn = mysql.connector.connect(
+ host=SERVER_IP,
+ user=USERNAME,
+ password=PASSWORD,
+ database=DATABASE,
+ port=PORT
+ )
+ cursor = conn.cursor()
+
+ # Check if the username already exists
+ cursor.execute("SELECT id FROM account WHERE username = %s", (account_name,))
+ if cursor.fetchone():
+ return "Username already taken."
+
+ cursor.execute("SELECT MAX(id) FROM account")
+ max_id = cursor.fetchone()[0]
+ new_id = max_id + 1 if max_id else 1
+
+ salt = generate_salt()
+ verifier = calculate_verifier(account_name, passwd1, salt)
+
+ cursor.execute(
+ "INSERT INTO account (id, username, salt, verifier, email) VALUES (%s, %s, %s, %s, %s)",
+ (new_id, account_name, salt, verifier, email)
+ )
+ conn.commit()
+
+ return "Account created successfully!"
+
+ except mysql.connector.Error as err:
+ return f"Error: {err}"
+
+ finally:
+ if cursor is not None:
+ cursor.close()
+ if conn is not None:
+ conn.close()
diff --git a/scripts/initialize_db.py b/scripts/initialize_db.py
new file mode 100644
index 0000000..1a71f8e
--- /dev/null
+++ b/scripts/initialize_db.py
@@ -0,0 +1,17 @@
+import sqlite3
+
+def initialize_db():
+ conn = sqlite3.connect('tokens.db')
+ cursor = conn.cursor()
+
+ cursor.execute('''
+ CREATE TABLE IF NOT EXISTS password_reset_tokens (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ email TEXT NOT NULL,
+ token TEXT NOT NULL,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+ )
+ ''')
+
+ conn.commit()
+ conn.close()
diff --git a/scripts/resetPassword.py b/scripts/resetPassword.py
index e69de29..0ca1496 100644
--- a/scripts/resetPassword.py
+++ b/scripts/resetPassword.py
@@ -0,0 +1,212 @@
+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"""
+
+
+
+
Password Reset Request
+
Click the button below to reset your password:
+
Reset Password
+
If you did not request this email, click the button below to disable the token:
+
Disable Token
+
If you have any questions, feel free to contact our support team.
+
+
+
+ """
+
+ 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/', 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)
diff --git a/static/images/background.png b/static/images/background.png
new file mode 100644
index 0000000..bc64185
Binary files /dev/null and b/static/images/background.png differ
diff --git a/static/images/resetemail.png b/static/images/resetemail.png
new file mode 100644
index 0000000..4b9d747
Binary files /dev/null and b/static/images/resetemail.png differ
diff --git a/static/style.css b/static/style.css
new file mode 100644
index 0000000..ac7407e
--- /dev/null
+++ b/static/style.css
@@ -0,0 +1,48 @@
+.form {
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+ padding: 20px;
+}
+
+.input-field {
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+}
+
+.input-field input,.input-field select {
+ padding: 10px;
+ font-size: 1rem;
+ border-radius: 5px;
+ border: 1px solid #ced4da;
+ width: 100%;
+}
+
+.input-field label {
+ font-size: 1rem;
+ margin-bottom: 5px;
+}
+
+.btn-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 20px;
+}
+
+.btn {
+ padding: 10px 20px;
+ font-size: 1rem;
+ text-transform: uppercase;
+ font-weight: bold;
+ color: white;
+ background-color: #007bff;
+ border: none;
+ border-radius: 5px;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+.btn:hover {
+ background-color: #0056b3;
+}
\ No newline at end of file
diff --git a/styles/style.css b/styles/style.css
deleted file mode 100644
index e69de29..0000000
diff --git a/templates/accountcreation.html b/templates/accountcreation.html
index 6c40fc5..fa95ab5 100644
--- a/templates/accountcreation.html
+++ b/templates/accountcreation.html
@@ -1,27 +1,80 @@
-
-
-
-
-
- Azerothcore Account Creator
-
-
- Account Creation
-