feat: max users + extensions env (#3919)
This commit is contained in:
@@ -24,6 +24,10 @@ LOG_ROTATION="100 MB"
|
|||||||
LOG_RETENTION="3 months"
|
LOG_RETENTION="3 months"
|
||||||
# for database cleanup commands
|
# for database cleanup commands
|
||||||
# CLEANUP_WALLETS_DAYS=90
|
# CLEANUP_WALLETS_DAYS=90
|
||||||
|
# Hard limit for total created users. Set to 0 to disable the limit.
|
||||||
|
# LNBITS_MAX_USERS=0
|
||||||
|
# Hard limit for total installed extensions. Set to 0 to disable the limit.
|
||||||
|
# LNBITS_MAX_EXTENSIONS=0
|
||||||
|
|
||||||
# === Admin Settings ===
|
# === Admin Settings ===
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ __pycache__
|
|||||||
*$py.class
|
*$py.class
|
||||||
.mypy_cache
|
.mypy_cache
|
||||||
.vscode
|
.vscode
|
||||||
|
.codex
|
||||||
*-lock.json
|
*-lock.json
|
||||||
.python-version
|
.python-version
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from .extensions import (
|
|||||||
drop_extension_db,
|
drop_extension_db,
|
||||||
get_installed_extension,
|
get_installed_extension,
|
||||||
get_installed_extensions,
|
get_installed_extensions,
|
||||||
|
get_installed_extensions_count,
|
||||||
get_user_active_extensions_ids,
|
get_user_active_extensions_ids,
|
||||||
get_user_extension,
|
get_user_extension,
|
||||||
get_user_extensions,
|
get_user_extensions,
|
||||||
@@ -58,6 +59,7 @@ from .users import (
|
|||||||
get_account_by_username,
|
get_account_by_username,
|
||||||
get_account_by_username_or_email,
|
get_account_by_username_or_email,
|
||||||
get_accounts,
|
get_accounts,
|
||||||
|
get_accounts_count,
|
||||||
get_user,
|
get_user,
|
||||||
get_user_access_control_lists,
|
get_user_access_control_lists,
|
||||||
get_user_from_account,
|
get_user_from_account,
|
||||||
@@ -117,11 +119,13 @@ __all__ = [
|
|||||||
"get_account_by_username",
|
"get_account_by_username",
|
||||||
"get_account_by_username_or_email",
|
"get_account_by_username_or_email",
|
||||||
"get_accounts",
|
"get_accounts",
|
||||||
|
"get_accounts_count",
|
||||||
"get_admin_settings",
|
"get_admin_settings",
|
||||||
"get_db_version",
|
"get_db_version",
|
||||||
"get_db_versions",
|
"get_db_versions",
|
||||||
"get_installed_extension",
|
"get_installed_extension",
|
||||||
"get_installed_extensions",
|
"get_installed_extensions",
|
||||||
|
"get_installed_extensions_count",
|
||||||
"get_latest_payments_by_extension",
|
"get_latest_payments_by_extension",
|
||||||
"get_payment",
|
"get_payment",
|
||||||
"get_payments",
|
"get_payments",
|
||||||
|
|||||||
@@ -90,6 +90,13 @@ async def get_installed_extensions(
|
|||||||
return all_extensions
|
return all_extensions
|
||||||
|
|
||||||
|
|
||||||
|
async def get_installed_extensions_count(conn: Connection | None = None) -> int:
|
||||||
|
row: dict | None = await (conn or db).fetchone(
|
||||||
|
"SELECT COUNT(*) as count FROM installed_extensions"
|
||||||
|
)
|
||||||
|
return int(row["count"]) if row else 0
|
||||||
|
|
||||||
|
|
||||||
async def get_user_extension(
|
async def get_user_extension(
|
||||||
user_id: str, extension: str, conn: Connection | None = None
|
user_id: str, extension: str, conn: Connection | None = None
|
||||||
) -> UserExtension | None:
|
) -> UserExtension | None:
|
||||||
|
|||||||
@@ -37,6 +37,13 @@ async def create_account(
|
|||||||
return account
|
return account
|
||||||
|
|
||||||
|
|
||||||
|
async def get_accounts_count(conn: Connection | None = None) -> int:
|
||||||
|
row: dict | None = await (conn or db).fetchone(
|
||||||
|
"SELECT COUNT(*) as count FROM accounts"
|
||||||
|
)
|
||||||
|
return int(row["count"]) if row else 0
|
||||||
|
|
||||||
|
|
||||||
async def update_account(account: Account, conn: Connection | None = None) -> Account:
|
async def update_account(account: Account, conn: Connection | None = None) -> Account:
|
||||||
account.updated_at = datetime.now(timezone.utc)
|
account.updated_at = datetime.now(timezone.utc)
|
||||||
await (conn or db).update("accounts", account)
|
await (conn or db).update("accounts", account)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from lnbits.core.crud import (
|
|||||||
delete_installed_extension,
|
delete_installed_extension,
|
||||||
get_db_version,
|
get_db_version,
|
||||||
get_installed_extension,
|
get_installed_extension,
|
||||||
|
get_installed_extensions_count,
|
||||||
update_installed_extension_state,
|
update_installed_extension_state,
|
||||||
)
|
)
|
||||||
from lnbits.core.crud.extensions import (
|
from lnbits.core.crud.extensions import (
|
||||||
@@ -38,6 +39,8 @@ async def install_extension(
|
|||||||
if installed_ext and installed_ext.meta:
|
if installed_ext and installed_ext.meta:
|
||||||
ext_info.meta.payments = installed_ext.meta.payments
|
ext_info.meta.payments = installed_ext.meta.payments
|
||||||
|
|
||||||
|
await check_extensions_limit(installed_ext)
|
||||||
|
|
||||||
if not skip_download:
|
if not skip_download:
|
||||||
await ext_info.download_archive()
|
await ext_info.download_archive()
|
||||||
|
|
||||||
@@ -63,6 +66,15 @@ async def install_extension(
|
|||||||
return extension
|
return extension
|
||||||
|
|
||||||
|
|
||||||
|
async def check_extensions_limit(installed_ext: InstallableExtension | None = None):
|
||||||
|
if settings.lnbits_max_extensions == 0 or installed_ext:
|
||||||
|
return
|
||||||
|
|
||||||
|
extensions_count = await get_installed_extensions_count()
|
||||||
|
if extensions_count >= settings.lnbits_max_extensions:
|
||||||
|
raise ValueError("Max amount of extensions have been installed")
|
||||||
|
|
||||||
|
|
||||||
async def uninstall_extension(ext_id: str):
|
async def uninstall_extension(ext_id: str):
|
||||||
await stop_extension_background_work(ext_id)
|
await stop_extension_background_work(ext_id)
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ from ..crud import (
|
|||||||
get_account_by_email,
|
get_account_by_email,
|
||||||
get_account_by_pubkey,
|
get_account_by_pubkey,
|
||||||
get_account_by_username,
|
get_account_by_username,
|
||||||
|
get_accounts_count,
|
||||||
get_super_settings,
|
get_super_settings,
|
||||||
get_user_extensions,
|
get_user_extensions,
|
||||||
get_user_from_account,
|
get_user_from_account,
|
||||||
@@ -55,6 +56,8 @@ async def create_user_account_no_ckeck(
|
|||||||
conn: Connection | None = None,
|
conn: Connection | None = None,
|
||||||
) -> User:
|
) -> User:
|
||||||
async with db.reuse_conn(conn) if conn else db.connect() as conn:
|
async with db.reuse_conn(conn) if conn else db.connect() as conn:
|
||||||
|
await check_users_limit(conn)
|
||||||
|
|
||||||
if account:
|
if account:
|
||||||
account.validate_fields()
|
account.validate_fields()
|
||||||
if account.username and await get_account_by_username(
|
if account.username and await get_account_by_username(
|
||||||
@@ -95,6 +98,15 @@ async def create_user_account_no_ckeck(
|
|||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
async def check_users_limit(conn: Connection | None = None):
|
||||||
|
if settings.lnbits_max_users == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
users_count = await get_accounts_count(conn=conn)
|
||||||
|
if users_count >= settings.lnbits_max_users:
|
||||||
|
raise ValueError("Max amount of users have been created")
|
||||||
|
|
||||||
|
|
||||||
async def update_user_account(account: Account) -> Account:
|
async def update_user_account(account: Account) -> Account:
|
||||||
account.validate_fields()
|
account.validate_fields()
|
||||||
|
|
||||||
|
|||||||
@@ -98,12 +98,16 @@ async def api_install_extension(data: CreateExtension):
|
|||||||
ext_info.clean_extension_files()
|
ext_info.clean_extension_files()
|
||||||
detail = (
|
detail = (
|
||||||
str(exc)
|
str(exc)
|
||||||
if isinstance(exc, AssertionError)
|
if isinstance(exc, (AssertionError, ValueError))
|
||||||
else f"Failed to install extension '{ext_info.id}'."
|
else f"Failed to install extension '{ext_info.id}'."
|
||||||
f"({ext_info.installed_version})."
|
f"({ext_info.installed_version})."
|
||||||
)
|
)
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
status_code=(
|
||||||
|
HTTPStatus.BAD_REQUEST
|
||||||
|
if isinstance(exc, (AssertionError, ValueError))
|
||||||
|
else HTTPStatus.INTERNAL_SERVER_ERROR
|
||||||
|
),
|
||||||
detail=detail,
|
detail=detail,
|
||||||
) from exc
|
) from exc
|
||||||
|
|
||||||
|
|||||||
@@ -1026,6 +1026,8 @@ class EnvSettings(LNbitsSettings):
|
|||||||
|
|
||||||
cleanup_wallets_days: int = Field(default=90, ge=0)
|
cleanup_wallets_days: int = Field(default=90, ge=0)
|
||||||
funding_source_max_retries: int = Field(default=4, ge=0)
|
funding_source_max_retries: int = Field(default=4, ge=0)
|
||||||
|
lnbits_max_users: int = Field(default=0, ge=0)
|
||||||
|
lnbits_max_extensions: int = Field(default=0, ge=0)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_default_extension_path(self) -> bool:
|
def has_default_extension_path(self) -> bool:
|
||||||
|
|||||||
Reference in New Issue
Block a user