admin / Syanpse-Vanguard
public
Syanpse-Vanguard / synapse-vanguard-v3 / sv / auth / users.py
3212 B · main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | """Local user store + password hashing. Federation (OIDC/SAML) will map external identities onto these same rows/roles in a later milestone.""" from __future__ import annotations import uuid from datetime import datetime, timezone import bcrypt from sv.auth.rbac import Role from sv.config import DEFAULT_TENANT_ID, settings from sv.storage.base import Query, StorageEngine def hash_password(plaintext: str) -> str: return bcrypt.hashpw(plaintext.encode(), bcrypt.gensalt()).decode() def verify_password(plaintext: str, hashed: str) -> bool: try: return bcrypt.checkpw(plaintext.encode(), hashed.encode()) except ValueError: return False async def get_user_by_username(engine: StorageEngine, username: str) -> dict | None: return await engine.fetchone(Query( "SELECT id, tenant_id, username, password_hash, role, disabled " "FROM users WHERE username = ?", [username], )) async def create_user(engine: StorageEngine, username: str, password: str, role: Role, tenant_id=DEFAULT_TENANT_ID) -> str: uid = str(uuid.uuid4()) await engine.run(Query( "INSERT INTO users (id, tenant_id, username, password_hash, role, " "disabled, created_ts) VALUES (?, ?, ?, ?, ?, FALSE, ?)", [uid, str(tenant_id), username, hash_password(password), role.value, datetime.now(timezone.utc).replace(tzinfo=None)], )) return uid async def list_users(engine: StorageEngine, tenant_id=DEFAULT_TENANT_ID) -> list[dict]: return await engine.fetchall(Query( "SELECT id, username, role, disabled, created_ts FROM users " "WHERE tenant_id = ? ORDER BY username", [str(tenant_id)], )) async def get_user_by_id(engine: StorageEngine, uid: str) -> dict | None: return await engine.fetchone(Query( "SELECT id, username, role, disabled FROM users WHERE id = ?", [uid], )) async def set_disabled(engine: StorageEngine, uid: str, disabled: bool) -> None: await engine.run(Query( "UPDATE users SET disabled = ? WHERE id = ?", [disabled, uid], )) async def set_role(engine: StorageEngine, uid: str, role: Role) -> None: await engine.run(Query( "UPDATE users SET role = ? WHERE id = ?", [role.value, uid], )) async def set_password(engine: StorageEngine, uid: str, password: str) -> None: await engine.run(Query( "UPDATE users SET password_hash = ? WHERE id = ?", [hash_password(password), uid], )) async def count_admins(engine: StorageEngine, tenant_id=DEFAULT_TENANT_ID) -> int: row = await engine.fetchone(Query( "SELECT COUNT(*) AS n FROM users WHERE tenant_id = ? AND role = ? " "AND disabled = FALSE", [str(tenant_id), Role.TENANT_ADMIN.value], )) return row["n"] if row else 0 async def seed_bootstrap_admin(engine: StorageEngine) -> None: """Create the initial TENANT_ADMIN from env if the users table is empty.""" row = await engine.fetchone(Query("SELECT COUNT(*) AS n FROM users")) if row and row["n"] > 0: return await create_user(engine, settings.bootstrap_admin_user, settings.bootstrap_admin_password, Role.TENANT_ADMIN) |