149 lines
6.0 KiB
Python
149 lines
6.0 KiB
Python
import logging
|
|
from pathlib import Path
|
|
|
|
from fastapi import APIRouter, Depends, Request, status
|
|
from fastapi.responses import HTMLResponse, RedirectResponse, Response
|
|
from fastapi.templating import Jinja2Templates
|
|
|
|
from app.config import Settings, get_settings
|
|
from app.dependencies import get_app_settings, get_auth_db, get_current_auth_session
|
|
from app.services.auth import AuthenticatedSession
|
|
from app.services.config_page import (
|
|
ConfigSaveError,
|
|
build_config_sections,
|
|
is_ticktick_oauth_ready,
|
|
save_config_updates,
|
|
)
|
|
from sqlalchemy.orm import Session
|
|
|
|
templates = Jinja2Templates(directory=str(Path(__file__).resolve().parents[2] / "templates"))
|
|
router = APIRouter(tags=["pages"])
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _ticktick_oauth_notice(status_value: str | None) -> tuple[str | None, str | None]:
|
|
if status_value == "success":
|
|
return "TickTick authorization completed successfully.", None
|
|
if status_value == "invalid-state":
|
|
return None, "TickTick authorization failed due to invalid OAuth state. Start the flow again."
|
|
if status_value == "invalid-callback":
|
|
return None, "TickTick authorization callback was missing required parameters."
|
|
if status_value == "failed":
|
|
return None, "TickTick authorization failed. Check server logs for the provider response and verify TickTick app credentials and redirect URI."
|
|
return None, None
|
|
|
|
|
|
@router.get("/", response_class=HTMLResponse)
|
|
def home(
|
|
request: Request,
|
|
current_auth: AuthenticatedSession | None = Depends(get_current_auth_session),
|
|
) -> RedirectResponse:
|
|
if current_auth is None:
|
|
return RedirectResponse(url="/login", status_code=status.HTTP_303_SEE_OTHER)
|
|
return RedirectResponse(url="/config", status_code=status.HTTP_303_SEE_OTHER)
|
|
|
|
|
|
@router.get("/admin", response_class=HTMLResponse)
|
|
def admin_redirect(
|
|
request: Request,
|
|
current_auth: AuthenticatedSession | None = Depends(get_current_auth_session),
|
|
) -> RedirectResponse:
|
|
if current_auth is None:
|
|
return RedirectResponse(url="/login", status_code=status.HTTP_303_SEE_OTHER)
|
|
return RedirectResponse(url="/config", status_code=status.HTTP_303_SEE_OTHER)
|
|
|
|
|
|
@router.get("/config", response_class=HTMLResponse)
|
|
def config_page(
|
|
request: Request,
|
|
auth_db_session: Session = Depends(get_auth_db),
|
|
settings: Settings = Depends(get_app_settings),
|
|
current_auth: AuthenticatedSession | None = Depends(get_current_auth_session),
|
|
) -> Response:
|
|
if current_auth is None:
|
|
return RedirectResponse(url="/login", status_code=status.HTTP_303_SEE_OTHER)
|
|
|
|
ticktick_oauth_notice, ticktick_oauth_error = _ticktick_oauth_notice(
|
|
request.query_params.get("ticktick_oauth")
|
|
)
|
|
|
|
context = {
|
|
"app_name": settings.app_name,
|
|
"app_env": settings.app_env,
|
|
"current_username": current_auth.user.username,
|
|
"csrf_token": current_auth.session.csrf_token,
|
|
"force_password_change": current_auth.user.force_password_change,
|
|
"password_change_error": None,
|
|
"config_error": None,
|
|
"config_saved": request.query_params.get("saved") == "1",
|
|
"config_sections": build_config_sections(auth_db_session, settings),
|
|
"ticktick_oauth_ready": is_ticktick_oauth_ready(settings),
|
|
"ticktick_oauth_notice": ticktick_oauth_notice,
|
|
"ticktick_oauth_error": ticktick_oauth_error,
|
|
}
|
|
return templates.TemplateResponse(request, "config.html", context)
|
|
|
|
|
|
@router.post("/config", response_class=HTMLResponse)
|
|
async def config_submit(
|
|
request: Request,
|
|
auth_db_session: Session = Depends(get_auth_db),
|
|
settings: Settings = Depends(get_app_settings),
|
|
current_auth: AuthenticatedSession | None = Depends(get_current_auth_session),
|
|
) -> Response:
|
|
if current_auth is None:
|
|
return RedirectResponse(url="/login", status_code=status.HTTP_303_SEE_OTHER)
|
|
|
|
form = await request.form()
|
|
csrf_token = form.get("csrf_token")
|
|
if csrf_token != current_auth.session.csrf_token:
|
|
logger.warning("Rejected config update due to CSRF validation failure")
|
|
context = {
|
|
"app_name": settings.app_name,
|
|
"app_env": settings.app_env,
|
|
"current_username": current_auth.user.username,
|
|
"csrf_token": current_auth.session.csrf_token,
|
|
"force_password_change": current_auth.user.force_password_change,
|
|
"password_change_error": None,
|
|
"config_error": "invalid config update request",
|
|
"config_saved": False,
|
|
"config_sections": build_config_sections(auth_db_session, settings),
|
|
"ticktick_oauth_ready": is_ticktick_oauth_ready(settings),
|
|
"ticktick_oauth_notice": None,
|
|
"ticktick_oauth_error": None,
|
|
}
|
|
return templates.TemplateResponse(
|
|
request,
|
|
"config.html",
|
|
context,
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
)
|
|
|
|
try:
|
|
save_config_updates(auth_db_session, dict(form), settings)
|
|
except ConfigSaveError:
|
|
logger.warning("Rejected config update due to invalid submitted values")
|
|
refreshed_settings = get_settings()
|
|
context = {
|
|
"app_name": refreshed_settings.app_name,
|
|
"app_env": refreshed_settings.app_env,
|
|
"current_username": current_auth.user.username,
|
|
"csrf_token": current_auth.session.csrf_token,
|
|
"force_password_change": current_auth.user.force_password_change,
|
|
"password_change_error": None,
|
|
"config_error": "invalid config submission",
|
|
"config_saved": False,
|
|
"config_sections": build_config_sections(auth_db_session, refreshed_settings),
|
|
"ticktick_oauth_ready": is_ticktick_oauth_ready(refreshed_settings),
|
|
"ticktick_oauth_notice": None,
|
|
"ticktick_oauth_error": None,
|
|
}
|
|
return templates.TemplateResponse(
|
|
request,
|
|
"config.html",
|
|
context,
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
)
|
|
|
|
return RedirectResponse(url="/config?saved=1", status_code=status.HTTP_303_SEE_OTHER)
|