add superuser and refactor check_admin function, also put it into satspay

This commit is contained in:
dni ⚡ 2022-12-05 20:41:23 +01:00
parent 9d67c8e4e5
commit c56a31e6f5
10 changed files with 42 additions and 57 deletions

View file

@ -63,9 +63,8 @@ async def get_user(user_id: str, conn: Optional[Connection] = None) -> Optional[
email=user["email"], email=user["email"],
extensions=[e[0] for e in extensions], extensions=[e[0] for e in extensions],
wallets=[Wallet(**w) for w in wallets], wallets=[Wallet(**w) for w in wallets],
admin=user["id"] in settings.lnbits_admin_users admin=user["id"] == settings.super_user
if settings.lnbits_admin_users or user["id"] in settings.lnbits_admin_users,
else False,
) )

View file

@ -6,7 +6,7 @@ import time
import uuid import uuid
from http import HTTPStatus from http import HTTPStatus
from io import BytesIO 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 from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse
import async_timeout import async_timeout
@ -18,13 +18,14 @@ from fastapi.params import Body
from loguru import logger from loguru import logger
from pydantic import BaseModel from pydantic import BaseModel
from pydantic.fields import Field from pydantic.fields import Field
from sse_starlette.sse import EventSourceResponse, ServerSentEvent from sse_starlette.sse import EventSourceResponse
from starlette.responses import HTMLResponse, StreamingResponse from starlette.responses import StreamingResponse
from lnbits import bolt11, lnurl from lnbits import bolt11, lnurl
from lnbits.core.models import Payment, Wallet from lnbits.core.models import Payment, Wallet
from lnbits.decorators import ( from lnbits.decorators import (
WalletTypeInfo, WalletTypeInfo,
check_admin,
get_key_type, get_key_type,
require_admin_key, require_admin_key,
require_invoice_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} 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( async def api_update_balance(
amount: int, wallet: WalletTypeInfo = Depends(get_key_type) 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() payHash = urlsafe_short_hash()
await create_payment( await create_payment(
@ -676,12 +673,9 @@ async def img(request: Request, data):
) )
@core_app.get("/api/v1/audit/") @core_app.get("/api/v1/audit/", dependencies=[Depends(check_admin)])
async def api_auditor(wallet: WalletTypeInfo = Depends(get_key_type)): async def api_auditor():
if wallet.wallet.user not in settings.lnbits_admin_users:
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="Not an admin user"
)
WALLET = get_wallet_class() WALLET = get_wallet_class()
total_balance = await get_total_balance() total_balance = await get_total_balance()
error_message, node_balance = await WALLET.status() error_message, node_balance = await WALLET.status()

View file

@ -128,7 +128,7 @@ async def wallet(
return template_renderer().TemplateResponse( return template_renderer().TemplateResponse(
"error.html", {"request": request, "err": "User not authorized."} "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 user.admin = True
if not wallet_id: if not wallet_id:
if user.wallets and not wallet_name: # type: ignore if user.wallets and not wallet_name: # type: ignore

View file

@ -146,8 +146,8 @@ async def get_key_type(
status_code=HTTPStatus.NOT_FOUND, detail="Wallet does not exist." status_code=HTTPStatus.NOT_FOUND, detail="Wallet does not exist."
) )
if ( if (
settings.lnbits_admin_users wallet.wallet.user != settings.super_user
and wallet.wallet.user not in settings.lnbits_admin_users or wallet.wallet.user not in settings.lnbits_admin_users
) and ( ) and (
settings.lnbits_admin_extensions settings.lnbits_admin_extensions
and pathname in 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." status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
) )
if g().user.id in settings.lnbits_admin_users:
g().user.admin = True
return g().user return g().user
async def check_admin(usr: UUID4) -> User: async def check_admin(usr: UUID4) -> User:
user = await check_user_exists(usr) user = await check_user_exists(usr)
if user.id != settings.super_user or (
if not user.id in settings.lnbits_admin_users: len(settings.lnbits_admin_users) > 0
and not user.id in settings.lnbits_admin_users
):
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.UNAUTHORIZED, status_code=HTTPStatus.UNAUTHORIZED,
detail="User not authorized. No admin privileges.", detail="User not authorized. No admin privileges.",
) )
user.admin = True
return user return user

View file

@ -40,7 +40,10 @@ async def get_admin_settings() -> AdminSettings:
async def update_admin_settings(data: UpdateSettings) -> Optional[AdminSettings]: async def update_admin_settings(data: UpdateSettings) -> Optional[AdminSettings]:
fields = [] 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: if not key in readonly_variables:
setattr(settings, key, value) setattr(settings, key, value)
if type(value) == list: if type(value) == list:

View file

@ -2,6 +2,7 @@ async def m001_create_admin_settings_table(db):
await db.execute( await db.execute(
""" """
CREATE TABLE IF NOT EXISTS admin.settings ( CREATE TABLE IF NOT EXISTS admin.settings (
super_user TEXT,
lnbits_admin_users TEXT, lnbits_admin_users TEXT,
lnbits_allowed_users TEXT, lnbits_allowed_users TEXT,
lnbits_disabled_extensions TEXT, lnbits_disabled_extensions TEXT,

View file

@ -76,3 +76,4 @@ class UpdateSettings(BaseModel):
class AdminSettings(UpdateSettings): class AdminSettings(UpdateSettings):
lnbits_allowed_funding_sources: Optional[List[str]] lnbits_allowed_funding_sources: Optional[List[str]]
super_user: Optional[str]

View file

@ -1,18 +1,15 @@
import json
from http import HTTPStatus from http import HTTPStatus
from fastapi import Response from fastapi import Response
from fastapi.param_functions import Depends from fastapi.param_functions import Depends
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from loguru import logger
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from starlette.requests import Request from starlette.requests import Request
from starlette.responses import HTMLResponse from starlette.responses import HTMLResponse
from lnbits.core.models import User 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.extensions.satspay.helpers import public_charge
from lnbits.settings import settings
from . import satspay_ext, satspay_renderer from . import satspay_ext, satspay_renderer
from .crud import get_charge, get_theme from .crud import get_charge, get_theme
@ -21,17 +18,15 @@ templates = Jinja2Templates(directory="templates")
@satspay_ext.get("/", response_class=HTMLResponse) @satspay_ext.get("/", response_class=HTMLResponse)
async def index(request: Request, user: User = Depends(check_user_exists)): async def index(request: Request, user: User = Depends(check_admin)):
admin = False
if settings.lnbits_admin_users and user.id in settings.lnbits_admin_users:
admin = True
return satspay_renderer().TemplateResponse( 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) @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) charge = await get_charge(charge_id)
if not charge: if not charge:
raise HTTPException( raise HTTPException(
@ -50,7 +45,7 @@ async def display(request: Request, charge_id: str):
@satspay_ext.get("/css/{css_id}") @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) theme = await get_theme(css_id)
if theme: if theme:
return Response(content=theme.custom_css, media_type="text/css") return Response(content=theme.custom_css, media_type="text/css")

View file

@ -1,19 +1,19 @@
import json import json
from http import HTTPStatus from http import HTTPStatus
from fastapi import Query
from fastapi.params import Depends from fastapi.params import Depends
from loguru import logger from loguru import logger
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from lnbits.core.crud import get_wallet
from lnbits.decorators import ( from lnbits.decorators import (
WalletTypeInfo, WalletTypeInfo,
check_admin,
get_key_type, get_key_type,
require_admin_key, require_admin_key,
require_invoice_key, require_invoice_key,
) )
from lnbits.extensions.satspay import satspay_ext from lnbits.extensions.satspay import satspay_ext
from lnbits.settings import settings
from .crud import ( from .crud import (
check_address_balance, check_address_balance,
@ -138,21 +138,14 @@ async def api_charge_balance(charge_id):
#############################THEMES########################## #############################THEMES##########################
@satspay_ext.post("/api/v1/themes") @satspay_ext.post("/api/v1/themes", dependencies=[Depends(check_admin)])
@satspay_ext.post("/api/v1/themes/{css_id}") @satspay_ext.post("/api/v1/themes/{css_id}", dependencies=[Depends(check_admin)])
async def api_themes_save( async def api_themes_save(
data: SatsPayThemes, data: SatsPayThemes,
wallet: WalletTypeInfo = Depends(require_invoice_key), 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: if css_id:
theme = await save_theme(css_id=css_id, data=data) theme = await save_theme(css_id=css_id, data=data)
else: else:

View file

@ -48,6 +48,7 @@ class Settings(BaseSettings):
forwarded_allow_ips: str = Field(default="*") forwarded_allow_ips: str = Field(default="*")
lnbits_path: str = Field(default=".") lnbits_path: str = Field(default=".")
lnbits_commit: str = Field(default="unknown") lnbits_commit: str = Field(default="unknown")
super_user: str = Field(default="")
# saas # saas
lnbits_saas_callback: Optional[str] = Field(default=None) lnbits_saas_callback: Optional[str] = Field(default=None)
@ -230,8 +231,7 @@ async def check_admin_settings():
logger.debug(f"{key}: {value}") logger.debug(f"{key}: {value}")
http = "https" if settings.lnbits_force_https else "http" 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={settings.super_user}"
admin_url = f"{http}://{settings.host}:{settings.port}/wallet?usr={user}"
logger.success(f"✔️ Access admin user account at: {admin_url}") logger.success(f"✔️ Access admin user account at: {admin_url}")
# callback for saas # callback for saas
@ -240,7 +240,7 @@ async def check_admin_settings():
and settings.lnbits_saas_secret and settings.lnbits_saas_secret
and settings.lnbits_saas_instance_id and settings.lnbits_saas_instance_id
): ):
send_admin_user_to_saas(user) send_admin_user_to_saas()
wallets_module = importlib.import_module("lnbits.wallets") wallets_module = importlib.import_module("lnbits.wallets")
@ -258,7 +258,7 @@ async def create_admin_settings(db):
from lnbits.core.crud import create_account from lnbits.core.crud import create_account
account = await create_account() account = await create_account()
settings.lnbits_admin_users.insert(0, account.id) settings.super_user = account.id
keys = [] keys = []
values = "" values = ""
for key, value in settings.dict(exclude_none=True).items(): for key, value in settings.dict(exclude_none=True).items():
@ -285,7 +285,7 @@ async def create_admin_settings(db):
return row return row
def send_admin_user_to_saas(user): def send_admin_user_to_saas():
if settings.lnbits_saas_callback: if settings.lnbits_saas_callback:
with httpx.Client() as client: with httpx.Client() as client:
headers = { headers = {
@ -294,7 +294,7 @@ def send_admin_user_to_saas(user):
} }
payload = { payload = {
"instance_id": settings.lnbits_saas_instance_id, "instance_id": settings.lnbits_saas_instance_id,
"adminuser": user, "adminuser": settings.super_user,
} }
try: try:
client.post( client.post(