From 8c111cf14eab1fc2aaa6a0b1207f02ccf72d9beb Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 9 Nov 2024 20:39:27 -0600 Subject: [PATCH] Minor formatting concerns, remove dangling IPAddress usage --- .vscode/settings.json | 9 ++- backend/linkpulse/__main__.py | 17 +++--- .../004_create_user_remove_ipaddress.py | 8 +-- backend/linkpulse/routers/auth.py | 56 ++++++++++++++++++- backend/linkpulse/routers/misc.py | 14 ++++- 5 files changed, 86 insertions(+), 18 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 14f1520..5169b3a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,19 +4,26 @@ "backref", "bpython", "Callsite", + "clsx", "excepthook", "inmemory", "linkpulse", "migratehistory", "Nixpacks", "ORJSON", + "pext", + "pwdlib", + "pytest", "pytz", + "rtype", "starlette", "structlog", + "tailwindcss", "timestamper" ], "python.analysis.extraPaths": ["./backend/"], "[github-actions-workflow]": { "editor.formatOnSave": false - } + }, + "python.analysis.diagnosticMode": "workspace" } diff --git a/backend/linkpulse/__main__.py b/backend/linkpulse/__main__.py index 2d13d2b..45a6b38 100644 --- a/backend/linkpulse/__main__.py +++ b/backend/linkpulse/__main__.py @@ -16,17 +16,18 @@ setup_logging() import os import sys -import structlog +import structlog logger = structlog.get_logger() -def main(*args): - """ - Primary entrypoint for the LinkPulse application - - Don't import any modules globally unless you're certain it's necessary. Imports should be tightly controlled. - """ +def main(*args: str) -> None: + """Primary entrypoint for the LinkPulse application + NOTE: Don't import any modules globally unless you're certain it's necessary. Imports should be tightly controlled. + :param args: The command-line arguments to parse and execute. + :type args: str""" + if args[0] == "serve": from linkpulse.utilities import is_development from uvicorn import run @@ -57,9 +58,9 @@ def main(*args): # import most useful objects, models, and functions lp = linkpulse # alias - from linkpulse.utilities import get_db from linkpulse.app import app - from linkpulse.models import BaseModel, IPAddress + from linkpulse.models import BaseModel, User, Session + from linkpulse.utilities import get_db db = get_db() diff --git a/backend/linkpulse/migrations/004_create_user_remove_ipaddress.py b/backend/linkpulse/migrations/004_create_user_remove_ipaddress.py index 9322940..d85aa7a 100644 --- a/backend/linkpulse/migrations/004_create_user_remove_ipaddress.py +++ b/backend/linkpulse/migrations/004_create_user_remove_ipaddress.py @@ -36,7 +36,7 @@ with suppress(ImportError): def migrate(migrator: Migrator, database: pw.Database, *, fake=False): """Write your migrations here.""" - + @migrator.create_model class User(pw.Model): id = pw.AutoField() @@ -48,12 +48,12 @@ def migrate(migrator: Migrator, database: pw.Database, *, fake=False): class Meta: table_name = "user" - migrator.remove_model('ipaddress') + migrator.remove_model("ipaddress") def rollback(migrator: Migrator, database: pw.Database, *, fake=False): """Write your rollback migrations here.""" - + @migrator.create_model class IPAddress(pw.Model): ip = pw.CharField(max_length=255, primary_key=True) @@ -63,4 +63,4 @@ def rollback(migrator: Migrator, database: pw.Database, *, fake=False): class Meta: table_name = "ipaddress" - migrator.remove_model('user') + migrator.remove_model("user") diff --git a/backend/linkpulse/routers/auth.py b/backend/linkpulse/routers/auth.py index 8f3e1f2..435aea5 100644 --- a/backend/linkpulse/routers/auth.py +++ b/backend/linkpulse/routers/auth.py @@ -9,12 +9,21 @@ router = APIRouter() def validate_session( token: str, user: bool = True ) -> Tuple[bool, bool, Optional[User]]: - """ - Given a token, validate that the session exists and is not expired. + """Given a token, validate that the session exists and is not expired. This function has side effects: - This function updates last_used if `user` is True. - This function will invalidate the session if it is expired. + + :param token: The session token to validate. + :type token: str + :param user: Whether to update the last_used timestamp of the session. + :type user: bool + :return: A tuple containing: + - A boolean indicating if the session exists. + - A boolean indicating if the session is valid. + - The User object if the session is valid, otherwise None. + :rtype: Tuple[bool, bool, Optional[User]] """ # Check if session exists session = Session.get_or_none(Session.token == token) @@ -28,3 +37,46 @@ def validate_session( if user: session.use() return True, True, session.user + + +@router.post("/api/login") +async def login(): + # Validate parameters + # Hash regardless of user existence to prevent timing attacks + # Check if user exists, return 401 if not + # Check if password matches, return 401 if not + # Create session + # Set Cookie of session token + # Return 200 with mild user information + pass + + +@router.post("/api/logout") +async def logout(): + # TODO: Force logout parameter, logout ALL sessions for User + # Get session token from Cookie + # Delete session + # Return 200 + pass + + +@router.post("/api/register") +async def register(): + # Validate parameters + # Hash password + # Create User + # Create Session + # Set Cookie of session token + # Return 200 with mild user information + pass + + +@router.get("/api/sessions") +async def sessions(): + pass + + +# GET /api/user/{id}/sessions +# GET /api/user/{id}/sessions/{token} +# DELETE /api/user/{id}/sessions +# POST /api/user/{id}/logout (delete all sessions) diff --git a/backend/linkpulse/routers/misc.py b/backend/linkpulse/routers/misc.py index 5c5b5df..e91c41e 100644 --- a/backend/linkpulse/routers/misc.py +++ b/backend/linkpulse/routers/misc.py @@ -1,3 +1,7 @@ +"""Miscellaneous endpoints for the Linkpulse API.""" + +from typing import Any + from fastapi import APIRouter from fastapi_cache.decorator import cache from linkpulse.utilities import get_db @@ -9,15 +13,19 @@ db = get_db() @router.get("/health") async def health(): + """An endpoint to check if the service is running. + :return: OK + :rtype: Literal['OK']""" # TODO: Check database connection return "OK" @router.get("/api/migration") @cache(expire=60) -async def get_migration(): - """ - Returns the details of the most recent migration. +async def get_migration() -> dict[str, Any]: + """Get the last migration name and timestamp from the migratehistory table. + :return: The last migration name and timestamp. + :rtype: dict[str, Any] """ # Kind of insecure, but this is just a demo thing to show that migratehistory is available. cursor = db.execute_sql(