Commit Graph

21 Commits

Author SHA1 Message Date
8f26776bb3 docs: add OpenAPI responses={} to all router endpoints
Add explicit HTTP status code documentation to every endpoint
across 15 router files. Each endpoint now declares all possible
response codes (200/201/204/400/401/404/409/429/502/503) with
descriptions so frontend can distinguish error types.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-03 01:12:08 +02:00
cc6dbcf3f0 feat: implement API versioning /api/v1/
- All backend routers moved to /api/v1/ prefix
- Frontend BASE_URL updated to /api/v1
- Setup redirect middleware updated to redirect to /api/v1/setup
- Health router path fixed: prefix=/api/v1/health, @router.get('')
- conftest.py: set server_status=online for test fixture
- Created Docs/API_VERSIONING.md with deprecation policy
- Updated Docs/Backend-Development.md with versioning section
- Updated Instructions.md curl examples

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 21:29:30 +02:00
0d5882b32f Fix HIGH priority issues: unbounded queries, rate limiting, health checks
Issue #3 - Unbounded Query Results (OOM):
- get_all_archived_history() now uses keyset pagination with bounded max_rows (50k default)
- Added 'id' field to records from get_archived_history() and get_archived_history_keyset()
- Protocol signature updated with page_size, max_rows, last_ban_id params

Issue #7 - Docker Health Check Fails:
- Added curl to Dockerfile.backend runtime image
- HEALTHCHECK now uses 'curl -f http://localhost:8000/api/health'
- compose.prod.yml: increased start_period to 40s, timeout to 10s
- Frontend healthcheck proxies to backend /api/health

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-01 21:47:36 +02:00
1e2576af2a ## 27) Error response body shape is inconsistent 2026-04-28 22:28:02 +02:00
b27765928a Standardize API response envelopes: use items for collection responses and update tests 2026-04-28 20:48:00 +02:00
507f153ab9 Enforce repository boundary: Remove DbDep from routers
This commit enforces the repository boundary by eliminating direct database connection
dependencies (DbDep) from all routers. Routers now depend on service context dependencies
that combine the database connection with the related repositories.

Changes:
- Add 5 service context dependencies in dependencies.py:
  * SessionServiceContext: db + session_repo
  * BlocklistServiceContext: db + blocklist_repo + import_log_repo + settings_repo
  * SettingsServiceContext: db + settings_repo
  * BanServiceContext: db + fail2ban_db_repo
  * HistoryServiceContext: db + fail2ban_db_repo + history_archive_repo

- Refactor all 9 routers (auth, bans, blocklist, config_misc, dashboard, geo,
  history, jails, setup) to use service contexts instead of DbDep.

- Update Backend-Development.md with clear examples of the new pattern and
  documentation of available service contexts.

Rationale:
- Enforces the repository boundary through the dependency system
- Makes database operations explicit and auditable
- Improves testability by allowing service contexts to be mocked
- Prevents accidental direct database access from routers

The deprecated DbDep remains available for backward compatibility with
services that have not yet been refactored, but routers can no longer import it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-28 07:35:23 +02:00
2e221f6852 Refactor: Move module-level mutable flags to JailServiceState
TASK-004: Replace module-level mutable runtime flags in service layer with
injected state holder, eliminating hidden global state and improving testability
and synchronization boundaries.

Changes:
- Create JailServiceState dataclass in app/utils/runtime_state.py to hold
  backend capability cache and synchronization lock
- Add JailServiceState as a field in RuntimeState (with default_factory)
- Remove module-level _backend_cmd_supported and _backend_cmd_lock from
  jail_service.py
- Refactor _check_backend_cmd_supported() to accept state parameter
- Inject JailServiceState into list_jails() and _fetch_jail_summary() via
  parameters
- Add get_jail_service_state() dependency provider in app/dependencies.py
- Add JailServiceStateDep type alias for router injection
- Update jails router to receive and pass state to service functions
- Update all tests to use jail_service_state fixture and pass state to functions
- Remove duplicate _MAX_PAGE_SIZE constant definition
- Document mutable state management in Backend-Development.md
- Update Architecture.md to describe JailServiceState and state nesting pattern

Benefits:
- Eliminates global mutable state and associated race conditions
- Makes state visible to callers (not hidden in module scope)
- Enables test isolation (each test gets fresh state)
- Prepares codebase for multi-worker deployments (state can be extracted to
  shared backend)
- Synchronization boundaries are now explicit (state.get_backend_cmd_lock())

Compliance:
- All tests pass (17 passed in TestListJails, TestGetJail, TestLockInitialization)
- No ruff linting errors
- Type-safe: JailServiceState properly typed with asyncio.Lock, bool | None

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-27 18:42:52 +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
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
6e1e3c4546 Remove unused service protocol aliases and use direct service imports 2026-04-17 16:01:27 +02:00
328f3575e2 Move Fail2Ban exceptions into central app.exceptions module 2026-04-15 10:22:48 +02:00
56ade7fb08 Task 13: wire geo_batch_lookup through dependency injection and mark task completed 2026-04-14 09:51:23 +02:00
f61d497e4e Refactor backend auth, setup, router, and runtime state handling 2026-04-10 21:00:36 +02:00
3371ff8324 Introduce service/repository dependency protocols and tests 2026-04-10 19:51:19 +02:00
3b58179845 Refactor router dependencies to use explicit fail2ban socket and HTTP session injection 2026-04-06 16:38:17 +02:00
42c030c706 Refactor backend to use request-scoped SQLite connections 2026-04-05 23:14:46 +02:00
a442836c5c refactor: complete Task 2/3 geo decouple + exceptions centralization; mark as done 2026-03-22 14:24:25 +01:00
baf45c6c62 feat: Task 4 — paginated banned-IPs section on jail detail page
Backend:
- Add JailBannedIpsResponse Pydantic model (ban.py)
- Add get_jail_banned_ips() service: server-side pagination, optional
  IP substring search, geo enrichment on page slice only (jail_service.py)
- Add GET /api/jails/{name}/banned endpoint with page/page_size/search
  query params, 400/404/502 error handling (routers/jails.py)
- 23 new tests: 13 service tests + 10 router tests (all passing)

Frontend:
- Add JailBannedIpsResponse TS interface (types/jail.ts)
- Add jailBanned endpoint helper (api/endpoints.ts)
- Add fetchJailBannedIps() API function (api/jails.ts)
- Add BannedIpsSection component: Fluent UI DataGrid, debounced search
  (300 ms), prev/next pagination, page-size dropdown, per-row unban
  button, loading spinner, empty state, error MessageBar (BannedIpsSection.tsx)
- Mount BannedIpsSection in JailDetailPage between stats and patterns
- 12 new Vitest tests for BannedIpsSection (all passing)
2026-03-14 16:28:43 +01:00
08b8f3872a fix: reload/stop jail 404 + access list simulator
Task 1 — fix Stop/Reload Jail returning 404
  Root cause: reload_jail and reload_all sent an empty config stream
  (["reload", name, [], []]).  In fail2ban's reload protocol the end-of-
  reload phase deletes every jail still in reload_state — i.e. every jail
  that received no configuration commands.  An empty stream means *all*
  affected jails are silently removed from the daemon's runtime, causing
  everything touching those jails afterwards (including stop) to receive
  UnknownJailException → HTTP 404.

  Fixes:
  - reload_jail: send ["start", name] in the config stream; startJail()
    removes the jail from reload_state so the end phase commits instead of
    deletes, and un-idles the jail.
  - reload_all: fetch current jail list first, build a ["start", name]
    entry for every active jail, then send reload --all with that stream.
  - stop_jail: made idempotent — if the jail is already gone (not-found
    error) the operation silently succeeds (200 OK) rather than returning
    404, matching the user expectation that stop = ensure-stopped.
  - Router: removed dead JailNotFoundError handler from stop endpoint.

  391 tests pass (2 new), ruff clean, mypy clean (pre-existing
  config.py error unchanged).

Task 2 — access list simulator
  - Docker/simulate_accesses.sh: writes fake HTTP-scan log lines in
    custom format (bangui-access: http scan from <IP> ...) to
    Docker/logs/access.log so the bangui-access jail detects them.
  - fail2ban/filter.d/bangui-access.conf: failregex matching the above.
  - fail2ban/jail.d/bangui-access.conf: polling jail on access.log,
    same settings as bangui-sim (maxretry=3, bantime=60s).
  - .gitignore: whitelist new bangui-access.conf files.
  - Docker/fail2ban-dev-config/README.md: added "Testing the Access
    List Feature" section with step-by-step instructions and updated
    Configuration Reference + Troubleshooting.
2026-03-06 19:49:31 +01:00
ebec5e0f58 Stage 6: jail management — backend service, routers, tests, and frontend
- jail_service.py: list/detail/control/ban/unban/ignore-list/IP-lookup
- jails.py router: 11 endpoints including ignore list management
- bans.py router: active bans, ban, unban
- geo.py router: IP lookup with geo enrichment
- models: Jail.actions, ActiveBan.country/.banned_at optional, GeoDetail
- 217 tests pass (40 service + 36 router + 141 existing), 76% coverage
- Frontend: types/jail.ts, api/jails.ts, hooks/useJails.ts
- JailsPage: jail overview table with controls, ban/unban forms,
  active bans table, IP lookup
- JailDetailPage: full detail, start/stop/idle/reload, patterns,
  ignore list management
2026-03-01 14:09:02 +01:00