Archived
Refactor UI assets and harden quality checks
This commit is contained in:
1 parent
23ab466c52
commit
4ecc8eb874
62 files changed
+3314
-3206
No files matched your search
@@ -1,5 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass
|
||||
|
||||
from fastapi import APIRouter, Request, Response
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from starlette.concurrency import run_in_threadpool
|
||||
@@ -7,9 +10,11 @@ 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 ..store import DdnsStore
|
||||
from ..web.forms import first_form_value, request_form
|
||||
from ..web.templates_management import _render_management_unlock_page
|
||||
from ..web.templates_management import ManagementPageContext, _render_management_unlock_page
|
||||
from .management_shared import (
|
||||
ManagementAccount,
|
||||
delete_unlock_cookie,
|
||||
get_active_management_account_async,
|
||||
has_management_access,
|
||||
@@ -23,6 +28,15 @@ from .path_params import ManagementSlugPath
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class _DashboardPasswordErrorContext:
|
||||
management_slug: str
|
||||
account: ManagementAccount
|
||||
form: dict[str, list[str]]
|
||||
request_ip: str
|
||||
current_password_error: str | None
|
||||
|
||||
|
||||
@router.post("/m/{management_slug}/unlock", include_in_schema=False)
|
||||
async def unlock_dashboard(
|
||||
management_slug: ManagementSlugPath, request: Request, settings: SettingsDep, store: StoreDep
|
||||
@@ -70,45 +84,21 @@ async def set_dashboard_password(
|
||||
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),
|
||||
)
|
||||
current_password_error = await _verify_current_dashboard_password(store, hostname, account, form)
|
||||
request_ip = client_ip(request, settings)
|
||||
error_response = await _dashboard_password_error_response(
|
||||
service,
|
||||
_DashboardPasswordErrorContext(
|
||||
management_slug=management_slug,
|
||||
account=account,
|
||||
form=form,
|
||||
request_ip=request_ip,
|
||||
current_password_error=current_password_error,
|
||||
),
|
||||
)
|
||||
if error_response:
|
||||
return error_response
|
||||
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)
|
||||
@@ -126,6 +116,61 @@ async def set_dashboard_password(
|
||||
return response
|
||||
|
||||
|
||||
def _dashboard_password_error(password: str, confirm: str) -> str | None:
|
||||
if len(password) < 8:
|
||||
return "Password must be at least 8 characters."
|
||||
if password != confirm:
|
||||
return "The passwords do not match."
|
||||
return None
|
||||
|
||||
|
||||
async def _dashboard_password_error_response(
|
||||
service: ServiceDep,
|
||||
context: _DashboardPasswordErrorContext,
|
||||
) -> HTMLResponse | None:
|
||||
if context.current_password_error:
|
||||
return await management_error_response_async(
|
||||
service,
|
||||
context.management_slug,
|
||||
context.account,
|
||||
context.current_password_error,
|
||||
context.request_ip,
|
||||
)
|
||||
password_error = _dashboard_password_error(
|
||||
first_form_value(context.form, "dashboard_password"),
|
||||
first_form_value(context.form, "dashboard_password_confirm"),
|
||||
)
|
||||
if password_error is None:
|
||||
return None
|
||||
page = await render_management_page_async(
|
||||
service,
|
||||
ManagementPageContext(
|
||||
management_slug=context.management_slug,
|
||||
account=context.account,
|
||||
csrf_token=dashboard_csrf_token(context.management_slug, context.account),
|
||||
message=password_error,
|
||||
request_ip=context.request_ip,
|
||||
),
|
||||
)
|
||||
return HTMLResponse(page, status_code=400)
|
||||
|
||||
|
||||
async def _verify_current_dashboard_password(
|
||||
store: DdnsStore,
|
||||
hostname: str,
|
||||
account: Mapping[str, object],
|
||||
form: dict[str, list[str]],
|
||||
) -> str | None:
|
||||
if not account.get("dashboard_password_hash"):
|
||||
return None
|
||||
is_current = await run_in_threadpool(
|
||||
store.verify_dashboard_password,
|
||||
hostname,
|
||||
first_form_value(form, "current_dashboard_password"),
|
||||
)
|
||||
return None if is_current else "Current password is incorrect."
|
||||
|
||||
|
||||
@router.post("/m/{management_slug}/password/remove", include_in_schema=False)
|
||||
async def remove_dashboard_password(
|
||||
management_slug: ManagementSlugPath,
|
||||
@@ -152,7 +197,6 @@ async def remove_dashboard_password(
|
||||
if not is_current:
|
||||
return await management_error_response_async(
|
||||
service,
|
||||
settings,
|
||||
management_slug,
|
||||
account,
|
||||
"Current password is incorrect.",
|
||||
|
||||
Reference in new issue
Block a user