From 079b2a9f3ccde5278021799dcbb674012d406028 Mon Sep 17 00:00:00 2001 From: Giorgio Ravera Date: Mon, 5 Jan 2026 17:27:42 +0100 Subject: [PATCH] Updated login --- Dockerfile | 2 -- backend/db/__init__.py | 0 backend/db/db.py | 28 ++++++++++++++++ backend/{db.py => db/hosts.py} | 23 +++---------- backend/db/users.py | 53 +++++++++++++++++++++++++++++ backend/main.py | 9 +++-- entrypoint.py | 61 ++++++++++++++++++++++++++++++++-- frontend/app.js | 5 ++- requirements.txt | 5 +-- 9 files changed, 155 insertions(+), 31 deletions(-) create mode 100644 backend/db/__init__.py create mode 100644 backend/db/db.py rename backend/{db.py => db/hosts.py} (79%) create mode 100644 backend/db/users.py diff --git a/Dockerfile b/Dockerfile index f70afbe..efa18e0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -44,8 +44,6 @@ COPY --from=builder /app/entrypoint.py /usr/local/bin/entrypoint.py # Ensure Python sees the installed packages ENV PYTHONPATH="/usr/local/lib/python3.12/site-packages" -ENV DB_PATH=/data/database.db -ENV DB_RESET=0 ENV HTTP_PORT=8000 ENV LOGIN_MAX_ATTEMPTS=5 ENV LOGIN_WINDOW_SECONDS=600 diff --git a/backend/db/__init__.py b/backend/db/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/db/db.py b/backend/db/db.py new file mode 100644 index 0000000..f7336f3 --- /dev/null +++ b/backend/db/db.py @@ -0,0 +1,28 @@ +# backend/db/db.py + +import os +import sqlite3 + +DB_PATH = os.environ.get("DB_PATH", "/data/database.db") + +_connection = None + +# ----------------------------- +# Connect to the database +# ----------------------------- +def get_db(): + global _connection + if _connection is None: + os.makedirs(os.path.dirname(DB_PATH) or ".", exist_ok=True) + _connection = sqlite3.connect(DB_PATH, check_same_thread=False) + _connection.row_factory = sqlite3.Row + _connection.execute("PRAGMA foreign_keys = ON;") + return _connection + +# ----------------------------- +# Init Database +# ----------------------------- +def init_db(): + conn = get_db() + cur = conn.cursor() + conn.commit() diff --git a/backend/db.py b/backend/db/hosts.py similarity index 79% rename from backend/db.py rename to backend/db/hosts.py index 292bd5f..44a3744 100644 --- a/backend/db.py +++ b/backend/db/hosts.py @@ -1,18 +1,8 @@ -import sqlite3 -import os -import ipaddress +# backend/db/hosts.py -DB_PATH = os.environ.get("DB_PATH", "/data/database.db") - -# ----------------------------- -# Connect to the database -# ----------------------------- -def get_db(): - conn = sqlite3.connect(DB_PATH, timeout=5) - conn.row_factory = sqlite3.Row - conn.execute("PRAGMA journal_mode=WAL;") - conn.execute("PRAGMA synchronous=NORMAL;") - return conn +import ipaddress +import os +from backend.db.db import get_db # ----------------------------- # SELECT ALL HOSTS @@ -21,7 +11,6 @@ def get_hosts(): conn = get_db() cur = conn.execute("SELECT * FROM hosts ORDER BY name") rows = cur.fetchall() - conn.close() return [dict(r) for r in rows] # ----------------------------- @@ -31,7 +20,6 @@ def get_host(host_id: int): conn = get_db() cur = conn.execute("SELECT * FROM hosts WHERE id = ?", (host_id,)) row = cur.fetchone() - conn.close() return dict(row) if row else None # ----------------------------- @@ -52,7 +40,6 @@ def add_host(data: dict): ) conn.commit() last_id = cur.lastrowid - conn.close() return last_id # ----------------------------- @@ -73,7 +60,6 @@ def update_host(host_id: int, data: dict): ) ) conn.commit() - conn.close() return True # ----------------------------- @@ -83,5 +69,4 @@ def delete_host(host_id: int): conn = get_db() conn.execute("DELETE FROM hosts WHERE id = ?", (host_id,)) conn.commit() - conn.close() return True diff --git a/backend/db/users.py b/backend/db/users.py new file mode 100644 index 0000000..0b4762a --- /dev/null +++ b/backend/db/users.py @@ -0,0 +1,53 @@ +# backend/db/users.py + +import json +from backend.db.db import get_db +from bcrypt import checkpw + +# ----------------------------- +# Get User from DB by username +# ----------------------------- +def get_user_by_username(username): + conn = get_db() + cur = conn.cursor() + cur.execute("SELECT * FROM users WHERE username = ?", (username,)) + return cur.fetchone() + +# ----------------------------- +# Create User +# ----------------------------- +def create_user(username, password_hash, email=None, is_admin=0, modules=None): + conn = get_db() + cur = conn.cursor() + + cur.execute(""" + INSERT INTO users ( + username, password_hash, email, is_admin, modules, status, + created_at, updated_at, password_changed_at + ) VALUES (?, ?, ?, ?, ?, 'active', strftime('%s','now'), strftime('%s','now'), strftime('%s','now')); + """, ( + username, + password_hash, + email, + is_admin, + json.dumps(modules or []) + )) + + conn.commit() + +# ----------------------------- +# Verify Login +# ----------------------------- +def verify_login(username, password): + user = get_user_by_username(username) + if not user: + return False + + if user["status"] != "active": + return False + + if not checkpw(password.encode(), user["password_hash"].encode()): + return False + + print(f"DEBUG: Checking password for user {user['username']} with status {user['status']}: {user['password_hash']}") + return True diff --git a/backend/main.py b/backend/main.py index 4d5cc81..50ad5b0 100644 --- a/backend/main.py +++ b/backend/main.py @@ -9,7 +9,7 @@ import ipaddress import time # Import local models -from backend.db import ( +from backend.db.hosts import ( get_hosts, get_host, add_host, @@ -17,6 +17,10 @@ from backend.db import ( delete_host ) +from backend.db.users import ( + verify_login +) + # Read Variables SECRET_KEY = os.getenv("SESSION_SECRET", secrets.token_urlsafe(64)) HTTP_PORT = os.getenv("HTTP_PORT", "8000") @@ -127,7 +131,8 @@ def api_login(request: Request, data: dict, response: Response): user = data.get("username") pwd = data.get("password") - if user == "admin" and pwd == "admin": + if (verify_login(user, pwd)): + #if user == "admin" and pwd == "admin": # reset tentativi su IP login_attempts.pop(ip, None) diff --git a/entrypoint.py b/entrypoint.py index e115de0..806c9e0 100755 --- a/entrypoint.py +++ b/entrypoint.py @@ -1,5 +1,6 @@ #!/usr/local/bin/python3 +import bcrypt import os import sys import sqlite3 @@ -8,21 +9,36 @@ import subprocess # ================================ # Variables # ================================ -DB_FILE = os.environ.get("DB_PATH", "database.db") +DB_FILE = os.environ.get("DB_PATH", "/data/database.db") DB_RESET = os.environ.get("DB_RESET", "0") == "1" DOMAIN = os.environ.get("DOMAIN", "example.com") PUBLIC_IP = os.environ.get("PUBLIC_IP", "127.0.0.1") +ADMIN_USER = os.environ.get("ADMIN_USER", "admin") +ADMIN_PASSWORD = os.environ.get("ADMIN_PASSWORD", "admin") +ADMIN_HASH = os.environ.get("ADMIN_HASH", "") IMAGE_NAME = "network-manager-distroless" IMAGE_VERSION = "1.0" +import bcrypt + +# ================================ +# Create hash password +# ================================ +def hash_password(password: str) -> str: + salt = bcrypt.gensalt(rounds=12) + hashed = bcrypt.hashpw(password.encode(), salt) + return hashed.decode() + # ================================ # Create DB if needed # ================================ def create_db(): + global DB_FILE, DB_RESET, DOMAIN, PUBLIC_IP, ADMIN_USER, ADMIN_PASSWORD, ADMIN_HASH + # Reset database if requested if DB_RESET and os.path.exists(DB_FILE): - print("INFO: Removing existing database...") + print("INFO: Removing existing database.") os.remove(DB_FILE) # Skip creation if DB already exists @@ -91,10 +107,50 @@ def create_db(): """) cur.execute("CREATE INDEX idx_txt_host ON txt_records(host_id);") + # Users RECORDS + cur.execute(""" + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT UNIQUE NOT NULL, + password_hash TEXT NOT NULL, + email TEXT UNIQUE, + is_admin INTEGER NOT NULL DEFAULT 0, + modules TEXT, -- JSON: ["dns","dhcp","vpn"] + status TEXT NOT NULL DEFAULT 'active', -- active, disabled, locked + failed_attempts INTEGER NOT NULL DEFAULT 0, + last_failed_at INTEGER, + last_login_at INTEGER, + password_changed_at INTEGER, + notes TEXT, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL + ); + """) + cur.execute("CREATE INDEX idx_users_username ON users(username);") + # Insert default admin user + if not ADMIN_HASH: + ADMIN_HASH = hash_password(ADMIN_PASSWORD) + else: + ADMIN_PASSWORD = "(hidden)" + cur.execute(""" + INSERT INTO users ( + username, password_hash, email, is_admin, modules, status, + created_at, updated_at, password_changed_at + ) VALUES (?, ?, ?, ?, ?, ?, strftime('%s','now'), strftime('%s','now'), strftime('%s','now')); + """, ( + ADMIN_USER, + ADMIN_HASH, + "admin@example.com", + 1, + '["dns","dhcp"]', + "active" + )) + conn.commit() conn.close() print(f"INFO: Database initialized successfully for {DOMAIN}.") + print(f"INFO: Admin user: {ADMIN_USER} with password {ADMIN_PASSWORD} - {ADMIN_HASH}.") print(f"INFO: Public IP: {PUBLIC_IP}.") # ================================ @@ -106,7 +162,6 @@ sys.stdout.reconfigure(line_buffering=True) print(f"INFO: Starting {IMAGE_NAME} docker image version {IMAGE_VERSION}.") - # Parse arguments args = sys.argv[1:] i = 0 diff --git a/frontend/app.js b/frontend/app.js index 8d33d86..5c873e6 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -288,7 +288,7 @@ async function handleLogin(e) { const data = await res.json(); if (data.status === "ok") { - window.location.href = "/hosts"; // ora funziona davvero + window.location.href = "/hosts"; } else { document.getElementById("loginError").textContent = "Wrong credentials"; } @@ -300,7 +300,7 @@ async function handleLogin(e) { async function handleLogout() { await fetch("/api/logout", { method: "POST", - credentials: "include" // 🔥 fondamentale per cancellare il cookie + credentials: "include" }); window.location.href = "/login"; @@ -312,7 +312,6 @@ async function handleLogout() { loadHosts(); document.getElementById("searchInput").value = ""; -//document.getElementById("searchInput").addEventListener("keydown", (e) => { document.addEventListener("keydown", (e) => { if (e.key === "Escape") { resetSorting(); diff --git a/requirements.txt b/requirements.txt index 4c745bc..1e40b78 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ -fastapi -uvicorn[standard] +bcrypt +fastapi itsdangerous +uvicorn[standard] -- 2.47.3