Commit Graph

593 Commits

Author SHA1 Message Date
5d9cef7760 TASK-013: Add nginx security headers (CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy)
- Added OWASP-recommended security headers to nginx server block
- CSP allows same-origin scripts and inline styles (required for Fluent UI v9)
- X-Frame-Options: DENY prevents clickjacking
- X-Content-Type-Options: nosniff prevents MIME-sniffing
- Referrer-Policy: no-referrer prevents URL leakage
- Permissions-Policy: disables geolocation, microphone, camera APIs
- HSTS commented out until HTTPS is fully configured
- All headers use 'always' directive for error responses (4xx, 5xx)
- Updated Architekture.md with security header documentation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 13:35:15 +02:00
3095fa3313 fix(frontend): deduplicate setup status API calls using shared hook
Implement request deduplication to prevent multiple duplicate calls to GET
/api/setup when multiple components mount simultaneously. The fix introduces:

1. New 'useSharedSetupStatus' hook with module-level caching
   - Shares a single in-flight request across all consumers
   - Implements 30-second cache TTL with cache invalidation
   - Notifies all subscribers when cache is invalidated

2. Refactored 'useSetup' hook to use shared cache
   - Internally uses useSharedSetupStatus for status checks
   - Calls invalidateSetupStatus() after successful setup submission
   - Maintains backward-compatible API

3. Updated components using setup status
   - SetupGuard and SetupPage automatically benefit from deduplication
   - No changes needed to consumer code

4. Updated tests
   - Mocked useSharedSetupStatus in component tests
   - Added comprehensive tests for cache behavior
   - All existing tests pass

5. Documentation updates
   - Added 'Request Deduplication & Shared Caching' section to Web-Development.md
   - Explains when and how to use shared hooks
   - Provides complete implementation example

This eliminates wasted resources from duplicate API calls and potential
race conditions where different requests return slightly different states.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 13:32:44 +02:00
5b24a9c142 TASK-011: Remove session token prefix from log output
Replace sensitive token fragments in structured logs with:
- login(): Use session_id=session.id (database row ID) instead of token_prefix
- logout(): Use token_hash (SHA256 one-way hash, first 12 chars) instead of token_prefix

This prevents partial token material leakage into log aggregation systems while
maintaining useful session correlation via hashed tokens or database IDs.

Also updated Backend-Development.md to clarify logging conventions for
sensitive data handling.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 13:19:26 +02:00
8698b89f6a TASK-010: Replace .split() with shlex.split() for fail2ban_start_command
- Add @field_validator for fail2ban_start_command to validate with shlex.split()
  at startup, catching misconfigured commands with mismatched quotes
- Replace .split() with shlex.split() in jail_config.py line 450
- Replace .split() with shlex.split() in config_misc.py line 154
- Update Backend-Development.md with configuration documentation explaining
  quoted path handling and common pitfalls
- Add comprehensive test suite (8 tests) covering valid commands, quoted paths,
  and mismatched quote errors

This fix ensures commands like '/opt/my tools/fail2ban-client' start are
correctly parsed as two tokens instead of three, preventing execution failures
when the path contains spaces.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 13:04:14 +02:00
4ab767e3d4 TASK-009: Mitigate SSRF vulnerability in blocklist URL validation
- Change BlocklistSourceCreate.url from str to AnyHttpUrl (Pydantic type)
  - Rejects non-http schemes (file://, ftp://, etc.) at model boundary

- Add is_private_ip() utility to detect RFC 1918 private ranges:
  - 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 (RFC 1918)
  - 127.0.0.0/8, ::1/128 (loopback)
  - 169.254.0.0/16, fe80::/10 (link-local)
  - IPv6 site-local, multicast, and reserved ranges

- Add async validate_blocklist_url() function:
  - Resolves hostname via DNS using loop.run_in_executor()
  - Rejects if hostname resolves to private/reserved IP
  - Raises ValueError on validation failure

- Integrate validation into service layer:
  - create_source() calls validate_blocklist_url() before persist
  - update_source() conditionally validates if url provided
  - Both raise ValueError on failure

- Update router endpoints with error handling:
  - create_blocklist() and update_blocklist() catch ValueError
  - Return HTTP 400 Bad Request with descriptive error message

- Add comprehensive test coverage (9 new SSRF tests):
  - file://, ftp://, localhost, 127.0.0.1, 192.168.x.x
  - 10.x.x.x, 172.16.x.x, 169.254.x.x (link-local)
  - Valid public URLs (passes validation)
  - All 36 service tests passing

- Update documentation:
  - Features.md: Document URL validation constraints
  - Backend-Development.md: Add SSRF prevention pattern section

Fixes SSRF vulnerability where authenticated users could supply
file://, ftp://, or private IP URLs and the backend would fetch them.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 12:57:23 +02:00
a5b55d1248 Add session cleanup task and update documentation
- Implement session_cleanup task for removing expired sessions
- Add comprehensive tests for session cleanup functionality
- Update architecture and task documentation
- Integrate cleanup task into application startup

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 12:49:13 +02:00
ea4c7c2f85 Implement login endpoint rate limiting (TASK-007)
- Add in-memory rate limiter with per-IP deque tracking of attempt timestamps
- Limit login attempts to 5 per 60 seconds per IP, return 429 on excess
- Add Retry-After header to rate limit responses
- Implement IP extraction utility with proxy trust validation (prevent X-Forwarded-For spoofing)
- Integrate rate limiter into auth router and dependencies
- Add 10-second asyncio.sleep on failed login attempts to further slow brute-force
- Add comprehensive tests for rate limiting (9 new tests, all passing)
- Update Features.md to document login rate limiting
- Update Backend-Development.md with rate limiting conventions and design patterns
- Fix test infrastructure issues: update password to meet complexity requirements
- Fix TestValidateSession tests to use Bearer token authentication
- All tests passing: 23 auth tests + full test suite coverage

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 12:40:52 +02:00
9725714aa2 docs: document nginx routing rules to prevent SPA fallback hiding API 404s
TASK-006: Document the nginx routing configuration that ensures API requests
returning 404 from FastAPI are not intercepted by the SPA wildcard fallback
rule. This prevents development bugs from being masked by 200 responses
containing HTML instead of 404 errors.

Added section 9.2 in Architekture.md covering:
- nginx location block priority (longest-prefix matching)
- Routing configuration for /api/, /assets/, and /
- Detailed routing behavior diagrams
- Critical implementation notes to prevent regressions

The current nginx.conf is already correct:
- /api/ location has no try_files and proxies directly to backend
- /assets/ location uses try_files with =404
- / catch-all uses SPA fallback to index.html

This ensures:
✓ API typos like /api/jailss return 404, not SPA HTML
✓ Frontend routes serve SPA HTML for client-side routing
✓ Static assets properly return 404 when missing

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 12:10:21 +02:00
f55c317f87 Backend refactoring updates
- Update Docker compose debug configuration
- Update backend documentation
- Update tasks documentation
- Update backend config module

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 12:05:01 +02:00
29daaa9906 TASK-004: Bootstrap frontend auth state from backend session check
Validates session on app mount by calling GET /api/auth/session instead of relying
solely on cached sessionStorage. This ensures the UI state always reflects server
reality — expired or revoked sessions are detected immediately.

Changes:
- Backend: Add GET /api/auth/session endpoint (requires valid session, returns 200/401)
- Frontend: Add useSessionValidation hook for mount-time validation
- Frontend: Add SessionValidationLoading component for validation spinner
- Frontend: Update AuthProvider to call validation on mount with loading state
- Frontend: Add validateSession API function
- Docs: Update Features.md with session validation behavior
- Docs: Update Web-Development.md with session validation pattern

Handles three outcomes:
1. Valid session (200): Proceed with cached state
2. Invalid session (401): Clear sessionStorage and redirect to login
3. Network error: Don't logout (backend may be temporarily unreachable)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 12:00:21 +02:00
d982fe3efc TASK-003: Document process-local constraint for RuntimeState and SessionCache
- Add comprehensive docstring to runtime_state.py explaining single-process
  constraint, impacts in multi-worker deployments, and solution approach
- Add comprehensive docstring to session_cache.py explaining process-local
  cache limitation, security implications, and Redis/database alternatives
- Update Architecture.md to clarify session cache is process-local and
  describe single-worker enforcement via TASK-002
- Update Architecture.md runtime state section with detailed explanation of
  per-process state and multi-worker impacts
- Add Backend-Development.md section 13.7.2 documenting session cache
  pluggability pattern with example Redis implementation
- All tests pass; linting passes; type checking has pre-existing errors

This is the short-term fix for TASK-003: enforce single-worker deployment
(TASK-002) and document the constraint clearly. The long-term fix (Redis
backend) is deferred as a follow-up.
2026-04-26 11:43:34 +02:00
825a67f13a Add multi-worker detection for APScheduler safety
- Add _check_single_worker_mode() to startup.py that detects and rejects
  multi-worker configurations, raising a clear RuntimeError with instructions
- Set BANGUI_WORKERS=1 as default in Dockerfile.backend
- Document single-worker requirement in compose.prod.yml
- Add 'Deployment Constraints' section to Architekture.md explaining why
  single-worker mode is required and detailing future multi-worker support
- Add '9.1 Background Tasks and Scheduler Architecture' section to
  Backend-Development.md documenting task structure and single-worker requirement
- Add comprehensive test suite (test_startup.py) covering all scenarios:
  allows single worker, rejects multi-worker, validates config format,
  and verifies informative error messages

This fix addresses TASK-002 which identified that in-process APScheduler is
unsafe in multi-worker deployments due to each worker creating independent
scheduler instances, causing duplicate background job execution.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 11:39:51 +02:00
def412797a Remove completed task T-20 from documentation
Removed the completed task about replacing inline style objects with makeStyles classes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 19:50:48 +02:00
045c8048fe Refactor: Replace inline style objects with makeStyles classes
Moved all static layout properties (display, gap, margin, padding, colour)
from inline style props to makeStyles classes in:

- MapBansTable.tsx: Pagination row flexbox layout
- JailDetailPage.tsx: Link styling for textDecoration
- HistoryPage.tsx: Summary text styling
- IpDetailView.tsx: Loading container and text formatting

Kept inline styles only for genuinely dynamic values:
- WorldMap.tsx: Tooltip position (follows mouse)
- TopCountriesPieChart.tsx: Legend color (from recharts data)
- TopCountriesBarChart.tsx: Chart height (derives from data length)

This change improves performance by leveraging Griffel's atomic CSS cache
and ensures consistency with the established Fluent UI pattern.

Updated Docs/Web-Development.md with explicit rule: inline styles only
for runtime-dynamic values, all static properties go in makeStyles.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 19:48:25 +02:00
c1135150c3 Refactor: Move DashboardFilterProvider to pages directory
- Move DashboardFilterProvider component and tests from providers/ to pages/
- Update DashboardPage imports to reflect new structure
- Update documentation with latest task progress

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 19:45:10 +02:00
69a0296c47 T-18: Merge useDashboardCountryData and useMapData into shared base hook
Create useBansByCountry as the shared base hook containing all common
fetch logic, abort-controller pattern, and state management. Both
useDashboardCountryData and useMapData now wrap this base hook:

- useDashboardCountryData: Thin wrapper that calls base hook with autoFetch=true
- useMapData: Wraps base hook with 300ms debounce layer

Changes:
- Create useBansByCountry.ts (base hook with optional autoFetch parameter)
- Refactor useDashboardCountryData.ts to use base hook
- Refactor useMapData.ts to use base hook with debounce wrapper
- Add tests for all three hooks

Benefits:
- Single source of truth for ban-by-country logic
- Bug fixes in base hook apply to both consumers
- Eliminates code duplication (~80 lines reduced)
- Maintains backward compatibility: existing call sites work unchanged

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 19:39:51 +02:00
3b527244aa Update task documentation and test fixes
- Update Tasks.md with current progress and status
- Fix useListData hook tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 19:34:16 +02:00
6490e9d3df T-16: Centralize PAGE_SIZE frontend constants
- Add BAN_PAGE_SIZE (100) and HISTORY_PAGE_SIZE (50) to frontend/src/utils/constants.ts
- Replace local PAGE_SIZE definitions in useBans.ts and HistoryPage.tsx with imports
- Eliminates risk of pagination constants silently diverging from backend defaults
- Single source of truth for all pagination sizes

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 19:27:24 +02:00
f84aeef249 Refactor authentication logic and API client
- Update AuthProvider with improved error handling and token management
- Enhance API client with better request/response handling
- Add comprehensive test coverage for auth flows
- Update documentation with current tasks

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 19:23:12 +02:00
6a062a72a7 refactor: move jail detail sub-sections from pages/jail to components/jail
Move reusable UI section components (JailInfoSection, PatternsSection,
BantimeEscalationSection, IgnoreListSection, CodeList) from pages/jail/
to components/jail/, aligning with the project convention that pages/
contains only route-level entry points while components/ contains reusable
UI building blocks.

Changes:
- Move 5 section components + jailDetailPageStyles.ts to components/jail/
- Update import paths in moved components (relative paths to commonStyles)
- Update JailDetailPage.tsx imports to reference components/jail/
- Delete empty pages/jail/ directory
- Document pages/ vs components/ distinction in Web-Development.md

All components use standard import structure and TypeScript passes type
checking. BannedIpsSection was already correctly placed in components/jail/.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 19:17:03 +02:00
8bd5713d38 Refactor jail detail hooks: split into useJailData and useJailCommands
- Split monolithic useJailDetail hook into separate concerns
- Created useJailData for fetching and managing jail data
- Created useJailCommands for jail operations (power, console, etc.)
- Updated JailDetailPage to use new hooks
- Updated tests to reflect new hook structure
- Removed old useJailDetail hook

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 19:14:16 +02:00
8d30a81346 feat(hooks): consolidate data-fetching patterns with useListData and usePolledData
- Refactor useJails (useJailList.ts) to use useListData with onSuccess for total
- Refactor useBanTrend to use useListData with onSuccess for bucket_size
- Refactor useDashboardCountryData to use useListData with onSuccess for aggregated data
- Refactor useHistory to use useListData with proper abort guard in finally()
- Create usePolledData for single-item endpoints with polling and window focus refetch
- Refactor useServerStatus to use usePolledData for 30s polling + window focus refetch
- Keep useIpHistory with manual pattern (single-item, no list semantics)
- Document deferred refactoring of useJailDetail (depends on T-13 for data/command split)

All data-fetching hooks now follow one of two consistent patterns:
1. useListData: for paginated/list endpoints with refresh semantics
2. usePolledData: for single-item endpoints with polling and focus-refetch

This eliminates code duplication, centralizes abort-guard logic, and enables
consistent fixes across all data-fetching hooks.

Resolves T-12.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 19:08:26 +02:00
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
4b8af1d43a Fix import formatting and sorting
Ruff formatting fixes for import organization.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 18:54:57 +02:00
1a3401f418 T-10: Fix get_geo_batch_lookup for proper injection with GeoCache instance
Instead of returning a bound method (geo_cache.lookup_batch), now inject
the GeoCache instance directly into routers and services. This provides
proper runtime isolation since T-04 made GeoCache a proper object.

Changes:
- Remove get_geo_batch_lookup() dependency provider
- Add GeoCacheDep type alias for injecting GeoCache instances
- Update all routers (bans, blocklist, dashboard, jails) to use GeoCacheDep
- Update ban_service, blocklist_service, jail_service to accept GeoCache
- Update service protocols to match new signatures
- Update docstrings to reference GeoCache methods instead of module functions

All callers now call geo_cache.lookup_batch(...) directly instead of
geo_batch_lookup(...), providing real dependency injection with proper
testing isolation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 18:53:47 +02:00
ac2028e1c2 Fix: Consolidate divergent _since_unix implementations (T-09)
Consolidate the two divergent implementations of _since_unix from ban_service.py
and history_service.py into a single shared utility function in time_utils.py.

Changes:
- Move _since_unix to app/utils/time_utils.py with consistent time.time() approach
- Move TIME_RANGE_SLACK_SECONDS constant to app/utils/constants.py
- Update ban_service.py to import since_unix from time_utils
- Update history_service.py to import since_unix from time_utils
- Both services now use the same window boundary calculation with 60-second slack
- Add comprehensive tests for the shared since_unix function
- Document timestamp handling rationale in Backend-Development.md

This ensures dashboard and history queries return consistent row counts for the
same time range by using the same timestamp calculation and slack window across
all services.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 18:44:59 +02:00
420ea18fd9 Refactor backend services and utilities
- Update service layer implementations
- Improve configuration handling utilities
- Update documentation tasks

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 18:39:30 +02:00
83452ffc23 Refactor backend services and jail configuration
- Refactor action_config_service, filter_config_service, jail_config_service, and jail_service
- Add jail_socket utility module for socket communication
- Update test_jail_service with new test cases
- Update architecture and task documentation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 18:34:03 +02:00
c3410bd554 Update Tasks documentation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 18:26:40 +02:00
24f9fdd358 T-06: Remove AppState Protocol, use ApplicationState directly
The AppState Protocol (lines 42-54) and ApplicationContext dataclass
(lines 57-69) described identical fields, creating maintenance burden.
The Protocol was only used for a single cast() in _build_app_context.

Changes:
- Remove AppState Protocol class
- Import ApplicationState from runtime_state.py
- Replace cast('AppState', request.app.state) with
  cast(ApplicationState, request.app.state)
- Remove unused Protocol import

This eliminates the redundancy while maintaining the same typing
guarantees. request.app.state is set to ApplicationState instances
during app initialization in main.py.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 18:25:56 +02:00
e57d19fd76 T-05: Remove app.state mutation from _build_app_context
Move session cache initialization from per-request _build_app_context to
startup lifespan handler. The session cache type is now decided once at app
startup based on settings, making _build_app_context pure (read-only).

Changes:
- Move cache initialization logic to new _update_session_cache() in main.py
- Call _update_session_cache() during lifespan startup to initialize cache
- Remove three if/elif/elif branches mutating state.session_cache from _build_app_context
- Add cache swap logic to set_runtime_settings() in runtime_state.py to handle
  runtime settings changes (e.g., setup wizard updates)
- Keep app.state.session_cache initialization in create_app() for test compatibility

This ensures:
- _build_app_context is pure and doesn't mutate app state on each request
- Session cache configuration decisions are centralized at startup
- Settings changes during runtime (via setup wizard) also trigger cache swap
- Cache initialization logic is isolated in one place

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 18:23:08 +02:00
d467190eb1 Refactor geo caching and service layer tests
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 18:15:31 +02:00
654dbdb000 T-04: Encapsulate geo_service module-level mutable state in GeoCache class
Create GeoCache class with all mutable state as instance attributes:
- _cache, _neg_cache, _dirty, _geoip_reader, _geoip_initialized, _cache_lock
- All public methods: lookup(), lookup_batch(), lookup_cached_only(), flush_dirty(), load_from_db(), clear(), etc.

Initialization & Dependency Injection:
- Instantiate GeoCache in startup.py and store on app.state.geo_cache
- Add get_geo_cache() dependency function in dependencies.py
- Inject into routes and tasks via FastAPI's dependency system

Backward Compatibility:
- Maintain module-level functions in geo_service.py as deprecated wrappers
- All old callers continue to work through _default_geo_cache instance
- Remove test-escape-hatch functions (clear_cache, clear_neg_cache moved to methods)

Background Tasks:
- Update geo_cache_flush.py and geo_re_resolve.py to receive GeoCache instance
- Tasks now operate on injected instance rather than module globals

Tests:
- Refactor test_geo_service.py with geo_cache fixture providing fresh instances
- Update patch paths to target GeoCache methods correctly
- Fix internal state assertions to access instance attributes

Documentation:
- Update Architekture.md to document GeoCache as managed stateful service
- Describe cache lifecycle (load on startup, flush periodically, re-resolve stale)
- Note process-local limitations for multi-worker deployments

Fixes violation of Single Responsibility Principle: module no longer owns both
lookup logic and cache lifecycle management. Cache is now a first-class
injectable service with transparent lifecycle.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 16:18:09 +02:00
fdfd24508f Refactor backend services and routers
- Reorganized dashboard router with improved structure
- Enhanced ban_service with better separation of concerns
- Updated history service with cleaner logic
- Improved constants and configuration handling
- Updated documentation of completed tasks

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 16:06:10 +02:00
fd685e8211 refactor: Remove unused HTTPException imports from routers
After removing all try/except blocks that used HTTPException for domain
exception conversion, these imports are no longer needed in the routers.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 16:01:44 +02:00
5480dce221 refactor: Remove duplicate router-level exception helpers
All routers now let domain exceptions propagate to the global handlers in main.py
instead of catching and converting them to HTTPException. This eliminates:

- Duplicate exception-to-HTTP-status mappings across 8 routers
- Duplicate helper functions (_bad_gateway, _not_found, _conflict, etc.)
- Inconsistent error response formats

Changes:
- Removed all try/except blocks from routers that catch domain exceptions
- Removed duplicate helper functions from all routers
- Added missing exception handlers to main.py for:
  * ActionNameError
  * FilterNameError
  * JailNameError
  * JailNotFoundInConfigError
  * FilterInvalidRegexError
- Removed unused imports from affected routers

All domain exceptions now propagate to the single authoritative mapping in
main.py, ensuring consistent error codes, messages, and logging across the API.

Affected routers:
- action_config.py: Removed _action_not_found, _bad_request, _not_found helpers
- bans.py: Removed try/except in ban/unban endpoints
- config_misc.py: Removed try/except blocks
- file_config.py: Removed 6 try/except blocks and _service_unavailable helper
- filter_config.py: Removed try/except blocks
- geo.py: Removed try/except in lookup_ip endpoint
- jail_config.py: Removed try/except blocks
- jails.py: Removed try/except blocks
- server.py: Removed try/except blocks

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 16:00:37 +02:00
b634ce876a refactor: Extract fail2ban response utilities into shared module
Consolidate duplicate _ok(), _to_dict(), ensure_list(), and is_not_found_error()
functions from 6 service modules into a single canonical implementation at
backend/app/utils/fail2ban_response.py.

Changes:
- Create fail2ban_response.py with canonical implementations
- Remove local duplicates from: ban_service, jail_service, config_service,
  health_service, server_service, config_file_utils
- Update all imports to use shared module
- Add comprehensive docstrings and examples
- Update Architecture.md and Backend-Development.md documentation

Benefits:
- Single source of truth for response parsing logic
- Eliminates code duplication across service layer
- Improves maintainability and consistency
- Enables centralized bug fixes and improvements

Tests: All 228 service tests passing, no regressions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 15:11:21 +02:00
6d21a53620 Update tasks and history page tests
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 09:59:21 +02:00
eaff272aae feat: Add dismissible warning UI for threshold loading errors
- Replace console.warn with visible MessageBar warning when map color thresholds fail to load
- Add DismissRegular icon button to allow users to dismiss the warning
- Add dismissedThresholdWarning state to manage warning visibility
- Add mock and test for useMapColorThresholds hook
- Add test case verifying warning displays and can be dismissed
- Remove TASK-QUALITY-04 from Tasks.md (completed)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 09:58:27 +02:00
ac44bab8e6 Update documentation and refactor useAutoSave hook
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 09:52:22 +02:00
9c5757eeb0 Refactor useHistory hook: replace HistoryQuery with explicit parameters and add documentation
- Split useHistory interface to accept explicit parameters (page, pageSize, range, origin, jail, ip, source) instead of HistoryQuery object
- Add comprehensive JSDoc for useHistory function
- Update HistoryPage and tests to use new parameter structure
- Move TaskList documentation from Tasks.md to Web-Development.md
- Improve type safety with explicit TimeRange and BanOriginFilter types

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 09:51:16 +02:00
8904e180d1 Fix: Prevent session-expiry errors from briefly showing in useConfigItem.save()
When save() encounters a 401 or 403 error, the HTTP client dispatches
SESSION_EXPIRED_EVENT which triggers auth handling and navigation to login.
However, setSaveError was called first, causing a brief flash of an
'Unauthorized' message before the redirect.

Now, isAuthError(err) checks if the error is a 401/403 before setting
saveError. Auth errors are rethrown without setting error state, allowing
the auth handler to deal with session expiry cleanly without UX confusion.

- Import isAuthError from api/client in useConfigItem hook
- Check for auth errors in the save() catch block before setSaveError
- Add tests for 401 and 403 error handling

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 09:45:11 +02:00
6d5be523ab fix: KVEditor effect dependency uses stable JSON serialization
Replace the flawed join-based comparison (entryKeys.join(',')) with
JSON.stringify() to properly handle keys containing commas. The previous
implementation could produce false equality when different key sets
shared the same comma-separated representation (e.g., 'a,b' key vs
separate 'a' and 'b' keys).

This ensures the effect fires correctly when keys change, fixing silent
failures to update derived state.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 09:41:49 +02:00
9375430e02 Refactor schedule functionality in frontend
- Extract schedule logic into custom useSchedule hook
- Update BlocklistScheduleSection to use the new hook
- Add tests for useSchedule hook
- Update documentation with task progress

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 09:40:19 +02:00
a87d892584 Update Tasks documentation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 09:34:02 +02:00
f3d6574160 Fix misleading auth token storage in sessionStorage
- Remove JWT token and expires_at from sessionStorage
- Simplify AuthProvider to use boolean isAuthenticated flag
- Persist only isAuthenticated boolean for page-reload continuity
- Update AuthProvider test to verify new auth model
- Add comprehensive auth documentation to Web-Development.md explaining:
  - Cookie-based authentication model
  - How frontend auth state persists
  - Why tokens are no longer stored
  - Error handling flow for 401/403 responses

The authentication model is cookie-based: the backend sets bangui_session
cookie on login, frontend automatically includes it via credentials:
'include', and the backend is the sole authority on session validity.
Previously stored tokens were never actually used and made the auth model
misleading during development.

Fixes TASK-STATE-04.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 09:30:29 +02:00
941502b710 Fix BanTable to use props exclusively (complete dual state source refactoring)
- BanTable now requires all filter props (timeRange, origin, source)
- Removed useDashboardFilters() hook dependency from BanTable
- Eliminated context fallback chain using ?? operator
- Component now exclusively reads from props, never from context

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 09:26:45 +02:00
814000fe68 Refactor DashboardFilterBar to use props exclusively, eliminate dual state source
- DashboardFilterBar now requires all filter props (timeRange, onTimeRangeChange, originFilter, onOriginFilterChange) instead of falling back to context
- Removed useDashboardFilters() hook dependency from DashboardFilterBar, BanTrendChart, and JailDistributionChart
- Updated DashboardPage to explicitly pass all filter values and callbacks from context to components
- Made props required on BanTrendChart and JailDistributionChart
- Updated all tests to reflect new prop requirements
- This eliminates the silent dual-source behavior that could lead to subtle bugs when components are used with different data sources

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 09:24:16 +02:00
10c534d090 Stabilize function references in useJails with useCallback
Previously, the withRefresh helper and all operations (startJail, stopJail, setIdle, reloadJail, reloadAll) were recreated on every render because they were defined in the hook body without useCallback. This caused unnecessary re-renders of child components using React.memo when parent state changed.

Now each operation is wrapped in useCallback with [load] as its dependency. This ensures function references remain stable between renders, allowing React.memo optimizations to work correctly in JailOverviewSection.

Tests confirm that function references are now stable between consecutive renders.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 09:18:31 +02:00
1bcc336c9b Add tests and documentation updates for log preview and regex tester hooks
- Add useLogPreview.test.ts with comprehensive test coverage
- Add useRegexTester.test.ts with comprehensive test coverage
- Update Docs/Tasks.md and Docs/Web-Development.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 09:14:58 +02:00