feat: add auth subsystem with WebAuthn passkeys support

New modules: lib/auth, lib/auth_users, lib/db, lib/db_sqlite, lib/password,
lib/webauthn, daemon/handlers/auth, scripts/bootstrap_auth, tests/test_auth

Frontend: webui/api/auth, hoover/components/auth, pages/login, passkeys, users

Updates: daemon/iface and server, lib/common and nginx, pyproject.toml deps,
install script, server.py, app.js, and websocket/api clients
This commit is contained in:
2026-07-24 01:21:39 +00:00
parent 04417cf05c
commit 56b200d233
28 changed files with 4900 additions and 82 deletions
+19
View File
@@ -13,6 +13,8 @@ from copy import deepcopy
from pathlib import Path
from typing import Any
from passlib.hash import sha256_crypt
_APPLY_HASH_KEY = "_last_applied_hash"
@@ -171,6 +173,22 @@ def ensure_dirs(*dirs: Path) -> None:
d.mkdir(parents=True, exist_ok=True)
def _hash_password(password: str) -> str:
"""Hash *password* using SHA-256 crypt (``$5$`` format) via passlib.
Used for nginx htpasswd files. NOT used for auth user passwords —
those use Argon2id via ``lib.password``.
Args:
password: Plain-text password to hash.
Returns:
The hashed password string suitable for ``.htpasswd``
(e.g. ``$5$rounds=…$…``).
"""
return sha256_crypt.hash(password)
def get_interface_ip(iface: str) -> str | None:
"""Return the primary IPv4 address of *iface* (without CIDR), or ``None``.
@@ -192,6 +210,7 @@ def get_interface_ip(iface: str) -> str | None:
__all__ = [
"_APPLY_HASH_KEY",
"_hash_password",
"config_hash",
"deep_merge",
"ensure_dirs",