# 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
+++ /dev/null
-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
--- /dev/null
+# 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()
--- /dev/null
+# 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
--- /dev/null
+# 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
import time
# Import local models
-from backend.db import (
+from backend.db.hosts import (
get_hosts,
get_host,
add_host,
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")
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)
#!/usr/local/bin/python3
+import bcrypt
import os
import sys
import sqlite3
# ================================
# 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
""")
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}.")
# ================================
print(f"INFO: Starting {IMAGE_NAME} docker image version {IMAGE_VERSION}.")
-
# Parse arguments
args = sys.argv[1:]
i = 0
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";
}
async function handleLogout() {
await fetch("/api/logout", {
method: "POST",
- credentials: "include" // 🔥 fondamentale per cancellare il cookie
+ credentials: "include"
});
window.location.href = "/login";
loadHosts();
document.getElementById("searchInput").value = "";
-//document.getElementById("searchInput").addEventListener("keydown", (e) => {
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
resetSorting();
-fastapi
-uvicorn[standard]
+bcrypt
+fastapi
itsdangerous
+uvicorn[standard]