Problem: Repository modules use structural typing to satisfy Protocol interfaces via cast(). A function rename, parameter change, or signature mismatch would silently pass mypy but fail at runtime. Solution (Option B — minimal): 1. Aligned Protocol signatures in protocols.py with actual implementations: - BlocklistRepository: dict[str, object] → dict[str, Any] (matches implementation) - ImportLogRepository: dict[str, object] → ImportLogRow (typed model) - GeoCacheRepository: dict[str, object] → GeoCacheRow; Iterable → Sequence - HistoryArchiveRepository: dict[str, object] → dict[str, Any] - ImportLogRepository: async compute_total_pages → sync (matches implementation) 2. Created CI validation script (backend/scripts/validate_repository_protocols.py) that runs at build time to ensure all repository modules satisfy their Protocol interfaces. Exit 0 if valid, 1 if any mismatch. Detects: - Missing functions - Parameter count mismatches - Type annotation mismatches - Return type mismatches 3. Updated backend/app/dependencies.py with explicit docstrings linking each get_*_repo() provider to Backend-Development.md § 13.7.1, explaining the module-as-Protocol pattern and that it is intentional and validated. 4. Documented the pattern in Backend-Development.md § 13.7.1: 'Repository Module Pattern — Module-as-Protocol Structural Compatibility' explaining why the pattern works, risks (silent breakage), and how the validation mitigates it. 5. Fixed type annotation in history_archive_repo.py: - get_all_archived_history returns list[dict] → list[dict[str, Any]] - Imported Any type Benefits: - Prevents silent breakage of repository interfaces - Formalizes the module-as-Protocol pattern as intentional - CI validation prevents regressions without refactoring cost - All repository tests pass (53/53) - mypy --strict passes on modified files Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
3.2 KiB
3.2 KiB
BanGUI — Architecture Issues & Refactoring Plan
This document catalogues architecture violations, code smells, and structural issues found during a full project review. Issues are grouped by category and prioritised.
Security Fixes
- Fixed open redirect vulnerability in
frontend/src/pages/LoginPage.tsxby validating the?next=parameter to ensure it is a relative path (starts with/but not//). The validation regex/^\/(?!\/)/.test(next)prevents protocol-relative URLs and external redirects. Invalid paths fall back to"/".
Completed Refactors
- Moved
Fail2BanConnectionErrorandFail2BanProtocolErrorfrombackend/app/utils/fail2ban_client.pyintobackend/app/exceptions.py. Updated all router, service, and test call sites to import these domain exceptions fromapp.exceptionsand retained backward compatibility through re-exporting inapp.utils.fail2ban_client. - Moved config file exceptions (
ConfigDirError,ConfigFileNotFoundError,ConfigFileExistsError,ConfigFileWriteError,ConfigFileNameError) frombackend/app/services/raw_config_io_service.pyintobackend/app/exceptions.py. Updated router and tests to import the shared domain exceptions fromapp.exceptions. - Added global domain exception handlers to
backend/app/main.pyso domain exceptions likeJailNotFoundError,ConfigValidationError, andConfigWriteErrormap consistently to 404, 400, and 500 responses. - Fixed stale activation tracking in
backend/app/routers/jail_config.pyby recordinglast_activationonly after a successful jail activation and preventing a failed activation attempt from leaving a stale runtime state record. - Fixed infinite re-fetch loop in
frontend/src/hooks/useJailConfigs.tsby wrapping theonSuccesscallback inuseCallbackwith empty dependencies. The bug occurred becauseuseListDataincludesonSuccessin its internalrefreshfunction's dependency array; an inline callback created a new reference on each render, causingrefreshto be recreated, which triggered theuseEffectagain, leading to an unbounded fetch loop. Callers ofuseListDatamust always wraponSuccesscallbacks inuseCallbackto maintain reference stability. - T-11 — Repository module-as-Protocol structural type-safety: Resolved the fragile
cast()pattern where repository modules were loosely typed against Protocol interfaces. Created a validation script (backend/scripts/validate_repository_protocols.py) that runs at CI time to ensure all repository modules satisfy their Protocol interfaces. Fixed signature mismatches inprotocols.pyto match actual implementations insession_repo,settings_repo,blocklist_repo,import_log_repo,geo_cache_repo,history_archive_repo, andfail2ban_db_repo(correcting return types likedict[str, Any]vsdict[str, object],SequencevsIterable, and typed models). Updatedbackend/app/dependencies.pywith explicit documentation linking each repository provider to the pattern explained in Backend-Development.md § 13.7.1. Option B (minimal): Instead of refactoring to class-based repositories (Option A), the pattern is now formally documented and validated, preventing silent breakage.