Fixed live editing, corrected many small errors, added more general best practices

This commit is contained in:
Tracker-Friendly 2024-04-29 00:15:40 +01:00
parent e178bbee57
commit 503b26403c
7 changed files with 78 additions and 53 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
database.db database.db
config.ini config.ini
config.ini.example config.ini.example
.idea

View File

@ -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. 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 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 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. To test if SHA-3 or argon2 is used, just try the SHA-3 and if 422 gets returned try argon2.

View File

@ -2,4 +2,5 @@
- Switch to WebSockets for updating notes + live updating of note list and more, this involves redoing some APIs - 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 - 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!) - Dedicated domain (not just a subdomain, if anyone can donate a domain let Arzumify know!)

View File

@ -2,19 +2,19 @@
import sqlite3 import sqlite3
import os import os
def generatedb(): def generatedb():
connection = sqlite3.connect("database.db") connection = sqlite3.connect("database.db")
with open("schema.sql") as f: with open("schema.sql") as f:
connection.executescript(f.read()) connection.executescript(f.read())
cur = connection.cursor()
connection.commit() connection.commit()
connection.close() connection.close()
print("[INFO] Generated database") print("[INFO] Generated database")
if not os.path.exists("database.db"): if not os.path.exists("database.db"):
generatedb() generatedb()
else: else:
@ -26,4 +26,4 @@ else:
elif ":3" in answer: elif ":3" in answer:
print(":3") print(":3")
else: else:
print("Stopped") print("Stopped")

106
main
View File

@ -8,7 +8,7 @@ import asyncio
from hypercorn.config import Config from hypercorn.config import Config
from hypercorn.asyncio import serve from hypercorn.asyncio import serve
from werkzeug.security import generate_password_hash, check_password_hash 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 # Parse configuration file, and check if anything is wrong with it
if not os.path.exists("config.ini"): if not os.path.exists("config.ini"):
@ -31,33 +31,38 @@ if SECRET_KEY == "supersecretkey" or SECRET_KEY == "placeholder":
app = Quart(__name__) app = Quart(__name__)
app.config["SECRET_KEY"] = SECRET_KEY app.config["SECRET_KEY"] = SECRET_KEY
# Database functions # Database functions
def get_db_connection(): def get_db_connection():
conn = sqlite3.connect("database.db") conn = sqlite3.connect("database.db")
conn.row_factory = sqlite3.Row conn.row_factory = sqlite3.Row
return conn return conn
def get_user(id):
def get_user(identifier):
conn = get_db_connection() conn = get_db_connection()
post = conn.execute("SELECT * FROM users WHERE id = ?", post = conn.execute("SELECT * FROM users WHERE id = ?",
(id,)).fetchone() (identifier,)).fetchone()
conn.close() conn.close()
if post is None: if post is None:
return None return None
return post return post
def get_note(id):
def get_note(identifier):
conn = get_db_connection() conn = get_db_connection()
post = conn.execute("SELECT * FROM notes WHERE id = ?", post = conn.execute("SELECT * FROM notes WHERE id = ?",
(id,)).fetchone() (identifier,)).fetchone()
conn.close() conn.close()
if post is None: if post is None:
return None return None
return post return post
def get_space(id):
def get_space(identifier):
conn = get_db_connection() 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() conn.close()
spacetaken = 0 spacetaken = 0
for x in notes: for x in notes:
@ -65,33 +70,35 @@ def get_space(id):
spacetaken = spacetaken + len(x["title"].encode("utf-8")) spacetaken = spacetaken + len(x["title"].encode("utf-8"))
return spacetaken 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() conn = get_db_connection()
post = conn.execute("SELECT * FROM sessions WHERE session = ?", post = conn.execute("SELECT * FROM sessions WHERE session = ?",
(id,)).fetchone() (identifier,)).fetchone()
conn.close() conn.close()
if post is None: if post is None:
return None return None
return post return post
def get_session_from_sessionid(id):
def get_session_from_sessionid(identifier):
conn = get_db_connection() conn = get_db_connection()
post = conn.execute("SELECT * FROM sessions WHERE sessionid = ?", post = conn.execute("SELECT * FROM sessions WHERE sessionid = ?",
(id,)).fetchone() (identifier,)).fetchone()
conn.close() conn.close()
if post is None: if post is None:
return None return None
return post return post
def check_username_taken(username): def check_username_taken(username):
conn = get_db_connection() conn = get_db_connection()
post = conn.execute("SELECT * FROM users WHERE lower(username) = ?", post = conn.execute("SELECT * FROM users WHERE lower(username) = ?",
@ -101,6 +108,7 @@ def check_username_taken(username):
return None return None
return post["id"] return post["id"]
# Disable CORS # Disable CORS
@app.after_request @app.after_request
async def add_cors_headers(response): async def add_cors_headers(response):
@ -109,13 +117,16 @@ async def add_cors_headers(response):
response.headers.add("Access-Control-Allow-Methods", "*") response.headers.add("Access-Control-Allow-Methods", "*")
return response return response
# Live editing store # Live editing store
messages = {} messages = {}
@app.route("/api/version", methods=("GET", "POST")) @app.route("/api/version", methods=("GET", "POST"))
async def apiversion(): async def apiversion():
return "Burgernotes Version 1.2" return "Burgernotes Version 1.2"
@app.route("/api/signup", methods=("GET", "POST")) @app.route("/api/signup", methods=("GET", "POST"))
async def apisignup(): async def apisignup():
if request.method == "POST": if request.method == "POST":
@ -138,7 +149,7 @@ async def apisignup():
if len(password) < 14: if len(password) < 14:
return {}, 422 return {}, 422
if not check_username_taken(username) == None: if not check_username_taken(username) is None:
return {}, 409 return {}, 409
hashedpassword = generate_password_hash(password) hashedpassword = generate_password_hash(password)
@ -148,7 +159,6 @@ async def apisignup():
(username, hashedpassword, str(time.time()))) (username, hashedpassword, str(time.time())))
userID = check_username_taken(username) userID = check_username_taken(username)
user = get_user(userID)
randomCharacters = secrets.token_hex(512) randomCharacters = secrets.token_hex(512)
@ -161,6 +171,7 @@ async def apisignup():
"key": randomCharacters "key": randomCharacters
}, 200 }, 200
@app.route("/api/login", methods=("GET", "POST")) @app.route("/api/login", methods=("GET", "POST"))
async def apilogin(): async def apilogin():
if request.method == "POST": if request.method == "POST":
@ -172,13 +183,13 @@ async def apilogin():
check_username_thing = check_username_taken(username) check_username_thing = check_username_taken(username)
if check_username_thing == None: if check_username_thing is None:
return {}, 401 return {}, 401
userID = check_username_taken(username) userID = check_username_taken(username)
user = get_user(userID) user = get_user(userID)
if not check_password_hash(user["password"], (password)): if not check_password_hash(user["password"], password):
return {}, 401 return {}, 401
randomCharacters = secrets.token_hex(512) randomCharacters = secrets.token_hex(512)
@ -217,6 +228,7 @@ async def apiuserinfo():
} }
return datatemplate return datatemplate
@app.route("/api/listnotes", methods=("GET", "POST")) @app.route("/api/listnotes", methods=("GET", "POST"))
async def apilistnotes(): async def apilistnotes():
if request.method == "POST": if request.method == "POST":
@ -241,6 +253,7 @@ async def apilistnotes():
return datatemplate, 200 return datatemplate, 200
@app.route("/api/exportnotes", methods=("GET", "POST")) @app.route("/api/exportnotes", methods=("GET", "POST"))
async def apiexportnotes(): async def apiexportnotes():
if request.method == "POST": if request.method == "POST":
@ -268,6 +281,7 @@ async def apiexportnotes():
return datatemplate, 200 return datatemplate, 200
@app.route("/api/newnote", methods=("GET", "POST")) @app.route("/api/newnote", methods=("GET", "POST"))
async def apinewnote(): async def apinewnote():
if request.method == "POST": if request.method == "POST":
@ -280,12 +294,13 @@ async def apinewnote():
conn = get_db_connection() conn = get_db_connection()
conn.execute("INSERT INTO notes (title, content, creator, created, edited) VALUES (?, ?, ?, ?, ?)", 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.commit()
conn.close() conn.close()
return {}, 200 return {}, 200
@app.route("/api/readnote", methods=("GET", "POST")) @app.route("/api/readnote", methods=("GET", "POST"))
async def apireadnote(): async def apireadnote():
if request.method == "POST": if request.method == "POST":
@ -298,8 +313,8 @@ async def apireadnote():
note = get_note(noteId) note = get_note(noteId)
if (note != None): if note is not None:
if (user["id"] == note["creator"]): if user["id"] == note["creator"]:
contenttemplate = { contenttemplate = {
"content": note["content"] "content": note["content"]
} }
@ -310,6 +325,7 @@ async def apireadnote():
else: else:
return {}, 422 return {}, 422
@app.route("/api/waitforedit", methods=("GET", "POST")) @app.route("/api/waitforedit", methods=("GET", "POST"))
async def waitforedit(): async def waitforedit():
if request.method == "POST": if request.method == "POST":
@ -317,26 +333,27 @@ async def waitforedit():
secretKey = data["secretKey"] secretKey = data["secretKey"]
userCookie = get_session(secretKey) userCookie = get_session(secretKey)
user = get_user(userCookie["id"]) user = get_user(userCookie["id"])
complete = true complete = True
start_time = time.time() start_time = time.time()
while user["id"] not in messages or not messages[user["id"]]: while user["id"] not in messages or not messages[user["id"]]:
await asyncio.sleep(0) await asyncio.sleep(0)
elapsed_time = time.time() - start_time elapsed_time = time.time() - start_time
if elapsed_time >= 20: if elapsed_time >= 20:
complete = False
break break
complete = false
message = messages[user["id"]].pop(0) message = messages[user["id"]].pop(0)
del messages[user["id"]] del messages[user["id"]]
if complete == true: if complete:
return { return {
"note": message "note": message
}, 200 }, 200
else: else:
return 400 return 400
@app.route("/api/editnote", methods=("GET", "POST")) @app.route("/api/editnote", methods=("GET", "POST"))
async def apieditnote(): async def apieditnote():
if request.method == "POST": if request.method == "POST":
@ -354,10 +371,11 @@ async def apieditnote():
if get_space(user["id"]) + len(content.encode("utf-8")) > int(MAX_STORAGE): if get_space(user["id"]) + len(content.encode("utf-8")) > int(MAX_STORAGE):
return {}, 418 return {}, 418
if (note != None): if note is not None:
if (user["id"] == note["creator"]): if user["id"] == note["creator"]:
conn = get_db_connection() 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.commit()
conn.close() conn.close()
@ -388,8 +406,8 @@ async def apieditnotetitle():
if get_space(user["id"]) + len(content.encode("utf-8")) > int(MAX_STORAGE): if get_space(user["id"]) + len(content.encode("utf-8")) > int(MAX_STORAGE):
return {}, 418 return {}, 418
if (note != None): if note is not None:
if (user["id"] == note["creator"]): if user["id"] == note["creator"]:
conn = get_db_connection() conn = get_db_connection()
conn.execute("UPDATE notes SET title = ?, edited = ? WHERE id = ?", (content, str(time.time()), noteId)) conn.execute("UPDATE notes SET title = ?, edited = ? WHERE id = ?", (content, str(time.time()), noteId))
conn.commit() conn.commit()
@ -414,8 +432,8 @@ async def apiremovenote():
note = get_note(noteId) note = get_note(noteId)
if (note != None): if note is not None:
if (user["id"] == note["creator"]): if user["id"] == note["creator"]:
conn = get_db_connection() conn = get_db_connection()
conn.execute("DELETE FROM notes WHERE id = ?", (noteId,)) conn.execute("DELETE FROM notes WHERE id = ?", (noteId,))
conn.commit() conn.commit()
@ -435,7 +453,6 @@ async def apideleteaccount():
secretKey = data["secretKey"] secretKey = data["secretKey"]
userCookie = get_session(secretKey) userCookie = get_session(secretKey)
user = get_user(userCookie["id"])
conn = get_db_connection() conn = get_db_connection()
conn.execute("DELETE FROM notes WHERE creator = ?", (userCookie["id"],)) conn.execute("DELETE FROM notes WHERE creator = ?", (userCookie["id"],))
@ -445,6 +462,7 @@ async def apideleteaccount():
return {}, 200 return {}, 200
@app.route("/api/sessions/list", methods=("GET", "POST")) @app.route("/api/sessions/list", methods=("GET", "POST"))
async def apisessionslist(): async def apisessionslist():
if request.method == "POST": if request.method == "POST":
@ -463,7 +481,7 @@ async def apisessionslist():
for x in sessions: for x in sessions:
device = x["device"] device = x["device"]
thisSession = False thisSession = False
if (x["session"] == secretKey): if x["session"] == secretKey:
thisSession = True thisSession = True
sessiontemplate = { sessiontemplate = {
"id": x["sessionid"], "id": x["sessionid"],
@ -474,6 +492,7 @@ async def apisessionslist():
return datatemplate, 200 return datatemplate, 200
@app.route("/api/sessions/remove", methods=("GET", "POST")) @app.route("/api/sessions/remove", methods=("GET", "POST"))
async def apisessionsremove(): async def apisessionsremove():
if request.method == "POST": if request.method == "POST":
@ -486,8 +505,8 @@ async def apisessionsremove():
session = get_session_from_sessionid(sessionId) session = get_session_from_sessionid(sessionId)
if (session != None): if session is not None:
if (user["id"] == session["id"]): if user["id"] == session["id"]:
conn = get_db_connection() conn = get_db_connection()
conn.execute("DELETE FROM sessions WHERE sessionid = ?", (session["sessionid"],)) conn.execute("DELETE FROM sessions WHERE sessionid = ?", (session["sessionid"],))
conn.commit() conn.commit()
@ -521,14 +540,17 @@ async def listusers():
else: else:
return {}, 401 return {}, 401
@app.errorhandler(500) @app.errorhandler(500)
async def burger(e): async def burger():
return {}, 500 return {}, 500
@app.errorhandler(404) @app.errorhandler(404)
async def burger(e): async def burger():
return {}, 404 return {}, 404
# Start server # Start server
hypercornconfig = Config() hypercornconfig = Config()
hypercornconfig.bind = (HOST + ":" + PORT) hypercornconfig.bind = (HOST + ":" + PORT)

View File

@ -5,13 +5,14 @@ import sys
print("type n to cancel") print("type n to cancel")
answer = input("delete user, what is user id?") answer = input("delete user, what is user id?")
if (answer == "n"): if answer == "n":
sys.exit() sys.exit()
def get_db_connection(): def get_db_connection():
conn = sqlite3.connect("database.db") connection = sqlite3.connect("database.db")
conn.row_factory = sqlite3.Row connection.row_factory = sqlite3.Row
return conn return connection
print("deleting notes") print("deleting notes")
@ -28,4 +29,4 @@ conn.execute("DELETE FROM users WHERE id = ?", (int(answer),))
conn.commit() conn.commit()
conn.close() conn.close()
print("success") print("success")

View File

@ -22,5 +22,5 @@ CREATE TABLE sessions (
sessionid INTEGER PRIMARY KEY AUTOINCREMENT, sessionid INTEGER PRIMARY KEY AUTOINCREMENT,
session TEXT NOT NULL, session TEXT NOT NULL,
id INTEGER NOT NULL, id INTEGER NOT NULL,
device TEXT NOT NULL DEFAULT "?" device TEXT NOT NULL DEFAULT '?'
); );