From 6bac34fc6be410b5375993b2e9caad6cf1f69d8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 10 Nov 2025 09:33:16 +0100 Subject: [PATCH] refactor: use decorators for disabled endpoints (#3481) --- lnbits/core/views/extensions_builder_api.py | 22 ++----- lnbits/core/views/generic.py | 69 +++++++++------------ lnbits/decorators.py | 17 +++++ 3 files changed, 51 insertions(+), 57 deletions(-) diff --git a/lnbits/core/views/extensions_builder_api.py b/lnbits/core/views/extensions_builder_api.py index 43d2b332..5cdac982 100644 --- a/lnbits/core/views/extensions_builder_api.py +++ b/lnbits/core/views/extensions_builder_api.py @@ -1,9 +1,8 @@ import os import shutil from hashlib import sha256 -from http import HTTPStatus -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends from fastapi.responses import FileResponse from lnbits.core.models import ( @@ -28,9 +27,9 @@ from lnbits.core.services.extensions_builder import ( ) from lnbits.decorators import ( check_admin, + check_extension_builder, check_user_exists, ) -from lnbits.settings import settings from ..crud import ( create_user_extension, @@ -47,19 +46,12 @@ extension_builder_router = APIRouter( @extension_builder_router.post( "/zip", summary="Build and download extension zip.", + dependencies=[Depends(check_extension_builder)], description=""" This endpoint generates a zip file for the extension based on the provided data. """, ) -async def api_build_extension( - data: ExtensionData, - user: User = Depends(check_user_exists), -) -> FileResponse: - if not settings.lnbits_extensions_builder_activate_non_admins and not user.admin: - raise HTTPException( - HTTPStatus.FORBIDDEN, - "Extension Builder is disabled for non admin users.", - ) +async def api_build_extension(data: ExtensionData) -> FileResponse: stub_ext_id = "extension_builder_stub" # todo: do not hardcode, fetch from manifest release, build_dir = await build_extension_from_data(data, stub_ext_id) @@ -132,16 +124,12 @@ async def api_deploy_extension( @extension_builder_router.post( "/preview", summary="Build and preview the extension ui.", + dependencies=[Depends(check_extension_builder)], ) async def api_preview_extension( data: ExtensionData, user: User = Depends(check_user_exists), ) -> SimpleStatus: - if not settings.lnbits_extensions_builder_activate_non_admins and not user.admin: - raise HTTPException( - HTTPStatus.FORBIDDEN, - "Extension Builder is disabled for non admin users.", - ) stub_ext_id = "extension_builder_stub" working_dir_name = "preview_" + sha256(user.id.encode("utf-8")).hexdigest() await build_extension_from_data(data, stub_ext_id, working_dir_name) diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py index 828db104..cc216f27 100644 --- a/lnbits/core/views/generic.py +++ b/lnbits/core/views/generic.py @@ -17,7 +17,12 @@ from lnbits.core.models import User from lnbits.core.models.extensions import ExtensionMeta, InstallableExtension from lnbits.core.services import create_invoice, create_user_account from lnbits.core.services.extensions import get_valid_extensions -from lnbits.decorators import check_admin, check_user_exists +from lnbits.decorators import ( + check_admin, + check_admin_ui, + check_extension_builder, + check_user_exists, +) from lnbits.helpers import check_callback_url, template_renderer from lnbits.settings import settings @@ -209,14 +214,13 @@ async def extensions(request: Request, user: User = Depends(check_user_exists)): @generic_router.get( - "/extensions/builder", name="extensions builder", response_class=HTMLResponse + "/extensions/builder", + name="extensions builder", + dependencies=[Depends(check_extension_builder)], ) -async def extensions_builder(request: Request, user: User = Depends(check_user_exists)): - if not settings.lnbits_extensions_builder_activate_non_admins and not user.admin: - raise HTTPException( - HTTPStatus.FORBIDDEN, - "Extension Builder is disabled for non admin users.", - ) +async def extensions_builder( + request: Request, user: User = Depends(check_user_exists) +) -> HTMLResponse: return template_renderer().TemplateResponse( request, "core/extensions_builder.html", @@ -230,19 +234,14 @@ async def extensions_builder(request: Request, user: User = Depends(check_user_e @generic_router.get( "/extensions/builder/preview/{ext_id}", name="extensions builder", - response_class=HTMLResponse, + dependencies=[Depends(check_extension_builder)], ) async def extensions_builder_preview( request: Request, ext_id: str, page_name: str | None = None, user: User = Depends(check_user_exists), -): - if not settings.lnbits_extensions_builder_activate_non_admins and not user.admin: - raise HTTPException( - HTTPStatus.FORBIDDEN, - "Extension Builder is disabled for non admin users.", - ) +) -> HTMLResponse: working_dir_name = "preview_" + sha256(user.id.encode("utf-8")).hexdigest() html_file_name = "index.html" if page_name == "public_page": @@ -383,10 +382,19 @@ async def manifest(request: Request, usr: str): } -@generic_router.get("/payments", response_class=HTMLResponse) -@generic_router.get("/wallets", response_class=HTMLResponse) -@generic_router.get("/account", response_class=HTMLResponse) -async def index(request: Request, user: User = Depends(check_user_exists)): +admin_ui_checks = [Depends(check_admin), Depends(check_admin_ui)] + + +@generic_router.get("/payments") +@generic_router.get("/wallets") +@generic_router.get("/account") +@generic_router.get("/users", dependencies=admin_ui_checks) +@generic_router.get("/audit", dependencies=admin_ui_checks) +@generic_router.get("/node", dependencies=admin_ui_checks) +@generic_router.get("/admin", dependencies=admin_ui_checks) +async def index( + request: Request, user: User = Depends(check_user_exists) +) -> HTMLResponse: return template_renderer().TemplateResponse( request, "index.html", @@ -396,27 +404,8 @@ async def index(request: Request, user: User = Depends(check_user_exists)): ) -@generic_router.get("/users", response_class=HTMLResponse) -@generic_router.get("/audit", response_class=HTMLResponse) -@generic_router.get("/node", response_class=HTMLResponse) -@generic_router.get("/admin", response_class=HTMLResponse) -async def index_admin(request: Request, admin: User = Depends(check_admin)): - if not settings.lnbits_admin_ui: - raise HTTPException( - status_code=HTTPStatus.SERVICE_UNAVAILABLE, detail="Admin UI is disabled." - ) - - return template_renderer().TemplateResponse( - request, - "index.html", - { - "user": admin.json(), - }, - ) - - -@generic_router.get("/node/public", response_class=HTMLResponse) -async def index_public(request: Request): +@generic_router.get("/node/public") +async def index_public(request: Request) -> HTMLResponse: return template_renderer().TemplateResponse(request, "index_public.html") diff --git a/lnbits/decorators.py b/lnbits/decorators.py index 5269284a..04cb84a4 100644 --- a/lnbits/decorators.py +++ b/lnbits/decorators.py @@ -359,3 +359,20 @@ def url_for_interceptor(original_method): # Upgraded extensions modify the path. # This interceptor ensures that the path is normalized. Request.url_for = url_for_interceptor(Request.url_for) # type: ignore[method-assign] + + +async def check_admin_ui() -> None: + if not settings.lnbits_admin_ui: + raise HTTPException( + status_code=HTTPStatus.SERVICE_UNAVAILABLE, detail="Admin UI is disabled." + ) + + +async def check_extension_builder( + user: Annotated[User, Depends(check_user_exists)], +) -> None: + if not settings.lnbits_extensions_builder_activate_non_admins and not user.admin: + raise HTTPException( + HTTPStatus.FORBIDDEN, + "Extension Builder is disabled for non admin users.", + )