diff --git a/lnbits/core/crud.py b/lnbits/core/crud.py index 6e498c60a..14082999f 100644 --- a/lnbits/core/crud.py +++ b/lnbits/core/crud.py @@ -63,9 +63,8 @@ async def get_user(user_id: str, conn: Optional[Connection] = None) -> Optional[ email=user["email"], extensions=[e[0] for e in extensions], wallets=[Wallet(**w) for w in wallets], - admin=user["id"] in settings.lnbits_admin_users - if settings.lnbits_admin_users - else False, + admin=user["id"] == settings.super_user + or user["id"] in settings.lnbits_admin_users, ) diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index dc5fb89c5..36feac37e 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -6,7 +6,7 @@ import time import uuid from http import HTTPStatus from io import BytesIO -from typing import Dict, List, Optional, Tuple, Union +from typing import Dict, Optional, Tuple, Union from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse import async_timeout @@ -18,13 +18,14 @@ from fastapi.params import Body from loguru import logger from pydantic import BaseModel from pydantic.fields import Field -from sse_starlette.sse import EventSourceResponse, ServerSentEvent -from starlette.responses import HTMLResponse, StreamingResponse +from sse_starlette.sse import EventSourceResponse +from starlette.responses import StreamingResponse from lnbits import bolt11, lnurl from lnbits.core.models import Payment, Wallet from lnbits.decorators import ( WalletTypeInfo, + check_admin, get_key_type, require_admin_key, require_invoice_key, @@ -72,14 +73,10 @@ async def api_wallet(wallet: WalletTypeInfo = Depends(get_key_type)): return {"name": wallet.wallet.name, "balance": wallet.wallet.balance_msat} -@core_app.put("/api/v1/wallet/balance/{amount}") +@core_app.put("/api/v1/wallet/balance/{amount}", dependencies=[Depends(check_admin)]) async def api_update_balance( amount: int, wallet: WalletTypeInfo = Depends(get_key_type) ): - if wallet.wallet.user not in settings.lnbits_admin_users: - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="Not an admin user" - ) payHash = urlsafe_short_hash() await create_payment( @@ -676,12 +673,9 @@ async def img(request: Request, data): ) -@core_app.get("/api/v1/audit/") -async def api_auditor(wallet: WalletTypeInfo = Depends(get_key_type)): - if wallet.wallet.user not in settings.lnbits_admin_users: - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="Not an admin user" - ) +@core_app.get("/api/v1/audit/", dependencies=[Depends(check_admin)]) +async def api_auditor(): + WALLET = get_wallet_class() total_balance = await get_total_balance() error_message, node_balance = await WALLET.status() diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py index 779d5632e..028682fe5 100644 --- a/lnbits/core/views/generic.py +++ b/lnbits/core/views/generic.py @@ -128,7 +128,7 @@ async def wallet( return template_renderer().TemplateResponse( "error.html", {"request": request, "err": "User not authorized."} ) - if user_id in settings.lnbits_admin_users: + if user_id == settings.super_user or user_id in settings.lnbits_admin_users: user.admin = True if not wallet_id: if user.wallets and not wallet_name: # type: ignore diff --git a/lnbits/decorators.py b/lnbits/decorators.py index 8206146cf..4b533bff9 100644 --- a/lnbits/decorators.py +++ b/lnbits/decorators.py @@ -146,8 +146,8 @@ async def get_key_type( status_code=HTTPStatus.NOT_FOUND, detail="Wallet does not exist." ) if ( - settings.lnbits_admin_users - and wallet.wallet.user not in settings.lnbits_admin_users + wallet.wallet.user != settings.super_user + or wallet.wallet.user not in settings.lnbits_admin_users ) and ( settings.lnbits_admin_extensions and pathname in settings.lnbits_admin_extensions @@ -241,19 +241,18 @@ async def check_user_exists(usr: UUID4) -> User: status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized." ) - if g().user.id in settings.lnbits_admin_users: - g().user.admin = True - return g().user async def check_admin(usr: UUID4) -> User: user = await check_user_exists(usr) - - if not user.id in settings.lnbits_admin_users: + if user.id != settings.super_user or ( + len(settings.lnbits_admin_users) > 0 + and not user.id in settings.lnbits_admin_users + ): raise HTTPException( status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized. No admin privileges.", ) - + user.admin = True return user diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py index 8e4078402..e635dab31 100644 --- a/lnbits/extensions/admin/crud.py +++ b/lnbits/extensions/admin/crud.py @@ -40,7 +40,10 @@ async def get_admin_settings() -> AdminSettings: async def update_admin_settings(data: UpdateSettings) -> Optional[AdminSettings]: fields = [] - for key, value in data.dict().items(): + # TODO: issue typens? + # somehow data, is type dict, but should be type UpdateSettings + # for key, value in data.dict().items(): #type: ignore + for key, value in data.items(): # type: ignore if not key in readonly_variables: setattr(settings, key, value) if type(value) == list: diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py index b4e217c10..1ad1cea6d 100644 --- a/lnbits/extensions/admin/migrations.py +++ b/lnbits/extensions/admin/migrations.py @@ -2,6 +2,7 @@ async def m001_create_admin_settings_table(db): await db.execute( """ CREATE TABLE IF NOT EXISTS admin.settings ( + super_user TEXT, lnbits_admin_users TEXT, lnbits_allowed_users TEXT, lnbits_disabled_extensions TEXT, diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py index 0a9c09026..938ff2348 100644 --- a/lnbits/extensions/admin/models.py +++ b/lnbits/extensions/admin/models.py @@ -76,3 +76,4 @@ class UpdateSettings(BaseModel): class AdminSettings(UpdateSettings): lnbits_allowed_funding_sources: Optional[List[str]] + super_user: Optional[str] diff --git a/lnbits/extensions/satspay/views.py b/lnbits/extensions/satspay/views.py index 5223b1ac6..b0f89025a 100644 --- a/lnbits/extensions/satspay/views.py +++ b/lnbits/extensions/satspay/views.py @@ -1,18 +1,15 @@ -import json from http import HTTPStatus from fastapi import Response from fastapi.param_functions import Depends from fastapi.templating import Jinja2Templates -from loguru import logger from starlette.exceptions import HTTPException from starlette.requests import Request from starlette.responses import HTMLResponse from lnbits.core.models import User -from lnbits.decorators import check_user_exists +from lnbits.decorators import check_admin from lnbits.extensions.satspay.helpers import public_charge -from lnbits.settings import settings from . import satspay_ext, satspay_renderer from .crud import get_charge, get_theme @@ -21,17 +18,15 @@ templates = Jinja2Templates(directory="templates") @satspay_ext.get("/", response_class=HTMLResponse) -async def index(request: Request, user: User = Depends(check_user_exists)): - admin = False - if settings.lnbits_admin_users and user.id in settings.lnbits_admin_users: - admin = True +async def index(request: Request, user: User = Depends(check_admin)): return satspay_renderer().TemplateResponse( - "satspay/index.html", {"request": request, "user": user.dict(), "admin": admin} + "satspay/index.html", + {"request": request, "user": user.dict(), "admin": user.admin}, ) @satspay_ext.get("/{charge_id}", response_class=HTMLResponse) -async def display(request: Request, charge_id: str): +async def display_charge(request: Request, charge_id: str): charge = await get_charge(charge_id) if not charge: raise HTTPException( @@ -50,7 +45,7 @@ async def display(request: Request, charge_id: str): @satspay_ext.get("/css/{css_id}") -async def display(css_id: str, response: Response): +async def display_css(css_id: str): theme = await get_theme(css_id) if theme: return Response(content=theme.custom_css, media_type="text/css") diff --git a/lnbits/extensions/satspay/views_api.py b/lnbits/extensions/satspay/views_api.py index 77337fb43..798e0df9d 100644 --- a/lnbits/extensions/satspay/views_api.py +++ b/lnbits/extensions/satspay/views_api.py @@ -1,19 +1,19 @@ import json from http import HTTPStatus +from fastapi import Query from fastapi.params import Depends from loguru import logger from starlette.exceptions import HTTPException -from lnbits.core.crud import get_wallet from lnbits.decorators import ( WalletTypeInfo, + check_admin, get_key_type, require_admin_key, require_invoice_key, ) from lnbits.extensions.satspay import satspay_ext -from lnbits.settings import settings from .crud import ( check_address_balance, @@ -138,21 +138,14 @@ async def api_charge_balance(charge_id): #############################THEMES########################## -@satspay_ext.post("/api/v1/themes") -@satspay_ext.post("/api/v1/themes/{css_id}") +@satspay_ext.post("/api/v1/themes", dependencies=[Depends(check_admin)]) +@satspay_ext.post("/api/v1/themes/{css_id}", dependencies=[Depends(check_admin)]) async def api_themes_save( data: SatsPayThemes, wallet: WalletTypeInfo = Depends(require_invoice_key), - css_id: str = None, + css_id: str = Query(...), ): - if ( - settings.lnbits_admin_users - and wallet.wallet.user not in settings.lnbits_admin_users - ): - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, - detail="Only server admins can create themes.", - ) + if css_id: theme = await save_theme(css_id=css_id, data=data) else: diff --git a/lnbits/settings.py b/lnbits/settings.py index 3ec6f15a3..cccfe95d7 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -48,6 +48,7 @@ class Settings(BaseSettings): forwarded_allow_ips: str = Field(default="*") lnbits_path: str = Field(default=".") lnbits_commit: str = Field(default="unknown") + super_user: str = Field(default="") # saas lnbits_saas_callback: Optional[str] = Field(default=None) @@ -230,8 +231,7 @@ async def check_admin_settings(): logger.debug(f"{key}: {value}") http = "https" if settings.lnbits_force_https else "http" - user = settings.lnbits_admin_users[0] - admin_url = f"{http}://{settings.host}:{settings.port}/wallet?usr={user}" + admin_url = f"{http}://{settings.host}:{settings.port}/wallet?usr={settings.super_user}" logger.success(f"✔️ Access admin user account at: {admin_url}") # callback for saas @@ -240,7 +240,7 @@ async def check_admin_settings(): and settings.lnbits_saas_secret and settings.lnbits_saas_instance_id ): - send_admin_user_to_saas(user) + send_admin_user_to_saas() wallets_module = importlib.import_module("lnbits.wallets") @@ -258,7 +258,7 @@ async def create_admin_settings(db): from lnbits.core.crud import create_account account = await create_account() - settings.lnbits_admin_users.insert(0, account.id) + settings.super_user = account.id keys = [] values = "" for key, value in settings.dict(exclude_none=True).items(): @@ -285,7 +285,7 @@ async def create_admin_settings(db): return row -def send_admin_user_to_saas(user): +def send_admin_user_to_saas(): if settings.lnbits_saas_callback: with httpx.Client() as client: headers = { @@ -294,7 +294,7 @@ def send_admin_user_to_saas(user): } payload = { "instance_id": settings.lnbits_saas_instance_id, - "adminuser": user, + "adminuser": settings.super_user, } try: client.post(