from __future__ import annotations from fastapi import APIRouter, Request, Response from fastapi.responses import HTMLResponse, RedirectResponse from starlette.concurrency import run_in_threadpool from ..dependencies import ServiceDep, SettingsDep, StoreDep from ..event_statuses import DASHBOARD_PASSWORD_CHANGED, DASHBOARD_PASSWORD_ENABLED, DASHBOARD_PASSWORD_REMOVED from ..security import client_ip, dashboard_csrf_token, require_dashboard_csrf from ..web.forms import first_form_value, request_form from ..web.templates_management import _render_management_unlock_page from .management_shared import ( delete_unlock_cookie, get_active_management_account_async, has_management_access, management_error_response_async, render_management_page_async, set_unlock_cookie, unlock_cookie_seconds, ) from .path_params import ManagementSlugPath router = APIRouter() @router.post("/m/{management_slug}/unlock", include_in_schema=False) async def unlock_dashboard( management_slug: ManagementSlugPath, request: Request, settings: SettingsDep, store: StoreDep ) -> Response: account = await get_active_management_account_async(store, management_slug) if not account.get("dashboard_password_hash"): return RedirectResponse(f"/m/{management_slug}", status_code=303) form = await request_form(request, settings) require_dashboard_csrf(management_slug, account, first_form_value(form, "csrf")) password = first_form_value(form, "dashboard_password") hostname = str(account["hostname"]) is_valid = await run_in_threadpool(store.verify_dashboard_password, hostname, password) if not is_valid: return HTMLResponse( _render_management_unlock_page( management_slug, account, dashboard_csrf_token(management_slug, account), "Incorrect password." ), status_code=401, ) response = RedirectResponse(f"/m/{management_slug}", status_code=303) set_unlock_cookie( response, settings, management_slug, account, unlock_cookie_seconds(settings, first_form_value(form, "stay_unlocked")), ) return response @router.post("/m/{management_slug}/password", include_in_schema=False) async def set_dashboard_password( management_slug: ManagementSlugPath, request: Request, settings: SettingsDep, store: StoreDep, service: ServiceDep, ) -> Response: account = await get_active_management_account_async(store, management_slug) if not has_management_access(request, settings, management_slug, account): return HTMLResponse( _render_management_unlock_page(management_slug, account, dashboard_csrf_token(management_slug, account)), status_code=401, ) form = await request_form(request, settings) require_dashboard_csrf(management_slug, account, first_form_value(form, "csrf")) hostname = str(account["hostname"]) if account.get("dashboard_password_hash"): is_current = await run_in_threadpool( store.verify_dashboard_password, hostname, first_form_value(form, "current_dashboard_password"), ) if not is_current: return await management_error_response_async( service, settings, management_slug, account, "Current password is incorrect.", client_ip(request, settings), ) password = first_form_value(form, "dashboard_password") confirm = first_form_value(form, "dashboard_password_confirm") if len(password) < 8: page = await render_management_page_async( service, settings, management_slug, account, dashboard_csrf_token(management_slug, account), message="Password must be at least 8 characters.", request_ip=client_ip(request, settings), ) return HTMLResponse(page, status_code=400) if password != confirm: page = await render_management_page_async( service, settings, management_slug, account, dashboard_csrf_token(management_slug, account), message="The passwords do not match.", request_ip=client_ip(request, settings), ) return HTMLResponse(page, status_code=400) had_password = bool(account.get("dashboard_password_hash")) await run_in_threadpool(store.set_dashboard_password, hostname, password) account = await get_active_management_account_async(store, management_slug) await run_in_threadpool( store.log_update_event, hostname, None, None, DASHBOARD_PASSWORD_CHANGED if had_password else DASHBOARD_PASSWORD_ENABLED, "dashboard password changed" if had_password else "dashboard password enabled", client_ip(request, settings), ) response = RedirectResponse(f"/m/{management_slug}", status_code=303) set_unlock_cookie(response, settings, management_slug, account, settings.dashboard_unlock_seconds) return response @router.post("/m/{management_slug}/password/remove", include_in_schema=False) async def remove_dashboard_password( management_slug: ManagementSlugPath, request: Request, settings: SettingsDep, store: StoreDep, service: ServiceDep, ) -> Response: account = await get_active_management_account_async(store, management_slug) if not has_management_access(request, settings, management_slug, account): return HTMLResponse( _render_management_unlock_page(management_slug, account, dashboard_csrf_token(management_slug, account)), status_code=401, ) form = await request_form(request, settings) require_dashboard_csrf(management_slug, account, first_form_value(form, "csrf")) hostname = str(account["hostname"]) if account.get("dashboard_password_hash"): is_current = await run_in_threadpool( store.verify_dashboard_password, hostname, first_form_value(form, "current_dashboard_password"), ) if not is_current: return await management_error_response_async( service, settings, management_slug, account, "Current password is incorrect.", client_ip(request, settings), ) await run_in_threadpool(store.clear_dashboard_password, hostname) await run_in_threadpool( store.log_update_event, hostname, None, None, DASHBOARD_PASSWORD_REMOVED, "dashboard password removed", client_ip(request, settings), ) response = RedirectResponse(f"/m/{management_slug}", status_code=303) delete_unlock_cookie(response, management_slug) return response