This repository has been archived on 2026-10-28. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
karldns.de/router_dyndns/routers/management_passwords.py
T

147 lines
6.3 KiB
Python
Raw Normal View History

2026-05-20 23:55:27 +02:00
from __future__ import annotations
from fastapi import APIRouter, Request, Response
from fastapi.responses import HTMLResponse, RedirectResponse
from starlette.concurrency import run_in_threadpool
from ..ddns_security import client_ip, dashboard_csrf_token, require_dashboard_csrf
from ..dependencies import ServiceDep, SettingsDep, StoreDep
from ..event_statuses import DASHBOARD_PASSWORD_CHANGED, DASHBOARD_PASSWORD_ENABLED, DASHBOARD_PASSWORD_REMOVED
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,
)
2026-05-21 10:05:40 +02:00
from .path_params import ManagementSlugPath
2026-05-20 23:55:27 +02:00
router = APIRouter()
@router.post("/m/{management_slug}/unlock", include_in_schema=False)
2026-05-21 10:05:40 +02:00
async def unlock_magic(management_slug: ManagementSlugPath, request: Request, settings: SettingsDep, store: StoreDep) -> Response:
2026-05-20 23:55:27 +02:00
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(
2026-05-21 10:05:40 +02:00
_render_management_unlock_page(management_slug, account, dashboard_csrf_token(management_slug, account), "Incorrect password."),
2026-05-20 23:55:27 +02:00
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(
2026-05-21 10:05:40 +02:00
management_slug: ManagementSlugPath,
2026-05-20 23:55:27 +02:00
request: Request,
settings: SettingsDep,
store: StoreDep,
service: ServiceDep,
) -> Response:
account = await get_active_management_account_async(store, management_slug)
2026-05-21 10:05:40 +02:00
if not has_management_access(request, settings, management_slug, account):
2026-05-20 23:55:27 +02:00
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:
2026-05-21 10:05:40 +02:00
return await management_error_response_async(service, settings, management_slug, account, "Current password is incorrect.")
2026-05-20 23:55:27 +02:00
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),
2026-05-21 10:05:40 +02:00
message="Password must be at least 8 characters.",
2026-05-20 23:55:27 +02:00
)
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),
2026-05-21 10:05:40 +02:00
message="The passwords do not match.",
2026-05-20 23:55:27 +02:00
)
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(
2026-05-21 10:05:40 +02:00
management_slug: ManagementSlugPath,
2026-05-20 23:55:27 +02:00
request: Request,
settings: SettingsDep,
store: StoreDep,
service: ServiceDep,
) -> Response:
account = await get_active_management_account_async(store, management_slug)
2026-05-21 10:05:40 +02:00
if not has_management_access(request, settings, management_slug, account):
2026-05-20 23:55:27 +02:00
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:
2026-05-21 10:05:40 +02:00
return await management_error_response_async(service, settings, management_slug, account, "Current password is incorrect.")
2026-05-20 23:55:27 +02:00
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