UWP Community Registration Portal
+Created by: Evan Niederwerfer at UW-Platteville
+diff --git a/accounts.py b/accounts.py index 04ded83..54bd788 100644 --- a/accounts.py +++ b/accounts.py @@ -1,9 +1,48 @@ from db import * +from sendmail import * +import bcrypt + class accounts: + + def hash_password(password): + salt = bcrypt.gensalt() + hashed_password = bcrypt.hashpw(password.encode('utf-8'), salt) + return hashed_password + def register(email, password): - if (db.add_account(email, password)): - print("Registered " + str(email) + " with password " + str(password)) + if (db.add_account(email, accounts.hash_password(password))): return True - def authenticate_account(email, password): - pass \ No newline at end of file + def authenticate_account(email, password): # Authenticate account with password + pass + + def check_password(stored_hash, input_password): + # Check if the entered password matches the stored hashed password + return bcrypt.checkpw(input_password.encode('utf-8'), stored_hash) + + # Verify the password + is_correct = check_password(hashed_password, "my_secure_password") + if is_correct: + print("Password is correct!") + else: + print("Invalid password.") + + def create_account_otp(email): + if db.account_exists(email): + otp = mail.gen_code() + if db.account_add_otp(email, otp): + mail.send(email, otp) + return True + else: + return False + else: + return False + + def authenticate_account_otp(email, otp): # Authenticate account without password + if (db.check_account_otp(email)): + if (int(otp) == int(db.get_account_otp(email))): + db.account_del_otp(email) + return True + elif (not db.check_account_otp(email)): + db.account_del_otp(email) + return False \ No newline at end of file diff --git a/codelistener.py b/codelistener.py index 9476eb8..fee6d3b 100644 --- a/codelistener.py +++ b/codelistener.py @@ -1,4 +1,4 @@ -from http.server import BaseHTTPRequestHandler, HTTPServer +from http.server import BaseHTTPRequestHandler, HTTPServer, SimpleHTTPRequestHandler from socket import * from sendmail import mail from verification import otp @@ -6,14 +6,19 @@ from db import * from accounts import * from urllib.parse import urlparse, parse_qs import os.path +import ssl +import re -class MyRequestHandler(BaseHTTPRequestHandler): +class MyRequestHandler(SimpleHTTPRequestHandler): base_path = "/home/arcodeskel/Desktop/Verification Platt Discord/" + def log_message(self, format, *args): + return + def do_GET(self): - requested_path = os.path.join(self.base_path, "pages", "index.php") + requested_path = os.path.join(self.base_path, "pages", "index.html") if not os.path.abspath(requested_path).startswith(os.path.abspath(self.base_path)): self.send_response(403) @@ -41,17 +46,47 @@ class MyRequestHandler(BaseHTTPRequestHandler): parsed_data = parse_qs(data_input) if (data_input.startswith("email=")): - + email = parsed_data.get('email', [None])[0] # defaults to none if email is not found + + requested_path = os.path.join(self.base_path, "pages", "exists.html") + + if (db.account_exists(email)): + with open(requested_path, 'r') as file: + file_to_open = file.read() + self.send_response(200) + self.end_headers() + self.wfile.write(bytes(file_to_open, 'utf-8')) + return + password = parsed_data.get('passwd', [None])[0] code = mail.gen_code() - db.add_session(email, password, code) - mail.send(email, code) + emailRegex = r'^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$' + passwordRegex = r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$' + + if (email != None and password != None and str(email).endswith("@uwplatt.edu") and re.match(emailRegex, email) and re.match(passwordRegex, password)): + db.add_session(email, password, code) + if (mail.send(email, code) == False): + requested_path = os.path.join(self.base_path, "pages", "fail.html") + with open(requested_path, 'r') as file: + file_to_open = file.read() + self.send_response(200) + self.end_headers() + self.wfile.write(bytes(file_to_open, 'utf-8')) + else: + requested_path = os.path.join(self.base_path, "pages", "fail.html") + with open(requested_path, 'r') as file: + file_to_open = file.read() + self.send_response(200) + self.end_headers() + self.wfile.write(bytes(file_to_open, 'utf-8')) + return - requested_path = os.path.join(self.base_path, "pages", "otp.php") + + requested_path = os.path.join(self.base_path, "pages", "otp.html") if not os.path.abspath(requested_path).startswith(os.path.abspath(self.base_path)): self.send_response(403) @@ -104,7 +139,7 @@ class MyRequestHandler(BaseHTTPRequestHandler): else: db.del_session(email) - requested_path = os.path.join(self.base_path, "pages", "success.html") + requested_path = os.path.join(self.base_path, "pages", "fail.html") if not os.path.abspath(requested_path).startswith(os.path.abspath(self.base_path)): self.send_response(403) self.end_headers() @@ -124,7 +159,7 @@ class MyRequestHandler(BaseHTTPRequestHandler): self.wfile.write(bytes(file_to_open, 'utf-8')) else: db.del_session(email) - requested_path = os.path.join(self.base_path, "pages", "success.html") + requested_path = os.path.join(self.base_path, "pages", "fail.html") if not os.path.abspath(requested_path).startswith(os.path.abspath(self.base_path)): self.send_response(403) self.end_headers() @@ -143,17 +178,17 @@ class MyRequestHandler(BaseHTTPRequestHandler): self.end_headers() self.wfile.write(bytes(file_to_open, 'utf-8')) - -Handler = MyRequestHandler - if (db.init()): pass else: - print("Init returned false, there might be an issue!") + print("Db did not return True. Something went very wrong!") -hostName = "localhost" -serverPort = 8080 +Handler = MyRequestHandler -server = HTTPServer((hostName, serverPort), Handler) +context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) +context.load_cert_chain(certfile='./certs/cert.pem', keyfile='./certs/key.pem') +context.check_hostname = False -server.serve_forever() +with HTTPServer(("0.0.0.0", 4443), MyRequestHandler) as httpd: + #httpd.socket = context.wrap_socket(httpd.socket, server_side=True) + httpd.serve_forever() \ No newline at end of file diff --git a/db.py b/db.py index 6ca11c5..05c15b1 100644 --- a/db.py +++ b/db.py @@ -7,7 +7,7 @@ class db: conn = sqlite3.connect('accounts.db') cursor = conn.cursor() cursor.execute('''CREATE TABLE IF NOT EXISTS sessions (email text UNIQUE, password integer, code integer, datedel text)''') - cursor.execute('''CREATE TABLE IF NOT EXISTS accounts (email text UNIQUE, password integer)''') + cursor.execute('''CREATE TABLE IF NOT EXISTS accounts (email text UNIQUE, password integer, discord text UNIQUE, mc text UNIQUE, code integer, datedel text)''') conn.commit() conn.close() return True @@ -89,4 +89,137 @@ class db: cursor.execute(insert_password, (password, email, )) conn.commit() conn.close() - return True \ No newline at end of file + return True + + def account_exists(email): + conn = sqlite3.connect('accounts.db') + cursor = conn.cursor() + match_sql = '''SELECT email FROM accounts WHERE email = ? LIMIT 1''' + cursor.execute(match_sql, (email, )) + fetch_acc = cursor.fetchone() + if fetch_acc is None or False: + return False + conn.close() + return True + + def account_add_otp(email, otp): + conn = sqlite3.connect('accounts.db') + cursor = conn.cursor() + insert_code = "UPDATE accounts SET code = ? WHERE email = ?" + insert_datedel = "UPDATE accounts SET datedel = ? WHERE email = ?" + + cursor.execute(insert_code, (otp, email, )) + cursor.execute(insert_datedel, (datetime.now() + timedelta(minutes=5), email, )) + + conn.commit() + + + conn.close() + return True + + def account_del_otp(email): + conn = sqlite3.connect('accounts.db') + cursor = conn.cursor() + empty = None + remove_code = "UPDATE sessions SET code = ? WHERE email = ?" + remove_datedel = "UPDATE sessions SET datedel = ? WHERE email = ?" + cursor.execute(remove_code, (empty, email, )) + cursor.execute(remove_datedel, (empty, email, )) + conn.commit() + conn.close() + return True + + + + def check_account_otp(email): + conn = sqlite3.connect('accounts.db') + cursor = conn.cursor() + past_sql = '''SELECT datedel FROM accounts WHERE email = ? LIMIT 1''' + cursor.execute(past_sql, (email, )) + fetch_past = cursor.fetchone() + past = fetch_past[0] + if (past is None or False): + return False + present = datetime.now() + conn.close() + if (str(present) > past): + db.account_del_otp(email) + return False + else: + return True + + def get_account_otp(email): + conn = sqlite3.connect('accounts.db') + cursor = conn.cursor() + getotp = '''SELECT code FROM accounts WHERE email = ?''' + cursor.execute(getotp, (email, )) + fetch_code = cursor.fetchone() + code = fetch_code[0] + if (code is None or False): + return False + else: + return code + + def discord_exists(email): + conn = sqlite3.connect('accounts.db') + cursor = conn.cursor() + get_discord = '''SELECT discord FROM accounts WHERE email = ?''' + cursor.execute(get_discord, (email, )) + fetch_discord = cursor.fetchone() + if (fetch_discord == None): + return False + discord = fetch_discord[0] + if (discord == None): + return False + return discord + + def add_discord(discord, email): + conn = sqlite3.connect('accounts.db') + cursor = conn.cursor() + update_discord = '''UPDATE accounts SET discord = ? WHERE email = ?''' + cursor.execute(update_discord, (discord, email)) + conn.commit() + conn.close() + return True + + def add_mc(mc, email): + conn = sqlite3.connect('accounts.db') + cursor = conn.cursor() + add = "UPDATE accounts SET mc = ? WHERE email = ?" + cursor.execute(add, (mc, email, )) + conn.commit() + conn.close() + return True + + def replace_mc(new, email): + conn = sqlite3.connect('accounts.db') + cursor = conn.cursor() + replace_name = "UPDATE accounts SET mc = ? WHERE email = ?" + cursor.execute(replace_name, (new, email, )) + conn.commit() + conn.close() + return True + + def mc_email_lookup(mc): + conn = sqlite3.connect('accounts.db') + cursor = conn.cursor() + get_mc = '''SELECT email FROM accounts WHERE mc = ?''' + cursor.execute(get_mc, (mc, )) + fetch_mc = cursor.fetchone() + if (fetch_mc == None): + return False + email = fetch_mc[0] + return email + + def mc_username_lookup(email): + conn = sqlite3.connect('accounts.db') + cursor = conn.cursor() + get_email = '''SELECT mc FROM accounts WHERE email = ?''' + cursor.execute(get_email, (email, )) + fetch_email = cursor.fetchone() + if (fetch_email == None): + return False + mc = fetch_email[0] + if (mc == None): + return False + return mc \ No newline at end of file diff --git a/discordbot.py b/discordbot.py new file mode 100644 index 0000000..793d7c2 --- /dev/null +++ b/discordbot.py @@ -0,0 +1,120 @@ +import discord +from discord.ext import commands +from discord.ext.commands import context +from discord import ui +from mctools import RCONClient +from db import * +from accounts import * +intents = discord.Intents.default() +intents.message_content = True +TOKEN = '' +MY_GUILD = discord.Object(id=) +bot = commands.Bot(command_prefix='$',intents=intents) +intents.messages = True + +class check_details(ui.Modal, title="Update/Register Profile Information:"): + + def __init__(self, email: str, code: int): + self.email = email + self.otpcode = code + self.mcname_by_email = db.mc_username_lookup(self.email) # NOT for use outside of the self.mcname placeholder + self.placeholder_mc = None # NOT for use outside of the self.mcname placeholder + + + if (self.mcname_by_email is not False or None): + self.placeholder_mc = self.mcname_by_email + + self.otp = ui.TextInput(label=f"OTP Code sent to {self.email}", style=discord.TextStyle.short, required=True, min_length=4, max_length=4, placeholder="eg 0000") + self.mcname = ui.TextInput(label="Minecraft Username (optional)", style=discord.TextStyle.short, required=False, default=self.placeholder_mc) + + super().__init__() + self.add_item(self.otp) + self.add_item(self.mcname) + + self.custom_id = f"check_details_modal_{self.email}" + + async def on_submit(self, interaction: discord.Interaction): + if (accounts.authenticate_account_otp(self.email, self.otp.value)): + if (self.email.endswith("@uwplatt.edu")): + member = interaction.user + role = discord.utils.get(interaction.guild.roles, name="pioneer") + await member.add_roles(role) + host = "" + port = 0000 + password = "" + rcon = RCONClient(host, port) + + if (db.mc_username_lookup(self.email) != self.mcname.value and db.mc_username_lookup(self.email) != False and db.mc_email_lookup(self.mcname.value) == False): + if rcon.login(password): + old = db.mc_username_lookup(self.email) + if (old is not False): + rcon.command(f"rank {old} guest") + db.add_mc(self.mcname.value, self.email) + rcon.command(f"rank {self.mcname.value} member") + await interaction.response.send_message(f"Profile updated! Email: {self.email}, New/Unchanged MC Username: {self.mcname}") + else: + await interaction.response.send_message("An error occured in deleting the old mc! The account may not be properly registered. Contact an administrator for guidance.") + return + + elif (db.mc_email_lookup(self.mcname.value) == False or db.mc_username_lookup(self.email) == False): + if rcon.login(password): + db.add_mc(self.mcname.value, self.email) + rcon.command(f"rank {self.mcname.value} member") + await interaction.response.send_message(f"Profile updated! Email: {self.email}, MC Username: {self.mcname}") + + + elif (db.mc_email_lookup(self.mcname.value) != self.email): + self.mcname = "Mc name not added/changed due to being taken under a different email. Please contact the administrator if you beleive this was in error." + await interaction.response.send_message(f"Profile updated! Email: {self.email}, MC Username: {self.mcname}") + + elif (db.mc_email_lookup(self.mcname.value) is False): + self.mcname = "N/A (user did not specify)" + await interaction.response.send_message(f"Profile updated! Email: {self.email}, MC Username: {self.mcname}") + else: + self.mcname = "An unknown error occured. Try again later." + await interaction.response.send_message(f"Profile updated! Email: {self.email}, MC Username: {self.mcname}") + + else: + await interaction.response.send_message("An error occured. Try again or contact the administrator.") + return True + +@bot.event +async def on_ready(): + print('Logged on as', bot.user) + await bot.tree.sync(guild=MY_GUILD) + +@bot.tree.command(name="login", description="login to account", guild=MY_GUILD) +async def login(interaction: discord.Interaction, email: str): + db.init() + + member = interaction.user + + if (interaction.channel.id != 0): + await interaction.channel.send("This command can only be used in the bot-cmds channel.") + return + + if (db.discord_exists(email) is False): + if (db.add_discord(member.id, email)): + if (accounts.create_account_otp(email)): + otpcode = db.get_account_otp(email) + modal = check_details(email=email, code=otpcode) + await interaction.response.send_modal(modal) + else: + await interaction.channel.send(f"Account {email} does not appear to exist! To register, go to https://www.register.uwplattmc.com") + return + + elif (int(member.id) == int(db.discord_exists((email)))): + + if (accounts.create_account_otp(email)): + otpcode = db.get_account_otp(email) + modal = check_details(email=email, code=otpcode) + await interaction.response.send_modal(modal) + else: + await interaction.channel.send(f"Account {email} does not appear to exist! To register, go to https://www.register.uwplattmc.com") + return + + else: + await interaction.channel.send("Only the registered discord account holder may make changes. Contact an if you beleive this is a mistake.") + return + +bot.run(TOKEN) diff --git a/pages/exists.html b/pages/exists.html new file mode 100644 index 0000000..c93585f --- /dev/null +++ b/pages/exists.html @@ -0,0 +1,22 @@ + + +
+ +Click here to retry
+This could be due to an incorrect email or an invalid/expired code. Contact site admin Evan for any difficulties.
-Click here to retry
- +This could be due to an incorrect email or an invalid/expired code. Contact an admin for support.
+Click here to retry
+Created by: Evan Niederwerfer at UW-Platteville
+