Harden auth and API: username change, 2FA fixes, input validation

- Add POST /api/auth/change-username (password + OTP when 2FA is on,
  format/reserved-name checks, uniqueness) and a Change Username modal
  in Account.vue; use the real username for the 2FA provisioning URI.
- Stop creating the built-in admin/password user; the initial admin is
  seeded only from OVPMON_INITIAL_ADMIN_USER/PASSWORD.
- Reject 2FA-pending temporary tokens on all protected routes (Flask
  token_required, Profiler verify_token); only /api/auth/verify-2fa
  accepts them.
- Stop logging the OTP and TOTP secret in enable_2fa.
- Profiler: validate profile username (pattern + realpath checks in
  schema, router, pki and generator) to prevent path traversal.
- Restrict CORS to the panel origin in Profiler and Flask APIs.
- UI: header username no longer sticks to the hardcoded Admin fallback;
  it is synced from /user/me and updated after a rename.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
iclaoudezinandClaude Sonnet 5.5 committed 2026-09-30 12:03:36 +00:00
1 parent 14ffd64801
commit 11c1b6379b
9 files changed
+241 -16

No files matched your search

+5
View File
@@ -61,6 +61,11 @@ def generate_server_config(db: Session, output_path: str = "server.conf"):
return config_content
def generate_client_config(db: Session, username: str, output_path: str):
from .pki import validate_username
validate_username(username)
base = os.path.realpath(os.path.dirname(output_path) or ".")
if os.path.realpath(output_path) != os.path.join(base, os.path.basename(output_path)) or os.path.basename(output_path) != f"{username}.ovpn":
raise ValueError("Invalid output path")
settings = get_system_settings(db)
pki = get_pki_settings(db)
+10
View File
@@ -7,6 +7,14 @@ from datetime import datetime
logger = logging.getLogger(__name__)
import re
USERNAME_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$")
def validate_username(username: str) -> str:
if not isinstance(username, str) or not USERNAME_RE.match(username) or ".." in username:
raise ValueError("Invalid username")
return username
EASY_RSA_DIR = os.path.join(os.getcwd(), "easy-rsa")
PKI_DIR = os.path.join(EASY_RSA_DIR, "pki")
INDEX_PATH = os.path.join(PKI_DIR, "index.txt")
@@ -170,11 +178,13 @@ def clear_pki(db: Session):
return "PKI directory did not exist, but User DB and Client profiles were wiped."
def build_client(username: str, db: Session):
validate_username(username)
env = _get_easyrsa_env(db)
_run_easyrsa(["build-client-full", username, "nopass"], env)
return True
def revoke_client(username: str, db: Session):
validate_username(username)
env = _get_easyrsa_env(db)
_run_easyrsa(["revoke", username], env)
_run_easyrsa(["gen-crl"], env)