From: PhiTux Date: Fri, 3 Oct 2025 17:34:42 +0000 (+0200) Subject: deleted python-code & migration-info for backupcodes X-Git-Url: http://git.99rst.org/?a=commitdiff_plain;h=871a633606e026d90c66746306e36c764771a0a2;p=DailyTxT.git deleted python-code & migration-info for backupcodes --- diff --git a/backend-python/.gitignore b/backend-python/.gitignore deleted file mode 100644 index fc8ad9f..0000000 --- a/backend-python/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -.venv -.envrc -data/ -dev/ -*.pyc -__pycache__/ \ No newline at end of file diff --git a/backend-python/requirements.txt b/backend-python/requirements.txt deleted file mode 100644 index a463667..0000000 --- a/backend-python/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -fastapi[standard] -pyjwt -passlib[bcrypt] -argon2_cffi -cryptography -pydantic-settings \ No newline at end of file diff --git a/backend-python/server/__init__.py b/backend-python/server/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend-python/server/main.py b/backend-python/server/main.py deleted file mode 100644 index c117d0e..0000000 --- a/backend-python/server/main.py +++ /dev/null @@ -1,30 +0,0 @@ -from fastapi import FastAPI -from .routers import users, logs -from fastapi.middleware.cors import CORSMiddleware -import logging -from sys import stdout -from .utils.settings import settings - -logger = logging.getLogger("dailytxtLogger") -consoleHandler = logging.StreamHandler(stdout) -consoleHandler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")) -logger.addHandler(consoleHandler) -logger.setLevel(logging.DEBUG) - -app = FastAPI() - -origins = settings.allowed_hosts - -app.add_middleware( - CORSMiddleware, - allow_origins=origins, - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) - -app.include_router(users.router, prefix="/users") -app.include_router(logs.router, prefix="/logs") - - -logger.info("Server started") \ No newline at end of file diff --git a/backend-python/server/routers/__init__.py b/backend-python/server/routers/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend-python/server/routers/logs.py b/backend-python/server/routers/logs.py deleted file mode 100644 index 2defafa..0000000 --- a/backend-python/server/routers/logs.py +++ /dev/null @@ -1,638 +0,0 @@ -import datetime -import logging -import re -from fastapi import APIRouter, Depends, Form, UploadFile, File, HTTPException -from fastapi.responses import StreamingResponse -from pydantic import BaseModel -from . import users -from ..utils import fileHandling -from ..utils import security -import html -from typing import Annotated - - -logger = logging.getLogger("dailytxtLogger") - -router = APIRouter() - - -class Log(BaseModel): - day: int - month: int - year: int - text: str - date_written: str - -@router.post("/saveLog") -async def saveLog(log: Log, cookie = Depends(users.isLoggedIn)): - year = log.year - month = log.month - day = log.day - - content:dict = fileHandling.getMonth(cookie["user_id"], year, month) - - history_available = False - # move old log to history - if "days" in content.keys(): - for dayLog in content["days"]: - if dayLog["day"] == day and "text" in dayLog.keys(): - history_available = True - historyVersion = 0 - if "history" not in dayLog.keys(): - dayLog["history"] = [] - else: - for historyLog in dayLog["history"]: - if historyLog["version"] > historyVersion: - historyVersion = historyLog["version"] - historyVersion += 1 - dayLog["history"].append({"version": historyVersion, "text": dayLog["text"], "date_written": dayLog["date_written"]}) - break - - # save new log - enc_key = security.get_enc_key(cookie["user_id"], cookie["derived_key"]) - - ''' IMPORTANT: - Escaping html characters here is NOT possible, since it would break the appearance - of html-code in the EDITOR. Code inside a markdown code-quote (`...`) will be auto-escaped. - Not a perfect solution, but actually any user can only load its own logs (and so XSS himself)... - ''' - encrypted_text = security.encrypt_text(log.text, enc_key) - encrypted_date_written = security.encrypt_text(html.escape(log.date_written), enc_key) - - if "days" not in content.keys(): - content["days"] = [] - content["days"].append({"day": day, "text": encrypted_text, "date_written": encrypted_date_written}) - else: - found = False - for dayLog in content["days"]: - if dayLog["day"] == day: - dayLog["text"] = encrypted_text - dayLog["date_written"] = encrypted_date_written - found = True - break - if not found: - content["days"].append({"day": day, "text": encrypted_text, "date_written": encrypted_date_written}) - - if not fileHandling.writeMonth(cookie["user_id"], year, month, content): - logger.error(f"Failed to save log for {cookie['user_id']} on {year}-{month:02d}-{day:02d}") - return {"success": False} - - return {"success": True, "history_available": history_available} - - -@router.get("/getLog") -async def getLog(day: int, month: int, year: int, cookie = Depends(users.isLoggedIn)): - - content:dict = fileHandling.getMonth(cookie["user_id"], year, month) - - dummy = {"text": "", "date_written": "", "files": [], "tags": []} - - if "days" not in content.keys(): - return dummy - - for dayLog in content["days"]: - if dayLog["day"] == day: - enc_key = security.get_enc_key(cookie["user_id"], cookie["derived_key"]) - text = "" - date_written = "" - history_available = False - if "text" in dayLog.keys(): - text = security.decrypt_text(dayLog["text"], enc_key) - date_written = security.decrypt_text(dayLog["date_written"], enc_key) - if "files" in dayLog.keys(): - for file in dayLog["files"]: - file["filename"] = security.decrypt_text(file["enc_filename"], enc_key) - if "history" in dayLog.keys() and len(dayLog["history"]) > 0: - history_available = True - return {"text": text, "date_written": date_written, "files": dayLog.get("files", []), "tags": dayLog.get("tags", []), "history_available": history_available} - - return dummy - -def get_start_index(text, index): - # find a whitespace two places before the index - - if index == 0: - return 0 - - for i in range(3): - startIndex = text.rfind(" ", 0, index-1) - index = startIndex - if startIndex == -1: - return 0 - - return startIndex + 1 - -def get_end_index(text, index): - # find a whitespace two places after the index - - if index == len(text) - 1: - return len(text) - - for i in range(3): - endIndex = text.find(" ", index+1) - index = endIndex - if endIndex == -1: - return len(text) - - return endIndex - - -def get_begin(text): - # get first 5 words - words = text.split() - if len(words) < 5: - return text - return " ".join(words[:5]) - -def get_context(text: str, searchString: str, exact: bool): - # replace whitespace with non-breaking space - text = re.sub(r'\s+', " ", text) - - if exact: - pos = text.find(searchString) - else: - pos = text.lower().find(searchString.lower()) - if pos == -1: - return "Dailytxt: Error formatting..." - - start = get_start_index(text, pos) - end = get_end_index(text, pos + len(searchString) - 1) - return text[start:pos] + "" + text[pos:pos+len(searchString)] + "" + text[pos+len(searchString):end] - - -@router.get("/searchString") -async def searchString(searchString: str, cookie = Depends(users.isLoggedIn)): - results = [] - - enc_key = security.get_enc_key(cookie["user_id"], cookie["derived_key"]) - - # search in all years and months (dirs) - for year in fileHandling.get_years(cookie["user_id"]): - for month in fileHandling.get_months(cookie["user_id"], year): - content:dict = fileHandling.getMonth(cookie["user_id"], year, int(month)) - if "days" not in content.keys(): - continue - for dayLog in content["days"]: - if "text" not in dayLog.keys(): - if "files" in dayLog.keys(): - for file in dayLog["files"]: - filename = security.decrypt_text(file["enc_filename"], enc_key) - if searchString.lower() in filename.lower(): - context = "📎 " + filename - results.append({"year": year, "month": month, "day": dayLog["day"], "text": context}) - break - continue - - text = security.decrypt_text(dayLog["text"], enc_key) - - # "..." -> exact - # ... | ... -> or - # ... ... -> and - - if searchString.startswith('"') and searchString.endswith('"'): - if searchString[1:-1] in text: - context = get_context(text, searchString[1:-1], True) - results.append({"year": year, "month": month, "day": dayLog["day"], "text": context}) - - - elif "|" in searchString: - words = searchString.split("|") - for word in words: - if word.strip().lower() in text.lower(): - context = get_context(text, word.strip(), False) - results.append({"year": year, "month": month, "day": dayLog["day"], "text": context}) - break - - - elif " " in searchString: - if all([word.strip().lower() in text.lower() for word in searchString.split()]): - context = get_context(text, searchString.split()[0].strip(), False) - results.append({"year": year, "month": month, "day": dayLog["day"], "text": context}) - - - else: - if searchString.lower() in text.lower(): - context = get_context(text, searchString, False) - results.append({"year": year, "month": month, "day": dayLog["day"], "text": context}) - - elif "files" in dayLog.keys(): - for file in dayLog["files"]: - filename = security.decrypt_text(file["enc_filename"], enc_key) - if searchString.lower() in filename.lower(): - context = "📎 " + filename - results.append({"year": year, "month": month, "day": dayLog["day"], "text": context}) - break - - - # sort by year and month and day - results.sort(key=lambda x: (int(x["year"]), int(x["month"]), int(x["day"])), reverse=False) - return results - -@router.get("/searchTag") -async def searchTag(tag_id: int, cookie = Depends(users.isLoggedIn)): - results = [] - enc_key = security.get_enc_key(cookie["user_id"], cookie["derived_key"]) - - # search in all years and months (dirs) - for year in fileHandling.get_years(cookie["user_id"]): - for month in fileHandling.get_months(cookie["user_id"], year): - content:dict = fileHandling.getMonth(cookie["user_id"], year, int(month)) - if "days" not in content.keys(): - continue - for dayLog in content["days"]: - if "tags" not in dayLog.keys(): - continue - if tag_id in dayLog["tags"]: - context = '' - if "text" in dayLog.keys(): - text = security.decrypt_text(dayLog["text"], enc_key) - context = get_begin(text) - results.append({"year": year, "month": month, "day": dayLog["day"], "text": context}) - - # sort by year and month and day - results.sort(key=lambda x: (int(x["year"]), int(x["month"]), int(x["day"])), reverse=False) - return results - -@router.get("/getMarkedDays") -async def getMarkedDays(month: str, year: str, cookie = Depends(users.isLoggedIn)): - days_with_logs = [] - days_with_files = [] - days_bookmarked = [] - - content:dict = fileHandling.getMonth(cookie["user_id"], year, int(month)) - if "days" not in content.keys(): - return {"days_with_logs": [], "days_with_files": [], "days_bookmarked": []} - - for dayLog in content["days"]: - if "text" in dayLog.keys(): - days_with_logs.append(dayLog["day"]) - if "files" in dayLog.keys() and len(dayLog["files"]) > 0: - days_with_files.append(dayLog["day"]) - if "bookmarked" in dayLog.keys() and dayLog["bookmarked"]: - days_bookmarked.append(dayLog["day"]) - - return {"days_with_logs": days_with_logs, "days_with_files": days_with_files, "days_bookmarked": days_bookmarked} - - -@router.get("/loadMonthForReading") -async def loadMonthForReading(month: int, year: int, cookie = Depends(users.isLoggedIn)): - content:dict = fileHandling.getMonth(cookie["user_id"], year, month) - if "days" not in content.keys(): - return [] - - days = [] - enc_key = security.get_enc_key(cookie["user_id"], cookie["derived_key"]) - for dayLog in content["days"]: - day = {"day": dayLog["day"]} - if "text" in dayLog.keys(): - day["text"] = security.decrypt_text(dayLog["text"], enc_key) - day["date_written"] = security.decrypt_text(dayLog["date_written"], enc_key) - if "tags" in dayLog.keys(): - day["tags"] = dayLog["tags"] - if "files" in dayLog.keys(): - day["files"] = [] - for file in dayLog["files"]: - file["filename"] = security.decrypt_text(file["enc_filename"], enc_key) - day["files"].append(file) - - # if one of the keys is in day: - if "text" in day or "files" in day or "tags" in day: - days.append(day) - - days.sort(key=lambda x: x["day"]) - - return days - -''' -Data ist sent as FormData, not as JSON -''' -@router.post("/uploadFile") -async def uploadFile(day: Annotated[int, Form()], month: Annotated[int, Form()], year: Annotated[int, Form()], uuid: Annotated[str, Form()], file: Annotated[UploadFile, File()], cookie = Depends(users.isLoggedIn)): - - # encrypt file - enc_key = security.get_enc_key(cookie["user_id"], cookie["derived_key"]) - encrypted_file = security.encrypt_file(file.file.read(), enc_key) - if not fileHandling.writeFile(encrypted_file, cookie["user_id"], uuid): - return {"success": False} - - # save file in log - content:dict = fileHandling.getMonth(cookie["user_id"], year, month) - - enc_filename = security.encrypt_text(file.filename, enc_key) - new_file = {"enc_filename": enc_filename, "uuid_filename": uuid, "size": file.size} - - if "days" not in content.keys(): - content["days"] = [] - content["days"].append({"day": day, "files": [new_file]}) - - else: - found = False - for dayLog in content["days"]: - if dayLog["day"] == day: - if "files" not in dayLog.keys(): - dayLog["files"] = [] - dayLog["files"].append(new_file) - found = True - break - if not found: - content["days"].append({"day": day, "files": [new_file]}) - - if not fileHandling.writeMonth(cookie["user_id"], year, month, content): - fileHandling.removeFile(cookie["user_id"], uuid) - return {"success": False} - - return {"success": True} - - -@router.get("/deleteFile") -async def deleteFile(uuid: str, day: int, month: int, year: int, cookie = Depends(users.isLoggedIn)): - content:dict = fileHandling.getMonth(cookie["user_id"], year, month) - if "days" not in content.keys(): - raise HTTPException(status_code=500, detail="Day not found - json error") - - for dayLog in content["days"]: - if dayLog["day"] != day: - continue - if not "files" in dayLog.keys(): - break - for file in dayLog["files"]: - if file["uuid_filename"] == uuid: - if not fileHandling.removeFile(cookie["user_id"], uuid): - raise HTTPException(status_code=500, detail="Failed to delete file") - dayLog["files"].remove(file) - if not fileHandling.writeMonth(cookie["user_id"], year, month, content): - raise HTTPException(status_code=500, detail="Failed to write changes of deleted file!") - return {"success": True} - - raise HTTPException(status_code=500, detail="Failed to delete file - not found in log") - -@router.get("/downloadFile") -async def downloadFile(uuid: str, cookie = Depends(users.isLoggedIn)): - enc_key = security.get_enc_key(cookie["user_id"], cookie["derived_key"]) - file = fileHandling.readFile(cookie["user_id"], uuid) - if file is None: - raise HTTPException(status_code=500, detail="Failed to read file") - content = security.decrypt_file(file, enc_key) - return StreamingResponse(iter([content])) - -@router.get("/getTags") -async def getTags(cookie = Depends(users.isLoggedIn)): - enc_key = security.get_enc_key(cookie["user_id"], cookie["derived_key"]) - - content:dict = fileHandling.getTags(cookie["user_id"]) - - if not 'tags' in content: - return [] - - else: - for tag in content['tags']: - tag['icon'] = security.decrypt_text(tag['icon'], enc_key) - tag['name'] = security.decrypt_text(tag['name'], enc_key) - tag['color'] = security.decrypt_text(tag['color'], enc_key) - return content['tags'] - - -class NewTag(BaseModel): - icon: str - name: str - color: str - -@router.post("/saveNewTag") -async def saveNewTag(tag: NewTag, cookie = Depends(users.isLoggedIn)): - enc_key = security.get_enc_key(cookie["user_id"], cookie["derived_key"]) - - content:dict = fileHandling.getTags(cookie["user_id"]) - - if not 'tags' in content: - content['tags'] = [] - content['next_id'] = 1 - - enc_icon = security.encrypt_text(tag.icon, enc_key) - enc_name = security.encrypt_text(tag.name, enc_key) - enc_color = security.encrypt_text(tag.color, enc_key) - - new_tag = {"id": content['next_id'], "icon": enc_icon, "name": enc_name, "color": enc_color} - content['next_id'] += 1 - content['tags'].append(new_tag) - - if not fileHandling.writeTags(cookie["user_id"], content): - return {"success": False} - else: - return {"success": True} - - -class EditTag(BaseModel): - id: int - icon: str - name: str - color: str - -@router.post("/editTag") -async def editTag(editTag: EditTag, cookie = Depends(users.isLoggedIn)): - enc_key = security.get_enc_key(cookie["user_id"], cookie["derived_key"]) - - content:dict = fileHandling.getTags(cookie["user_id"]) - - if not 'tags' in content: - raise HTTPException(status_code=500, detail="Tag not found - json error") - - for tag in content['tags']: - if tag['id'] == editTag.id: - tag['icon'] = security.encrypt_text(editTag.icon, enc_key) - tag['name'] = security.encrypt_text(editTag.name, enc_key) - tag['color'] = security.encrypt_text(editTag.color, enc_key) - if not fileHandling.writeTags(cookie["user_id"], content): - raise HTTPException(status_code=500, detail="Failed to write tag - error writing tags") - else: - return {"success": True} - - raise HTTPException(status_code=500, detail="Tag not found - not in tags") - -@router.get("/deleteTag") -async def deleteTag(id: int, cookie = Depends(users.isLoggedIn)): - # remove from every log if present - for year in fileHandling.get_years(cookie["user_id"]): - for month in fileHandling.get_months(cookie["user_id"], year): - content:dict = fileHandling.getMonth(cookie["user_id"], year, int(month)) - if "days" not in content.keys(): - continue - for dayLog in content["days"]: - if "tags" in dayLog.keys() and id in dayLog["tags"]: - dayLog["tags"].remove(id) - if not fileHandling.writeMonth(cookie["user_id"], year, int(month), content): - raise HTTPException(status_code=500, detail="Failed to delete tag - error writing log") - - # remove from tags - content:dict = fileHandling.getTags(cookie["user_id"]) - if not 'tags' in content: - raise HTTPException(status_code=500, detail="Tag not found - json error") - - for tag in content['tags']: - if tag['id'] == id: - content['tags'].remove(tag) - if not fileHandling.writeTags(cookie["user_id"], content): - raise HTTPException(status_code=500, detail="Failed to delete tag - error writing tags") - else: - return {"success": True} - - raise HTTPException(status_code=500, detail="Tag not found - not in tags") - -class AddTagToLog(BaseModel): - day: int - month: int - year: int - tag_id: int - -@router.post("/addTagToLog") -async def addTagToLog(data: AddTagToLog, cookie = Depends(users.isLoggedIn)): - content:dict = fileHandling.getMonth(cookie["user_id"], data.year, data.month) - if "days" not in content.keys(): - content["days"] = [] - - dayFound = False - for dayLog in content["days"]: - if dayLog["day"] != data.day: - continue - dayFound = True - if not "tags" in dayLog.keys(): - dayLog["tags"] = [] - if data.tag_id in dayLog["tags"]: - # fail silently - return {"success": True} - dayLog["tags"].append(data.tag_id) - break - - if not dayFound: - content["days"].append({"day": data.day, "tags": [data.tag_id]}) - - if not fileHandling.writeMonth(cookie["user_id"], data.year, data.month, content): - raise HTTPException(status_code=500, detail="Failed to write tag - error writing log") - return {"success": True} - -@router.post("/removeTagFromLog") -async def removeTagFromLog(data: AddTagToLog, cookie = Depends(users.isLoggedIn)): - content:dict = fileHandling.getMonth(cookie["user_id"], data.year, data.month) - if "days" not in content.keys(): - raise HTTPException(status_code=500, detail="Day not found - json error") - - for dayLog in content["days"]: - if dayLog["day"] != data.day: - continue - if not "tags" in dayLog.keys(): - raise HTTPException(status_code=500, detail="Failed to remove tag - not found in log") - if not data.tag_id in dayLog["tags"]: - raise HTTPException(status_code=500, detail="Failed to remove tag - not found in log") - dayLog["tags"].remove(data.tag_id) - if not fileHandling.writeMonth(cookie["user_id"], data.year, data.month, content): - raise HTTPException(status_code=500, detail="Failed to remove tag - error writing log") - return {"success": True} - -@router.get("/getTemplates") -async def getTemplates(cookie = Depends(users.isLoggedIn)): - enc_key = security.get_enc_key(cookie["user_id"], cookie["derived_key"]) - - content:dict = fileHandling.getTemplates(cookie["user_id"]) - - if not 'templates' in content: - return [] - - else: - for template in content['templates']: - template['name'] = security.decrypt_text(template['name'], enc_key) - template['text'] = security.decrypt_text(template['text'], enc_key) - return content['templates'] - -class Templates(BaseModel): - templates: list[dict] - -@router.post("/saveTemplates") -async def saveTemplates(templates: Templates, cookie = Depends(users.isLoggedIn)): - enc_key = security.get_enc_key(cookie["user_id"], cookie["derived_key"]) - - content = {'templates': []} - - for template in templates.templates: - enc_name = security.encrypt_text(template["name"], enc_key) - enc_text = security.encrypt_text(template["text"], enc_key) - - new_template = {"name": enc_name, "text": enc_text} - content['templates'].append(new_template) - - if not fileHandling.writeTemplates(cookie["user_id"], content): - raise HTTPException(status_code=500, detail="Failed to write templates - error writing templates") - else: - return {"success": True} - - -@router.get("/getOnThisDay") -async def getOnThisDay(day: int, month: int, year: int, last_years: str, cookie = Depends(users.isLoggedIn)): - enc_key = security.get_enc_key(cookie["user_id"], cookie["derived_key"]) - - results = [] - - old_years = [year - int(y) for y in last_years.split(",") if y.isdigit()] - - for old_year in old_years: - content:dict = fileHandling.getMonth(cookie["user_id"], old_year, month) - - try: - for day_content in content['days']: - if day_content['day'] == day: - text = security.decrypt_text(day_content['text'], enc_key) if 'text' in day_content else '' - if text == '': - continue - results.append({'years_old': year - old_year, 'day': day, 'month': month, 'year': old_year, 'text': text}) - break - except: - continue - - return results - - -@router.get("/getHistory") -async def getHistory(day: int, month: int, year: int, cookie = Depends(users.isLoggedIn)): - enc_key = security.get_enc_key(cookie["user_id"], cookie["derived_key"]) - - content:dict = fileHandling.getMonth(cookie["user_id"], year, month) - - if "days" not in content.keys(): - return [] - - for dayLog in content["days"]: - if dayLog["day"] == day and "history" in dayLog.keys(): - history = [] - for historyLog in dayLog["history"]: - history.append({ - "text": security.decrypt_text(historyLog["text"], enc_key), - "date_written": security.decrypt_text(historyLog["date_written"], enc_key) - }) - return history - - return [] - -@router.get("/bookmarkDay") -async def bookmarkDay(day: int, month: int, year: int, cookie = Depends(users.isLoggedIn)): - content:dict = fileHandling.getMonth(cookie["user_id"], year, month) - - if "days" not in content.keys(): - content["days"] = [] - - day_found = False - bookmarked = True - for dayLog in content["days"]: - if dayLog["day"] == day: - day_found = True - if "bookmarked" in dayLog and dayLog["bookmarked"]: - dayLog["bookmarked"] = False - bookmarked = False - else: - dayLog["bookmarked"] = True - break - - if not day_found: - content["days"].append({"day": day, "bookmarked": True}) - - if not fileHandling.writeMonth(cookie["user_id"], year, month, content): - raise HTTPException(status_code=500, detail="Failed to bookmark day - error writing log") - - return {"success": True, "bookmarked": bookmarked} \ No newline at end of file diff --git a/backend-python/server/routers/users.py b/backend-python/server/routers/users.py deleted file mode 100644 index b543c6f..0000000 --- a/backend-python/server/routers/users.py +++ /dev/null @@ -1,184 +0,0 @@ -import asyncio -import datetime -import json -import secrets -from fastapi import APIRouter, Cookie, Depends, HTTPException, Response -from pydantic import BaseModel -from ..utils import fileHandling -from ..utils import security -from ..utils.settings import settings -import logging -import base64 -import jwt - -logger = logging.getLogger("dailytxtLogger") - -router = APIRouter() - -class Login(BaseModel): - username: str - password: str - -@router.post("/login") -async def login(login: Login, response: Response): - - # check if user exists - content:dict = fileHandling.getUsers() - if len(content) == 0 or "users" not in content.keys() or len(content["users"]) == 0 or not any(user["username"] == login.username for user in content["users"]): - logger.error(f"Login failed. User '{login.username}' not found") - raise HTTPException(status_code=404, detail="User/Password combination not found") - - # get user data - user = next(user for user in content["users"] if user["username"] == login.username) - if not security.verify_password(login.password, user["password"]): - logger.error(f"Login failed. Password for user '{login.username}' is incorrect") - raise HTTPException(status_code=404, detail="User/Password combination not found") - - # get intermediate key - derived_key = base64.b64encode(security.derive_key_from_password(login.password, user["salt"])).decode() - - # build jwt - token = create_jwt(user["user_id"], user["username"], derived_key) - response.set_cookie(key="token", value=token, httponly=True, samesite="lax") - return {"username": user["username"]} - -def create_jwt(user_id, username, derived_key): - return jwt.encode({"exp": datetime.datetime.now(tz=datetime.timezone.utc) + datetime.timedelta(days=settings.logout_after_days), "user_id": user_id, "name": username, "derived_key": derived_key}, settings.secret_token, algorithm="HS256") - -def decode_jwt(token): - return jwt.decode(token, settings.secret_token, algorithms="HS256") - -def isLoggedIn(token: str = Cookie()) -> int: - try: - decoded = decode_jwt(token) - return decoded - except jwt.ExpiredSignatureError: - raise HTTPException(status_code=440, detail="Token expired") - except: - raise HTTPException(status_code=401, detail="Not logged in") - - -@router.get("/logout") -def logout(response: Response): - response.delete_cookie("token", httponly=True) - return {"success": True} - - -class Register(BaseModel): - username: str - password: str - -@router.post("/register") -async def register(register: Register): - content:dict = fileHandling.getUsers() - - # check if username already exists - if len(content) > 0: - if ("users" not in content.keys()): - logger.error("users.json is not in the correct format. Key 'users' is missing.") - raise HTTPException(status_code=500, detail="users.json is not in the correct format") - for user in content["users"]: - if user["username"] == register.username: - logger.error(f"Registration failed. Username '{register.username}' already exists") - raise HTTPException(status_code=400, detail="Username already exists") - - # create new user-data - username = register.username - password = security.hash_password(register.password) - salt = secrets.token_urlsafe(16) - enc_enc_key = security.create_new_enc_enc_key(register.password, salt).decode() - - - if len(content) == 0: - content = {"id_counter": 1, "users": [ - { - "user_id": 1, - "dailytxt_version": 2, - "username": username, - "password": password, - "salt": salt, - "enc_enc_key": enc_enc_key - } - ]} - - - else: - content["id_counter"] += 1 - content["users"].append( - { - "user_id": content["id_counter"], - "dailytxt_version": 2, - "username": username, - "password": password, - "salt": salt, - "enc_enc_key": enc_enc_key - } - ) - - try: - fileHandling.writeUsers(content) - except Exception as e: - raise HTTPException(status_code=500, detail="Internal Server Error when trying to write users.json") from e - else: - return {"success": True} - -def get_default_user_settings(): - return { - "autoloadImagesByDefault": False, - "setAutoloadImagesPerDevice": True, - "useOnThisDay": True, - "onThisDayYears": [1,5,10], - "useBrowserTimezone": True, - "timezone": "UTC" - } - -@router.get("/getUserSettings") -async def get_user_settings(cookie = Depends(isLoggedIn)): - user_id = cookie["user_id"] - content_enc = fileHandling.getUserSettings(user_id) - - if len(content_enc) > 0: - # decrypt settings - enc_key = security.get_enc_key(cookie["user_id"], cookie["derived_key"]) - content = json.loads(security.decrypt_text(content_enc, enc_key)) - else: - content = {} - - default = get_default_user_settings() - - for key in default.keys(): - if key not in content.keys(): - content[key] = default[key] - - return content - - -@router.post("/saveUserSettings") -async def save_user_settings(settings: dict, cookie = Depends(isLoggedIn)): - user_id = cookie["user_id"] - content = fileHandling.getUserSettings(user_id) - - enc_key = security.get_enc_key(cookie["user_id"], cookie["derived_key"]) - if len(content) > 0: - # decrypt settings - content = json.loads(security.decrypt_text(content, enc_key)) - else: - content = {} - - # if content is empty dict - if content is None or len(content) == 0: - content = get_default_user_settings() - - # update settings - for key in settings.keys(): - content[key] = settings[key] - - # encrypt settings - content_enc = security.encrypt_text(json.dumps(content), enc_key) - - try: - fileHandling.writeUserSettings(user_id, content_enc) - except Exception as e: - raise HTTPException(status_code=500, detail="Internal Server Error when trying to write users.json") from e - else: - return {"success": True} diff --git a/backend-python/server/utils/fileHandling.py b/backend-python/server/utils/fileHandling.py deleted file mode 100644 index 608d688..0000000 --- a/backend-python/server/utils/fileHandling.py +++ /dev/null @@ -1,187 +0,0 @@ -import json -import os -import logging - -from fastapi import HTTPException -from .settings import settings - -logger = logging.getLogger("dailytxtLogger") - -def getUsers(): - try: - f = open(os.path.join(settings.data_path, "users.json"), "r") - except FileNotFoundError: - logger.info("users.json - File not found") - return {} - except Exception as e: - logger.exception(e) - raise HTTPException(status_code=500, detail="Internal Server Error when trying to open users.json") - else: - with f: - s = f.read() - if s == "": - return {} - return json.loads(s) - -def getMonth(user_id, year, month): - try: - f = open(os.path.join(settings.data_path, f"{user_id}/{year}/{month:02d}.json"), "r") - except FileNotFoundError: - logger.debug(f"{user_id}/{year}/{month:02d}.json - File not found") - return {} - except Exception as e: - logger.exception(e) - raise HTTPException(status_code=500, detail="Internal Server Error when trying to open {year}-{month}.json") - else: - with f: - s = f.read() - if s == "": - return {} - return json.loads(s) - -def writeUsers(content): - # print working directory - try: - f = open(os.path.join(settings.data_path, "users.json"), "w") - except Exception as e: - logger.exception(e) - return e - else: - with f: - f.write(json.dumps(content, indent=settings.indent)) - return True - -def writeMonth(user_id, year, month, content): - try: - os.makedirs(os.path.join(settings.data_path, f"{user_id}/{year}"), exist_ok=True) - f = open(os.path.join(settings.data_path, f"{user_id}/{year}/{month:02d}.json"), "w") - except Exception as e: - logger.exception(e) - return False - else: - with f: - f.write(json.dumps(content, indent=settings.indent)) - return True - -def get_years(user_id): - for entry in os.scandir(os.path.join(settings.data_path, str(user_id))): - if entry.is_dir() and entry.name.isnumeric() and len(entry.name) == 4: - yield entry.name - -def get_months(user_id, year): - for entry in os.scandir(os.path.join(settings.data_path, str(user_id), year)): - if entry.is_file() and entry.name.endswith(".json"): - yield entry.name.split(".")[0] - -def writeFile(file, user_id, uuid): - try: - os.makedirs(os.path.join(settings.data_path, str(user_id), 'files'), exist_ok=True) - f = open(os.path.join(settings.data_path, str(user_id), 'files', uuid), "w") - except Exception as e: - logger.exception(e) - return False - else: - with f: - f.write(file) - return True - -def readFile(user_id, uuid): - try: - f = open(os.path.join(settings.data_path, str(user_id), 'files', uuid), "r") - except FileNotFoundError: - logger.info(f"{user_id}/files/{uuid} - File not found") - raise HTTPException(status_code=404, detail="File not found") - except Exception as e: - logger.exception(e) - raise HTTPException(status_code=500, detail="Internal Server Error when trying to open file {uuid}") - else: - with f: - return f.read() - -def removeFile(user_id, uuid): - try: - os.remove(os.path.join(settings.data_path, str(user_id), 'files', uuid)) - except Exception as e: - logger.exception(e) - return False - else: - return True - -def getTags(user_id): - try: - f = open(os.path.join(settings.data_path, str(user_id), "tags.json"), "r") - except FileNotFoundError: - logger.info(f"{user_id}/tags.json - File not found") - return {} - except Exception as e: - logger.exception(e) - raise HTTPException(status_code=500, detail="Internal Server Error when trying to open tags.json") - else: - with f: - s = f.read() - if s == "": - return {} - return json.loads(s) - -def writeTags(user_id, content): - try: - f = open(os.path.join(settings.data_path, str(user_id), "tags.json"), "w") - except Exception as e: - logger.exception(e) - return False - else: - with f: - f.write(json.dumps(content, indent=settings.indent)) - return True - -def getUserSettings(user_id): - try: - f = open(os.path.join(settings.data_path, str(user_id), "settings.encrypted"), "r") - except FileNotFoundError: - logger.info(f"{user_id}/settings.encrypted - File not found") - return {} - except Exception as e: - logger.exception(e) - raise HTTPException(status_code=500, detail="Internal Server Error when trying to open settings.json") - else: - with f: - s = f.read() - return s - -def writeUserSettings(user_id, content): - try: - f = open(os.path.join(settings.data_path, str(user_id), "settings.encrypted"), "w") - except Exception as e: - logger.exception(e) - return False - else: - with f: - f.write(content) - return True - -def getTemplates(user_id): - try: - f = open(os.path.join(settings.data_path, str(user_id), "templates.json"), "r") - except FileNotFoundError: - logger.info(f"{user_id}/templates.json - File not found") - return {} - except Exception as e: - logger.exception(e) - raise HTTPException(status_code=500, detail="Internal Server Error when trying to open templates.json") - else: - with f: - s = f.read() - if s == "": - return {} - return json.loads(s) - -def writeTemplates(user_id, content): - try: - f = open(os.path.join(settings.data_path, str(user_id), "templates.json"), "w") - except Exception as e: - logger.exception(e) - return False - else: - with f: - f.write(json.dumps(content, indent=settings.indent)) - return True \ No newline at end of file diff --git a/backend-python/server/utils/security.py b/backend-python/server/utils/security.py deleted file mode 100644 index aec5102..0000000 --- a/backend-python/server/utils/security.py +++ /dev/null @@ -1,50 +0,0 @@ -from fastapi import HTTPException -from passlib.hash import argon2 -from argon2.low_level import hash_secret_raw, Type -from cryptography.fernet import Fernet -import base64 -from . import fileHandling - -def hash_password(password: str) -> str: - return argon2.hash(password) - -def verify_password(password: str, hash: str) -> bool: - return argon2.verify(password, hash) - -def derive_key_from_password(password: str, salt: str) -> bytes: - return hash_secret_raw(secret=password.encode(), salt=salt.encode(), time_cost=2, memory_cost=2**15, parallelism=1, hash_len=32, type=Type.ID) - -def create_new_enc_enc_key(password: str, salt: str) -> bytes: - derived_key = derive_key_from_password(password, salt) # password derived key only to encrypt the actual encryption key - key = Fernet.generate_key() # actual encryption key - f = Fernet(base64.urlsafe_b64encode(derived_key)) - return f.encrypt(key) - -def get_enc_key(user_id: int, derived_key: str) -> bytes: - content = fileHandling.getUsers() - - if not "users" in content.keys(): - raise HTTPException(status_code=500, detail="users.json is not in the correct format. Key 'users' is missing.") - - for user in content["users"]: - if user["user_id"] == user_id: - key = user["enc_enc_key"] - - f = Fernet(base64.urlsafe_b64encode(base64.b64decode(derived_key))) - return base64.urlsafe_b64encode(base64.urlsafe_b64decode(f.decrypt(key))) - -def encrypt_text(text: str, key: str) -> str: - f = Fernet(key) - return f.encrypt(text.encode()).decode() - -def decrypt_text(text: str, key: str) -> str: - f = Fernet(key) - return f.decrypt(text.encode()).decode() - -def encrypt_file(file: bytes, key: str) -> str: - f = Fernet(key) - return f.encrypt(file).decode() - -def decrypt_file(file: str, key: str) -> bytes: - f = Fernet(key) - return f.decrypt(file.encode()) \ No newline at end of file diff --git a/backend-python/server/utils/settings.py b/backend-python/server/utils/settings.py deleted file mode 100644 index 5eb1691..0000000 --- a/backend-python/server/utils/settings.py +++ /dev/null @@ -1,12 +0,0 @@ -import secrets -from pydantic_settings import BaseSettings - -class Settings(BaseSettings): - data_path: str = "/data" - development: bool = False - secret_token: str = secrets.token_urlsafe(32) - logout_after_days: int = 30 - allowed_hosts: list[str] = ["http://localhost:5173","http://127.0.0.1:5173"] - indent: int | None = None - -settings = Settings() \ No newline at end of file diff --git a/frontend/src/routes/login/+page.svelte b/frontend/src/routes/login/+page.svelte index 379a9a4..2021701 100644 --- a/frontend/src/routes/login/+page.svelte +++ b/frontend/src/routes/login/+page.svelte @@ -421,6 +421,9 @@ })} {/if} +
+ {@html $t('login.migration.account_info')} +
{/if} {/if}