big fx part2

This commit is contained in:
dni ⚡
2024-10-10 09:07:45 +02:00
parent 383f6ec721
commit 200f5c52a7
12 changed files with 395 additions and 537 deletions
+69 -198
View File
@@ -1,10 +1,10 @@
import json
from datetime import datetime
from time import time
from typing import Literal, Optional
from uuid import uuid4
import shortuuid
from passlib.context import CryptContext
from lnbits.core.db import db
from lnbits.core.extensions.models import (
@@ -32,92 +32,25 @@ from .models import (
UpdateUserPassword,
UpdateUserPubkey,
User,
UserConfig,
Wallet,
WebPushSubscription,
)
async def create_account(
user_id: Optional[str] = None,
username: Optional[str] = None,
pubkey: Optional[str] = None,
email: Optional[str] = None,
password: Optional[str] = None,
user_config: Optional[UserConfig] = None,
account: Optional[Account] = None,
conn: Optional[Connection] = None,
) -> User:
user_id = user_id or uuid4().hex
extra = json.dumps(dict(user_config)) if user_config else "{}"
now = int(time())
now_ph = db.timestamp_placeholder("now")
await (conn or db).execute(
f"""
INSERT INTO accounts
(id, username, pass, email, pubkey, extra, created_at, updated_at)
VALUES
(:user, :username, :password, :email, :pubkey, :extra, {now_ph}, {now_ph})
""",
{
"user": user_id,
"username": username,
"password": password,
"email": email,
"pubkey": pubkey,
"extra": extra,
"now": now,
},
)
new_account = await get_account(user_id=user_id, conn=conn)
assert new_account, "Newly created account couldn't be retrieved"
return new_account
) -> Account:
if not account:
now = datetime.now()
account = Account(id=uuid4().hex, created_at=now, updated_at=now)
await (conn or db).insert("accounts", account)
return account
async def update_account(
user_id: str,
username: Optional[str] = None,
email: Optional[str] = None,
user_config: Optional[UserConfig] = None,
) -> Optional[User]:
user = await get_account(user_id)
assert user, "User not found"
if email:
assert not user.email or email == user.email, "Cannot change email."
account = await get_account_by_email(email)
assert not account or account.id == user_id, "Email already in use."
if username:
assert not user.username or username == user.username, "Cannot change username."
account = await get_account_by_username(username)
assert not account or account.id == user_id, "Username already exists."
username = user.username or username
email = user.email or email
extra = user_config or user.config
now = int(time())
now_ph = db.timestamp_placeholder("now")
await db.execute(
f"""
UPDATE accounts SET (username, email, extra, updated_at) =
(:username, :email, :extra, {now_ph})
WHERE id = :user
""",
{
"username": username,
"email": email,
"extra": json.dumps(dict(extra)) if extra else "{}",
"now": now,
"user": user_id,
},
)
user = await get_user(user_id)
assert user, "Updated account couldn't be retrieved"
return user
async def update_account(account: Account) -> None:
account.updated_at = datetime.now()
await db.update("accounts", account)
async def delete_account(user_id: str, conn: Optional[Connection] = None) -> None:
@@ -162,16 +95,12 @@ async def get_accounts(
async def get_account(
user_id: str, conn: Optional[Connection] = None
) -> Optional[User]:
user = await (conn or db).fetchone(
"""
SELECT id, email, username, pubkey, created_at, updated_at, extra
FROM accounts WHERE id = :id
""",
) -> Optional[Account]:
return await (conn or db).fetchone(
"SELECT * FROM accounts WHERE id = :id",
{"id": user_id},
User,
Account,
)
return user
async def delete_accounts_no_wallets(
@@ -193,24 +122,6 @@ async def delete_accounts_no_wallets(
)
async def get_user_password(user_id: str) -> Optional[str]:
row = await db.fetchone(
"SELECT pass FROM accounts WHERE id = :user",
{"user": user_id},
)
return row.get("pass")
# TODO: refactor not a crud function
async def verify_user_password(user_id: str, password: str) -> bool:
existing_password = await get_user_password(user_id)
if not existing_password:
return False
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
return pwd_context.verify(password, existing_password)
async def update_user_password(data: UpdateUserPassword, last_login_time: int) -> User:
assert 0 <= time() - last_login_time <= settings.auth_credetials_update_threshold, (
@@ -272,93 +183,58 @@ async def update_user_pubkey(data: UpdateUserPubkey, last_login_time: int) -> Us
async def get_account_by_username(
username: str, conn: Optional[Connection] = None
) -> Optional[User]:
row = await (conn or db).fetchone(
"""
SELECT id, username, pubkey, email, created_at, updated_at
FROM accounts WHERE username = :username
""",
) -> Optional[Account]:
return await (conn or db).fetchone(
"SELECT * FROM accounts WHERE username = :username",
{"username": username},
Account,
)
return User(**row) if row else None
async def get_account_by_pubkey(
pubkey: str, conn: Optional[Connection] = None
) -> Optional[User]:
row = await (conn or db).fetchone(
"""
SELECT id, username, pubkey, email, created_at, updated_at
FROM accounts WHERE pubkey = :pubkey
""",
) -> Optional[Account]:
return await (conn or db).fetchone(
"SELECT * FROM accounts WHERE pubkey = :pubkey",
{"pubkey": pubkey},
Account,
)
return User(**row) if row else None
async def get_account_by_email(
email: str, conn: Optional[Connection] = None
) -> Optional[User]:
row = await (conn or db).fetchone(
"""
SELECT id, username, pubkey, email, created_at, updated_at
FROM accounts WHERE email = :email
""",
) -> Optional[Account]:
return await (conn or db).fetchone(
"SELECT * FROM accounts WHERE email = :email",
{"email": email},
Account,
)
return User(**row) if row else None
async def get_account_by_username_or_email(
username_or_email: str, conn: Optional[Connection] = None
) -> Optional[User]:
user = await get_account_by_username(username_or_email, conn)
if not user:
user = await get_account_by_email(username_or_email, conn)
return user
async def get_user(user_id: str, conn: Optional[Connection] = None) -> Optional[User]:
user = await (conn or db).fetchone(
"""
SELECT id, email, username, pubkey, pass, extra, created_at, updated_at
FROM accounts WHERE id = :id
""",
{"id": user_id},
) -> Optional[Account]:
return await (conn or db).fetchone(
"SELECT * FROM accounts WHERE email = :value or username = :value",
{"value": username_or_email},
Account,
)
if user:
extensions = await get_user_active_extensions_ids(user_id, conn)
wallets = await (conn or db).fetchall(
"""
SELECT *, COALESCE((
SELECT balance FROM balances WHERE wallet = wallets.id
), 0) AS balance_msat
FROM wallets
WHERE "user" = :user and wallets.deleted = false
""",
{"user": user_id},
)
else:
return None
async def get_user(account: Account, conn: Optional[Connection] = None) -> User:
extensions = await get_user_active_extensions_ids(account.id, conn)
wallets = await get_wallets(account.id, conn)
return User(
id=user["id"],
email=user["email"],
username=user["username"],
pubkey=user["pubkey"],
extensions=[
e for e in extensions if User.is_extension_for_user(e[0], user["id"])
],
wallets=[Wallet(**w) for w in wallets],
admin=user["id"] == settings.super_user
or user["id"] in settings.lnbits_admin_users,
super_user=user["id"] == settings.super_user,
has_password=True if user["pass"] else False,
config=UserConfig(**json.loads(user["extra"])) if user["extra"] else None,
id=account.id,
email=account.email,
username=account.username,
extra=account.extra,
created_at=account.created_at,
updated_at=account.updated_at,
extensions=extensions,
wallets=wallets,
admin=account.is_super_user or account.is_admin or False,
super_user=account.is_super_user or False,
has_password=account.password_hash is not None,
)
@@ -437,7 +313,7 @@ async def delete_installed_extension(
)
async def drop_extension_db(*, ext_id: str, conn: Optional[Connection] = None) -> None:
async def drop_extension_db(ext_id: str, conn: Optional[Connection] = None) -> None:
db_version = await (conn or db).fetchone(
"SELECT * FROM dbversions WHERE db = :id",
{"id": ext_id},
@@ -504,19 +380,17 @@ async def get_user_extensions(
)
async def create_user_extension(
user_extension: UserExtension, conn: Optional[Connection] = None
) -> None:
await (conn or db).insert("extensions", user_extension)
async def update_user_extension(
user_extension: UserExtension, conn: Optional[Connection] = None
) -> None:
where = """extension = :extension AND "user" = :user"""
await (conn or db).update("extensions", user_extension, where)
# await (conn or db).execute(
# """
# INSERT INTO extensions ("user", extension, active)
# VALUES (:user, :ext, :active)
# ON CONFLICT ("user", extension) DO UPDATE SET active = :active
# """,
# {"user": user_id, "ext": extension, "active": active},
# )
async def get_user_active_extensions_ids(
@@ -623,7 +497,7 @@ async def force_delete_wallet(
async def delete_wallet_by_id(
*, wallet_id: str, conn: Optional[Connection] = None
wallet_id: str, conn: Optional[Connection] = None
) -> Optional[int]:
now = int(time())
result = await (conn or db).execute(
@@ -675,38 +549,34 @@ async def get_wallet(
async def get_wallets(user_id: str, conn: Optional[Connection] = None) -> list[Wallet]:
rows = await (conn or db).fetchall(
return await (conn or db).fetchall(
"""
SELECT *, COALESCE((SELECT balance FROM balances WHERE wallet = wallets.id), 0)
AS balance_msat FROM wallets WHERE "user" = :user
""",
{"user": user_id},
Wallet,
)
return [Wallet(**row) for row in rows]
async def get_wallet_for_key(
key: str,
conn: Optional[Connection] = None,
) -> Optional[Wallet]:
row = await (conn or db).fetchone(
return await (conn or db).fetchone(
"""
SELECT *, COALESCE((SELECT balance FROM balances WHERE wallet = wallets.id), 0)
AS balance_msat FROM wallets
WHERE (adminkey = :key OR inkey = :key) AND deleted = false
""",
{"key": key},
Wallet,
)
if not row:
return None
return Wallet(**row)
async def get_total_balance(conn: Optional[Connection] = None):
row = await (conn or db).fetchone("SELECT SUM(balance) FROM balances")
result = await (conn or db).execute("SELECT SUM(balance) FROM balances")
row = result.mappings().first()
return row.get("balance", 0)
@@ -759,8 +629,10 @@ async def get_wallet_payment(
return payment
async def get_latest_payments_by_extension(ext_name: str, ext_id: str, limit: int = 5):
rows = await db.fetchall(
async def get_latest_payments_by_extension(
ext_name: str, ext_id: str, limit: int = 5
) -> list[Payment]:
return await db.fetchall(
f"""
SELECT * FROM apipayments
WHERE status = '{PaymentState.SUCCESS}'
@@ -769,10 +641,9 @@ async def get_latest_payments_by_extension(ext_name: str, ext_id: str, limit: in
ORDER BY time DESC LIMIT {limit}
""",
{"ext_name": f"%{ext_name}%", "ext_id": f"%{ext_id}%"},
Payment,
)
return rows
async def get_payments_paginated(
*,
@@ -1254,19 +1125,19 @@ async def create_tinyurl(domain: str, endless: bool, wallet: str):
async def get_tinyurl(tinyurl_id: str) -> Optional[TinyURL]:
row = await db.fetchone(
return await db.fetchone(
"SELECT * FROM tiny_url WHERE id = :tinyurl",
{"tinyurl": tinyurl_id},
TinyURL,
)
return TinyURL.from_row(row) if row else None
async def get_tinyurl_by_url(url: str) -> list[TinyURL]:
rows = await db.fetchall(
return await db.fetchall(
"SELECT * FROM tiny_url WHERE url = :url",
{"url": url},
TinyURL,
)
return [TinyURL.from_row(row) for row in rows]
async def delete_tinyurl(tinyurl_id: str):
+1
View File
@@ -120,6 +120,7 @@ class UserExtensionInfo(BaseModel):
class UserExtension(BaseModel):
user: str
extension: str
active: bool
extra: Optional[UserExtensionInfo] = None
+1
View File
@@ -562,3 +562,4 @@ async def m023_add_column_column_to_apipayments(db):
await db.execute("DROP INDEX by_hash")
await db.execute("ALTER TABLE apipayments RENAME COLUMN hash TO payment_hash")
await db.execute("ALTER TABLE apipayments RENAME COLUMN wallet TO wallet_id")
await db.execute("ALTER TABLE accounts RENAME COLUMN pass TO password_hash")
+35 -11
View File
@@ -1,15 +1,16 @@
from __future__ import annotations
import datetime
import hashlib
import hmac
import time
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
from typing import Callable, Optional
from ecdsa import SECP256k1, SigningKey
from fastapi import Query
from passlib.context import CryptContext
from pydantic import BaseModel, validator
from lnbits.db import FilterModel
@@ -104,14 +105,37 @@ class UserExtra(BaseModel):
class Account(BaseModel):
id: str
is_super_user: Optional[bool] = False
is_admin: Optional[bool] = False
username: Optional[str] = None
password_hash: Optional[str] = None
email: Optional[str] = None
balance_msat: Optional[int] = 0
transaction_count: Optional[int] = 0
wallet_count: Optional[int] = 0
last_payment: Optional[datetime.datetime] = None
last_payment: Optional[datetime] = None
extra: Optional[UserExtra] = None
created_at: datetime = datetime.now()
updated_at: datetime = datetime.now()
@property
def is_super_user(self) -> bool:
return self.id == settings.super_user
@property
def is_admin(self) -> bool:
return self.id in settings.lnbits_admin_users
def hash_password(self, password: str) -> str:
"""sets and returns the hashed password"""
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
self.password_hash = pwd_context.hash(password)
return self.password_hash
def verify_password(self, password: str) -> bool:
"""returns True if the password matches the hash"""
if not self.password_hash:
return False
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
return pwd_context.verify(password, self.password_hash)
class AccountFilters(FilterModel):
@@ -126,7 +150,7 @@ class AccountFilters(FilterModel):
]
id: str
last_payment: Optional[datetime.datetime] = None
last_payment: Optional[datetime] = None
transaction_count: Optional[int] = None
wallet_count: Optional[int] = None
username: Optional[str] = None
@@ -135,6 +159,8 @@ class AccountFilters(FilterModel):
class User(BaseModel):
id: str
created_at: datetime
updated_at: datetime
email: Optional[str] = None
username: Optional[str] = None
pubkey: Optional[str] = None
@@ -144,8 +170,6 @@ class User(BaseModel):
super_user: bool = False
has_password: bool = False
extra: Optional[UserExtra] = None
created_at: Optional[int] = None
updated_at: Optional[int] = None
@property
def wallet_ids(self) -> list[str]:
@@ -237,7 +261,7 @@ class CreatePayment(BaseModel):
amount: int
memo: str
preimage: Optional[str] = None
expiry: Optional[datetime.datetime] = None
expiry: Optional[datetime] = None
extra: Optional[dict] = None
webhook: Optional[str] = None
fee: int = 0
@@ -336,11 +360,11 @@ class PaymentFilters(FilterModel):
amount: int
fee: int
memo: Optional[str]
time: datetime.datetime
time: datetime
bolt11: str
preimage: str
payment_hash: str
expiry: Optional[datetime.datetime]
expiry: Optional[datetime]
extra: dict = {}
wallet_id: str
webhook: Optional[str]
@@ -348,7 +372,7 @@ class PaymentFilters(FilterModel):
class PaymentHistoryPoint(BaseModel):
date: datetime.datetime
date: datetime
income: int
spending: int
balance: int
+17 -51
View File
@@ -1,11 +1,12 @@
import asyncio
import json
import time
from datetime import datetime
from io import BytesIO
from pathlib import Path
from typing import Optional
from urllib.parse import parse_qs, urlparse
from uuid import UUID, uuid4
from uuid import uuid4
import httpx
from bolt11 import MilliSatoshi
@@ -13,7 +14,6 @@ from bolt11 import decode as bolt11_decode
from cryptography.hazmat.primitives import serialization
from fastapi import Depends, WebSocket
from loguru import logger
from passlib.context import CryptContext
from py_vapid import Vapid
from py_vapid.utils import b64urlencode
@@ -52,8 +52,6 @@ from .crud import (
create_payment,
create_wallet,
get_account,
get_account_by_email,
get_account_by_username,
get_payments,
get_standalone_payment,
get_super_settings,
@@ -64,16 +62,15 @@ from .crud import (
update_payment_details,
update_payment_status,
update_super_user,
update_user_extension,
)
from .helpers import to_valid_user_id
from .models import (
Account,
BalanceDelta,
CreatePayment,
Payment,
PaymentState,
User,
UserConfig,
UserExtra,
Wallet,
)
@@ -762,7 +759,7 @@ async def check_admin_settings():
send_admin_user_to_saas()
account = await get_account(settings.super_user)
if account and account.config and account.config.provider == "env":
if account and account.extra and account.extra.provider == "env":
settings.first_install = True
logger.success(
@@ -809,59 +806,28 @@ def update_cached_settings(sets_dict: dict):
async def init_admin_settings(super_user: Optional[str] = None) -> SuperSettings:
async def new_account(account_id: str) -> Account:
now = datetime.now()
account = Account(
id=account_id,
extra=UserExtra(provider="env"),
created_at=now,
updated_at=now,
)
await create_account(account)
return account
account = None
if super_user:
account = await get_account(super_user)
if not account:
account = await create_account(
user_id=super_user, user_config=UserConfig(provider="env")
)
if not account.wallets or len(account.wallets) == 0:
account = await new_account(super_user or uuid4().hex)
await create_wallet(user_id=account.id)
editable_settings = EditableSettings.from_dict(settings.dict())
return await create_admin_settings(account.id, editable_settings.dict())
async def create_user_account(
user_id: Optional[str] = None,
email: Optional[str] = None,
username: Optional[str] = None,
pubkey: Optional[str] = None,
password: Optional[str] = None,
wallet_name: Optional[str] = None,
user_config: Optional[UserConfig] = None,
) -> User:
if not settings.new_accounts_allowed:
raise ValueError("Account creation is disabled.")
if username and await get_account_by_username(username):
raise ValueError("Username already exists.")
if email and await get_account_by_email(email):
raise ValueError("Email already exists.")
if user_id:
user_uuid4 = UUID(hex=user_id, version=4)
assert user_uuid4.hex == user_id, "User ID is not valid UUID4 hex string"
else:
user_id = uuid4().hex
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
password = pwd_context.hash(password) if password else None
account = await create_account(
user_id, username, pubkey, email, password, user_config
)
wallet = await create_wallet(user_id=account.id, wallet_name=wallet_name)
account.wallets = [wallet]
for ext_id in settings.lnbits_user_default_extensions:
await update_user_extension(user_id=account.id, extension=ext_id, active=True)
return account
class WebsocketConnectionManager:
def __init__(self) -> None:
self.active_connections: list[WebSocket] = []
+11 -14
View File
@@ -3,7 +3,6 @@ import json
from http import HTTPStatus
from io import BytesIO
from time import time
from typing import Dict, List
from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse
import httpx
@@ -13,8 +12,9 @@ from fastapi import (
Depends,
)
from fastapi.exceptions import HTTPException
from starlette.responses import StreamingResponse
from fastapi.responses import StreamingResponse
from lnbits.core.crud import create_account, create_wallet
from lnbits.core.models import (
BaseWallet,
ConversionData,
@@ -38,11 +38,7 @@ from lnbits.utils.exchange_rates import (
satoshis_amount_as_fiat,
)
from ..services import create_user_account, perform_lnurlauth
# backwards compatibility for extension
# TODO: remove api_payment and pay_invoice imports from extensions
from .payment_api import api_payment, pay_invoice # noqa: F401
from ..services import perform_lnurlauth
api_router = APIRouter(tags=["Core"])
@@ -61,7 +57,7 @@ async def health() -> dict:
name="Wallets",
description="Get basic info for all of user's wallets.",
)
async def api_wallets(user: User = Depends(check_user_exists)) -> List[BaseWallet]:
async def api_wallets(user: User = Depends(check_user_exists)) -> list[BaseWallet]:
return [BaseWallet(**w.dict()) for w in user.wallets]
@@ -72,8 +68,9 @@ async def api_create_account(data: CreateWallet) -> Wallet:
status_code=HTTPStatus.FORBIDDEN,
detail="Account creation is disabled.",
)
account = await create_user_account(wallet_name=data.name)
return account.wallets[0]
account = await create_account()
wallet = await create_wallet(user_id=account.id, wallet_name=data.name)
return wallet
@api_router.get("/api/v1/lnurlscan/{code}")
@@ -101,7 +98,7 @@ async def api_lnurlscan(
) from exc
# params is what will be returned to the client
params: Dict = {"domain": domain}
params: dict = {"domain": domain}
if "tag=login" in url:
params.update(kind="auth")
@@ -150,7 +147,7 @@ async def api_lnurlscan(
# callback with k1 already in it
parsed_callback: ParseResult = urlparse(data["callback"])
qs: Dict = parse_qs(parsed_callback.query)
qs: dict = parse_qs(parsed_callback.query)
qs["k1"] = data["k1"]
# balanceCheck/balanceNotify
@@ -207,13 +204,13 @@ async def api_perform_lnurlauth(
@api_router.get("/api/v1/rate/{currency}")
async def api_check_fiat_rate(currency: str) -> Dict[str, float]:
async def api_check_fiat_rate(currency: str) -> dict[str, float]:
rate = await get_fiat_rate_satoshis(currency)
return {"rate": rate}
@api_router.get("/api/v1/currencies")
async def api_list_currencies_available() -> List[str]:
async def api_list_currencies_available() -> list[str]:
return allowed_currencies()
+163 -171
View File
@@ -2,18 +2,13 @@ import base64
import importlib
import json
from time import time
from http import HTTPStatus
from typing import Callable, Optional
from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi import APIRouter, Depends, HTTPException, Request
from fastapi.responses import JSONResponse, RedirectResponse
from fastapi_sso.sso.base import OpenID, SSOBase
from loguru import logger
from starlette.status import (
HTTP_400_BAD_REQUEST,
HTTP_401_UNAUTHORIZED,
HTTP_403_FORBIDDEN,
HTTP_500_INTERNAL_SERVER_ERROR,
)
from lnbits.core.services import create_user_account
from lnbits.decorators import access_token_payload, check_user_exists
@@ -23,14 +18,17 @@ from lnbits.helpers import (
encrypt_internal_message,
is_valid_email_address,
is_valid_username,
urlsafe_short_hash,
)
from lnbits.settings import AuthMethods, settings
from lnbits.utils.nostr import normalize_public_key, verify_event
from ..crud import (
create_account,
get_account,
get_account_by_email,
get_account_by_pubkey,
get_account_by_username,
get_account_by_username_or_email,
get_user,
get_user_password,
@@ -39,8 +37,10 @@ from ..crud import (
update_user_pubkey,
verify_user_password,
)
from ..models import (
AccessTokenPayload,
Account,
CreateUser,
LoginUsernamePassword,
LoginUsr,
@@ -50,7 +50,7 @@ from ..models import (
UpdateUserPassword,
UpdateUserPubkey,
User,
UserConfig,
UserExtra,
)
auth_router = APIRouter(prefix="/api/v1/auth", tags=["Auth"])
@@ -65,23 +65,14 @@ async def get_auth_user(user: User = Depends(check_user_exists)) -> User:
async def login(data: LoginUsernamePassword) -> JSONResponse:
if not settings.is_auth_method_allowed(AuthMethods.username_and_password):
raise HTTPException(
HTTP_401_UNAUTHORIZED, "Login by 'Username and Password' not allowed."
HTTPStatus.UNAUTHORIZED, "Login by 'Username and Password' not allowed."
)
try:
user = await get_account_by_username_or_email(data.username)
if not user:
raise HTTPException(HTTP_401_UNAUTHORIZED, "Invalid credentials.")
if not await verify_user_password(user.id, data.password):
raise HTTPException(HTTP_401_UNAUTHORIZED, "Invalid credentials.")
return _auth_success_response(user.username, user.id, user.email)
except HTTPException as exc:
raise exc
except Exception as exc:
logger.debug(exc)
raise HTTPException(HTTP_500_INTERNAL_SERVER_ERROR, "Cannot login.") from exc
account = await get_account_by_username_or_email(data.username)
if not account or not account.verify_password(data.password):
raise HTTPException(
status_code=HTTPStatus.UNAUTHORIZED, detail="Invalid credentials."
)
return _auth_success_response(account.username, account.id)
@auth_router.post("/nostr", description="Login via Nostr")
@@ -111,19 +102,16 @@ async def nostr_login(request: Request) -> JSONResponse:
@auth_router.post("/usr", description="Login via the User ID")
async def login_usr(data: LoginUsr) -> JSONResponse:
if not settings.is_auth_method_allowed(AuthMethods.user_id_only):
raise HTTPException(HTTP_401_UNAUTHORIZED, "Login by 'User ID' not allowed.")
try:
user = await get_user(data.usr)
if not user:
raise HTTPException(HTTP_401_UNAUTHORIZED, "User ID does not exist.")
return _auth_success_response(user.username or "", user.id, user.email)
except HTTPException as exc:
raise exc
except Exception as exc:
logger.debug(exc)
raise HTTPException(HTTP_500_INTERNAL_SERVER_ERROR, "Cannot login.") from exc
raise HTTPException(
status_code=HTTPStatus.UNAUTHORIZED,
detail="Login by 'User ID' not allowed.",
)
account = await get_account(data.usr)
if not account:
raise HTTPException(
status_code=HTTPStatus.UNAUTHORIZED, detail="User ID does not exist."
)
return _auth_success_response(account.username, account.id)
@auth_router.get("/{provider}", description="SSO Provider")
@@ -133,7 +121,8 @@ async def login_with_sso_provider(
provider_sso = _new_sso(provider)
if not provider_sso:
raise HTTPException(
HTTP_401_UNAUTHORIZED, f"Login by '{provider}' not allowed."
status_code=HTTPStatus.UNAUTHORIZED,
detail=f"Login by '{provider}' not allowed.",
)
provider_sso.redirect_uri = str(request.base_url) + f"api/v1/auth/{provider}/token"
@@ -147,7 +136,8 @@ async def handle_oauth_token(request: Request, provider: str) -> RedirectRespons
provider_sso = _new_sso(provider)
if not provider_sso:
raise HTTPException(
HTTP_401_UNAUTHORIZED, f"Login by '{provider}' not allowed."
status_code=HTTPStatus.UNAUTHORIZED,
detail=f"Login by '{provider}' not allowed.",
)
try:
@@ -160,18 +150,18 @@ async def handle_oauth_token(request: Request, provider: str) -> RedirectRespons
except HTTPException as exc:
raise exc
except ValueError as exc:
raise HTTPException(HTTP_403_FORBIDDEN, str(exc)) from exc
raise HTTPException(HTTPStatus.FORBIDDEN, str(exc)) from exc
except Exception as exc:
logger.debug(exc)
raise HTTPException(
HTTP_500_INTERNAL_SERVER_ERROR,
f"Cannot authenticate user with {provider} Auth.",
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail=f"Cannot authenticate user with {provider} Auth.",
) from exc
@auth_router.post("/logout")
async def logout() -> JSONResponse:
response = JSONResponse({"status": "success"}, status_code=status.HTTP_200_OK)
response = JSONResponse({"status": "success"}, status_code=HTTPStatus.OK)
response.delete_cookie("cookie_access_token")
response.delete_cookie("is_lnbits_user_authorized")
response.delete_cookie("is_access_token_expired")
@@ -184,62 +174,36 @@ async def logout() -> JSONResponse:
async def register(data: CreateUser) -> JSONResponse:
if not settings.is_auth_method_allowed(AuthMethods.username_and_password):
raise HTTPException(
HTTP_401_UNAUTHORIZED, "Register by 'Username and Password' not allowed."
status_code=HTTPStatus.UNAUTHORIZED,
detail="Register by 'Username and Password' not allowed.",
)
if data.password != data.password_repeat:
raise HTTPException(HTTP_400_BAD_REQUEST, "Passwords do not match.")
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail="Passwords do not match."
)
if not data.username:
raise HTTPException(HTTP_400_BAD_REQUEST, "Missing username.")
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail="Missing username."
)
if not is_valid_username(data.username):
raise HTTPException(HTTP_400_BAD_REQUEST, "Invalid username.")
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail="Invalid username."
)
if data.email and not is_valid_email_address(data.email):
raise HTTPException(HTTP_400_BAD_REQUEST, "Invalid email.")
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Invalid email.")
try:
user = await create_user_account(
email=data.email, username=data.username, password=data.password
)
return _auth_success_response(user.username, user.id, user.email)
account = Account(
id=urlsafe_short_hash(),
email=data.email,
username=data.username,
)
account.hash_password(data.password)
await create_account(account)
return _auth_success_response(account.username)
except ValueError as exc:
raise HTTPException(HTTP_403_FORBIDDEN, str(exc)) from exc
except Exception as exc:
logger.debug(exc)
raise HTTPException(
HTTP_500_INTERNAL_SERVER_ERROR, "Cannot create user."
) from exc
@auth_router.put("/password")
async def update_password(
data: UpdateUserPassword,
user: User = Depends(check_user_exists),
payload: AccessTokenPayload = Depends(access_token_payload),
) -> Optional[User]:
if data.user_id != user.id:
raise HTTPException(HTTP_400_BAD_REQUEST, "Invalid user ID.")
try:
if data.username and not user.username:
await update_account(user_id=user.id, username=data.username)
# old accounts do not have a pasword
if await get_user_password(data.user_id):
assert data.password_old, "Missing old password"
old_pwd_ok = await verify_user_password(data.user_id, data.password_old)
assert old_pwd_ok, "Invalid credentials."
return await update_user_password(data, payload.auth_time or 0)
except AssertionError as exc:
raise HTTPException(HTTP_403_FORBIDDEN, str(exc)) from exc
except Exception as exc:
logger.debug(exc)
raise HTTPException(
HTTP_500_INTERNAL_SERVER_ERROR, "Cannot update user password."
) from exc
@auth_router.put("/pubkey")
@@ -259,52 +223,68 @@ async def update_pubkey(
raise HTTPException(HTTP_403_FORBIDDEN, str(exc)) from exc
except Exception as exc:
logger.debug(exc)
@auth_router.put("/password")
async def update_password(
data: UpdateUserPassword,
user: User = Depends(check_user_exists),
payload: AccessTokenPayload = Depends(access_token_payload),
) -> Optional[User]:
if data.user_id != user.id:
raise HTTPException(
HTTP_500_INTERNAL_SERVER_ERROR, "Cannot update user pubkey."
) from exc
status_code=HTTPStatus.BAD_REQUEST, detail="Invalid user ID."
)
if data.username and await get_account_by_username(data.username):
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail="Username already exists."
)
account = await get_account(user.id)
if not account:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Account not found."
)
account.username = data.username
account.hash_password(data.password)
await update_account(account)
return await get_user(account)
@auth_router.put("/reset")
async def reset_password(data: ResetUserPassword) -> JSONResponse:
if not settings.is_auth_method_allowed(AuthMethods.username_and_password):
raise HTTPException(
HTTP_401_UNAUTHORIZED, "Auth by 'Username and Password' not allowed."
HTTPStatus.UNAUTHORIZED, "Auth by 'Username and Password' not allowed."
)
try:
assert data.reset_key[:10] == "reset_key_", "This is not a reset key."
assert data.reset_key[:10] == "reset_key_", "This is not a reset key."
reset_data_json = decrypt_internal_message(
base64.b64decode(data.reset_key[10:]).decode()
)
assert reset_data_json, "Cannot process reset key."
reset_data_json = decrypt_internal_message(
base64.b64decode(data.reset_key[10:]).decode()
)
assert reset_data_json, "Cannot process reset key."
action, user_id, request_time = json.loads(reset_data_json)
assert action == "reset", "Expected reset action."
assert user_id is not None, "Missing user ID."
assert request_time is not None, "Missing reset time."
action, user_id, request_time = json.loads(reset_data_json)
assert action == "reset", "Expected reset action."
assert user_id is not None, "Missing user ID."
assert request_time is not None, "Missing reset time."
user = await get_account(user_id)
assert user, "User not found."
user = await get_account(user_id)
assert user, "User not found."
update_pwd = UpdateUserPassword(
user_id=user.id,
username=user.username or "",
password=data.password,
password_repeat=data.password_repeat,
)
user = await update_user_password(update_pwd, request_time)
update_pwd = UpdateUserPassword(
user_id=user.id,
username=user.username or "",
password=data.password,
password_repeat=data.password_repeat,
)
user = await update_user_password(update_pwd, request_time)
return _auth_success_response(
username=user.username, user_id=user_id, email=user.email
)
except AssertionError as exc:
raise HTTPException(HTTP_403_FORBIDDEN, str(exc)) from exc
except Exception as exc:
logger.warning(exc)
raise HTTPException(
HTTP_500_INTERNAL_SERVER_ERROR, "Cannot reset user password."
) from exc
return _auth_success_response(
username=user.username, user_id=user_id, email=user.email
)
@auth_router.put("/update")
@@ -312,80 +292,92 @@ async def update(
data: UpdateUser, user: User = Depends(check_user_exists)
) -> Optional[User]:
if data.user_id != user.id:
raise HTTPException(HTTP_400_BAD_REQUEST, "Invalid user ID.")
if data.username and not is_valid_username(data.username):
raise HTTPException(HTTP_400_BAD_REQUEST, "Invalid username.")
if data.email != user.email:
raise HTTPException(HTTP_400_BAD_REQUEST, "Email mismatch.")
try:
return await update_account(user.id, data.username, None, data.config)
except AssertionError as exc:
raise HTTPException(HTTP_403_FORBIDDEN, str(exc)) from exc
except Exception as exc:
logger.debug(exc)
raise HTTPException(
HTTP_500_INTERNAL_SERVER_ERROR, "Cannot update user."
) from exc
status_code=HTTPStatus.BAD_REQUEST, detail="Invalid user ID."
)
if data.username and not is_valid_username(data.username):
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail="Invalid username."
)
if data.email != user.email:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail="Email mismatch.",
)
account = await get_account(user.id)
if not account:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Account not found."
)
if data.username and await get_account_by_username(data.username):
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail="Username already exists."
)
if data.email and await get_account_by_email(data.email):
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail="Email already exists."
)
account = await get_account(user.id)
if not account:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Account not found."
)
if data.username:
account.username = data.username
if data.email:
account.email = data.email
if data.extra:
account.extra = data.extra
await update_account(account)
return await get_user(account)
@auth_router.put("/first_install")
async def first_install(data: UpdateSuperuserPassword) -> JSONResponse:
if not settings.first_install:
raise HTTPException(HTTP_401_UNAUTHORIZED, "This is not your first install")
try:
await update_account(
user_id=settings.super_user,
username=data.username,
user_config=UserConfig(provider="lnbits"),
)
super_user = UpdateUserPassword(
user_id=settings.super_user,
password=data.password,
password_repeat=data.password_repeat,
username=data.username,
)
user = await update_user_password(super_user, int(time()))
settings.first_install = False
return _auth_success_response(user.username, user.id, user.email)
except AssertionError as exc:
raise HTTPException(HTTP_403_FORBIDDEN, str(exc)) from exc
except Exception as exc:
logger.debug(exc)
raise HTTPException(
HTTP_500_INTERNAL_SERVER_ERROR, "Cannot init user password."
) from exc
raise HTTPException(HTTPStatus.UNAUTHORIZED, "This is not your first install")
account = await get_account(settings.super_user)
if not account:
raise HTTPException(HTTPStatus.INTERNAL_SERVER_ERROR, "Superuser not found.")
account.username = data.username
account.extra = account.extra or UserExtra()
account.extra.provider = "lnbits"
account.hash_password(data.password)
await update_account(account)
settings.first_install = False
return _auth_success_response(username=account.username)
async def _handle_sso_login(userinfo: OpenID, verified_user_id: Optional[str] = None):
email = userinfo.email
if not email or not is_valid_email_address(email):
raise HTTPException(HTTP_400_BAD_REQUEST, "Invalid email.")
raise HTTPException(HTTPStatus.BAD_REQUEST, "Invalid email.")
redirect_path = "/wallet"
user_config = UserConfig(**dict(userinfo))
user_config.email_verified = True
account = await get_account_by_email(email)
if verified_user_id:
if account:
raise HTTPException(HTTP_401_UNAUTHORIZED, "Email already used.")
raise HTTPException(HTTPStatus.UNAUTHORIZED, "Email already used.")
account = await get_account(verified_user_id)
if not account:
raise HTTPException(HTTP_401_UNAUTHORIZED, "Cannot verify user email.")
raise HTTPException(HTTPStatus.UNAUTHORIZED, "Cannot verify user email.")
redirect_path = "/account"
if account:
user = await update_account(account.id, email=email, user_config=user_config)
account.extra = account.extra or UserExtra()
account.extra.email_verified = True
await update_account(account)
else:
if not settings.new_accounts_allowed:
raise HTTPException(HTTP_400_BAD_REQUEST, "Account creation is disabled.")
user = await create_user_account(email=email, user_config=user_config)
if not user:
raise HTTPException(HTTP_401_UNAUTHORIZED, "User not found.")
raise HTTPException(HTTPStatus.BAD_REQUEST, "Account creation is disabled.")
account = Account(
id=urlsafe_short_hash(), email=email, extra=UserExtra(email_verified=True)
)
await create_account(account)
return _auth_redirect_response(redirect_path, email)
+77 -61
View File
@@ -1,7 +1,4 @@
from http import HTTPStatus
from typing import (
List,
)
from bolt11 import decode as bolt11_decode
from fastapi import (
@@ -25,6 +22,7 @@ from lnbits.core.extensions.models import (
InstallableExtension,
PayToEnableInfo,
ReleasePaymentInfo,
UserExtension,
UserExtensionInfo,
)
from lnbits.core.models import (
@@ -38,6 +36,7 @@ from lnbits.decorators import (
)
from ..crud import (
create_user_extension,
delete_dbversion,
drop_extension_db,
get_dbversions,
@@ -46,7 +45,6 @@ from ..crud import (
get_user_extension,
update_extension_pay_to_enable,
update_user_extension,
update_user_extension_extra,
)
extension_router = APIRouter(
@@ -176,18 +174,24 @@ async def api_enable_extension(
assert ext, f"Extension '{ext_id}' is not installed."
assert ext.active, f"Extension '{ext_id}' is not activated."
user_ext = await get_user_extension(user.id, ext_id)
if not user_ext:
user_ext = UserExtension(user=user.id, extension=ext_id, active=False)
await create_user_extension(user_ext)
if user.admin or not ext.requires_payment:
await update_user_extension(user_id=user.id, extension=ext_id, active=True)
user_ext.active = True
await update_user_extension(user_ext)
return SimpleStatus(success=True, message=f"Extension '{ext_id}' enabled.")
user_ext = await get_user_extension(user.id, ext_id)
if not (user_ext and user_ext.extra and user_ext.extra.payment_hash_to_enable):
if not (user_ext.extra and user_ext.extra.payment_hash_to_enable):
raise HTTPException(
HTTPStatus.PAYMENT_REQUIRED, f"Extension '{ext_id}' requires payment."
)
if user_ext.is_paid:
await update_user_extension(user_id=user.id, extension=ext_id, active=True)
user_ext.active = True
await update_user_extension(user_ext)
return SimpleStatus(
success=True, message=f"Paid extension '{ext_id}' enabled."
)
@@ -207,10 +211,9 @@ async def api_enable_extension(
f"Invoice generated but not paid for enabeling extension '{ext_id}'.",
)
user_ext.active = True
user_ext.extra.paid_to_enable = True
await update_user_extension_extra(user.id, ext_id, user_ext.extra)
await update_user_extension(user_id=user.id, extension=ext_id, active=True)
await update_user_extension(user_ext)
return SimpleStatus(success=True, message=f"Paid extension '{ext_id}' enabled.")
except AssertionError as exc:
@@ -233,16 +236,15 @@ async def api_disable_extension(
raise HTTPException(
HTTPStatus.BAD_REQUEST, f"Extension '{ext_id}' doesn't exist."
)
try:
logger.info(f"Disabeling extension: {ext_id}.")
await update_user_extension(user_id=user.id, extension=ext_id, active=False)
return SimpleStatus(success=True, message=f"Extension '{ext_id}' disabled.")
except Exception as exc:
logger.warning(exc)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail=(f"Failed to disable '{ext_id}'."),
) from exc
user_ext = await get_user_extension(user.id, ext_id)
if not user_ext or not user_ext.active:
return SimpleStatus(
success=True, message=f"Extension '{ext_id}' already disabled."
)
logger.info(f"Disabeling extension: {ext_id}.")
user_ext.active = False
await update_user_extension(user_ext)
return SimpleStatus(success=True, message=f"Extension '{ext_id}' disabled.")
@extension_router.put("/{ext_id}/activate", dependencies=[Depends(check_admin)])
@@ -319,9 +321,9 @@ async def api_uninstall_extension(ext_id: str) -> SimpleStatus:
@extension_router.get("/{ext_id}/releases", dependencies=[Depends(check_admin)])
async def get_extension_releases(ext_id: str) -> List[ExtensionRelease]:
async def get_extension_releases(ext_id: str) -> list[ExtensionRelease]:
try:
extension_releases: List[ExtensionRelease] = (
extension_releases: list[ExtensionRelease] = (
await InstallableExtension.get_extension_releases(ext_id)
)
@@ -386,45 +388,59 @@ async def get_pay_to_install_invoice(
async def get_pay_to_enable_invoice(
ext_id: str, data: PayToEnableInfo, user: User = Depends(check_user_exists)
):
try:
assert data.amount and data.amount > 0, "A non-zero amount must be specified."
ext = await get_installed_extension(ext_id)
assert ext, f"Extension '{ext_id}' not found."
assert ext.pay_to_enable, f"Payment Info not found for extension '{ext_id}'."
assert (
ext.pay_to_enable.required
), f"Payment not required for extension '{ext_id}'."
assert ext.pay_to_enable.wallet and ext.pay_to_enable.amount, (
f"Payment wallet or amount missing for extension '{ext_id}'."
"Please contact the administrator."
)
assert (
data.amount >= ext.pay_to_enable.amount
), f"Minimum amount is {ext.pay_to_enable.amount} sats."
payment_hash, payment_request = await create_invoice(
wallet_id=ext.pay_to_enable.wallet,
amount=data.amount,
memo=f"Enable '{ext.name}' extension.",
)
user_ext = await get_user_extension(user.id, ext_id)
user_ext_info = (
user_ext.extra if user_ext and user_ext.extra else UserExtensionInfo()
)
user_ext_info.payment_hash_to_enable = payment_hash
await update_user_extension_extra(user.id, ext_id, user_ext_info)
return {"payment_hash": payment_hash, "payment_request": payment_request}
except AssertionError as exc:
raise HTTPException(HTTPStatus.BAD_REQUEST, str(exc)) from exc
except Exception as exc:
logger.warning(exc)
if not data.amount or data.amount <= 0:
raise HTTPException(
HTTPStatus.INTERNAL_SERVER_ERROR, "Cannot request invoice."
) from exc
status_code=HTTPStatus.BAD_REQUEST, detail="Amount must be greater than 0."
)
ext = await get_installed_extension(ext_id)
if not ext:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail=f"Extension '{ext_id}' not found."
)
if not ext.pay_to_enable:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=f"Payment info not found for extension '{ext_id}'.",
)
if not ext.pay_to_enable.required:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=f"Payment not required for extension '{ext_id}'.",
)
if not ext.pay_to_enable.wallet or not ext.pay_to_enable.amount:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=f"Payment wallet or amount missing for extension '{ext_id}'.",
)
if data.amount < ext.pay_to_enable.amount:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=(
f"Amount {data.amount} sats is less than required "
f"{ext.pay_to_enable.amount} sats."
),
)
payment_hash, payment_request = await create_invoice(
wallet_id=ext.pay_to_enable.wallet,
amount=data.amount,
memo=f"Enable '{ext.name}' extension.",
)
user_ext = await get_user_extension(user.id, ext_id)
if not user_ext:
user_ext = UserExtension(user=user.id, extension=ext_id, active=False)
await create_user_extension(user_ext)
user_ext_info = user_ext.extra if user_ext.extra else UserExtensionInfo()
user_ext_info.payment_hash_to_enable = payment_hash
user_ext.extra = user_ext_info
await update_user_extension(user_ext)
return {"payment_hash": payment_hash, "payment_request": payment_request}
@extension_router.get(
+14 -14
View File
@@ -25,9 +25,11 @@ from ...utils.exchange_rates import allowed_currencies, currencies
from ..crud import (
create_account,
create_wallet,
get_account,
get_dbversions,
get_installed_extensions,
get_user,
get_wallet,
)
generic_router = APIRouter(
@@ -136,7 +138,8 @@ async def extensions(request: Request, user: User = Depends(check_user_exists)):
]
# refresh user state. Eg: enabled extensions.
user = await get_user(user.id) or user
# TODO: refactor
# user = await get_user(user.id) or user
return template_renderer().TemplateResponse(
request,
@@ -165,18 +168,16 @@ async def wallet(
wal: Optional[UUID4] = Query(None),
):
if wal:
wallet_id = wal.hex
wallet = await get_wallet(wal.hex)
elif len(user.wallets) == 0:
wallet = await create_wallet(user_id=user.id)
user = await get_user(user_id=user.id) or user
wallet_id = wallet.id
user.wallets.append(wallet)
elif lnbits_last_active_wallet and user.get_wallet(lnbits_last_active_wallet):
wallet_id = lnbits_last_active_wallet
wallet = await get_wallet(lnbits_last_active_wallet)
else:
wallet_id = user.wallets[0].id
wallet = user.wallets[0]
user_wallet = user.get_wallet(wallet_id)
if not user_wallet or user_wallet.deleted:
if not wallet or wallet.deleted:
return template_renderer().TemplateResponse(
request, "error.html", {"err": "Wallet not found"}, HTTPStatus.NOT_FOUND
)
@@ -186,14 +187,14 @@ async def wallet(
"core/wallet.html",
{
"user": user.dict(),
"wallet": user_wallet.dict(),
"wallet": wallet.dict(),
"currencies": allowed_currencies(),
"service_fee": settings.lnbits_service_fee,
"service_fee_max": settings.lnbits_service_fee_max,
"web_manifest": f"/manifest/{user.id}.webmanifest",
},
)
resp.set_cookie("lnbits_last_active_wallet", wallet_id)
resp.set_cookie("lnbits_last_active_wallet", wallet.id)
return resp
@@ -228,11 +229,10 @@ async def service_worker(request: Request):
@generic_router.get("/manifest/{usr}.webmanifest")
async def manifest(request: Request, usr: str):
host = urlparse(str(request.url)).netloc
user = await get_user(usr)
if not user:
account = await get_account(usr)
if not account:
raise HTTPException(status_code=HTTPStatus.NOT_FOUND)
user = await get_user(account)
return {
"short_name": settings.lnbits_site_title,
"name": settings.lnbits_site_title + " Wallet",
+1 -11
View File
@@ -41,17 +41,7 @@ users_router = APIRouter(prefix="/users/api/v1", dependencies=[Depends(check_adm
async def api_get_users(
filters: Filters = Depends(parse_filters(AccountFilters)),
) -> Page[Account]:
try:
filtered = await get_accounts(filters=filters)
for user in filtered.data:
user.is_super_user = user.id == settings.super_user
user.is_admin = user.id in settings.lnbits_admin_users or user.is_super_user
return filtered
except Exception as exc:
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail=f"Could not fetch users. {exc!s}",
) from exc
return await get_accounts(filters=filters)
@users_router.delete("/user/{user_id}", status_code=HTTPStatus.OK)
+2 -5
View File
@@ -65,7 +65,7 @@ class KeyChecker(SecurityBase):
name="X-API-KEY",
description="Wallet API Key - HEADER",
)
self.model: APIKey = openapi_model
self.model: APIKey = openapi_model # type: ignore
async def __call__(self, request: Request) -> WalletTypeInfo:
@@ -147,11 +147,8 @@ async def check_user_exists(
if not account or not settings.is_user_allowed(account.id):
raise HTTPException(HTTPStatus.UNAUTHORIZED, "User not allowed.")
user = await get_user(account.id)
assert user, "User not found for account."
user = await get_user(account)
await _check_user_extension_access(user.id, r["path"])
return user
+4 -1
View File
@@ -17,6 +17,7 @@ from lnbits.core.crud import (
create_account,
create_wallet,
get_account_by_username,
get_account,
get_user,
update_payment_status,
)
@@ -148,7 +149,9 @@ def from_super_user(from_user):
@pytest_asyncio.fixture(scope="session")
async def superuser():
user = await get_user(settings.super_user)
account = await get_account(settings.super_user)
assert account, "Superuser not found"
user = await get_user(account)
yield user