]> git.giorgioravera.it Git - network-manager.git/commitdiff
Updated login
authorGiorgio Ravera <giorgio.ravera@gmail.com>
Mon, 5 Jan 2026 16:27:42 +0000 (17:27 +0100)
committerGiorgio Ravera <giorgio.ravera@gmail.com>
Mon, 5 Jan 2026 16:27:42 +0000 (17:27 +0100)
Dockerfile
backend/db.py [deleted file]
backend/db/__init__.py [new file with mode: 0644]
backend/db/db.py [new file with mode: 0644]
backend/db/hosts.py [new file with mode: 0644]
backend/db/users.py [new file with mode: 0644]
backend/main.py
entrypoint.py
frontend/app.js
requirements.txt

index f70afbe4c8cfa6f4668de470f36a27b02d1dcdde..efa18e0d1832d9bb31e83ab78cf20b389e309a81 100644 (file)
@@ -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.py b/backend/db.py
deleted file mode 100644 (file)
index 292bd5f..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-import sqlite3
-import os
-import ipaddress
-
-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
-
-# -----------------------------
-# SELECT ALL HOSTS
-# -----------------------------
-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]
-
-# -----------------------------
-# SELECT SINGLE HOST
-# -----------------------------
-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
-
-# -----------------------------
-# INSERT HOST
-# -----------------------------
-def add_host(data: dict):
-    conn = get_db()
-    cur = conn.execute(
-        "INSERT INTO hosts (name, ipv4, ipv6, mac, note, ssl_enabled) VALUES (?, ?, ?, ?, ?, ?)",
-        (
-            data["name"],
-            data.get("ipv4"),
-            data.get("ipv6"),
-            data.get("mac"),
-            data.get("note"),
-            data.get("ssl_enabled", 0)
-        )
-    )
-    conn.commit()
-    last_id = cur.lastrowid
-    conn.close()
-    return last_id
-
-# -----------------------------
-# UPDATE HOST
-# -----------------------------
-def update_host(host_id: int, data: dict):
-    conn = get_db()
-    conn.execute(
-        "UPDATE hosts SET name=?, ipv4=?, ipv6=?, mac=?, note=?, ssl_enabled=? WHERE id=?",
-        (
-            data["name"],
-            data.get("ipv4"),
-            data.get("ipv6"),
-            data.get("mac"),
-            data.get("note"),
-            data.get("ssl_enabled", 0),
-            host_id
-        )
-    )
-    conn.commit()
-    conn.close()
-    return True
-
-# -----------------------------
-# DELETE HOST
-# -----------------------------
-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/__init__.py b/backend/db/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/backend/db/db.py b/backend/db/db.py
new file mode 100644 (file)
index 0000000..f7336f3
--- /dev/null
@@ -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/hosts.py b/backend/db/hosts.py
new file mode 100644 (file)
index 0000000..44a3744
--- /dev/null
@@ -0,0 +1,72 @@
+# backend/db/hosts.py
+
+import ipaddress
+import os
+from backend.db.db import get_db
+
+# -----------------------------
+# SELECT ALL HOSTS
+# -----------------------------
+def get_hosts():
+    conn = get_db()
+    cur = conn.execute("SELECT * FROM hosts ORDER BY name")
+    rows = cur.fetchall()
+    return [dict(r) for r in rows]
+
+# -----------------------------
+# SELECT SINGLE HOST
+# -----------------------------
+def get_host(host_id: int):
+    conn = get_db()
+    cur = conn.execute("SELECT * FROM hosts WHERE id = ?", (host_id,))
+    row = cur.fetchone()
+    return dict(row) if row else None
+
+# -----------------------------
+# INSERT HOST
+# -----------------------------
+def add_host(data: dict):
+    conn = get_db()
+    cur = conn.execute(
+        "INSERT INTO hosts (name, ipv4, ipv6, mac, note, ssl_enabled) VALUES (?, ?, ?, ?, ?, ?)",
+        (
+            data["name"],
+            data.get("ipv4"),
+            data.get("ipv6"),
+            data.get("mac"),
+            data.get("note"),
+            data.get("ssl_enabled", 0)
+        )
+    )
+    conn.commit()
+    last_id = cur.lastrowid
+    return last_id
+
+# -----------------------------
+# UPDATE HOST
+# -----------------------------
+def update_host(host_id: int, data: dict):
+    conn = get_db()
+    conn.execute(
+        "UPDATE hosts SET name=?, ipv4=?, ipv6=?, mac=?, note=?, ssl_enabled=? WHERE id=?",
+        (
+            data["name"],
+            data.get("ipv4"),
+            data.get("ipv6"),
+            data.get("mac"),
+            data.get("note"),
+            data.get("ssl_enabled", 0),
+            host_id
+        )
+    )
+    conn.commit()
+    return True
+
+# -----------------------------
+# DELETE HOST
+# -----------------------------
+def delete_host(host_id: int):
+    conn = get_db()
+    conn.execute("DELETE FROM hosts WHERE id = ?", (host_id,))
+    conn.commit()
+    return True
diff --git a/backend/db/users.py b/backend/db/users.py
new file mode 100644 (file)
index 0000000..0b4762a
--- /dev/null
@@ -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
index 4d5cc8162d4dfdc4520acb1fbceabc0454a577c8..50ad5b0c61fa9ec38e8ef7edba9548b8818a6e79 100644 (file)
@@ -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)
 
index e115de039ad2a59be7dccc712a381ccdcb9ecbed..806c9e0aa49b592f6890e7967ba58adde03ee782 100755 (executable)
@@ -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
index 8d33d86b67dac6fe794621268c194042ea20b63b..5c873e6beca88e3a51cc0874a8cea3add3a75354 100644 (file)
@@ -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();  
index 4c745bc74275214db28b63da2fc2d9539f7c47e1..1e40b78f890f1b5f8460cd90fe601146f19bbeec 100644 (file)
@@ -1,3 +1,4 @@
-fastapi 
-uvicorn[standard]
+bcrypt
+fastapi
 itsdangerous
+uvicorn[standard]