Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f01c91e3e | ||
|
|
ba5005fc44 | ||
|
|
2329770acd |
@@ -105,7 +105,8 @@ async def get_settings_field(
|
|||||||
)
|
)
|
||||||
if not row:
|
if not row:
|
||||||
return None
|
return None
|
||||||
return SettingsField(id=row["id"], value=json.loads(row["value"]), tag=row["tag"])
|
value = json.loads(row["value"]) if row["value"] else None
|
||||||
|
return SettingsField(id=row["id"], value=value, tag=row["tag"])
|
||||||
|
|
||||||
|
|
||||||
async def set_settings_field(id_: str, value: Any | None, tag: str | None = "core"):
|
async def set_settings_field(id_: str, value: Any | None, tag: str | None = "core"):
|
||||||
|
|||||||
@@ -173,6 +173,12 @@ async def check_admin_settings():
|
|||||||
if account and account.extra and account.extra.provider == "env":
|
if account and account.extra and account.extra.provider == "env":
|
||||||
settings.first_install = True
|
settings.first_install = True
|
||||||
|
|
||||||
|
if settings.has_first_install_token_changed():
|
||||||
|
logger.warning("First install token is changed. Resetting admin settings.")
|
||||||
|
new_settings = await init_admin_settings()
|
||||||
|
settings.super_user = new_settings.super_user
|
||||||
|
settings.first_install = True
|
||||||
|
|
||||||
logger.success(
|
logger.success(
|
||||||
"✔️ Admin UI is enabled. run `uv run lnbits-cli superuser` "
|
"✔️ Admin UI is enabled. run `uv run lnbits-cli superuser` "
|
||||||
"to get the superuser."
|
"to get the superuser."
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from fastapi.responses import JSONResponse, RedirectResponse
|
|||||||
from fastapi_sso.sso.base import OpenID, SSOBase
|
from fastapi_sso.sso.base import OpenID, SSOBase
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
from lnbits.core.crud.settings import set_settings_field
|
||||||
from lnbits.core.crud.users import (
|
from lnbits.core.crud.users import (
|
||||||
get_user_access_control_lists,
|
get_user_access_control_lists,
|
||||||
update_user_access_control_list,
|
update_user_access_control_list,
|
||||||
@@ -532,6 +533,13 @@ async def first_install(data: UpdateSuperuserPassword) -> JSONResponse:
|
|||||||
account.hash_password(data.password)
|
account.hash_password(data.password)
|
||||||
await update_account(account)
|
await update_account(account)
|
||||||
settings.first_install = False
|
settings.first_install = False
|
||||||
|
|
||||||
|
# only confrm it after the super user has been successfully updated
|
||||||
|
if settings.first_install_token:
|
||||||
|
settings.first_install_token_confirmed = data.first_install_token
|
||||||
|
await set_settings_field(
|
||||||
|
"first_install_token_confirmed", data.first_install_token
|
||||||
|
)
|
||||||
return _auth_success_response(account.username, account.id, account.email)
|
return _auth_success_response(account.username, account.id, account.email)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -447,6 +447,7 @@ class SecuritySettings(LNbitsSettings):
|
|||||||
|
|
||||||
lnbits_max_outgoing_payment_amount_sats: int = Field(default=10_000_000, ge=0)
|
lnbits_max_outgoing_payment_amount_sats: int = Field(default=10_000_000, ge=0)
|
||||||
lnbits_max_incoming_payment_amount_sats: int = Field(default=10_000_000, ge=0)
|
lnbits_max_incoming_payment_amount_sats: int = Field(default=10_000_000, ge=0)
|
||||||
|
first_install_token_confirmed: str | None = Field(default=None)
|
||||||
|
|
||||||
def is_wallet_max_balance_exceeded(self, amount):
|
def is_wallet_max_balance_exceeded(self, amount):
|
||||||
return (
|
return (
|
||||||
@@ -1015,6 +1016,13 @@ class EnvSettings(LNbitsSettings):
|
|||||||
def has_default_extension_path(self) -> bool:
|
def has_default_extension_path(self) -> bool:
|
||||||
return self.lnbits_extensions_path == "lnbits"
|
return self.lnbits_extensions_path == "lnbits"
|
||||||
|
|
||||||
|
def has_first_install_token_changed(self) -> bool:
|
||||||
|
if not self.first_install_token:
|
||||||
|
return False
|
||||||
|
if not settings.first_install_token_confirmed:
|
||||||
|
return False
|
||||||
|
return self.first_install_token != settings.first_install_token_confirmed
|
||||||
|
|
||||||
def check_auth_secret_key(self):
|
def check_auth_secret_key(self):
|
||||||
if self.auth_secret_key:
|
if self.auth_secret_key:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -15,7 +15,11 @@ from lnbits.settings import settings
|
|||||||
def log_server_info():
|
def log_server_info():
|
||||||
logger.info("LNbits Info")
|
logger.info("LNbits Info")
|
||||||
if settings.first_install:
|
if settings.first_install:
|
||||||
logger.success("This is a fresh install of LNbits.")
|
if settings.has_first_install_token_changed():
|
||||||
|
logger.success("This is a first install token reset.")
|
||||||
|
else:
|
||||||
|
logger.success("This is a fresh install of LNbits.")
|
||||||
|
|
||||||
if settings.first_install_token:
|
if settings.first_install_token:
|
||||||
logger.success(
|
logger.success(
|
||||||
f"FIRST_INSTALL_TOKEN: `{settings.first_install_token}`. "
|
f"FIRST_INSTALL_TOKEN: `{settings.first_install_token}`. "
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "lnbits"
|
name = "lnbits"
|
||||||
version = "1.5.2-rc3"
|
version = "1.5.3"
|
||||||
requires-python = ">=3.10,<3.13"
|
requires-python = ">=3.10,<3.13"
|
||||||
description = "LNbits, free and open-source Lightning wallet and accounts system."
|
description = "LNbits, free and open-source Lightning wallet and accounts system."
|
||||||
authors = [{ name = "Alan Bits", email = "alan@lnbits.com" }]
|
authors = [{ name = "Alan Bits", email = "alan@lnbits.com" }]
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ async def app(settings: Settings):
|
|||||||
username="superadmin",
|
username="superadmin",
|
||||||
password="secret1234",
|
password="secret1234",
|
||||||
password_repeat="secret1234",
|
password_repeat="secret1234",
|
||||||
|
first_install_token=settings.first_install_token,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from lnbits.core.crud import create_account, delete_account, get_account
|
||||||
|
from lnbits.core.crud.settings import get_settings_field, set_settings_field
|
||||||
|
from lnbits.core.db import db
|
||||||
|
from lnbits.core.models import Account, UpdateSuperuserPassword, UserExtra
|
||||||
|
from lnbits.core.services.users import check_admin_settings
|
||||||
|
from lnbits.core.views.auth_api import first_install
|
||||||
|
from lnbits.settings import settings
|
||||||
|
|
||||||
|
|
||||||
|
async def _restore_setting_field(field_name: str, original_row) -> None:
|
||||||
|
if original_row is None:
|
||||||
|
await db.execute(
|
||||||
|
"DELETE FROM system_settings WHERE id = :id AND tag = :tag",
|
||||||
|
{"id": field_name, "tag": "core"},
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
await set_settings_field(field_name, original_row.value, original_row.tag)
|
||||||
|
|
||||||
|
|
||||||
|
def test_has_first_install_token_changed_requires_a_confirmed_mismatch():
|
||||||
|
original_token = settings.first_install_token
|
||||||
|
original_confirmed = settings.first_install_token_confirmed
|
||||||
|
|
||||||
|
try:
|
||||||
|
settings.first_install_token = "new-token"
|
||||||
|
settings.first_install_token_confirmed = "old-token"
|
||||||
|
assert settings.has_first_install_token_changed() is True
|
||||||
|
|
||||||
|
settings.first_install_token_confirmed = "new-token"
|
||||||
|
assert settings.has_first_install_token_changed() is False
|
||||||
|
|
||||||
|
settings.first_install_token_confirmed = None
|
||||||
|
assert settings.has_first_install_token_changed() is False
|
||||||
|
|
||||||
|
settings.first_install_token = None
|
||||||
|
assert settings.has_first_install_token_changed() is False
|
||||||
|
finally:
|
||||||
|
settings.first_install_token = original_token
|
||||||
|
settings.first_install_token_confirmed = original_confirmed
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
async def test_first_install_confirms_first_install_token(app):
|
||||||
|
temp_super_user = uuid4().hex
|
||||||
|
username = f"super_{temp_super_user[:8]}"
|
||||||
|
original_super_user = settings.super_user
|
||||||
|
original_first_install = settings.first_install
|
||||||
|
original_first_install_token = settings.first_install_token
|
||||||
|
original_first_install_token_confirmed = settings.first_install_token_confirmed
|
||||||
|
original_confirmed_row = await get_settings_field("first_install_token_confirmed")
|
||||||
|
|
||||||
|
await create_account(Account(id=temp_super_user, extra=UserExtra(provider="env")))
|
||||||
|
|
||||||
|
try:
|
||||||
|
settings.super_user = temp_super_user
|
||||||
|
settings.first_install = True
|
||||||
|
settings.first_install_token = "expected-token"
|
||||||
|
settings.first_install_token_confirmed = None
|
||||||
|
|
||||||
|
response = await first_install(
|
||||||
|
UpdateSuperuserPassword(
|
||||||
|
username=username,
|
||||||
|
password="secret1234",
|
||||||
|
password_repeat="secret1234",
|
||||||
|
first_install_token="expected-token",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert settings.first_install is False
|
||||||
|
assert settings.first_install_token_confirmed == "expected-token"
|
||||||
|
|
||||||
|
confirmed_row = await get_settings_field("first_install_token_confirmed")
|
||||||
|
assert confirmed_row is not None
|
||||||
|
assert confirmed_row.value == "expected-token"
|
||||||
|
|
||||||
|
account = await get_account(temp_super_user)
|
||||||
|
assert account is not None
|
||||||
|
assert account.username == username
|
||||||
|
assert account.extra.provider == "lnbits"
|
||||||
|
assert account.verify_password("secret1234")
|
||||||
|
finally:
|
||||||
|
await _restore_setting_field(
|
||||||
|
"first_install_token_confirmed", original_confirmed_row
|
||||||
|
)
|
||||||
|
settings.super_user = original_super_user
|
||||||
|
settings.first_install = original_first_install
|
||||||
|
settings.first_install_token = original_first_install_token
|
||||||
|
settings.first_install_token_confirmed = original_first_install_token_confirmed
|
||||||
|
await delete_account(temp_super_user)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
async def test_check_admin_settings_clears_persisted_super_user_when_token_changes(app):
|
||||||
|
temp_super_user = uuid4().hex
|
||||||
|
original_super_user = settings.super_user
|
||||||
|
original_first_install = settings.first_install
|
||||||
|
original_first_install_token = settings.first_install_token
|
||||||
|
original_first_install_token_confirmed = settings.first_install_token_confirmed
|
||||||
|
original_super_user_row = await get_settings_field("super_user")
|
||||||
|
original_confirmed_row = await get_settings_field("first_install_token_confirmed")
|
||||||
|
super_user_file = Path(settings.lnbits_data_folder) / ".super_user"
|
||||||
|
|
||||||
|
await create_account(
|
||||||
|
Account(id=temp_super_user, extra=UserExtra(provider="lnbits"))
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await set_settings_field("super_user", temp_super_user)
|
||||||
|
await set_settings_field("first_install_token_confirmed", "old-token")
|
||||||
|
|
||||||
|
settings.lnbits_admin_ui = True
|
||||||
|
settings.super_user = temp_super_user
|
||||||
|
settings.first_install = False
|
||||||
|
settings.first_install_token = "new-token"
|
||||||
|
settings.first_install_token_confirmed = "old-token"
|
||||||
|
|
||||||
|
await check_admin_settings()
|
||||||
|
|
||||||
|
super_user_row = await get_settings_field("super_user")
|
||||||
|
assert super_user_row is not None
|
||||||
|
assert super_user_row.value
|
||||||
|
assert settings.first_install is True
|
||||||
|
finally:
|
||||||
|
await _restore_setting_field("super_user", original_super_user_row)
|
||||||
|
await _restore_setting_field(
|
||||||
|
"first_install_token_confirmed", original_confirmed_row
|
||||||
|
)
|
||||||
|
settings.super_user = original_super_user
|
||||||
|
settings.first_install = original_first_install
|
||||||
|
settings.first_install_token = original_first_install_token
|
||||||
|
settings.first_install_token_confirmed = original_first_install_token_confirmed
|
||||||
|
super_user_file.write_text(original_super_user)
|
||||||
|
await delete_account(temp_super_user)
|
||||||
Reference in New Issue
Block a user