fix: delete user with wallets (#3711)
This commit is contained in:
@@ -45,17 +45,13 @@ async def update_wallet(
|
|||||||
|
|
||||||
|
|
||||||
async def delete_wallet(
|
async def delete_wallet(
|
||||||
*,
|
|
||||||
user_id: str,
|
user_id: str,
|
||||||
wallet_id: str,
|
wallet_id: str,
|
||||||
deleted: bool = True,
|
deleted: bool = True,
|
||||||
conn: Connection | None = None,
|
conn: Connection | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
_clear_wallet_cache(wallet_id)
|
||||||
now = int(time())
|
now = int(time())
|
||||||
cached_wallet: BaseWallet | None = cache.pop(f"auth:wallet:{wallet_id}")
|
|
||||||
if cached_wallet:
|
|
||||||
cache.pop(f"auth:x-api-key:{cached_wallet.adminkey}")
|
|
||||||
cache.pop(f"auth:x-api-key:{cached_wallet.inkey}")
|
|
||||||
|
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
# Timestamp placeholder is safe from SQL injection (not user input)
|
# Timestamp placeholder is safe from SQL injection (not user input)
|
||||||
@@ -69,6 +65,7 @@ async def delete_wallet(
|
|||||||
|
|
||||||
|
|
||||||
async def force_delete_wallet(wallet_id: str, conn: Connection | None = None) -> None:
|
async def force_delete_wallet(wallet_id: str, conn: Connection | None = None) -> None:
|
||||||
|
_clear_wallet_cache(wallet_id)
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"DELETE FROM wallets WHERE id = :wallet",
|
"DELETE FROM wallets WHERE id = :wallet",
|
||||||
{"wallet": wallet_id},
|
{"wallet": wallet_id},
|
||||||
@@ -78,6 +75,7 @@ async def force_delete_wallet(wallet_id: str, conn: Connection | None = None) ->
|
|||||||
async def delete_wallet_by_id(
|
async def delete_wallet_by_id(
|
||||||
wallet_id: str, conn: Connection | None = None
|
wallet_id: str, conn: Connection | None = None
|
||||||
) -> int | None:
|
) -> int | None:
|
||||||
|
_clear_wallet_cache(wallet_id)
|
||||||
now = int(time())
|
now = int(time())
|
||||||
result = await (conn or db).execute(
|
result = await (conn or db).execute(
|
||||||
# Timestamp placeholder is safe from SQL injection (not user input)
|
# Timestamp placeholder is safe from SQL injection (not user input)
|
||||||
@@ -294,3 +292,10 @@ async def get_total_balance(conn: Connection | None = None):
|
|||||||
result = await (conn or db).execute("SELECT SUM(balance) as balance FROM balances")
|
result = await (conn or db).execute("SELECT SUM(balance) as balance FROM balances")
|
||||||
row = result.mappings().first()
|
row = result.mappings().first()
|
||||||
return row.get("balance", 0) or 0
|
return row.get("balance", 0) or 0
|
||||||
|
|
||||||
|
|
||||||
|
def _clear_wallet_cache(wallet_id):
|
||||||
|
cached_wallet: BaseWallet | None = cache.pop(f"auth:wallet:{wallet_id}")
|
||||||
|
if cached_wallet:
|
||||||
|
cache.pop(f"auth:x-api-key:{cached_wallet.adminkey}")
|
||||||
|
cache.pop(f"auth:x-api-key:{cached_wallet.inkey}")
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ from lnbits.core.crud import (
|
|||||||
update_admin_settings,
|
update_admin_settings,
|
||||||
update_wallet,
|
update_wallet,
|
||||||
)
|
)
|
||||||
|
from lnbits.core.crud.wallets import delete_wallet_by_id
|
||||||
from lnbits.core.models import (
|
from lnbits.core.models import (
|
||||||
AccountFilters,
|
AccountFilters,
|
||||||
AccountOverview,
|
AccountOverview,
|
||||||
@@ -157,11 +158,8 @@ async def api_users_delete_user(
|
|||||||
user_id: str, account: Account = Depends(check_admin)
|
user_id: str, account: Account = Depends(check_admin)
|
||||||
) -> SimpleStatus:
|
) -> SimpleStatus:
|
||||||
wallets = await get_wallets(user_id, deleted=False)
|
wallets = await get_wallets(user_id, deleted=False)
|
||||||
if len(wallets) > 0:
|
for wallet in wallets:
|
||||||
raise HTTPException(
|
await delete_wallet_by_id(wallet.id)
|
||||||
status_code=HTTPStatus.BAD_REQUEST,
|
|
||||||
detail="Cannot delete user with wallets.",
|
|
||||||
)
|
|
||||||
|
|
||||||
if user_id == settings.super_user:
|
if user_id == settings.super_user:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import pytest
|
|||||||
import shortuuid
|
import shortuuid
|
||||||
from httpx import AsyncClient
|
from httpx import AsyncClient
|
||||||
|
|
||||||
|
from lnbits.core.crud.wallets import get_wallets
|
||||||
from lnbits.core.models.users import Account, User
|
from lnbits.core.models.users import Account, User
|
||||||
from lnbits.core.services.users import create_user_account
|
from lnbits.core.services.users import create_user_account
|
||||||
from lnbits.settings import Settings
|
from lnbits.settings import Settings
|
||||||
@@ -510,3 +511,44 @@ async def test_search_users(http_client: AsyncClient, superuser_token):
|
|||||||
data = create_resp.json()
|
data = create_resp.json()
|
||||||
assert data["total"] == 1
|
assert data["total"] == 1
|
||||||
assert data["data"][0]["username"] == users[0].username
|
assert data["data"][0]["username"] == users[0].username
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
async def test_delete_user_success(http_client: AsyncClient, superuser_token):
|
||||||
|
# Create a user first
|
||||||
|
tiny_id = shortuuid.uuid()[:8]
|
||||||
|
data = {
|
||||||
|
"username": f"delete_{tiny_id}",
|
||||||
|
"password": "secret1234",
|
||||||
|
"password_repeat": "secret1234",
|
||||||
|
"email": f"delete_{tiny_id}@lnbits.com",
|
||||||
|
}
|
||||||
|
create_resp = await http_client.post(
|
||||||
|
"/users/api/v1/user",
|
||||||
|
json=data,
|
||||||
|
headers={"Authorization": f"Bearer {superuser_token}"},
|
||||||
|
)
|
||||||
|
assert create_resp.status_code == 200
|
||||||
|
user = create_resp.json()
|
||||||
|
user_id = user["id"]
|
||||||
|
|
||||||
|
wallets = await get_wallets(user_id=user_id)
|
||||||
|
assert len(wallets) == 1
|
||||||
|
|
||||||
|
# Delete the user
|
||||||
|
delete_resp = await http_client.delete(
|
||||||
|
f"/users/api/v1/user/{user_id}",
|
||||||
|
headers={"Authorization": f"Bearer {superuser_token}"},
|
||||||
|
)
|
||||||
|
assert delete_resp.status_code == 200
|
||||||
|
assert delete_resp.json()["success"] is True
|
||||||
|
|
||||||
|
# Ensure user is deleted
|
||||||
|
get_resp = await http_client.get(
|
||||||
|
f"/users/api/v1/user/{user_id}",
|
||||||
|
headers={"Authorization": f"Bearer {superuser_token}"},
|
||||||
|
)
|
||||||
|
assert get_resp.status_code == 404
|
||||||
|
|
||||||
|
wallets = await get_wallets(user_id=user_id)
|
||||||
|
assert len(wallets) == 0
|
||||||
|
|||||||
Reference in New Issue
Block a user