Files
BanGUI/Docs/Refactoring.md
Lukas b44b72053a T-11: Validate repository Protocol structural compatibility — minimal approach (Option B)
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>
2026-04-25 18:59:49 +02:00

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.tsx by 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 Fail2BanConnectionError and Fail2BanProtocolError from backend/app/utils/fail2ban_client.py into backend/app/exceptions.py. Updated all router, service, and test call sites to import these domain exceptions from app.exceptions and retained backward compatibility through re-exporting in app.utils.fail2ban_client.
  • Moved config file exceptions (ConfigDirError, ConfigFileNotFoundError, ConfigFileExistsError, ConfigFileWriteError, ConfigFileNameError) from backend/app/services/raw_config_io_service.py into backend/app/exceptions.py. Updated router and tests to import the shared domain exceptions from app.exceptions.
  • Added global domain exception handlers to backend/app/main.py so domain exceptions like JailNotFoundError, ConfigValidationError, and ConfigWriteError map consistently to 404, 400, and 500 responses.
  • Fixed stale activation tracking in backend/app/routers/jail_config.py by recording last_activation only 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.ts by wrapping the onSuccess callback in useCallback with empty dependencies. The bug occurred because useListData includes onSuccess in its internal refresh function's dependency array; an inline callback created a new reference on each render, causing refresh to be recreated, which triggered the useEffect again, leading to an unbounded fetch loop. Callers of useListData must always wrap onSuccess callbacks in useCallback to 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 in protocols.py to match actual implementations in session_repo, settings_repo, blocklist_repo, import_log_repo, geo_cache_repo, history_archive_repo, and fail2ban_db_repo (correcting return types like dict[str, Any] vs dict[str, object], Sequence vs Iterable, and typed models). Updated backend/app/dependencies.py with 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.