From 503b26403c01aef9dd75b5d38005fac88a648d0a Mon Sep 17 00:00:00 2001 From: Johnny Date: Mon, 29 Apr 2024 00:15:40 +0100 Subject: [PATCH] Fixed live editing, corrected many small errors, added more general best practices --- .gitignore | 1 + APIDOCS.md | 4 +- ROADMAP.md | 1 + init_db | 6 +-- main | 106 ++++++++++++++++++++++++++++++++--------------------- removeuser | 11 +++--- schema.sql | 2 +- 7 files changed, 78 insertions(+), 53 deletions(-) diff --git a/.gitignore b/.gitignore index 23bfc0d..715e166 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ database.db config.ini config.ini.example +.idea \ No newline at end of file diff --git a/APIDOCS.md b/APIDOCS.md index 194d1cc..773038c 100644 --- a/APIDOCS.md +++ b/APIDOCS.md @@ -14,7 +14,7 @@ To prevent the server from knowing the encryption key, the password you provide If you wish to change the user's password, set "passwordchange" to "yes" and "newpass" to the new hash. -Some users use the legacy argon2id mode (by which i mean about 8, so only implement if you feel like it), and to implement argon2id functionality, you hash like this: +Some users use the legacy argon2 id mode (by which I mean about 8, so only implement if you feel like it), and to implement argon2 id functionality, you hash like this: ``` Parallelism should be 1 @@ -29,7 +29,7 @@ The output should be in the encoded format, not the hashed format Salt should be the SHA512 of the password ``` -(Yes i know this is really bad practice, guess why we are replacing it) +(Yes I know these are really awful practice, guess why we are replacing it) To test if SHA-3 or argon2 is used, just try the SHA-3 and if 422 gets returned try argon2. diff --git a/ROADMAP.md b/ROADMAP.md index a7c3eb2..0ff22a8 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,4 +2,5 @@ - Switch to WebSockets for updating notes + live updating of note list and more, this involves redoing some APIs - Compress notes to reduce bandwidth and storage +- Use OAuth for authentication - Dedicated domain (not just a subdomain, if anyone can donate a domain let Arzumify know!) diff --git a/init_db b/init_db index be00da1..247a0d8 100755 --- a/init_db +++ b/init_db @@ -2,19 +2,19 @@ import sqlite3 import os + def generatedb(): connection = sqlite3.connect("database.db") with open("schema.sql") as f: connection.executescript(f.read()) - cur = connection.cursor() - connection.commit() connection.close() print("[INFO] Generated database") + if not os.path.exists("database.db"): generatedb() else: @@ -26,4 +26,4 @@ else: elif ":3" in answer: print(":3") else: - print("Stopped") \ No newline at end of file + print("Stopped") diff --git a/main b/main index 7018cc5..a97469c 100644 --- a/main +++ b/main @@ -8,7 +8,7 @@ import asyncio from hypercorn.config import Config from hypercorn.asyncio import serve from werkzeug.security import generate_password_hash, check_password_hash -from quart import Quart, request, url_for, flash, redirect, session, make_response, send_from_directory, stream_with_context, Response, request +from quart import Quart, request # Parse configuration file, and check if anything is wrong with it if not os.path.exists("config.ini"): @@ -31,33 +31,38 @@ if SECRET_KEY == "supersecretkey" or SECRET_KEY == "placeholder": app = Quart(__name__) app.config["SECRET_KEY"] = SECRET_KEY + # Database functions def get_db_connection(): conn = sqlite3.connect("database.db") conn.row_factory = sqlite3.Row return conn -def get_user(id): + +def get_user(identifier): conn = get_db_connection() post = conn.execute("SELECT * FROM users WHERE id = ?", - (id,)).fetchone() + (identifier,)).fetchone() conn.close() if post is None: return None return post -def get_note(id): + +def get_note(identifier): conn = get_db_connection() post = conn.execute("SELECT * FROM notes WHERE id = ?", - (id,)).fetchone() + (identifier,)).fetchone() conn.close() if post is None: return None return post -def get_space(id): + +def get_space(identifier): conn = get_db_connection() - notes = conn.execute("SELECT content, title FROM notes WHERE creator = ? ORDER BY id DESC;", (id,)).fetchall() + notes = conn.execute("SELECT content, title FROM notes WHERE creator = ? ORDER BY id DESC;", + (identifier,)).fetchall() conn.close() spacetaken = 0 for x in notes: @@ -65,33 +70,35 @@ def get_space(id): spacetaken = spacetaken + len(x["title"].encode("utf-8")) return spacetaken -def get_note_count(id): - conn = get_db_connection() - notes = conn.execute("SELECT content, title FROM notes WHERE creator = ? ORDER BY id DESC;", (id,)).fetchall() - conn.close() - notecount = 0 - for x in notes: - notecount = notecount + 1 - return notecount -def get_session(id): +def get_note_count(identifier): + conn = get_db_connection() + notes = conn.execute("SELECT content, title FROM notes WHERE creator = ? ORDER BY id DESC;", + (identifier,)).fetchall() + conn.close() + return len(notes) + + +def get_session(identifier): conn = get_db_connection() post = conn.execute("SELECT * FROM sessions WHERE session = ?", - (id,)).fetchone() + (identifier,)).fetchone() conn.close() if post is None: return None return post -def get_session_from_sessionid(id): + +def get_session_from_sessionid(identifier): conn = get_db_connection() post = conn.execute("SELECT * FROM sessions WHERE sessionid = ?", - (id,)).fetchone() + (identifier,)).fetchone() conn.close() if post is None: return None return post + def check_username_taken(username): conn = get_db_connection() post = conn.execute("SELECT * FROM users WHERE lower(username) = ?", @@ -101,6 +108,7 @@ def check_username_taken(username): return None return post["id"] + # Disable CORS @app.after_request async def add_cors_headers(response): @@ -109,13 +117,16 @@ async def add_cors_headers(response): response.headers.add("Access-Control-Allow-Methods", "*") return response + # Live editing store messages = {} + @app.route("/api/version", methods=("GET", "POST")) async def apiversion(): return "Burgernotes Version 1.2" + @app.route("/api/signup", methods=("GET", "POST")) async def apisignup(): if request.method == "POST": @@ -138,7 +149,7 @@ async def apisignup(): if len(password) < 14: return {}, 422 - if not check_username_taken(username) == None: + if not check_username_taken(username) is None: return {}, 409 hashedpassword = generate_password_hash(password) @@ -148,7 +159,6 @@ async def apisignup(): (username, hashedpassword, str(time.time()))) userID = check_username_taken(username) - user = get_user(userID) randomCharacters = secrets.token_hex(512) @@ -161,6 +171,7 @@ async def apisignup(): "key": randomCharacters }, 200 + @app.route("/api/login", methods=("GET", "POST")) async def apilogin(): if request.method == "POST": @@ -172,13 +183,13 @@ async def apilogin(): check_username_thing = check_username_taken(username) - if check_username_thing == None: + if check_username_thing is None: return {}, 401 userID = check_username_taken(username) user = get_user(userID) - if not check_password_hash(user["password"], (password)): + if not check_password_hash(user["password"], password): return {}, 401 randomCharacters = secrets.token_hex(512) @@ -217,6 +228,7 @@ async def apiuserinfo(): } return datatemplate + @app.route("/api/listnotes", methods=("GET", "POST")) async def apilistnotes(): if request.method == "POST": @@ -241,6 +253,7 @@ async def apilistnotes(): return datatemplate, 200 + @app.route("/api/exportnotes", methods=("GET", "POST")) async def apiexportnotes(): if request.method == "POST": @@ -268,6 +281,7 @@ async def apiexportnotes(): return datatemplate, 200 + @app.route("/api/newnote", methods=("GET", "POST")) async def apinewnote(): if request.method == "POST": @@ -280,12 +294,13 @@ async def apinewnote(): conn = get_db_connection() conn.execute("INSERT INTO notes (title, content, creator, created, edited) VALUES (?, ?, ?, ?, ?)", - (noteName, "", user["id"], str(time.time()), str(time.time()))) + (noteName, "", user["id"], str(time.time()), str(time.time()))) conn.commit() conn.close() return {}, 200 + @app.route("/api/readnote", methods=("GET", "POST")) async def apireadnote(): if request.method == "POST": @@ -298,8 +313,8 @@ async def apireadnote(): note = get_note(noteId) - if (note != None): - if (user["id"] == note["creator"]): + if note is not None: + if user["id"] == note["creator"]: contenttemplate = { "content": note["content"] } @@ -310,6 +325,7 @@ async def apireadnote(): else: return {}, 422 + @app.route("/api/waitforedit", methods=("GET", "POST")) async def waitforedit(): if request.method == "POST": @@ -317,26 +333,27 @@ async def waitforedit(): secretKey = data["secretKey"] userCookie = get_session(secretKey) user = get_user(userCookie["id"]) - complete = true + complete = True start_time = time.time() while user["id"] not in messages or not messages[user["id"]]: await asyncio.sleep(0) elapsed_time = time.time() - start_time if elapsed_time >= 20: + complete = False break - complete = false message = messages[user["id"]].pop(0) del messages[user["id"]] - if complete == true: + if complete: return { "note": message }, 200 else: return 400 + @app.route("/api/editnote", methods=("GET", "POST")) async def apieditnote(): if request.method == "POST": @@ -354,10 +371,11 @@ async def apieditnote(): if get_space(user["id"]) + len(content.encode("utf-8")) > int(MAX_STORAGE): return {}, 418 - if (note != None): - if (user["id"] == note["creator"]): + if note is not None: + if user["id"] == note["creator"]: conn = get_db_connection() - conn.execute("UPDATE notes SET content = ?, title = ?, edited = ? WHERE id = ?", (content, title, str(time.time()), noteId)) + conn.execute("UPDATE notes SET content = ?, title = ?, edited = ? WHERE id = ?", + (content, title, str(time.time()), noteId)) conn.commit() conn.close() @@ -388,8 +406,8 @@ async def apieditnotetitle(): if get_space(user["id"]) + len(content.encode("utf-8")) > int(MAX_STORAGE): return {}, 418 - if (note != None): - if (user["id"] == note["creator"]): + if note is not None: + if user["id"] == note["creator"]: conn = get_db_connection() conn.execute("UPDATE notes SET title = ?, edited = ? WHERE id = ?", (content, str(time.time()), noteId)) conn.commit() @@ -414,8 +432,8 @@ async def apiremovenote(): note = get_note(noteId) - if (note != None): - if (user["id"] == note["creator"]): + if note is not None: + if user["id"] == note["creator"]: conn = get_db_connection() conn.execute("DELETE FROM notes WHERE id = ?", (noteId,)) conn.commit() @@ -435,7 +453,6 @@ async def apideleteaccount(): secretKey = data["secretKey"] userCookie = get_session(secretKey) - user = get_user(userCookie["id"]) conn = get_db_connection() conn.execute("DELETE FROM notes WHERE creator = ?", (userCookie["id"],)) @@ -445,6 +462,7 @@ async def apideleteaccount(): return {}, 200 + @app.route("/api/sessions/list", methods=("GET", "POST")) async def apisessionslist(): if request.method == "POST": @@ -463,7 +481,7 @@ async def apisessionslist(): for x in sessions: device = x["device"] thisSession = False - if (x["session"] == secretKey): + if x["session"] == secretKey: thisSession = True sessiontemplate = { "id": x["sessionid"], @@ -474,6 +492,7 @@ async def apisessionslist(): return datatemplate, 200 + @app.route("/api/sessions/remove", methods=("GET", "POST")) async def apisessionsremove(): if request.method == "POST": @@ -486,8 +505,8 @@ async def apisessionsremove(): session = get_session_from_sessionid(sessionId) - if (session != None): - if (user["id"] == session["id"]): + if session is not None: + if user["id"] == session["id"]: conn = get_db_connection() conn.execute("DELETE FROM sessions WHERE sessionid = ?", (session["sessionid"],)) conn.commit() @@ -521,14 +540,17 @@ async def listusers(): else: return {}, 401 + @app.errorhandler(500) -async def burger(e): +async def burger(): return {}, 500 + @app.errorhandler(404) -async def burger(e): +async def burger(): return {}, 404 + # Start server hypercornconfig = Config() hypercornconfig.bind = (HOST + ":" + PORT) diff --git a/removeuser b/removeuser index 73e6d7c..3884430 100644 --- a/removeuser +++ b/removeuser @@ -5,13 +5,14 @@ import sys print("type n to cancel") answer = input("delete user, what is user id?") -if (answer == "n"): +if answer == "n": sys.exit() + def get_db_connection(): - conn = sqlite3.connect("database.db") - conn.row_factory = sqlite3.Row - return conn + connection = sqlite3.connect("database.db") + connection.row_factory = sqlite3.Row + return connection print("deleting notes") @@ -28,4 +29,4 @@ conn.execute("DELETE FROM users WHERE id = ?", (int(answer),)) conn.commit() conn.close() -print("success") \ No newline at end of file +print("success") diff --git a/schema.sql b/schema.sql index f4cb36d..b075616 100644 --- a/schema.sql +++ b/schema.sql @@ -22,5 +22,5 @@ CREATE TABLE sessions ( sessionid INTEGER PRIMARY KEY AUTOINCREMENT, session TEXT NOT NULL, id INTEGER NOT NULL, - device TEXT NOT NULL DEFAULT "?" + device TEXT NOT NULL DEFAULT '?' ); \ No newline at end of file