from __future__ import annotations import secrets from datetime import date, datetime, timezone from fastapi import APIRouter, Form, Request from fastapi.responses import JSONResponse, RedirectResponse from sqlalchemy import select from app.core.db.common import CommonSessionLocal from app.core.security.csrf import get_or_create_csrf_token, validate_csrf from app.core.security.passwords import hash_password from app.core.security.session_auth import get_current_user from app.core.templating import templates from app.modules.core.audit.service import model_snapshot, pair_before_after, write_audit_log from app.modules.core.iam.invite_service import issue_invite_token from app.modules.email_integration.services import send_user_invite_email from app.core.settings import get_settings from app.modules.core.iam.lifecycle import ( LifecycleError, activate_user, deactivate_user, disable_login, enable_login, ensure_manageable_lifecycle, lock_user, restore_user, soft_delete_user, unlock_user, ) from app.modules.core.iam.models import User from app.modules.core.iam.scope import ( build_scope, ensure_assignable_roles, ensure_manageable_existing_user, ensure_users_manage_scope, ensure_users_view_scope, get_user_role_ids, get_user_role_names, list_scoped_users, list_visible_branches, list_visible_tenants, resolve_target_tenant_branch, ) from app.modules.core.iam.services import build_user_form_payload, build_user_listing_payload from app.modules.core.rbac.deps import get_user_permissions, get_user_roles from app.modules.core.rbac.models import UserRole from app.modules.core.rbac.permission_guard import require_permission from app.modules.core.tenancy.models import Branch router = APIRouter(prefix="/system-settings/users", tags=["users-ui"]) def _public_invite_url(invite_token: str) -> str: base = (get_settings().ERP_PUBLIC_BASE_URL or "").strip().rstrip("/") or "http://localhost:8000" return f"{base}/invite/accept?token={invite_token}" def _redirect_login(): return RedirectResponse(url="/login", status_code=303) def _redirect_denied(): from app.core.http_responses import ui_access_denied return ui_access_denied() def _flash_redirect(url: str) -> RedirectResponse: return RedirectResponse(url=url, status_code=303) def _common_context(request: Request, db, current_user: User, extra: dict | None = None): ctx = { "request": request, "current_user": current_user, "current_user_roles": get_user_roles(db, current_user.id), "current_user_permissions": get_user_permissions(db, current_user.id), "csrf_token": get_or_create_csrf_token(request), } if extra: ctx.update(extra) return ctx def _user_admin_roles(db, current_user: User) -> set[str]: return set(get_user_roles(db, current_user.id)) def _ensure_user_admin(db, current_user: User): roles = _user_admin_roles(db, current_user) if "System Admin" in roles or "Firm Admin" in roles: return raise PermissionError("Only System Admin and Firm Admin can create or manage users.") INTERNAL_EMPLOYEE_ROLE_NAMES = {"Firm Admin", "Partner", "Manager", "Branch Manager", "Staff"} EMPLOYEE_EXCLUDED_ROLE_NAMES = {"System Admin", "Client", "Consultant"} def _role_name_set(roles) -> set[str]: return {getattr(role, "name", "") for role in roles if getattr(role, "name", "")} def _requires_employee_profile(role_names: set[str]) -> bool: """Internal firm users must have an employee profile. Platform-only users, clients and consultants are intentionally excluded. If an account has at least one internal employee role, we create/link the employee profile, unless it is also a System Admin account. """ if "System Admin" in role_names: return False return bool(role_names.intersection(INTERNAL_EMPLOYEE_ROLE_NAMES)) def _parse_optional_date(value: str | None) -> date | None: value = (value or "").strip() if not value: return None return date.fromisoformat(value) def _next_employee_code(db, tenant_id: int) -> str: from app.modules.employees.models import Employee prefix = "EMP" rows = db.execute( select(Employee.employee_code) .where(Employee.tenant_id == tenant_id, Employee.employee_code.ilike(f"{prefix}%")) .order_by(Employee.employee_code.desc()) ).all() max_no = 0 for (code,) in rows: suffix = "".join(ch for ch in str(code or "") if ch.isdigit()) if suffix: max_no = max(max_no, int(suffix)) return f"{prefix}{max_no + 1:05d}" def _ensure_employee_profile_for_internal_user( db, *, actor: User, user_obj: User, role_names: set[str], employee_code: str | None = None, mobile: str | None = None, department: str | None = None, designation: str | None = None, date_of_joining: str | None = None, employment_type: str | None = None, ) -> dict: """Create/link an Employee profile for internal firm users. This function is intentionally additive. It does not create employee profiles for System Admin, Client or Consultant-only users, and it does not overwrite an existing linked employee profile. """ if not _requires_employee_profile(role_names): return {"required": False, "created": False, "linked_existing": False, "employee_id": None} if not user_obj.tenant_id or not user_obj.branch_id: raise ValueError("Tenant and Branch are mandatory for internal employee users.") from app.modules.employees.models import Employee existing_linked = db.execute( select(Employee).where(Employee.tenant_id == user_obj.tenant_id, Employee.user_id == user_obj.id) ).scalar_one_or_none() if existing_linked: return {"required": True, "created": False, "linked_existing": True, "employee_id": existing_linked.id} email = (user_obj.email or "").strip().lower() existing_unlinked = None if email: existing_unlinked = db.execute( select(Employee).where( Employee.tenant_id == user_obj.tenant_id, Employee.user_id.is_(None), Employee.email == email, ) ).scalar_one_or_none() if existing_unlinked: existing_unlinked.user_id = user_obj.id existing_unlinked.branch_id = user_obj.branch_id existing_unlinked.full_name = user_obj.full_name or existing_unlinked.full_name existing_unlinked.updated_by_user_id = actor.id existing_unlinked.updated_at_utc = datetime.now(timezone.utc) return {"required": True, "created": False, "linked_existing": True, "employee_id": existing_unlinked.id} code = (employee_code or "").strip() or _next_employee_code(db, int(user_obj.tenant_id)) code_exists = db.execute( select(Employee).where(Employee.tenant_id == user_obj.tenant_id, Employee.employee_code == code) ).scalar_one_or_none() if code_exists: raise ValueError(f"Employee code '{code}' already exists in this tenant.") emp_type = (employment_type or "full_time").strip() or "full_time" if emp_type not in {"full_time", "part_time", "article_assistant", "intern", "consultant", "contract"}: emp_type = "full_time" employee = Employee( tenant_id=int(user_obj.tenant_id), branch_id=int(user_obj.branch_id), user_id=user_obj.id, employee_code=code, full_name=(user_obj.full_name or email or code).strip(), email=email or None, mobile=(mobile or "").strip() or None, date_of_joining=_parse_optional_date(date_of_joining), employment_type=emp_type, status="active", is_active=True, department=(department or "").strip() or None, designation=(designation or "").strip() or None, created_by_user_id=actor.id, updated_by_user_id=actor.id, ) db.add(employee) db.flush() return {"required": True, "created": True, "linked_existing": False, "employee_id": employee.id} def _form_context( db, request: Request, current_user: User, scope, *, user_obj=None, assigned_role_ids=None, flash=None, title="Create User", form_mode="create", ): payload = build_user_form_payload(db, scope, current_user, user_obj=user_obj, assigned_role_ids=assigned_role_ids) payload.update({"title": title, "flash": flash, "form_mode": form_mode}) return _common_context(request, db, current_user, payload) @router.get("") def users_list(request: Request, q: str = "", page: int = 1, per_page: int = 10): db = CommonSessionLocal() try: current_user = get_current_user(request, db=db) if not current_user: return _redirect_login() try: require_permission(db, current_user, "users.view") except Exception: return _redirect_denied() scope = build_scope(db, current_user) try: ensure_users_view_scope(scope) except Exception: return _flash_redirect("/system-settings") payload = build_user_listing_payload(db, scope, q=q, page=page, per_page=per_page) payload.update({"title": "Users", "scope": scope}) return templates.TemplateResponse( "modules/core/iam/templates/users_list.html", _common_context(request, db, current_user, payload), ) finally: db.close() @router.get("/branch-options") def branch_options(request: Request, tenant_id: int): db = CommonSessionLocal() try: current_user = get_current_user(request, db=db) if not current_user: return JSONResponse([], status_code=401) try: require_permission(db, current_user, "users.manage") _ensure_user_admin(db, current_user) except Exception: return JSONResponse([], status_code=403) scope = build_scope(db, current_user) try: ensure_users_manage_scope(scope) except Exception: return JSONResponse([], status_code=403) if scope.is_system_admin: branch_rows = db.execute( select(Branch) .where(Branch.tenant_id == tenant_id) .order_by(Branch.name) ).scalars().all() else: if tenant_id != current_user.tenant_id: return JSONResponse([], status_code=403) branch_rows = list_visible_branches(db, scope, tenant_id) return JSONResponse( [ {"id": b.id, "name": b.name, "code": b.code} for b in branch_rows ] ) finally: db.close() @router.get("/new") def user_create(request: Request): db = CommonSessionLocal() try: current_user = get_current_user(request, db=db) if not current_user: return _redirect_login() try: require_permission(db, current_user, "users.manage") _ensure_user_admin(db, current_user) except Exception: return _redirect_denied() scope = build_scope(db, current_user) try: ensure_users_manage_scope(scope) except Exception: return _flash_redirect("/system-settings/users") return templates.TemplateResponse( "modules/core/iam/templates/user_form.html", _form_context(db, request, current_user, scope, title="Create User", form_mode="create"), ) finally: db.close() @router.post("/new") def user_create_submit( request: Request, email: str = Form(...), full_name: str = Form(...), password: str = Form(...), tenant_id: int | None = Form(None), branch_id: int | None = Form(None), role_ids: list[int] = Form([]), is_active: str | None = Form(None), allow_login: str | None = Form(None), invite_user: str | None = Form(None), employee_code: str = Form(""), employee_mobile: str = Form(""), employee_department: str = Form(""), employee_designation: str = Form(""), employee_date_of_joining: str = Form(""), employee_employment_type: str = Form("full_time"), csrf_token: str = Form(...), ): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: current_user = get_current_user(request, db=db) if not current_user: return _redirect_login() try: require_permission(db, current_user, "users.manage") _ensure_user_admin(db, current_user) except Exception: return _redirect_denied() scope = build_scope(db, current_user) try: ensure_users_manage_scope(scope) resolved_tenant_id, resolved_branch_id = resolve_target_tenant_branch(db, scope, tenant_id, branch_id) roles = ensure_assignable_roles(db, scope, role_ids) except Exception as exc: return templates.TemplateResponse( "modules/core/iam/templates/user_form.html", _form_context( db, request, current_user, scope, assigned_role_ids=role_ids, flash=str(exc), title="Create User", form_mode="create", ), status_code=403, ) email = email.lower().strip() if db.execute(select(User).where(User.email == email)).scalar_one_or_none(): return templates.TemplateResponse( "modules/core/iam/templates/user_form.html", _form_context( db, request, current_user, scope, assigned_role_ids=role_ids, flash="Email already exists.", title="Create User", form_mode="create", ), status_code=400, ) temp_password = password or secrets.token_urlsafe(12) user = User( email=email, full_name=full_name.strip(), password_hash=hash_password(temp_password), tenant_id=resolved_tenant_id, branch_id=resolved_branch_id, is_active=is_active is not None, allow_login=allow_login is not None, is_locked=False, deleted_at=None, must_change_password=invite_user is not None, password_changed_at_utc=None, ) db.add(user) db.flush() for role in roles: db.add(UserRole(user_id=user.id, role_id=role.id)) role_names = _role_name_set(roles) try: employee_link_result = _ensure_employee_profile_for_internal_user( db, actor=current_user, user_obj=user, role_names=role_names, employee_code=employee_code, mobile=employee_mobile, department=employee_department, designation=employee_designation, date_of_joining=employee_date_of_joining, employment_type=employee_employment_type, ) except Exception as exc: db.rollback() return templates.TemplateResponse( "modules/core/iam/templates/user_form.html", _form_context( db, request, current_user, scope, assigned_role_ids=role_ids, flash=f"User was not created: {exc}", title="Create User", form_mode="create", ), status_code=400, ) db.commit() db.refresh(user) invite_url = None if invite_user is not None: invite_token = issue_invite_token(db, user) invite_url = _public_invite_url(invite_token) write_audit_log( db, action="user.create", entity_type="user", actor=current_user, request=request, entity_id=user.id, entity_name=user.email, target_tenant_id=user.tenant_id, target_branch_id=user.branch_id, details={ "after": model_snapshot( user, ["email", "full_name", "tenant_id", "branch_id", "is_active", "allow_login", "is_locked", "deleted_at"], ), "role_ids": [role.id for role in roles], "employee_profile": employee_link_result, }, ) if invite_url: try: send_user_invite_email(db, user=user, invite_token=invite_token) db.commit() except Exception as exc: print(f"[EMAIL INVITE ERROR] user={user.email} error={exc}") return templates.TemplateResponse( "modules/core/iam/templates/invite_link.html", _common_context( request, db, current_user, {"title": "Invite Link", "invite_url": invite_url, "invited_user": user}, ), ) return RedirectResponse(url="/system-settings/users", status_code=303) finally: db.close() @router.post("/{user_id}/invite") def user_invite(request: Request, user_id: int, csrf_token: str = Form(...)): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: current_user = get_current_user(request, db=db) if not current_user: return _redirect_login() try: require_permission(db, current_user, "users.manage") require_permission(db, current_user, "users.invite") _ensure_user_admin(db, current_user) except Exception: return _redirect_denied() scope = build_scope(db, current_user) user_obj = db.execute(select(User).where(User.id == user_id)).scalar_one_or_none() if not user_obj: return RedirectResponse(url="/system-settings/users", status_code=303) ensure_manageable_existing_user(db, scope, user_obj) invite_token = issue_invite_token(db, user_obj) invite_url = _public_invite_url(invite_token) try: send_user_invite_email(db, user=user_obj, invite_token=invite_token) db.commit() except Exception as exc: print(f"[EMAIL INVITE ERROR] user={user_obj.email} error={exc}") write_audit_log( db, action="user.invited", entity_type="user", actor=current_user, request=request, entity_id=user_obj.id, entity_name=user_obj.email, target_tenant_id=user_obj.tenant_id, target_branch_id=user_obj.branch_id, details={"invite_url": invite_url}, ) return templates.TemplateResponse( "modules/core/iam/templates/invite_link.html", _common_context( request, db, current_user, {"title": "Invite Link", "invite_url": invite_url, "invited_user": user_obj}, ), ) finally: db.close() @router.get("/{user_id}/edit") def user_edit(request: Request, user_id: int): db = CommonSessionLocal() try: current_user = get_current_user(request, db=db) if not current_user: return _redirect_login() try: require_permission(db, current_user, "users.manage") _ensure_user_admin(db, current_user) except Exception: return _redirect_denied() scope = build_scope(db, current_user) user_obj = db.execute(select(User).where(User.id == user_id)).scalar_one_or_none() if not user_obj: return RedirectResponse(url="/system-settings/users", status_code=303) try: ensure_manageable_existing_user(db, scope, user_obj) ensure_users_manage_scope(scope) except Exception: return RedirectResponse(url="/system-settings/users", status_code=303) assigned_role_ids = list(get_user_role_ids(db, user_id)) return templates.TemplateResponse( "modules/core/iam/templates/user_form.html", _form_context( db, request, current_user, scope, title=f"Edit User - {user_obj.email}", form_mode="edit", user_obj=user_obj, assigned_role_ids=assigned_role_ids, ), ) finally: db.close() @router.post("/{user_id}/edit") def user_edit_submit( request: Request, user_id: int, full_name: str = Form(...), password: str = Form(""), tenant_id: int | None = Form(None), branch_id: int | None = Form(None), role_ids: list[int] = Form([]), is_active: str | None = Form(None), allow_login: str | None = Form(None), invite_user: str | None = Form(None), employee_code: str = Form(""), employee_mobile: str = Form(""), employee_department: str = Form(""), employee_designation: str = Form(""), employee_date_of_joining: str = Form(""), employee_employment_type: str = Form("full_time"), csrf_token: str = Form(...), ): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: current_user = get_current_user(request, db=db) if not current_user: return _redirect_login() try: require_permission(db, current_user, "users.manage") _ensure_user_admin(db, current_user) except Exception: return _redirect_denied() scope = build_scope(db, current_user) user_obj = db.execute(select(User).where(User.id == user_id)).scalar_one_or_none() if not user_obj: return RedirectResponse(url="/system-settings/users", status_code=303) try: ensure_users_manage_scope(scope) ensure_manageable_existing_user(db, scope, user_obj) resolved_tenant_id, resolved_branch_id = resolve_target_tenant_branch(db, scope, tenant_id, branch_id) roles = ensure_assignable_roles(db, scope, role_ids) except Exception as exc: return templates.TemplateResponse( "modules/core/iam/templates/user_form.html", _form_context( db, request, current_user, scope, title=f"Edit User - {user_obj.email}", form_mode="edit", user_obj=user_obj, assigned_role_ids=role_ids, flash=str(exc), ), status_code=403, ) before_snapshot = model_snapshot( user_obj, ["email", "full_name", "tenant_id", "branch_id", "is_active", "allow_login", "is_locked", "deleted_at"], ) old_role_ids = list(get_user_role_ids(db, user_obj.id)) user_obj.full_name = full_name.strip() user_obj.tenant_id = resolved_tenant_id user_obj.branch_id = resolved_branch_id user_obj.is_active = is_active is not None user_obj.allow_login = allow_login is not None if password.strip(): user_obj.password_hash = hash_password(password.strip()) db.execute(UserRole.__table__.delete().where(UserRole.user_id == user_obj.id)) for role in roles: db.add(UserRole(user_id=user_obj.id, role_id=role.id)) role_names = _role_name_set(roles) try: employee_link_result = _ensure_employee_profile_for_internal_user( db, actor=current_user, user_obj=user_obj, role_names=role_names, employee_code=employee_code, mobile=employee_mobile, department=employee_department, designation=employee_designation, date_of_joining=employee_date_of_joining, employment_type=employee_employment_type, ) except Exception as exc: db.rollback() return templates.TemplateResponse( "modules/core/iam/templates/user_form.html", _form_context( db, request, current_user, scope, title=f"Edit User - {user_obj.email}", form_mode="edit", user_obj=user_obj, assigned_role_ids=role_ids, flash=f"User was not updated: {exc}", ), status_code=400, ) db.commit() if invite_user is not None: invite_token = issue_invite_token(db, user_obj) invite_url = _public_invite_url(invite_token) try: send_user_invite_email(db, user=user_obj, invite_token=invite_token) db.commit() except Exception as exc: print(f"[EMAIL INVITE ERROR] user={user_obj.email} error={exc}") write_audit_log( db, action="user.invited", entity_type="user", actor=current_user, request=request, entity_id=user_obj.id, entity_name=user_obj.email, target_tenant_id=user_obj.tenant_id, target_branch_id=user_obj.branch_id, details={"invite_url": invite_url}, ) write_audit_log( db, action="user.update", entity_type="user", actor=current_user, request=request, entity_id=user_obj.id, entity_name=user_obj.email, target_tenant_id=user_obj.tenant_id, target_branch_id=user_obj.branch_id, details={ **pair_before_after( before_snapshot, model_snapshot( user_obj, ["email", "full_name", "tenant_id", "branch_id", "is_active", "allow_login", "is_locked", "deleted_at"], ), ), "old_role_ids": old_role_ids, "new_role_ids": [role.id for role in roles], "employee_profile": employee_link_result, }, ) return RedirectResponse(url="/system-settings/users", status_code=303) finally: db.close() def _lifecycle_template_response(request: Request, db, current_user: User, scope, flash: str, status_code: int = 400): users = list_scoped_users(db, scope) roles = {user_id: get_user_role_names(db, user_id) for user_id in [u.id for u in users]} tenants = {t.id: t for t in list_visible_tenants(db, scope)} visible_branch_tenant_id = current_user.tenant_id if not scope.is_system_admin else None branch_rows = ( db.execute(select(Branch).order_by(Branch.name)).scalars().all() if scope.is_system_admin else list_visible_branches(db, scope, visible_branch_tenant_id) ) branches = {b.id: b for b in branch_rows} return templates.TemplateResponse( "modules/core/iam/templates/users_list.html", _common_context( request, db, current_user, { "title": "Users", "users": users, "user_roles": roles, "tenants": tenants, "branches": branches, "scope": scope, "flash": flash, }, ), status_code=status_code, ) def _run_lifecycle_action(db, request: Request, current_user: User, user_id: int, action: str): scope = build_scope(db, current_user) user_obj = db.execute(select(User).where(User.id == user_id)).scalar_one_or_none() if not user_obj: return RedirectResponse(url="/system-settings/users", status_code=303) try: _ensure_user_admin(db, current_user) ensure_users_manage_scope(scope) ensure_manageable_lifecycle(scope, db, current_user, user_obj, action.replace("-", " ")) except (Exception, LifecycleError) as exc: return _lifecycle_template_response(request, db, current_user, scope, str(exc), 403) before_snapshot = model_snapshot( user_obj, ["email", "full_name", "tenant_id", "branch_id", "is_active", "allow_login", "is_locked", "deleted_at"], ) if action == "activate": activate_user(user_obj) audit_action = "user.activate" elif action == "deactivate": deactivate_user(user_obj) audit_action = "user.deactivate" elif action == "enable-login": enable_login(user_obj) audit_action = "user.enable_login" elif action == "disable-login": disable_login(user_obj) audit_action = "user.disable_login" elif action == "lock": lock_user(user_obj) audit_action = "user.lock" elif action == "unlock": unlock_user(user_obj) audit_action = "user.unlock" elif action == "delete": soft_delete_user(user_obj) audit_action = "user.soft_delete" elif action == "restore": restore_user(user_obj) audit_action = "user.restore" else: return _lifecycle_template_response(request, db, current_user, scope, "Unknown action", 400) db.commit() write_audit_log( db, action=audit_action, entity_type="user", actor=current_user, request=request, entity_id=user_obj.id, entity_name=user_obj.email, target_tenant_id=user_obj.tenant_id, target_branch_id=user_obj.branch_id, details=pair_before_after( before_snapshot, model_snapshot( user_obj, ["email", "full_name", "tenant_id", "branch_id", "is_active", "allow_login", "is_locked", "deleted_at"], ), ), ) return RedirectResponse(url="/system-settings/users", status_code=303) @router.post("/{user_id}/activate") def user_activate_submit(request: Request, user_id: int, csrf_token: str = Form(...)): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: current_user = get_current_user(request, db=db) if not current_user: return _redirect_login() try: require_permission(db, current_user, "users.manage") except Exception: return _redirect_denied() return _run_lifecycle_action(db, request, current_user, user_id, "activate") finally: db.close() @router.post("/{user_id}/deactivate") def user_deactivate_submit(request: Request, user_id: int, csrf_token: str = Form(...)): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: current_user = get_current_user(request, db=db) if not current_user: return _redirect_login() try: require_permission(db, current_user, "users.manage") except Exception: return _redirect_denied() return _run_lifecycle_action(db, request, current_user, user_id, "deactivate") finally: db.close() @router.post("/{user_id}/enable-login") def user_enable_login_submit(request: Request, user_id: int, csrf_token: str = Form(...)): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: current_user = get_current_user(request, db=db) if not current_user: return _redirect_login() try: require_permission(db, current_user, "users.manage") except Exception: return _redirect_denied() return _run_lifecycle_action(db, request, current_user, user_id, "enable-login") finally: db.close() @router.post("/{user_id}/disable-login") def user_disable_login_submit(request: Request, user_id: int, csrf_token: str = Form(...)): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: current_user = get_current_user(request, db=db) if not current_user: return _redirect_login() try: require_permission(db, current_user, "users.manage") except Exception: return _redirect_denied() return _run_lifecycle_action(db, request, current_user, user_id, "disable-login") finally: db.close() @router.post("/{user_id}/lock") def user_lock_submit(request: Request, user_id: int, csrf_token: str = Form(...)): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: current_user = get_current_user(request, db=db) if not current_user: return _redirect_login() try: require_permission(db, current_user, "users.manage") except Exception: return _redirect_denied() return _run_lifecycle_action(db, request, current_user, user_id, "lock") finally: db.close() @router.post("/{user_id}/unlock") def user_unlock_submit(request: Request, user_id: int, csrf_token: str = Form(...)): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: current_user = get_current_user(request, db=db) if not current_user: return _redirect_login() try: require_permission(db, current_user, "users.manage") except Exception: return _redirect_denied() return _run_lifecycle_action(db, request, current_user, user_id, "unlock") finally: db.close() @router.post("/{user_id}/delete") def user_delete_submit(request: Request, user_id: int, csrf_token: str = Form(...)): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: current_user = get_current_user(request, db=db) if not current_user: return _redirect_login() try: require_permission(db, current_user, "users.manage") except Exception: return _redirect_denied() return _run_lifecycle_action(db, request, current_user, user_id, "delete") finally: db.close() @router.post("/{user_id}/restore") def user_restore_submit(request: Request, user_id: int, csrf_token: str = Form(...)): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: current_user = get_current_user(request, db=db) if not current_user: return _redirect_login() try: require_permission(db, current_user, "users.manage") except Exception: return _redirect_denied() return _run_lifecycle_action(db, request, current_user, user_id, "restore") finally: db.close()