- Renamed "AES67" mode to "Network" for clearer user understanding - Added structured stream controls with consistent start/stop buttons and status display - Changed presentation delay input from microseconds to milliseconds for better usability - Restricted retransmission (RTN) options to valid range of 1-4 - Added help tooltips for assisted listening and immediate rendering options - Fixed portaudio configuration to enable ALSA support and remove Co-authored-by: pstruebi <struebin.patrick.com> Reviewed-on: https://gitea.pstruebi.xyz/auracaster/bumble-auracast/pulls/11
95 lines
2.2 KiB
Python
95 lines
2.2 KiB
Python
import os
|
|
import json
|
|
import base64
|
|
import hashlib
|
|
import hmac
|
|
from pathlib import Path
|
|
from typing import Optional, Tuple, Dict
|
|
|
|
__all__ = [
|
|
"is_pw_disabled",
|
|
"state_dir",
|
|
"pw_file_path",
|
|
"ensure_state_dir",
|
|
"hash_password",
|
|
"save_pw_record",
|
|
"load_pw_record",
|
|
"verify_password",
|
|
]
|
|
|
|
|
|
# Environment-controlled bypass
|
|
|
|
def is_pw_disabled() -> bool:
|
|
val = os.getenv("DISABLE_FRONTEND_PW", "")
|
|
return str(val).strip().lower() in ("1", "true", "yes", "on")
|
|
|
|
|
|
# Storage paths and permissions (store next to multicast_frontend.py)
|
|
|
|
def state_dir() -> Path:
|
|
# utils/ -> auracast/ -> server/
|
|
return Path(__file__).resolve().parents[1] / "server"
|
|
|
|
|
|
def pw_file_path() -> Path:
|
|
return state_dir() / "credentials.json"
|
|
|
|
|
|
def ensure_state_dir() -> None:
|
|
d = state_dir()
|
|
d.mkdir(parents=True, exist_ok=True)
|
|
try:
|
|
os.chmod(d, 0o700)
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
# Hashing and verification
|
|
|
|
def hash_password(password: str, salt: Optional[bytes] = None) -> Tuple[bytes, bytes]:
|
|
if salt is None:
|
|
salt = os.urandom(16)
|
|
key = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, 150_000, dklen=32)
|
|
return salt, key
|
|
|
|
|
|
def save_pw_record(salt: bytes, key: bytes) -> None:
|
|
ensure_state_dir()
|
|
rec = {
|
|
"salt": base64.b64encode(salt).decode("ascii"),
|
|
"key": base64.b64encode(key).decode("ascii"),
|
|
"kdf": "pbkdf2_sha256",
|
|
"iterations": 150000,
|
|
}
|
|
p = pw_file_path()
|
|
p.write_text(json.dumps(rec))
|
|
try:
|
|
os.chmod(p, 0o600)
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def load_pw_record() -> Optional[Dict]:
|
|
p = pw_file_path()
|
|
if not p.exists():
|
|
return None
|
|
try:
|
|
rec = json.loads(p.read_text())
|
|
if "salt" in rec and "key" in rec:
|
|
return rec
|
|
except Exception:
|
|
return None
|
|
return None
|
|
|
|
|
|
def verify_password(password: str, rec: Dict) -> bool:
|
|
try:
|
|
salt = base64.b64decode(rec["salt"])
|
|
expected = base64.b64decode(rec["key"])
|
|
iters = int(rec.get("iterations", 150000))
|
|
key = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, iters, dklen=32)
|
|
return hmac.compare_digest(key, expected)
|
|
except Exception:
|
|
return False
|