diff --git a/createuser.py b/createuser.py deleted file mode 100644 index 718b364..0000000 --- a/createuser.py +++ /dev/null @@ -1,104 +0,0 @@ -import psycopg2 -from psycopg2 import sql -from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT -from dotenv import load_dotenv -from os import getenv -from argon2 import PasswordHasher - -load_dotenv() - -DB_HOST = getenv("DB_HOST")+":"+getenv("DB_PORT") -DB_USER = getenv("DB_USER") -DB_PASSWORD = getenv("DB_PASSWORD") -DB_NAME = "outpost" - -class newUser: - - username = "" - name = "" - email = "" - password = "" - - def checkIfAvailible(self): - try: - conn = psycopg2.connect( - host=DB_HOST, - user=DB_USER, - password=DB_PASSWORD, - database=DB_NAME - ) - cur = conn.cursor() - cur.execute(""" - SELECT * FROM users - WHERE username = %s - """, [self.username]) - if len(cur.fetchall()) > 0: - return False - else: - return True - except: - return False - - def createUserTable(self): - try: - conn = psycopg2.connect( - host=DB_HOST, - user=DB_USER, - password=DB_PASSWORD, - database=DB_NAME - ) - cur = conn.cursor() - - table_name = f""""userpermissions".{id.lower().replace(' ', '_').replace('-', '_').replace('.', '_')}""" - - cur.execute(""" - CREATE TABLE IF NOT EXISTS %s ( - key VARCHAR(255) PRIMARY KEY, - value TEXT - ) - """, (table_name)) - - conn.commit() - cur.close() - conn.close() - - except Exception as e: - print(f"Error creating user table: {e}") - raise - - def createUser(self): - try: - ph = PasswordHasher() - conn = psycopg2.connect( - host=DB_HOST, - user=DB_USER, - password=DB_PASSWORD, - database=DB_NAME - ) - cur = conn.cursor() - cur.execute(""" - INSERT INTO users ( - name, - username, - email, - password_hash - ) values ( - %s, - %s, - %s, - %s - ); - """, ( - self.name, - self.username, - self.email, - ph.hash(self.password) - )) - user = cur.fetchone() - conn.commit() - cur.close() - conn.close() - return user - except: - return False - diff --git a/db.py b/db.py new file mode 100644 index 0000000..4ceb993 --- /dev/null +++ b/db.py @@ -0,0 +1,236 @@ +import psycopg2 +from psycopg2 import sql +from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT +from argon2 import PasswordHasher +import secrets + +def logEvent(action, details, user_id, user_ip, user_agent, dbuser, dbpass, dbhost, dbname): + try: + conn = psycopg2.connect( + host=dbhost, + user=dbuser, + password=dbpass, + database=dbname + ) + cur = conn.cursor() + + cur.execute(""" + INSERT INTO logs (action, details, user_id, user_ip, user_agent) + VALUES (%s, %s, %s, %s, %s) + """, (action, details, user_id, user_ip, user_agent)) + + conn.commit() + + cur.close() + conn.close() + + except Exception as e: + print(f"Error logging event: {e}") + raise + +def createGroup(name, parent, dbuser, dbpass, dbhost, dbname): + try: + conn = psycopg2.connect( + host=dbhost, + user=dbuser, + password=dbpass, + database=dbname + ) + cur = conn.cursor() + + if parent: + cur.execute("SELECT id FROM groups WHERE name = %s", (parent,)) + parent_record = cur.fetchone() + if parent_record: + parent_id = parent_record[0] + else: + parent_id = None + else: + parent_id = None + + cur.execute(""" + INSERT INTO groups (name, parent) + VALUES (%s, %s) RETURNING id + """, (name, parent_id)) + + group_id = cur.fetchone()[0] + conn.commit() + + cur.close() + conn.close() + + return group_id + + except Exception as e: + print(f"Error creating group: {e}") + raise + +def getGroupByName(name, dbuser, dbpass, dbhost, dbname): + try: + conn = psycopg2.connect( + host=dbhost, + user=dbuser, + password=dbpass, + database=dbname + ) + cur = conn.cursor() + + cur.execute("SELECT id, name, parent, creation_date FROM groups WHERE name = %s", (name,)) + group_record = cur.fetchone() + + cur.close() + conn.close() + + if group_record: + return { + "id": group_record[0], + "name": group_record[1], + "parent": group_record[2], + "creation_date": group_record[3] + } + else: + return None + + except Exception as e: + print(f"Error retrieving group: {e}") + raise + +def createUser(name, username, email, password, group, dbuser, dbpass, dbhost, dbname): + try: + conn = psycopg2.connect( + host=dbhost, + user=dbuser, + password=dbpass, + database=dbname + ) + cur = conn.cursor() + + password_hash = PasswordHasher().hash(password) + + cur.execute(""" + INSERT INTO users (name, username, email, password_hash, group_id) + VALUES (%s, %s, %s, %s, %s) RETURNING id + """, (name, username, email, password_hash, group)) + + user_id = cur.fetchone()[0] + conn.commit() + + cur.close() + conn.close() + + return user_id + + except Exception as e: + print(f"Error creating user: {e}") + raise + +def createToken(user_id, dbuser, dbpass, dbhost, dbname): + try: + conn = psycopg2.connect( + host=dbhost, + user=dbuser, + password=dbpass, + database=dbname + ) + cur = conn.cursor() + # generate a secure random token id (URL-safe) + token_id = secrets.token_urlsafe(32) + + cur.execute(""" + INSERT INTO userTokens (id, owner_id) + VALUES (%s, %s) RETURNING id, creation_date, expiration_date + """, (token_id, user_id)) + + token_data = cur.fetchone() + conn.commit() + + cur.close() + conn.close() + + return token_data[0] + + except Exception as e: + print(f"Error creating token: {e}") + raise + +def loginUser(username, password, dbuser, dbpass, dbhost, dbname): + try: + conn = psycopg2.connect( + host=dbhost, + user=dbuser, + password=dbpass, + database=dbname + ) + cur = conn.cursor() + + cur.execute("SELECT id, password_hash FROM users WHERE username = %s", (username,)) + user_record = cur.fetchone() + + cur.close() + conn.close() + + if user_record: + user_id, password_hash = user_record + ph = PasswordHasher() + try: + ph.verify(password_hash, password) + return createToken(user_id, dbuser, dbpass, dbhost, dbname) + except: + return None + else: + return None + + except Exception as e: + print(f"Error logging in user: {e}") + raise + +def removeToken(token_id, dbuser, dbpass, dbhost, dbname): + try: + conn = psycopg2.connect( + host=dbhost, + user=dbuser, + password=dbpass, + database=dbname + ) + cur = conn.cursor() + + cur.execute("DELETE FROM userTokens WHERE id = %s", (token_id,)) + conn.commit() + + cur.close() + conn.close() + + return True + + except Exception as e: + print(f"Error removing token: {e}") + return False + +def verifyToken(token_id, dbuser, dbpass, dbhost, dbname): + try: + conn = psycopg2.connect( + host=dbhost, + user=dbuser, + password=dbpass, + database=dbname + ) + cur = conn.cursor() + + cur.execute(""" + SELECT owner_id FROM userTokens + WHERE id = %s AND expiration_date > CURRENT_TIMESTAMP + """, (token_id,)) + + token_record = cur.fetchone() + + cur.close() + conn.close() + + if token_record: + return token_record[0] + else: + return None + + except Exception as e: + print(f"Error verifying token: {e}") + return None \ No newline at end of file diff --git a/initdb.py b/initdb.py index 3167643..169c643 100644 --- a/initdb.py +++ b/initdb.py @@ -1,39 +1,26 @@ import psycopg2 from psycopg2 import sql from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT -from dotenv import load_dotenv -from os import getenv -load_dotenv() - -# Database connection parameters -DB_HOST = getenv("DB_HOST")+":"+getenv("DB_PORT") -DB_USER = getenv("DB_USER") -DB_PASSWORD = getenv("DB_PASSWORD") -DB_NAME = "outpost" - -def create_database(): - """Create the outpost database if it doesn't exist""" +def createDatabase(dbuser, dbpass, dbhost, dbname): try: - # Connect to PostgreSQL server (default postgres database) conn = psycopg2.connect( - host=DB_HOST, - user=DB_USER, - password=DB_PASSWORD, + host=dbhost, + user=dbuser, + password=dbpass, database="postgres" ) conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) cur = conn.cursor() - # Check if database exists - cur.execute("SELECT 1 FROM pg_database WHERE datname = %s", (DB_NAME,)) + cur.execute("SELECT 1 FROM pg_database WHERE datname = %s", (dbname,)) exists = cur.fetchone() if not exists: - cur.execute(sql.SQL("CREATE DATABASE {}").format(sql.Identifier(DB_NAME))) - print(f"Database '{DB_NAME}' created successfully") + cur.execute(sql.SQL("CREATE DATABASE {}").format(sql.Identifier(dbname))) + print(f"Database '{dbname}' created successfully") else: - print(f"Database '{DB_NAME}' already exists") + print(f"Database '{dbname}' already exists") cur.close() conn.close() @@ -42,37 +29,85 @@ def create_database(): print(f"Error creating database: {e}") raise -def create_tables(): +def createTables(dbuser, dbpass, dbhost, dbname): try: conn = psycopg2.connect( - host=DB_HOST, - user=DB_USER, - password=DB_PASSWORD, - database=DB_NAME + host=dbhost, + user=dbuser, + password=dbpass, + database=dbname ) cur = conn.cursor() + cur.execute(""" + CREATE TABLE IF NOT EXISTS groups ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + name VARCHAR(255) UNIQUE NOT NULL, + parent UUID REFERENCES groups(id), + creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + """) + print("Table 'groups' created or already exists") + cur.execute(""" CREATE TABLE IF NOT EXISTS users ( - id SERIAL PRIMARY KEY, + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), name VARCHAR(255) NOT NULL, username VARCHAR(255) UNIQUE NOT NULL, email VARCHAR(255), creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - password_hash VARCHAR(255) NOT NULL + password_hash VARCHAR(255) NOT NULL, + group_id UUID REFERENCES groups(id) ) """) print("Table 'users' created or already exists") cur.execute(""" - CREATE TABLE IF NOT EXISTS logging ( + CREATE TABLE IF NOT EXISTS userData ( id SERIAL PRIMARY KEY, - time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - user_id INTEGER REFERENCES users(id), - action TEXT NOT NULL + user_id uuid REFERENCES users(id), + service_id VARCHAR(255) NOT NULL, + creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + property VARCHAR(255) NOT NULL, + value VARCHAR(255) NOT NULL ) """) - print("Table 'logging' created or already exists") + print("Table 'userData' created or already exists") + + cur.execute(""" + CREATE TABLE IF NOT EXISTS groupData ( + id SERIAL PRIMARY KEY, + group_id uuid REFERENCES groups(id), + service_id VARCHAR(255) NOT NULL, + creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + property VARCHAR(255) NOT NULL, + value VARCHAR(255) NOT NULL + ) + """) + print("Table 'userData' created or already exists") + + cur.execute(""" + CREATE TABLE IF NOT EXISTS logs ( + id SERIAL PRIMARY KEY, + user_id uuid REFERENCES users(id), + action VARCHAR(255), + details VARCHAR(255), + user_ip VARCHAR(255), + user_agent VARCHAR(255), + date TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + """) + print("Table 'logs' created or already exists") + + cur.execute(""" + CREATE TABLE IF NOT EXISTS userTokens ( + id VARCHAR(255) PRIMARY KEY, + owner_id uuid REFERENCES users(id), + creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + expiration_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP + INTERVAL '30 days' + ) + """) + print("Table 'userTokens' created or already exists") conn.commit() cur.close() @@ -80,50 +115,4 @@ def create_tables(): except Exception as e: print(f"Error creating tables: {e}") - raise - -def create_user_table(id): - try: - conn = psycopg2.connect( - host=DB_HOST, - user=DB_USER, - password=DB_PASSWORD, - database=DB_NAME - ) - cur = conn.cursor() - - table_name = f"userpermissions.{id.lower().replace(' ', '_').replace('-', '_').replace('.', '_')}" - - cur.execute(sql.SQL(""" - CREATE TABLE IF NOT EXISTS {} ( - key VARCHAR(255) PRIMARY KEY, - value TEXT - ) - """).format(sql.Identifier(table_name))) - - print(f"Table '{table_name}' created or already exists") - - conn.commit() - cur.close() - conn.close() - - except Exception as e: - print(f"Error creating user table: {e}") - raise - -def main(): - """Main function to set up the database""" - print("Starting database setup...") - - # Step 1: Create database - create_database() - - # Step 2: Create tables - create_tables() - - print("\nDatabase setup completed successfully!") - print("\nTo create a user-specific table, call:") - print("create_user_table('username')") - -if __name__ == "__main__": - main() \ No newline at end of file + raise \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..578a4ab --- /dev/null +++ b/main.py @@ -0,0 +1,74 @@ +import flask +from flask import render_template, jsonify, request, redirect, Response +import initdb +import db +from dotenv import load_dotenv +from os import getenv + +load_dotenv() + +DB_HOST = getenv("DB_HOST") +DB_USER = getenv("DB_USER") +DB_PASSWORD = getenv("DB_PASSWORD") +DB_NAME = getenv("DB_NAME") + +appName = "Outpost" + +app = flask.Flask(__name__) + +@app.route('/', methods = ['GET']) +def index(): + token = request.cookies.get('auth_token', 'none') + userId = db.verifyToken(token, DB_USER, DB_PASSWORD, DB_HOST, DB_NAME) + if (userId == None): + return render_template('login.html', appName=appName) + else: + return render_template("home.html", pageTitle="Home | " + appName) + +@app.route('/api/login', methods = ['POST']) +def handleLogin(): + username = request.json['username'].lower() + password = request.json['password'] + newToken = db.loginUser(username, password, DB_USER, DB_PASSWORD, DB_HOST, DB_NAME) + if newToken == None: + print("Invalid login attempt") + return jsonify("Invalid username or password") + return jsonify(newToken) + +@app.route('/signup', methods = ['GET']) +def signup(): + token = request.cookies.get('auth_token', 'none') + userId = db.verifyToken(token, DB_USER, DB_PASSWORD, DB_HOST, DB_NAME) + if (userId == None): + return render_template('signup.html', appName=appName) + else: + return render_template("home.html", pageTitle="Home | " + appName) + +@app.route('/logout', methods = ['GET']) +def logout(): + token = request.cookies.get('auth_token', 'none') + try: + token = request.headers['remove-token'] + except: + pass + db.removeToken(token, DB_USER, DB_PASSWORD, DB_HOST, DB_NAME) + return render_template('logout.html', appName=appName) + +@app.route('/api/signup', methods = ['POST']) +def handleSignup(): + try: + username = request.json['username'].lower() + email = request.json['email'].lower() + password = request.json['password'] + displayName = request.json['displayname'] + db.createUser(displayName, username, email, password, DB_USER, DB_PASSWORD, DB_HOST, DB_NAME) + except: + return jsonify("An error occured") + + +if __name__ == '__main__': + initdb.createDatabase(DB_USER, DB_PASSWORD, DB_HOST, DB_NAME) + initdb.createTables(DB_USER, DB_PASSWORD, DB_HOST, DB_NAME) + if not db.getGroupByName("root", DB_USER, DB_PASSWORD, DB_HOST, DB_NAME): + db.createGroup("root", None, DB_USER, DB_PASSWORD, DB_HOST, DB_NAME) + app.run(debug=True) \ No newline at end of file diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..0189575 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,545 @@ + + + + + + Login | {{ appName }} + + + + + +
+
+ +
+
+ +
+ {{ appName }} +
+ + +
+

Login

+

Please enter your email or username to log in

+ +
+
+
+ + +
+
+ + +
+
+ + + + + + + + + +
+
+ + + + \ No newline at end of file