diff --git a/.env.example b/.env.example
index b481a3c6c..5e9c8214e 100644
--- a/.env.example
+++ b/.env.example
@@ -162,6 +162,8 @@ LNBITS_ADMIN_USERS=""
# Extensions only admin can access
LNBITS_ADMIN_EXTENSIONS="ngrok, admin"
+# Extensions enabled by default when a user is created
+LNBITS_USER_DEFAULT_EXTENSIONS="lnurlp"
# Start LNbits core only. The extensions are not loaded.
# LNBITS_EXTENSIONS_DEACTIVATE_ALL=true
diff --git a/lnbits/core/crud.py b/lnbits/core/crud.py
index 7e51ed35d..51a8cf4e0 100644
--- a/lnbits/core/crud.py
+++ b/lnbits/core/crud.py
@@ -2,7 +2,7 @@ import datetime
import json
from time import time
from typing import Any, Dict, List, Literal, Optional
-from uuid import UUID, uuid4
+from uuid import uuid4
import shortuuid
from passlib.context import CryptContext
@@ -26,7 +26,6 @@ from lnbits.settings import (
from .models import (
Account,
AccountFilters,
- CreateUser,
Payment,
PaymentFilters,
PaymentHistoryPoint,
@@ -42,63 +41,23 @@ from .models import (
# --------
-async def create_user(
- data: CreateUser, user_config: Optional[UserConfig] = None
-) -> User:
- if not settings.new_accounts_allowed:
- raise ValueError("Account creation is disabled.")
- if await get_account_by_username(data.username):
- raise ValueError("Username already exists.")
-
- if data.email and await get_account_by_email(data.email):
- raise ValueError("Email already exists.")
-
- pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
-
- user_id = uuid4().hex
- tsph = db.timestamp_placeholder
- now = int(time())
- await db.execute(
- f"""
- INSERT INTO accounts
- (id, email, username, pass, extra, created_at, updated_at)
- VALUES (?, ?, ?, ?, ?, {tsph}, {tsph})
- """,
- (
- user_id,
- data.email,
- data.username,
- pwd_context.hash(data.password),
- json.dumps(dict(user_config)) if user_config else "{}",
- now,
- now,
- ),
- )
- new_account = await get_account(user_id=user_id)
- assert new_account, "Newly created account couldn't be retrieved"
- return new_account
-
-
async def create_account(
- conn: Optional[Connection] = None,
user_id: Optional[str] = None,
+ username: Optional[str] = None,
email: Optional[str] = None,
+ password: Optional[str] = None,
user_config: Optional[UserConfig] = None,
+ conn: Optional[Connection] = None,
) -> User:
- 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
-
+ user_id = user_id or uuid4().hex
extra = json.dumps(dict(user_config)) if user_config else "{}"
now = int(time())
await (conn or db).execute(
f"""
- INSERT INTO accounts (id, email, extra, created_at, updated_at)
- VALUES (?, ?, ?, {db.timestamp_placeholder}, {db.timestamp_placeholder})
+ INSERT INTO accounts (id, username, pass, email, extra, created_at, updated_at)
+ VALUES (?, ?, ?, ?, ?, {db.timestamp_placeholder}, {db.timestamp_placeholder})
""",
- (user_id, email, extra, now, now),
+ (user_id, username, password, email, extra, now, now),
)
new_account = await get_account(user_id=user_id, conn=conn)
diff --git a/lnbits/core/services.py b/lnbits/core/services.py
index e9237aa20..155ff470c 100644
--- a/lnbits/core/services.py
+++ b/lnbits/core/services.py
@@ -6,12 +6,14 @@ from io import BytesIO
from pathlib import Path
from typing import Dict, List, Optional, Tuple, TypedDict
from urllib.parse import parse_qs, urlparse
+from uuid import UUID, uuid4
import httpx
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
@@ -50,6 +52,8 @@ from .crud import (
create_wallet,
delete_wallet_payment,
get_account,
+ get_account_by_email,
+ get_account_by_username,
get_payments,
get_standalone_payment,
get_super_settings,
@@ -60,9 +64,10 @@ 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 BalanceDelta, Payment, UserConfig, Wallet
+from .models import BalanceDelta, Payment, User, UserConfig, Wallet
class PaymentError(Exception):
@@ -775,6 +780,38 @@ async def init_admin_settings(super_user: Optional[str] = None) -> SuperSettings
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,
+ password: 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, email, password, user_config)
+
+ 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] = []
diff --git a/lnbits/core/templates/admin/_tab_server.html b/lnbits/core/templates/admin/_tab_server.html
index 8c265a464..33843c51c 100644
--- a/lnbits/core/templates/admin/_tab_server.html
+++ b/lnbits/core/templates/admin/_tab_server.html
@@ -45,56 +45,7 @@
-
Admin Extensions
-Miscellaneous
-Extension Sources
-Extension Sources
+Admin Extensions
+User Default Extensions
+Miscellaneous
+