feat: allow creating/update user with nostr npub1 pubkey (#3298)

This commit is contained in:
Tiago Vasconcelos 2025-08-21 15:35:23 +01:00 committed by GitHub
parent 5ba06d42d0
commit ef371e303c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 94 additions and 2 deletions

View file

@ -47,6 +47,7 @@ from lnbits.helpers import (
) )
from lnbits.settings import EditableSettings, settings from lnbits.settings import EditableSettings, settings
from lnbits.utils.exchange_rates import allowed_currencies from lnbits.utils.exchange_rates import allowed_currencies
from lnbits.utils.nostr import normalize_public_key
users_router = APIRouter( users_router = APIRouter(
prefix="/users/api/v1", dependencies=[Depends(check_admin)], tags=["Users"] prefix="/users/api/v1", dependencies=[Depends(check_admin)], tags=["Users"]
@ -94,6 +95,9 @@ async def api_create_user(data: CreateUser) -> CreateUser:
data.extra = data.extra or UserExtra() data.extra = data.extra or UserExtra()
data.extra.provider = data.extra.provider or "lnbits" data.extra.provider = data.extra.provider or "lnbits"
if data.pubkey:
data.pubkey = normalize_public_key(data.pubkey)
account = Account( account = Account(
id=uuid4().hex, id=uuid4().hex,
username=data.username, username=data.username,
@ -127,6 +131,9 @@ async def api_update_user(
HTTPStatus.BAD_REQUEST, "Use 'reset password' functionality." HTTPStatus.BAD_REQUEST, "Use 'reset password' functionality."
) )
if data.pubkey:
data.pubkey = normalize_public_key(data.pubkey)
account = Account( account = Account(
id=user_id, id=user_id,
username=data.username, username=data.username,

View file

@ -6,7 +6,7 @@ from httpx import AsyncClient
from lnbits.core.models.users import User from lnbits.core.models.users import User
from lnbits.settings import Settings from lnbits.settings import Settings
from lnbits.utils.nostr import generate_keypair from lnbits.utils.nostr import generate_keypair, hex_to_npub
@pytest.mark.anyio @pytest.mark.anyio
@ -234,7 +234,6 @@ async def test_update_user_success(http_client: AsyncClient, superuser_token):
async def test_update_bad_external_id( async def test_update_bad_external_id(
http_client: AsyncClient, user_alan: User, superuser_token http_client: AsyncClient, user_alan: User, superuser_token
): ):
update_data = {"id": user_alan.id, "external_id": "external 1234"} update_data = {"id": user_alan.id, "external_id": "external 1234"}
resp = await http_client.put( resp = await http_client.put(
f"/users/api/v1/user/{user_alan.id}", f"/users/api/v1/user/{user_alan.id}",
@ -380,3 +379,89 @@ async def test_update_superuser_only_allowed_by_superuser(
) )
assert resp.json()["detail"] == "Action only allowed for super user." assert resp.json()["detail"] == "Action only allowed for super user."
@pytest.mark.anyio
async def test_create_user_with_npub(http_client: AsyncClient, superuser_token):
tiny_id = shortuuid.uuid()[:8]
_, pubkey = generate_keypair()
data = {
"username": f"user_{tiny_id}",
"password": "secret1234",
"password_repeat": "secret1234",
"email": f"user_{tiny_id}@lnbits.com",
"pubkey": hex_to_npub(pubkey),
}
create_resp = await http_client.post(
"/users/api/v1/user",
json=data,
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert create_resp.status_code == 200
assert create_resp.json()["pubkey"] == pubkey
@pytest.mark.anyio
async def test_update_user_npub_success(http_client: AsyncClient, superuser_token):
# Create a user first
tiny_id = shortuuid.uuid()[:8]
data = {
"username": f"update_{tiny_id}",
"password": "secret1234",
"password_repeat": "secret1234",
"email": f"update_{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_id = create_resp.json()["id"]
# Update the user
_, pubkey = generate_keypair()
update_data = {
"id": user_id,
"username": f"updated_{tiny_id}",
"email": f"updated_{tiny_id}@lnbits.com",
"pubkey": hex_to_npub(pubkey),
"extra": {"provider": "lnbits"},
"extensions": [],
}
resp = await http_client.put(
f"/users/api/v1/user/{user_id}",
json=update_data,
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert resp.status_code == 200
assert resp.json()["username"] == update_data["username"]
assert resp.json()["email"] == update_data["email"]
assert resp.json()["pubkey"] == pubkey
@pytest.mark.anyio
@pytest.mark.parametrize(
"invalid_pubkey",
[
"npub1flrz7qu87n8y04jwy6r74z44pczcwaesumth08uxrusv4sm7efs83zq8z",
"4fc62f0387f4ce47d64e2687ea89f5a8702c3bb98736bbbcf30f906561bf653",
],
)
async def test_create_user_invalid_npub(
http_client: AsyncClient, superuser_token, invalid_pubkey
):
tiny_id = shortuuid.uuid()[:8]
data = {
"username": f"user_{tiny_id}",
"password": "secret1234",
"password_repeat": "secret1234",
"email": f"user_{tiny_id}@lnbits.com",
"pubkey": invalid_pubkey,
}
create_resp = await http_client.post(
"/users/api/v1/user",
json=data,
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert create_resp.status_code == 400