from __future__ import annotations import sqlite3 from dataclasses import dataclass from app.core.time import to_iso from app.database.database import Database @dataclass(slots=True) class AdminUser: id: int username: str password_hash: str last_login_at: str | None @dataclass(slots=True) class SessionRecord: token_hash: str csrf_hash: str user_id: int username: str expires_at: str class AuthRepository: def __init__(self, database: Database) -> None: self.database = database def has_admin(self) -> bool: with self.database.connect() as connection: return ( connection.execute("SELECT 1 FROM admin_users WHERE id = 1").fetchone() is not None ) def create_admin(self, username: str, password_hash: str) -> AdminUser: now = to_iso() try: with self.database.connect() as connection, connection: connection.execute( """ INSERT INTO admin_users(id, username, password_hash, created_at, updated_at) VALUES (1, ?, ?, ?, ?) """, (username, password_hash, now, now), ) except sqlite3.IntegrityError as exc: raise ValueError("ADMIN_ALREADY_EXISTS") from exc return AdminUser(1, username, password_hash, None) def get_admin(self) -> AdminUser | None: with self.database.connect() as connection: row = connection.execute( "SELECT id, username, password_hash, last_login_at FROM admin_users WHERE id = 1" ).fetchone() if row is None: return None return AdminUser(**dict(row)) def update_login_time(self) -> None: now = to_iso() with self.database.connect() as connection, connection: connection.execute( "UPDATE admin_users SET last_login_at = ?, updated_at = ? WHERE id = 1", (now, now) ) def update_password(self, password_hash: str) -> None: now = to_iso() with self.database.connect() as connection, connection: connection.execute( "UPDATE admin_users SET password_hash = ?, updated_at = ? WHERE id = 1", (password_hash, now), ) connection.execute("DELETE FROM auth_sessions") def create_session( self, *, token_hash: str, csrf_hash: str, expires_at: str, ) -> None: now = to_iso() with self.database.connect() as connection, connection: connection.execute("DELETE FROM auth_sessions WHERE expires_at <= ?", (now,)) connection.execute( """ INSERT INTO auth_sessions( token_hash, user_id, csrf_hash, expires_at, created_at, last_seen_at ) VALUES (?, 1, ?, ?, ?, ?) """, (token_hash, csrf_hash, expires_at, now, now), ) def get_session(self, token_hash: str) -> SessionRecord | None: now = to_iso() with self.database.connect() as connection, connection: row = connection.execute( """ SELECT s.token_hash, s.csrf_hash, s.user_id, u.username, s.expires_at FROM auth_sessions AS s JOIN admin_users AS u ON u.id = s.user_id WHERE s.token_hash = ? AND s.expires_at > ? """, (token_hash, now), ).fetchone() if row: connection.execute( "UPDATE auth_sessions SET last_seen_at = ? WHERE token_hash = ?", (now, token_hash), ) return SessionRecord(**dict(row)) if row else None def delete_session(self, token_hash: str) -> None: with self.database.connect() as connection, connection: connection.execute("DELETE FROM auth_sessions WHERE token_hash = ?", (token_hash,)) class SessionConflictError(Exception): pass