Compare commits
4 Commits
v0.9.19-rc
...
v0.9.19-rc
| Author | SHA1 | Date | |
|---|---|---|---|
| 5a12d1c22f | |||
| aebe0d0236 | |||
| 99e1b74405 | |||
| 9fe52755a5 |
@@ -1 +1 @@
|
||||
v0.9.19-rc.3
|
||||
v0.9.19-rc.5
|
||||
|
||||
105
Docker/compose.prod.yml
Normal file
105
Docker/compose.prod.yml
Normal file
@@ -0,0 +1,105 @@
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# BanGUI — Production Compose
|
||||
#
|
||||
# Usage:
|
||||
# docker compose -f Docker/compose.prod.yml up -d
|
||||
# podman compose -f Docker/compose.prod.yml up -d
|
||||
#
|
||||
# Features:
|
||||
# - Multi-stage built images (no volume-mounted source code)
|
||||
# - Frontend served by nginx with API reverse proxy
|
||||
# - Backend running uvicorn without --reload
|
||||
# - Only port 8080 exposed to host
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
|
||||
name: bangui
|
||||
|
||||
services:
|
||||
# ── fail2ban ─────────────────────────────────────────────────
|
||||
fail2ban:
|
||||
image: lscr.io/linuxserver/fail2ban:latest
|
||||
container_name: bangui-fail2ban
|
||||
restart: unless-stopped
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- NET_RAW
|
||||
network_mode: host
|
||||
environment:
|
||||
TZ: "${BANGUI_TIMEZONE:-UTC}"
|
||||
PUID: 0
|
||||
PGID: 0
|
||||
volumes:
|
||||
- ../data/fail2ban-dev-config:/config
|
||||
- fail2ban-run:/var/run/fail2ban
|
||||
- /var/log:/var/log:ro
|
||||
- ../data/log:/remotelogs/bangui
|
||||
healthcheck:
|
||||
test: ["CMD", "fail2ban-client", "ping"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
start_period: 15s
|
||||
retries: 3
|
||||
|
||||
# ── Backend (FastAPI + uvicorn) ─────────────────────────────
|
||||
backend:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: Docker/Dockerfile.backend
|
||||
target: runtime
|
||||
container_name: bangui-backend
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
fail2ban:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
BANGUI_DATABASE_PATH: "/data/bangui.db"
|
||||
BANGUI_FAIL2BAN_SOCKET: "/var/run/fail2ban/fail2ban.sock"
|
||||
BANGUI_FAIL2BAN_CONFIG_DIR: "/config/fail2ban"
|
||||
BANGUI_LOG_FILE: "/data/log/bangui.log"
|
||||
BANGUI_LOG_LEVEL: "${BANGUI_LOG_LEVEL:-info}"
|
||||
BANGUI_SESSION_SECRET: "${BANGUI_SESSION_SECRET:?BANGUI_SESSION_SECRET must be set — generate with: python -c 'import secrets; print(secrets.token_hex(32))'}"
|
||||
BANGUI_TIMEZONE: "${BANGUI_TIMEZONE:-UTC}"
|
||||
BANGUI_SESSION_COOKIE_SECURE: "${BANGUI_SESSION_COOKIE_SECURE:-true}"
|
||||
BANGUI_CORS_ALLOWED_ORIGINS: "${BANGUI_CORS_ALLOWED_ORIGINS:-}"
|
||||
volumes:
|
||||
- ../data:/data
|
||||
- ../fail2ban-master:/app/fail2ban-master:ro
|
||||
- fail2ban-run:/var/run/fail2ban:ro
|
||||
- ../data/fail2ban-dev-config:/config:rw
|
||||
networks:
|
||||
- bangui-net
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8000/api/v1/health/live || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
start_period: 40s
|
||||
retries: 3
|
||||
|
||||
# ── Frontend (nginx serving built SPA) ──────────────────────
|
||||
frontend:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: Docker/Dockerfile.frontend
|
||||
container_name: bangui-frontend
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
backend:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "${BANGUI_PORT:-8080}:80"
|
||||
networks:
|
||||
- bangui-net
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -qO /dev/null http://localhost:80/ || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
start_period: 5s
|
||||
retries: 3
|
||||
|
||||
volumes:
|
||||
fail2ban-run:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
bangui-net:
|
||||
driver: bridge
|
||||
@@ -102,10 +102,15 @@ CREATE TABLE IF NOT EXISTS schema_migrations (
|
||||
"""
|
||||
|
||||
# Ordered list of DDL statements to execute on initialisation.
|
||||
# NOTE: _CREATE_SESSIONS_TOKEN_INDEX is intentionally omitted here.
|
||||
# The old 0.8.0 schema has a `sessions.token` column (not `token_hash`), so
|
||||
# running CREATE INDEX … ON sessions (token_hash) in migration 1 would fail
|
||||
# with "no such column: token_hash" on legacy databases. Migration 2 drops
|
||||
# and recreates the sessions table with token_hash and also creates the index,
|
||||
# so there is no need to create it in migration 1.
|
||||
_SCHEMA_STATEMENTS: list[str] = [
|
||||
_CREATE_SETTINGS,
|
||||
_CREATE_SESSIONS,
|
||||
_CREATE_SESSIONS_TOKEN_INDEX,
|
||||
_CREATE_BLOCKLIST_SOURCES,
|
||||
_CREATE_IMPORT_LOG,
|
||||
_CREATE_GEO_CACHE,
|
||||
@@ -133,8 +138,24 @@ CREATE UNIQUE INDEX idx_sessions_token_hash ON sessions (token_hash);
|
||||
3: """
|
||||
-- Migration 3: Add last_seen timestamp to geo_cache for retention policy.
|
||||
-- Tracks when each IP was last referenced to enable purging of stale entries.
|
||||
-- Default to current timestamp for existing rows.
|
||||
ALTER TABLE geo_cache ADD COLUMN last_seen TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'));
|
||||
-- SQLite rejects ALTER TABLE ADD COLUMN with a non-constant NOT NULL default
|
||||
-- when the table already contains rows, so we rebuild the table instead.
|
||||
-- Existing rows receive last_seen = cached_at as a reasonable approximation
|
||||
-- (the IP was at least seen when it was first cached).
|
||||
DROP TABLE IF EXISTS geo_cache_new;
|
||||
CREATE TABLE geo_cache_new (
|
||||
ip TEXT PRIMARY KEY,
|
||||
country_code TEXT,
|
||||
country_name TEXT,
|
||||
asn TEXT,
|
||||
org TEXT,
|
||||
cached_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||
last_seen TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
||||
);
|
||||
INSERT INTO geo_cache_new (ip, country_code, country_name, asn, org, cached_at, last_seen)
|
||||
SELECT ip, country_code, country_name, asn, org, cached_at, cached_at FROM geo_cache;
|
||||
DROP TABLE geo_cache;
|
||||
ALTER TABLE geo_cache_new RENAME TO geo_cache;
|
||||
""",
|
||||
4: """
|
||||
-- Migration 4: Add scheduler_lock table for multi-worker safety.
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "bangui-backend"
|
||||
version = "0.9.19-rc.1"
|
||||
version = "0.9.19-rc.4"
|
||||
description = "BanGUI backend — fail2ban web management interface"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
|
||||
147
check_auth.py
Normal file
147
check_auth.py
Normal file
@@ -0,0 +1,147 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Diagnostic script for BanGUI auth/session 401 issue.
|
||||
|
||||
Tests the full auth flow against http://192.168.178.43:8080/api/v1/auth
|
||||
using password "Hallo123!".
|
||||
|
||||
Usage:
|
||||
python3 check_auth.py
|
||||
"""
|
||||
|
||||
import json
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
BASE_URL = "http://192.168.178.43:8080/api/v1"
|
||||
PASSWORD = "Hallo123!"
|
||||
|
||||
|
||||
def make_request(url, method="GET", data=None, headers=None, cookie=None):
|
||||
"""Make an HTTP request and return (status, headers, body, cookies)."""
|
||||
req_headers = headers or {}
|
||||
if data:
|
||||
req_headers["Content-Type"] = "application/json"
|
||||
if cookie:
|
||||
req_headers["Cookie"] = cookie
|
||||
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
data=json.dumps(data).encode("utf-8") if data else None,
|
||||
headers=req_headers,
|
||||
method=method,
|
||||
)
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(req) as resp:
|
||||
body = resp.read().decode("utf-8")
|
||||
cookies = resp.headers.get_all("Set-Cookie") or []
|
||||
return resp.status, dict(resp.headers), body, cookies
|
||||
except urllib.error.HTTPError as e:
|
||||
body = e.read().decode("utf-8")
|
||||
cookies = e.headers.get_all("Set-Cookie") or []
|
||||
return e.code, dict(e.headers), body, cookies
|
||||
except Exception as e:
|
||||
return None, {}, str(e), []
|
||||
|
||||
|
||||
def extract_cookie_value(set_cookie_headers, cookie_name):
|
||||
"""Extract cookie value from Set-Cookie headers."""
|
||||
for header in set_cookie_headers:
|
||||
if header.startswith(cookie_name + "="):
|
||||
return header.split(";")[0]
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("BanGUI Auth Diagnostic Script")
|
||||
print("Target:", BASE_URL)
|
||||
print("=" * 60)
|
||||
|
||||
# 1. Check health endpoint (no auth needed)
|
||||
print("\n[1] GET /health")
|
||||
status, headers, body, _ = make_request(f"{BASE_URL}/health")
|
||||
print(f" Status: {status}")
|
||||
print(f" Response: {body[:200]}")
|
||||
|
||||
# 2. Check CORS preflight for login
|
||||
print("\n[2] OPTIONS /auth/login (CORS preflight)")
|
||||
status, headers, body, _ = make_request(
|
||||
f"{BASE_URL}/auth/login",
|
||||
method="OPTIONS",
|
||||
headers={
|
||||
"Origin": "http://192.168.178.43:8080",
|
||||
"Access-Control-Request-Method": "POST",
|
||||
"Access-Control-Request-Headers": "Content-Type",
|
||||
},
|
||||
)
|
||||
print(f" Status: {status}")
|
||||
print(f" Access-Control-Allow-Origin: {headers.get('Access-Control-Allow-Origin', 'MISSING')}")
|
||||
print(f" Access-Control-Allow-Credentials: {headers.get('Access-Control-Allow-Credentials', 'MISSING')}")
|
||||
|
||||
# 3. Login
|
||||
print(f"\n[3] POST /auth/login (password: {PASSWORD})")
|
||||
status, headers, body, cookies = make_request(
|
||||
f"{BASE_URL}/auth/login",
|
||||
method="POST",
|
||||
data={"password": PASSWORD},
|
||||
headers={"Origin": "http://192.168.178.43:8080"},
|
||||
)
|
||||
print(f" Status: {status}")
|
||||
print(f" Response: {body}")
|
||||
print(f" Set-Cookie headers: {cookies}")
|
||||
|
||||
session_cookie = extract_cookie_value(cookies, "bangui_session")
|
||||
if session_cookie:
|
||||
print(f" Extracted session cookie: {session_cookie[:50]}...")
|
||||
else:
|
||||
print(" WARNING: No bangui_session cookie received!")
|
||||
|
||||
# 4. Validate session with cookie
|
||||
print("\n[4] GET /auth/session (with cookie)")
|
||||
if session_cookie:
|
||||
status, headers, body, _ = make_request(
|
||||
f"{BASE_URL}/auth/session",
|
||||
cookie=session_cookie,
|
||||
headers={"Origin": "http://192.168.178.43:8080"},
|
||||
)
|
||||
print(f" Status: {status}")
|
||||
print(f" Response: {body}")
|
||||
else:
|
||||
print(" SKIPPED (no cookie from login)")
|
||||
|
||||
# 5. Validate session WITHOUT cookie (should be 401)
|
||||
print("\n[5] GET /auth/session (without cookie)")
|
||||
status, headers, body, _ = make_request(f"{BASE_URL}/auth/session")
|
||||
print(f" Status: {status}")
|
||||
print(f" Response: {body}")
|
||||
|
||||
# 6. Check backend settings (if available via /setup or other endpoint)
|
||||
print("\n[6] GET /setup (check if setup is complete)")
|
||||
status, headers, body, _ = make_request(f"{BASE_URL}/setup")
|
||||
print(f" Status: {status}")
|
||||
print(f" Response: {body[:200]}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("DIAGNOSIS SUMMARY")
|
||||
print("=" * 60)
|
||||
|
||||
if session_cookie and "Secure" in str(cookies):
|
||||
print("\n PROBLEM FOUND: Session cookie has 'Secure' flag set,")
|
||||
print(" but you are accessing over HTTP (not HTTPS).")
|
||||
print(" Browsers will NOT send Secure cookies over HTTP!")
|
||||
print("\n FIX: Set SESSION_COOKIE_SECURE=false in your backend .env")
|
||||
print(" and restart the backend.")
|
||||
|
||||
if not session_cookie and status == 401:
|
||||
print("\n PROBLEM FOUND: Login succeeded but no session cookie was set.")
|
||||
print(" This usually means the cookie is being rejected by the browser")
|
||||
print(" due to Secure flag on HTTP, or SameSite restrictions.")
|
||||
|
||||
print("\n If CORS Access-Control-Allow-Origin is missing or wrong,")
|
||||
print(" add your frontend origin to CORS_ALLOWED_ORIGINS in .env")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "bangui-frontend",
|
||||
"version": "0.9.19",
|
||||
"version": "0.9.19-rc.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "bangui-frontend",
|
||||
"version": "0.9.19",
|
||||
"version": "0.9.19-rc.4",
|
||||
"dependencies": {
|
||||
"@fluentui/react-components": "^9.55.0",
|
||||
"@fluentui/react-icons": "^2.0.257",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "bangui-frontend",
|
||||
"private": true,
|
||||
"version": "0.9.19-rc.3",
|
||||
"version": "0.9.19-rc.5",
|
||||
"description": "BanGUI frontend — fail2ban web management interface",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
Reference in New Issue
Block a user