Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8705650d8 | ||
|
|
8a9a96a52c | ||
|
|
ad028b303c | ||
|
|
b744af1de5 | ||
|
|
45e773b66f | ||
|
|
f692ac6f12 | ||
|
|
78202a027b | ||
|
|
28fa0906a6 |
@@ -6,6 +6,10 @@
|
||||
# The following settings are ONLY set in your .env file.
|
||||
# They are NOT managed by the Admin UI and are not stored in the database.
|
||||
|
||||
# === First Install Token ===
|
||||
# if set the user is required to enter this token on the /first_install page
|
||||
# FIRST_INSTALL_TOKEN="myaccesstoken"
|
||||
|
||||
# === Logging and Development ===
|
||||
|
||||
DEBUG=False
|
||||
|
||||
@@ -66,7 +66,6 @@ from .middleware import (
|
||||
add_first_install_middleware,
|
||||
add_ip_block_middleware,
|
||||
add_ratelimit_middleware,
|
||||
add_route_access_middleware,
|
||||
)
|
||||
from .tasks import internal_invoice_listener, invoice_listener, run_interval
|
||||
|
||||
@@ -193,7 +192,6 @@ def create_app() -> FastAPI:
|
||||
|
||||
# adds security middleware
|
||||
add_ip_block_middleware(app)
|
||||
add_route_access_middleware(app)
|
||||
add_ratelimit_middleware(app)
|
||||
|
||||
register_exception_handlers(app)
|
||||
|
||||
@@ -6,23 +6,16 @@ from typing import Literal
|
||||
|
||||
from fastapi import Query
|
||||
from lnurl import LnurlWithdrawResponse
|
||||
from loguru import logger
|
||||
from pydantic import BaseModel, Field, validator
|
||||
|
||||
from lnbits.db import FilterModel
|
||||
from lnbits.fiat import get_fiat_provider
|
||||
from lnbits.fiat.base import (
|
||||
FiatPaymentFailedStatus,
|
||||
FiatPaymentPendingStatus,
|
||||
FiatPaymentStatus,
|
||||
FiatPaymentSuccessStatus,
|
||||
)
|
||||
from lnbits.utils.exchange_rates import allowed_currencies
|
||||
from lnbits.wallets import get_funding_source
|
||||
from lnbits.wallets.base import (
|
||||
PaymentFailedStatus,
|
||||
PaymentPendingStatus,
|
||||
PaymentStatus,
|
||||
PaymentSuccessStatus,
|
||||
)
|
||||
|
||||
|
||||
@@ -130,59 +123,23 @@ class Payment(BaseModel):
|
||||
"fiat_"
|
||||
)
|
||||
|
||||
# DEPRECATED: in v1.5.0, use service check_payment_status instead
|
||||
async def check_status(
|
||||
self, skip_internal_payment_notifications: bool | None = False
|
||||
) -> PaymentStatus:
|
||||
if self.is_internal:
|
||||
if self.success:
|
||||
return PaymentSuccessStatus()
|
||||
if self.failed:
|
||||
return PaymentFailedStatus()
|
||||
if self.is_in and self.fiat_provider:
|
||||
fiat_status = await self.check_fiat_status(
|
||||
skip_internal_payment_notifications
|
||||
)
|
||||
return PaymentStatus(paid=fiat_status.paid)
|
||||
return PaymentPendingStatus()
|
||||
funding_source = get_funding_source()
|
||||
if self.is_out:
|
||||
status = await funding_source.get_payment_status(self.checking_id)
|
||||
else:
|
||||
status = await funding_source.get_invoice_status(self.checking_id)
|
||||
return status
|
||||
logger.warning("payment.check_status() is deprecated.")
|
||||
from lnbits.core.services.payments import check_payment_status
|
||||
|
||||
return await check_payment_status(self, skip_internal_payment_notifications)
|
||||
|
||||
# DEPRECATED: in v1.5.0, use service check_payment_status instead
|
||||
async def check_fiat_status(
|
||||
self, skip_internal_payment_notifications: bool | None = False
|
||||
) -> FiatPaymentStatus:
|
||||
if not self.is_internal:
|
||||
return FiatPaymentPendingStatus()
|
||||
if self.success:
|
||||
return FiatPaymentSuccessStatus()
|
||||
if self.failed:
|
||||
return FiatPaymentFailedStatus()
|
||||
logger.warning("payment.check_fiat_status() is deprecated.")
|
||||
from lnbits.core.services.fiat_providers import check_fiat_status
|
||||
|
||||
if not self.fiat_provider:
|
||||
return FiatPaymentPendingStatus()
|
||||
|
||||
checking_id = self.extra.get("fiat_checking_id")
|
||||
if not checking_id:
|
||||
return FiatPaymentPendingStatus()
|
||||
|
||||
fiat_provider = await get_fiat_provider(self.fiat_provider)
|
||||
if not fiat_provider:
|
||||
return FiatPaymentPendingStatus()
|
||||
fiat_status = await fiat_provider.get_invoice_status(checking_id)
|
||||
|
||||
if skip_internal_payment_notifications:
|
||||
return fiat_status
|
||||
|
||||
if fiat_status.success:
|
||||
# notify receivers asynchronously
|
||||
from lnbits.tasks import internal_invoice_queue
|
||||
|
||||
await internal_invoice_queue.put(self.checking_id)
|
||||
|
||||
return fiat_status
|
||||
return await check_fiat_status(self, skip_internal_payment_notifications)
|
||||
|
||||
|
||||
class PaymentFilters(FilterModel):
|
||||
|
||||
@@ -358,6 +358,7 @@ class UpdateSuperuserPassword(BaseModel):
|
||||
username: str = Query(default=..., min_length=2, max_length=20)
|
||||
password: str = Query(default=..., min_length=8, max_length=50)
|
||||
password_repeat: str = Query(default=..., min_length=8, max_length=50)
|
||||
first_install_token: str | None = Query(None)
|
||||
|
||||
|
||||
class LoginUsr(BaseModel):
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from .fiat_providers import check_fiat_status
|
||||
from .funding_source import (
|
||||
get_balance_delta,
|
||||
switch_to_voidwallet,
|
||||
@@ -7,6 +8,7 @@ from .notifications import enqueue_admin_notification, send_payment_notification
|
||||
from .payments import (
|
||||
calculate_fiat_amounts,
|
||||
cancel_hold_invoice,
|
||||
check_payment_status,
|
||||
check_transaction_status,
|
||||
check_wallet_limits,
|
||||
create_fiat_invoice,
|
||||
@@ -40,6 +42,8 @@ __all__ = [
|
||||
"calculate_fiat_amounts",
|
||||
"cancel_hold_invoice",
|
||||
"check_admin_settings",
|
||||
"check_fiat_status",
|
||||
"check_payment_status",
|
||||
"check_transaction_status",
|
||||
"check_wallet_limits",
|
||||
"check_webpush_settings",
|
||||
|
||||
@@ -12,6 +12,12 @@ from lnbits.core.models import CreatePayment, Payment, PaymentState
|
||||
from lnbits.core.models.misc import SimpleStatus
|
||||
from lnbits.db import Connection
|
||||
from lnbits.fiat import get_fiat_provider
|
||||
from lnbits.fiat.base import (
|
||||
FiatPaymentFailedStatus,
|
||||
FiatPaymentPendingStatus,
|
||||
FiatPaymentStatus,
|
||||
FiatPaymentSuccessStatus,
|
||||
)
|
||||
from lnbits.settings import settings
|
||||
|
||||
|
||||
@@ -29,6 +35,40 @@ async def handle_fiat_payment_confirmation(
|
||||
logger.warning(e)
|
||||
|
||||
|
||||
async def check_fiat_status(
|
||||
payment: Payment, skip_internal_payment_notifications: bool | None = False
|
||||
) -> FiatPaymentStatus:
|
||||
if not payment.is_internal:
|
||||
return FiatPaymentPendingStatus()
|
||||
if payment.success:
|
||||
return FiatPaymentSuccessStatus()
|
||||
if payment.failed:
|
||||
return FiatPaymentFailedStatus()
|
||||
|
||||
if not payment.fiat_provider:
|
||||
return FiatPaymentPendingStatus()
|
||||
|
||||
checking_id = payment.extra.get("fiat_checking_id")
|
||||
if not checking_id:
|
||||
return FiatPaymentPendingStatus()
|
||||
|
||||
fiat_provider = await get_fiat_provider(payment.fiat_provider)
|
||||
if not fiat_provider:
|
||||
return FiatPaymentPendingStatus()
|
||||
fiat_status = await fiat_provider.get_invoice_status(checking_id)
|
||||
|
||||
if skip_internal_payment_notifications:
|
||||
return fiat_status
|
||||
|
||||
if fiat_status.success:
|
||||
# notify receivers asynchronously
|
||||
from lnbits.tasks import internal_invoice_queue
|
||||
|
||||
await internal_invoice_queue.put(payment.checking_id)
|
||||
|
||||
return fiat_status
|
||||
|
||||
|
||||
def check_stripe_signature(
|
||||
payload: bytes,
|
||||
sig_header: str | None,
|
||||
|
||||
@@ -25,6 +25,7 @@ from lnbits.utils.exchange_rates import fiat_amount_as_satoshis, satoshis_amount
|
||||
from lnbits.wallets import fake_wallet, get_funding_source
|
||||
from lnbits.wallets.base import (
|
||||
InvoiceResponse,
|
||||
PaymentFailedStatus,
|
||||
PaymentPendingStatus,
|
||||
PaymentResponse,
|
||||
PaymentStatus,
|
||||
@@ -47,6 +48,7 @@ from ..models import (
|
||||
PaymentState,
|
||||
Wallet,
|
||||
)
|
||||
from .fiat_providers import check_fiat_status
|
||||
from .notifications import send_payment_notification_in_background
|
||||
|
||||
payment_lock = asyncio.Lock()
|
||||
@@ -364,7 +366,7 @@ async def update_pending_payments(wallet_id: str):
|
||||
async def update_pending_payment(
|
||||
payment: Payment, conn: Connection | None = None
|
||||
) -> Payment:
|
||||
status = await payment.check_status()
|
||||
status = await check_payment_status(payment)
|
||||
if status.failed:
|
||||
payment.status = PaymentState.FAILED
|
||||
await update_payment(payment, conn=conn)
|
||||
@@ -618,7 +620,29 @@ async def check_transaction_status(
|
||||
if payment.status == PaymentState.SUCCESS.value:
|
||||
return PaymentSuccessStatus(fee_msat=payment.fee)
|
||||
|
||||
return await payment.check_status()
|
||||
return await check_payment_status(payment)
|
||||
|
||||
|
||||
async def check_payment_status(
|
||||
payment: Payment, skip_internal_payment_notifications: bool | None = False
|
||||
) -> PaymentStatus:
|
||||
if payment.is_internal:
|
||||
if payment.success:
|
||||
return PaymentSuccessStatus()
|
||||
if payment.failed:
|
||||
return PaymentFailedStatus()
|
||||
if payment.is_in and payment.fiat_provider:
|
||||
fiat_status = await check_fiat_status(
|
||||
payment, skip_internal_payment_notifications
|
||||
)
|
||||
return PaymentStatus(paid=fiat_status.paid)
|
||||
return PaymentPendingStatus()
|
||||
funding_source = get_funding_source()
|
||||
if payment.is_out:
|
||||
status = await funding_source.get_payment_status(payment.checking_id)
|
||||
else:
|
||||
status = await funding_source.get_invoice_status(payment.checking_id)
|
||||
return status
|
||||
|
||||
|
||||
async def get_payments_daily_stats(
|
||||
@@ -752,7 +776,7 @@ async def _pay_internal_invoice(
|
||||
await update_payment(internal_payment, conn=conn)
|
||||
logger.success(f"internal payment successful {internal_payment.checking_id}")
|
||||
|
||||
send_payment_notification_in_background(wallet, payment)
|
||||
await _send_payment_notification_in_background(wallet.id, payment, conn=conn)
|
||||
|
||||
# notify receiver asynchronously
|
||||
from lnbits.tasks import internal_invoice_queue
|
||||
@@ -825,7 +849,8 @@ async def _pay_external_invoice(
|
||||
payment = await update_payment_success_status(
|
||||
payment, payment_response, conn=conn
|
||||
)
|
||||
send_payment_notification_in_background(wallet, payment)
|
||||
|
||||
await _send_payment_notification_in_background(wallet.id, payment, conn=conn)
|
||||
logger.success(f"payment successful {payment_response.checking_id}")
|
||||
|
||||
payment.checking_id = payment_response.checking_id
|
||||
@@ -868,7 +893,7 @@ async def _verify_external_payment(
|
||||
raise PaymentError("Payment already paid.", status="success")
|
||||
|
||||
# payment failed
|
||||
status = await payment.check_status()
|
||||
status = await check_payment_status(payment)
|
||||
if status.failed:
|
||||
raise PaymentError(
|
||||
"Payment is failed node, retrying is not possible.", status="failed"
|
||||
@@ -1033,3 +1058,13 @@ async def cancel_hold_invoice(payment: Payment) -> InvoiceResponse:
|
||||
await update_payment(payment)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
async def _send_payment_notification_in_background(
|
||||
wallet_id: str, payment: Payment, conn: Connection | None = None
|
||||
):
|
||||
# fetch balance again
|
||||
wallet = await get_wallet(wallet_id, conn=conn)
|
||||
if not wallet:
|
||||
raise PaymentError(f"Could not fetch wallet '{wallet_id}'.", status="failed")
|
||||
send_payment_notification_in_background(wallet, payment)
|
||||
|
||||
@@ -503,6 +503,12 @@ async def update_ui_customization(
|
||||
async def first_install(data: UpdateSuperuserPassword) -> JSONResponse:
|
||||
if not settings.first_install:
|
||||
raise HTTPException(HTTPStatus.FORBIDDEN, "This is not your first install")
|
||||
if settings.first_install_token:
|
||||
if not data.first_install_token:
|
||||
raise HTTPException(HTTPStatus.UNAUTHORIZED, "Missing first_install_token.")
|
||||
if settings.first_install_token != data.first_install_token:
|
||||
raise HTTPException(HTTPStatus.UNAUTHORIZED, "Invalid first_install_token.")
|
||||
|
||||
account = await get_account(settings.super_user)
|
||||
if not account:
|
||||
raise HTTPException(HTTPStatus.INTERNAL_SERVER_ERROR, "Superuser not found.")
|
||||
|
||||
@@ -9,6 +9,7 @@ from lnbits.core.crud.payments import (
|
||||
from lnbits.core.models.misc import SimpleStatus
|
||||
from lnbits.core.models.payments import CreateInvoice
|
||||
from lnbits.core.services.fiat_providers import (
|
||||
check_fiat_status,
|
||||
check_stripe_signature,
|
||||
verify_paypal_webhook,
|
||||
)
|
||||
@@ -87,7 +88,7 @@ async def _handle_stripe_intent_session_completed(event: dict):
|
||||
if not payment:
|
||||
logger.warning(f"No payment found for hash: '{payment_hash}'.")
|
||||
return
|
||||
await payment.check_fiat_status()
|
||||
await check_fiat_status(payment)
|
||||
|
||||
|
||||
async def _handle_stripe_checkout_session_completed(event: dict):
|
||||
@@ -110,7 +111,7 @@ async def _handle_stripe_checkout_session_completed(event: dict):
|
||||
payment = await get_standalone_payment(payment_hash)
|
||||
if not payment:
|
||||
raise ValueError(f"No payment found for hash: '{payment_hash}'.")
|
||||
await payment.check_fiat_status()
|
||||
await check_fiat_status(payment)
|
||||
|
||||
|
||||
async def _handle_stripe_subscription_invoice_paid(event: dict):
|
||||
@@ -155,7 +156,7 @@ async def _handle_stripe_subscription_invoice_paid(event: dict):
|
||||
),
|
||||
)
|
||||
|
||||
await payment.check_fiat_status()
|
||||
await check_fiat_status(payment)
|
||||
|
||||
|
||||
async def _get_stripe_subscription_payment_options(
|
||||
@@ -192,7 +193,7 @@ async def handle_paypal_event(event: dict):
|
||||
if not payment:
|
||||
logger.warning(f"No payment found for hash: '{payment_hash}'.")
|
||||
return
|
||||
await payment.check_fiat_status()
|
||||
await check_fiat_status(payment)
|
||||
return
|
||||
|
||||
if event_type in (
|
||||
@@ -247,7 +248,7 @@ async def _handle_paypal_subscription_payment(resource: dict):
|
||||
),
|
||||
)
|
||||
|
||||
await payment.check_fiat_status()
|
||||
await check_fiat_status(payment)
|
||||
|
||||
|
||||
def _paypal_extract_payment_hash(resource: dict) -> str | None:
|
||||
|
||||
+8
-54
@@ -70,60 +70,11 @@ def template_renderer(additional_folders: list | None = None) -> Jinja2Templates
|
||||
t.env.globals["static_url_for"] = static_url_for
|
||||
t.env.globals["normalize_path"] = normalize_path
|
||||
|
||||
window_settings = {
|
||||
"AD_SPACE": settings.lnbits_ad_space,
|
||||
"AD_SPACE_ENABLED": settings.lnbits_ad_space_enabled,
|
||||
"AD_SPACE_TITLE": settings.lnbits_ad_space_title,
|
||||
"EXTENSIONS": list(settings.lnbits_installed_extensions_ids),
|
||||
"HIDE_API": settings.lnbits_hide_api,
|
||||
"SITE_TITLE": settings.lnbits_site_title,
|
||||
"SITE_TAGLINE": settings.lnbits_site_tagline,
|
||||
"SITE_DESCRIPTION": settings.lnbits_site_description,
|
||||
"LNBITS_ADMIN_UI": settings.lnbits_admin_ui,
|
||||
"LNBITS_AUDIT_ENABLED": settings.lnbits_audit_enabled,
|
||||
"LNBITS_AUTH_METHODS": settings.auth_allowed_methods,
|
||||
"LNBITS_AUTH_KEYCLOAK_ORG": settings.keycloak_client_custom_org,
|
||||
"LNBITS_AUTH_KEYCLOAK_ICON": settings.keycloak_client_custom_icon,
|
||||
"LNBITS_CUSTOM_IMAGE": settings.lnbits_custom_image,
|
||||
"LNBITS_CUSTOM_BADGE": settings.lnbits_custom_badge,
|
||||
"LNBITS_CUSTOM_BADGE_COLOR": settings.lnbits_custom_badge_color,
|
||||
"LNBITS_EXTENSIONS_DEACTIVATE_ALL": settings.lnbits_extensions_deactivate_all,
|
||||
"LNBITS_NEW_ACCOUNTS_ALLOWED": settings.new_accounts_allowed,
|
||||
"LNBITS_NODE_UI": settings.lnbits_node_ui and settings.has_nodemanager,
|
||||
"LNBITS_NODE_UI_AVAILABLE": settings.has_nodemanager,
|
||||
"LNBITS_QR_LOGO": settings.lnbits_qr_logo,
|
||||
"LNBITS_APPLE_TOUCH_ICON": settings.lnbits_apple_touch_icon,
|
||||
"LNBITS_SERVICE_FEE": settings.lnbits_service_fee,
|
||||
"LNBITS_SERVICE_FEE_MAX": settings.lnbits_service_fee_max,
|
||||
"LNBITS_SERVICE_FEE_WALLET": settings.lnbits_service_fee_wallet,
|
||||
"LNBITS_SHOW_HOME_PAGE_ELEMENTS": settings.lnbits_show_home_page_elements,
|
||||
"LNBITS_THEME_OPTIONS": settings.lnbits_theme_options,
|
||||
"WALLET_FEATURED_BUTTON_LABEL": settings.lnbits_wallet_featured_button_label,
|
||||
"WALLET_FEATURED_BUTTON_URL": settings.lnbits_wallet_featured_button_url,
|
||||
"WALLET_FEATURED_BUTTON_ICON": settings.lnbits_wallet_featured_button_icon,
|
||||
"LNBITS_VERSION": settings.version,
|
||||
"USE_CUSTOM_LOGO": settings.lnbits_custom_logo,
|
||||
"LNBITS_DEFAULT_REACTION": settings.lnbits_default_reaction,
|
||||
"LNBITS_DEFAULT_THEME": settings.lnbits_default_theme,
|
||||
"LNBITS_DEFAULT_BORDER": settings.lnbits_default_border,
|
||||
"LNBITS_DEFAULT_GRADIENT": settings.lnbits_default_gradient,
|
||||
"LNBITS_DEFAULT_BGIMAGE": settings.lnbits_default_bgimage,
|
||||
"VOIDWALLET": settings.lnbits_backend_wallet_class == "VoidWallet",
|
||||
"WEBPUSH_PUBKEY": settings.lnbits_webpush_pubkey,
|
||||
"LNBITS_EXTENSIONS_REVIEWS_URL": settings.lnbits_extensions_reviews_url,
|
||||
"LNBITS_DENOMINATION": settings.lnbits_denomination,
|
||||
"has_holdinvoice": settings.has_holdinvoice,
|
||||
"LNBITS_NOSTR_CONFIGURED": settings.is_nostr_notifications_configured(),
|
||||
"LNBITS_TELEGRAM_CONFIGURED": settings.is_telegram_notifications_configured(),
|
||||
"LNBITS_EXT_BUILDER": settings.lnbits_extensions_builder_activate_non_admins,
|
||||
"LNBITS_CURRENCIES": list(currencies.keys()),
|
||||
"LNBITS_ALLOWED_CURRENCIES": settings.lnbits_allowed_currencies,
|
||||
"CACHE_KEY": settings.server_startup_time,
|
||||
}
|
||||
|
||||
t.env.globals["WINDOW_SETTINGS"] = window_settings
|
||||
for key, value in window_settings.items():
|
||||
t.env.globals[key] = value
|
||||
# used in base.html
|
||||
t.env.globals["SITE_TITLE"] = settings.lnbits_site_title
|
||||
t.env.globals["LNBITS_APPLE_TOUCH_ICON"] = settings.lnbits_apple_touch_icon
|
||||
t.env.globals["SETTINGS"] = settings.to_public().dict(by_alias=True)
|
||||
t.env.globals["CURRENCIES"] = list(currencies.keys())
|
||||
|
||||
if settings.bundle_assets:
|
||||
t.env.globals["INCLUDED_JS"] = ["bundle.min.js"]
|
||||
@@ -137,6 +88,9 @@ def template_renderer(additional_folders: list | None = None) -> Jinja2Templates
|
||||
t.env.globals["INCLUDED_CSS"] = vendor_files["css"]
|
||||
t.env.globals["INCLUDED_COMPONENTS"] = vendor_files["components"]
|
||||
|
||||
# backwards compatibility for extensions (tpos)
|
||||
t.env.globals["LNBITS_DENOMINATION"] = settings.lnbits_denomination
|
||||
|
||||
return t
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import asyncio
|
||||
import json
|
||||
import re
|
||||
from datetime import datetime, timezone
|
||||
from http import HTTPStatus
|
||||
from typing import Any
|
||||
@@ -19,53 +18,6 @@ from lnbits.core.models import AuditEntry
|
||||
from lnbits.helpers import normalize_path, template_renderer
|
||||
from lnbits.settings import settings
|
||||
|
||||
_LOCALHOST_IPS = {"127.0.0.1", "::1"}
|
||||
_PUBLIC_ASSET_PATHS = {
|
||||
"/favicon.ico",
|
||||
"/service-worker.js",
|
||||
}
|
||||
|
||||
|
||||
def _normalize_match_path(path: str) -> str:
|
||||
if not path:
|
||||
return "/"
|
||||
if path != "/" and path.endswith("/"):
|
||||
return path.rstrip("/")
|
||||
return path
|
||||
|
||||
|
||||
def _route_pattern_matches(pattern: str, path: str) -> bool:
|
||||
if not pattern:
|
||||
return False
|
||||
if not pattern.startswith("/"):
|
||||
pattern = f"/{pattern}"
|
||||
pattern = _normalize_match_path(pattern)
|
||||
path = _normalize_match_path(path)
|
||||
if pattern.endswith("*"):
|
||||
prefix = pattern.rstrip("*")
|
||||
return path.startswith(prefix)
|
||||
if pattern == path:
|
||||
return True
|
||||
escaped = re.escape(pattern)
|
||||
escaped = re.sub(r"\\\{[^/]+\\\}", r"[^/]+", escaped)
|
||||
return re.fullmatch(escaped, path) is not None
|
||||
|
||||
|
||||
def _response_by_accepted_type(request: Request, msg: str, status_code: HTTPStatus):
|
||||
accept_header = request.headers.get("accept", "")
|
||||
if "text/html" in accept_header.split(","):
|
||||
return HTMLResponse(
|
||||
status_code=status_code,
|
||||
content=template_renderer()
|
||||
.TemplateResponse(
|
||||
request,
|
||||
"error.html",
|
||||
{"err": msg, "status_code": status_code, "message": msg},
|
||||
)
|
||||
.body,
|
||||
)
|
||||
return JSONResponse(status_code=status_code, content={"detail": msg})
|
||||
|
||||
|
||||
class InstalledExtensionMiddleware:
|
||||
# This middleware class intercepts calls made to the extensions API and:
|
||||
@@ -283,42 +235,6 @@ def add_ip_block_middleware(app: FastAPI):
|
||||
return await call_next(request)
|
||||
|
||||
|
||||
def add_route_access_middleware(app: FastAPI):
|
||||
@app.middleware("http")
|
||||
async def route_access_middleware(request: Request, call_next):
|
||||
if not settings.lnbits_route_access_control_enabled:
|
||||
return await call_next(request)
|
||||
if not request.client:
|
||||
return JSONResponse(
|
||||
status_code=HTTPStatus.FORBIDDEN,
|
||||
content={"detail": "No request client"},
|
||||
)
|
||||
if request.client.host in _LOCALHOST_IPS:
|
||||
return await call_next(request)
|
||||
|
||||
path = request.url.path or "/"
|
||||
if "/static/" in path or path in _PUBLIC_ASSET_PATHS:
|
||||
return await call_next(request)
|
||||
whitelist = settings.lnbits_route_access_whitelist
|
||||
blacklist = settings.lnbits_route_access_blacklist
|
||||
|
||||
if whitelist:
|
||||
if any(_route_pattern_matches(route, path) for route in whitelist):
|
||||
return await call_next(request)
|
||||
return _response_by_accepted_type(
|
||||
request, f"Route not whitelisted: {path}", HTTPStatus.FORBIDDEN
|
||||
)
|
||||
|
||||
if blacklist and any(
|
||||
_route_pattern_matches(route, path) for route in blacklist
|
||||
):
|
||||
return _response_by_accepted_type(
|
||||
request, f"Route is blacklisted: {path}", HTTPStatus.FORBIDDEN
|
||||
)
|
||||
|
||||
return await call_next(request)
|
||||
|
||||
|
||||
def add_first_install_middleware(app: FastAPI):
|
||||
@app.middleware("http")
|
||||
async def first_install_middleware(request: Request, call_next):
|
||||
|
||||
+110
-3
@@ -421,9 +421,6 @@ class SecuritySettings(LNbitsSettings):
|
||||
lnbits_rate_limit_unit: str = Field(default="minute")
|
||||
lnbits_allowed_ips: list[str] = Field(default=[])
|
||||
lnbits_blocked_ips: list[str] = Field(default=[])
|
||||
lnbits_route_access_control_enabled: bool = Field(default=False)
|
||||
lnbits_route_access_whitelist: list[str] = Field(default=[])
|
||||
lnbits_route_access_blacklist: list[str] = Field(default=[])
|
||||
lnbits_callback_url_rules: list[str] = Field(
|
||||
default=["https?://([a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})(:\\d+)?"]
|
||||
)
|
||||
@@ -986,6 +983,7 @@ class EnvSettings(LNbitsSettings):
|
||||
enable_log_to_file: bool = Field(default=True)
|
||||
log_rotation: str = Field(default="100 MB")
|
||||
log_retention: str = Field(default="3 months")
|
||||
first_install_token: str | None = Field(default=None)
|
||||
|
||||
cleanup_wallets_days: int = Field(default=90, ge=0)
|
||||
funding_source_max_retries: int = Field(default=4, ge=0)
|
||||
@@ -1134,6 +1132,115 @@ class Settings(EditableSettings, ReadOnlySettings, TransientSettings, BaseSettin
|
||||
and ext_id in self.lnbits_all_extensions_ids
|
||||
)
|
||||
|
||||
def to_public(self) -> PublicSettings:
|
||||
return PublicSettings.from_settings(self)
|
||||
|
||||
|
||||
class PublicSettings(BaseModel):
|
||||
"""
|
||||
PublicSettings is used for templating and public information.
|
||||
WARNING: do not expose private information, PublicSettings is exposed publicly.
|
||||
"""
|
||||
|
||||
allow_register: bool = Field(alias="allowRegister")
|
||||
qr_logo: str = Field(alias="qrLogo")
|
||||
site_title: str = Field(alias="siteTitle")
|
||||
site_tagline: str = Field(alias="siteTagline")
|
||||
site_description: str | None = Field(alias="siteDescription")
|
||||
auth_methods: list[str] = Field(alias="authMethods")
|
||||
keycloak_org: str | None = Field(alias="keycloakOrg")
|
||||
keycloak_icon: str | None = Field(alias="keycloakIcon")
|
||||
has_holdinvoice: bool = Field(alias="hasHoldinvoice")
|
||||
has_nodemanager: bool = Field(alias="hasNodemanager")
|
||||
show_nodemanager: bool = Field(alias="showNodemanager")
|
||||
custom_logo: str | None = Field(alias="customLogo")
|
||||
show_voidwallet: bool = Field(alias="showVoidwallet")
|
||||
version: str = Field(alias="version")
|
||||
show_home_page_elements: bool = Field(alias="showHomePageElements")
|
||||
hide_api: bool = Field(alias="hideApi")
|
||||
cache_key: str = Field(alias="cacheKey")
|
||||
webpush_pubkey: str | None = Field(alias="webpushPubkey")
|
||||
show_extensions: bool = Field(alias="showExtensions")
|
||||
show_audit: bool = Field(alias="showAudit")
|
||||
show_admin: bool = Field(alias="showAdmin")
|
||||
ad_space: list[list[str]] = Field(alias="adSpace")
|
||||
ad_space_title: str = Field(alias="adSpaceTitle")
|
||||
show_ad_space: bool = Field(alias="showAdSpace")
|
||||
custom_image: str | None = Field(alias="customImage")
|
||||
custom_badge: str | None = Field(alias="customBadge")
|
||||
custom_badge_color: str | None = Field(alias="customBadgeColor")
|
||||
service_fee: float = Field(alias="serviceFee")
|
||||
service_fee_max: int = Field(alias="serviceFeeMax")
|
||||
service_fee_wallet: str | None = Field(alias="serviceFeeWallet")
|
||||
theme_options: list[str] = Field(alias="themeOptions")
|
||||
default_reaction: str = Field(alias="defaultReaction")
|
||||
default_theme: str = Field(alias="defaultTheme")
|
||||
default_border: str = Field(alias="defaultBorder")
|
||||
default_bgimage: str | None = Field(alias="defaultBgimage")
|
||||
default_gradient: bool = Field(alias="defaultGradient")
|
||||
denomination: str | None = Field()
|
||||
extensions: list[str] = Field()
|
||||
allowed_currencies: list[str] = Field(alias="allowedCurrencies")
|
||||
extensions_reviews_url: str = Field(alias="extensionsReviewsUrl")
|
||||
ext_builder: bool = Field(alias="extBuilder")
|
||||
nostr_configured: bool = Field(alias="nostrConfigured")
|
||||
telegram_configured: bool = Field(alias="telegramConfigured")
|
||||
wallet_featured_button_label: str | None = Field(alias="walletFeaturedButtonLabel")
|
||||
wallet_featured_button_url: str | None = Field(alias="walletFeaturedButtonUrl")
|
||||
wallet_featured_button_icon: str | None = Field(alias="walletFeaturedButtonIcon")
|
||||
|
||||
@classmethod
|
||||
def from_settings(cls, settings: Settings):
|
||||
ads = [x.split(";") for x in settings.lnbits_ad_space.split(",")]
|
||||
return cls(
|
||||
adSpace=ads,
|
||||
adSpaceTitle=settings.lnbits_ad_space_title,
|
||||
showAdSpace=settings.lnbits_ad_space_enabled and len(ads) > 0,
|
||||
allowRegister=settings.new_accounts_allowed,
|
||||
qrLogo=settings.lnbits_qr_logo,
|
||||
siteDescription=settings.lnbits_site_description,
|
||||
siteTitle=settings.lnbits_site_title,
|
||||
siteTagline=settings.lnbits_site_tagline,
|
||||
authMethods=settings.auth_allowed_methods,
|
||||
keycloakOrg=settings.keycloak_client_custom_org,
|
||||
keycloakIcon=settings.keycloak_client_custom_icon,
|
||||
hasHoldinvoice=settings.has_holdinvoice,
|
||||
hasNodemanager=settings.has_nodemanager,
|
||||
showNodemanager=settings.lnbits_node_ui and settings.has_nodemanager,
|
||||
customLogo=settings.lnbits_custom_logo,
|
||||
showVoidwallet=settings.lnbits_backend_wallet_class == "VoidWallet",
|
||||
version=settings.version,
|
||||
showHomePageElements=settings.lnbits_show_home_page_elements,
|
||||
hideApi=settings.lnbits_hide_api,
|
||||
cacheKey=str(settings.server_startup_time),
|
||||
webpushPubkey=settings.lnbits_webpush_pubkey,
|
||||
showExtensions=not settings.lnbits_extensions_deactivate_all,
|
||||
showAudit=settings.lnbits_audit_enabled,
|
||||
showAdmin=settings.lnbits_admin_ui,
|
||||
customImage=settings.lnbits_custom_image,
|
||||
customBadge=settings.lnbits_custom_badge,
|
||||
customBadgeColor=settings.lnbits_custom_badge_color,
|
||||
serviceFee=settings.lnbits_service_fee,
|
||||
serviceFeeMax=settings.lnbits_service_fee_max,
|
||||
serviceFeeWallet=settings.lnbits_service_fee_wallet,
|
||||
themeOptions=settings.lnbits_theme_options,
|
||||
defaultReaction=settings.lnbits_default_reaction,
|
||||
defaultTheme=settings.lnbits_default_theme,
|
||||
defaultBorder=settings.lnbits_default_border,
|
||||
defaultGradient=settings.lnbits_default_gradient,
|
||||
defaultBgimage=settings.lnbits_default_bgimage,
|
||||
denomination=settings.lnbits_denomination,
|
||||
extensions=list(settings.lnbits_installed_extensions_ids),
|
||||
allowedCurrencies=settings.lnbits_allowed_currencies,
|
||||
extensionsReviewsUrl=settings.lnbits_extensions_reviews_url,
|
||||
extBuilder=settings.lnbits_extensions_builder_activate_non_admins,
|
||||
nostrConfigured=settings.is_nostr_notifications_configured(),
|
||||
telegramConfigured=settings.is_telegram_notifications_configured(),
|
||||
walletFeaturedButtonLabel=settings.lnbits_wallet_featured_button_label,
|
||||
walletFeaturedButtonUrl=settings.lnbits_wallet_featured_button_url,
|
||||
walletFeaturedButtonIcon=settings.lnbits_wallet_featured_button_icon,
|
||||
)
|
||||
|
||||
|
||||
class SuperSettings(EditableSettings):
|
||||
super_user: str
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+10
-10
File diff suppressed because one or more lines are too long
@@ -363,18 +363,6 @@ window.localisation.en = {
|
||||
watchdog: 'Watchdog',
|
||||
server_logs: 'Server Logs',
|
||||
ip_blocker: 'IP Blocker',
|
||||
route_access_control: 'Route Access Control',
|
||||
route_access_control_enable: 'Enable route access control',
|
||||
route_access_control_hint:
|
||||
'When enabled, non-local requests are restricted. Requests from 127.0.0.1 (and ::1) are always allowed.',
|
||||
route_access_whitelist_label: 'Route Whitelist',
|
||||
route_access_whitelist_hint:
|
||||
'Only allow non-local access to these routes (leave empty to disable whitelist mode).',
|
||||
route_access_blacklist_label: 'Route Blacklist',
|
||||
route_access_blacklist_hint:
|
||||
'Block non-local access to these routes (ignored when a whitelist is set).',
|
||||
route_access_save_confirm:
|
||||
'Are you sure you want to save? This can impact access to LNbits if you are not accessing locally.',
|
||||
security: 'Security',
|
||||
security_tools: 'Security tools',
|
||||
block_access_hint: 'Block access by IP',
|
||||
@@ -530,6 +518,7 @@ window.localisation.en = {
|
||||
hash: 'Hash: ',
|
||||
welcome_lnbits: 'Welcome to LNbits',
|
||||
setup_su_account: 'Set up the Superuser account below.',
|
||||
first_install_token: 'First Install Token (optional)',
|
||||
create_ticker_converter: 'Create Currency Ticker Converter',
|
||||
enable_audit: 'Enable Audit',
|
||||
recommended: 'Recommended',
|
||||
|
||||
@@ -35,23 +35,6 @@ window.app.component('lnbits-extension-rating', {
|
||||
|
||||
window.app.component('lnbits-manage', {
|
||||
template: '#lnbits-manage',
|
||||
computed: {
|
||||
showAdmin() {
|
||||
return this.LNBITS_ADMIN_UI
|
||||
},
|
||||
showUsers() {
|
||||
return this.LNBITS_ADMIN_UI
|
||||
},
|
||||
showNode() {
|
||||
return this.LNBITS_NODE_UI
|
||||
},
|
||||
showAudit() {
|
||||
return this.LNBITS_AUDIT_ENABLED
|
||||
},
|
||||
showExtensions() {
|
||||
return this.LNBITS_EXTENSIONS_DEACTIVATE_ALL === false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isActive(path) {
|
||||
return window.location.pathname === path
|
||||
@@ -462,9 +445,7 @@ window.app.component('username-password', {
|
||||
username: this.userName,
|
||||
password: this.password_1,
|
||||
passwordRepeat: this.password_2,
|
||||
reset_key: this.resetKey,
|
||||
keycloakOrg: LNBITS_AUTH_KEYCLOAK_ORG || 'Keycloak',
|
||||
keycloakIcon: LNBITS_AUTH_KEYCLOAK_ICON
|
||||
reset_key: this.resetKey
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -8,58 +8,11 @@ window.app.component('lnbits-admin-security', {
|
||||
serverlogEnabled: false,
|
||||
nostrAcceptedUrl: '',
|
||||
formAllowedIPs: '',
|
||||
formCallbackUrlRule: '',
|
||||
routeOptions: [],
|
||||
routeOptionsFiltered: [],
|
||||
routeOptionsLoading: false
|
||||
formCallbackUrlRule: ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadOpenApiRoutes()
|
||||
},
|
||||
created() {},
|
||||
methods: {
|
||||
async loadOpenApiRoutes() {
|
||||
this.routeOptionsLoading = true
|
||||
try {
|
||||
const response = await fetch('/openapi.json')
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load OpenAPI spec')
|
||||
}
|
||||
const data = await response.json()
|
||||
const paths = Object.keys(data.paths || {}).sort()
|
||||
this.routeOptions = paths
|
||||
this.routeOptionsFiltered = paths
|
||||
} catch (error) {
|
||||
console.warn(error)
|
||||
} finally {
|
||||
this.routeOptionsLoading = false
|
||||
}
|
||||
},
|
||||
addRouteOption(value, done) {
|
||||
const route = value.trim()
|
||||
if (!route) {
|
||||
done()
|
||||
return
|
||||
}
|
||||
if (!this.routeOptions.includes(route)) {
|
||||
this.routeOptions.push(route)
|
||||
this.routeOptions.sort()
|
||||
}
|
||||
this.routeOptionsFiltered = this.routeOptions
|
||||
done(route)
|
||||
},
|
||||
filterRouteOptions(val, update) {
|
||||
update(() => {
|
||||
if (!val) {
|
||||
this.routeOptionsFiltered = this.routeOptions
|
||||
return
|
||||
}
|
||||
const needle = val.toLowerCase()
|
||||
this.routeOptionsFiltered = this.routeOptions.filter(route =>
|
||||
route.toLowerCase().includes(needle)
|
||||
)
|
||||
})
|
||||
},
|
||||
addAllowedIPs() {
|
||||
const allowedIPs = this.formAllowedIPs.trim()
|
||||
const allowed_ips = this.formData.lnbits_allowed_ips
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
window.app.component('lnbits-footer', {
|
||||
template: '#lnbits-footer',
|
||||
computed: {
|
||||
version() {
|
||||
return this.LNBITS_VERSION || 'unknown version'
|
||||
},
|
||||
title() {
|
||||
return `${this.SITE_TITLE}, ${this.SITE_TAGLINE}`
|
||||
return `${this.g.settings.siteTitle}, ${this.g.settings.siteTagline}`
|
||||
},
|
||||
showFooter() {
|
||||
return this.LNBITS_SHOW_HOME_PAGE_ELEMENTS == true
|
||||
version() {
|
||||
return this.$t('lnbits_version') + ': ' + this.g.settings.version
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,48 +1,9 @@
|
||||
window.app.component('lnbits-header', {
|
||||
template: '#lnbits-header',
|
||||
computed: {
|
||||
hasServiceFeeMax() {
|
||||
return (
|
||||
this.g.user &&
|
||||
this.LNBITS_SERVICE_FEE_MAX &&
|
||||
this.LNBITS_SERVICE_FEE_MAX > 0
|
||||
)
|
||||
},
|
||||
serviceFeeMax() {
|
||||
return this.LNBITS_SERVICE_FEE_MAX || 0
|
||||
},
|
||||
hasServiceFee() {
|
||||
return (
|
||||
this.g.user && this.LNBITS_SERVICE_FEE && this.LNBITS_SERVICE_FEE > 0
|
||||
)
|
||||
},
|
||||
serviceFee() {
|
||||
return this.LNBITS_SERVICE_FEE || 0
|
||||
},
|
||||
hasCustomBadge() {
|
||||
return this.LNBITS_CUSTOM_BADGE && this.LNBITS_CUSTOM_BADGE != ''
|
||||
},
|
||||
customBadge() {
|
||||
return this.LNBITS_CUSTOM_BADGE || ''
|
||||
},
|
||||
customBadgeColor() {
|
||||
return this.LNBITS_CUSTOM_BADGE_COLOR || ''
|
||||
},
|
||||
title() {
|
||||
return this.SITE_TITLE
|
||||
},
|
||||
titleIsLnbits() {
|
||||
return this.SITE_TITLE == 'LNbits'
|
||||
},
|
||||
customLogoUrl() {
|
||||
return this.USE_CUSTOM_LOGO || null
|
||||
},
|
||||
showAdmin() {
|
||||
return this.g.user && (this.g.user.super_user || this.g.user.admin)
|
||||
},
|
||||
showVoidwallet() {
|
||||
return this.g.user && this.VOIDWALLET == true
|
||||
},
|
||||
displayName() {
|
||||
return (
|
||||
this.g.user?.extra?.display_name ||
|
||||
|
||||
@@ -95,8 +95,8 @@ window.app.component('lnbits-home-logos', {
|
||||
showLogos() {
|
||||
return (
|
||||
this.g.isSatsDenomination &&
|
||||
this.SITE_TITLE == 'LNbits' &&
|
||||
this.LNBITS_SHOW_HOME_PAGE_ELEMENTS == true
|
||||
this.g.settings.siteTitle == 'LNbits' &&
|
||||
this.g.settings.showHomePageElements == true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ window.app.component('lnbits-qrcode', {
|
||||
},
|
||||
logo: {
|
||||
type: String,
|
||||
default: LNBITS_QR_LOGO
|
||||
default: window.g.settings.qrLogo || null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
||||
@@ -127,7 +127,7 @@ window.app.component('lnbits-theme', {
|
||||
if (this.g.mobileSimple === true) {
|
||||
document.body.classList.add('mobile-simple')
|
||||
}
|
||||
Object.entries(this.g.user.uiCustomization || {}).forEach(
|
||||
Object.entries(this.g.user?.uiCustomization || {}).forEach(
|
||||
([key, value]) => {
|
||||
if (key in this.g) {
|
||||
this.g[key] = value
|
||||
|
||||
+35
-40
@@ -9,26 +9,22 @@ const localStore = (key, defaultValue) => {
|
||||
}
|
||||
|
||||
window.g = Vue.reactive({
|
||||
errorCode: null,
|
||||
errorMessage: null,
|
||||
user: null,
|
||||
wallet: null,
|
||||
isPublicPage: true,
|
||||
isUserAuthorized: !!Quasar.Cookies.get('is_lnbits_user_authorized'),
|
||||
isUserImpersonated: !!Quasar.Cookies.get('is_lnbits_user_impersonated'),
|
||||
offline: !navigator.onLine,
|
||||
hasCamera: false,
|
||||
visibleDrawer: false,
|
||||
extensions: WINDOW_SETTINGS.EXTENSIONS,
|
||||
fiatBalance: 0,
|
||||
exchangeRate: 0,
|
||||
fiatTracking: false,
|
||||
payments: [],
|
||||
walletEventListeners: [],
|
||||
updatePayments: false, // used for updating the lnbits-payment-list
|
||||
updatePaymentsHash: false, // used for closing the receive dialog
|
||||
currencies: WINDOW_SETTINGS.LNBITS_CURRENCIES ?? [],
|
||||
allowedCurrencies: WINDOW_SETTINGS.LNBITS_ALLOWED_CURRENCIES ?? [],
|
||||
// vars from server
|
||||
settings: SETTINGS,
|
||||
currencies: CURRENCIES,
|
||||
extensions: SETTINGS.extensions,
|
||||
allowedCurrencies: SETTINGS.allowedCurrencies,
|
||||
denomination: SETTINGS.denomination,
|
||||
isSatsDenomination: SETTINGS.denomination == 'sats',
|
||||
// local storage vars
|
||||
themeChoice: localStore('lnbits.theme', SETTINGS.defaultTheme),
|
||||
borderChoice: localStore('lnbits.border', SETTINGS.defaultBorder),
|
||||
gradientChoice: localStore('lnbits.gradientBg', SETTINGS.defaultGradient),
|
||||
reactionChoice: localStore('lnbits.reactions', SETTINGS.defaultReaction),
|
||||
bgimageChoice: localStore(
|
||||
'lnbits.backgroundImage',
|
||||
SETTINGS.defaultBgimage || ''
|
||||
),
|
||||
locale: localStore('lnbits.lang', navigator.languages[1] ?? 'en'),
|
||||
disclaimerShown: localStore('lnbits.disclaimerShown', false),
|
||||
isFiatPriority: localStore('lnbits.isFiatPriority', false),
|
||||
@@ -36,26 +32,25 @@ window.g = Vue.reactive({
|
||||
walletFlip: localStore('lnbits.walletFlip', false),
|
||||
lastActiveWallet: localStore('lnbits.lastActiveWallet', null),
|
||||
darkChoice: localStore('lnbits.darkMode', true),
|
||||
themeChoice: localStore('lnbits.theme', WINDOW_SETTINGS.LNBITS_DEFAULT_THEME),
|
||||
borderChoice: localStore(
|
||||
'lnbits.border',
|
||||
WINDOW_SETTINGS.LNBITS_DEFAULT_BORDER || 'hard-border'
|
||||
),
|
||||
gradientChoice: localStore(
|
||||
'lnbits.gradientBg',
|
||||
WINDOW_SETTINGS.LNBITS_DEFAULT_GRADIENT || false
|
||||
),
|
||||
reactionChoice: localStore(
|
||||
'lnbits.reactions',
|
||||
WINDOW_SETTINGS.LNBITS_DEFAULT_REACTION || 'confettiBothSides'
|
||||
),
|
||||
bgimageChoice: localStore(
|
||||
'lnbits.backgroundImage',
|
||||
WINDOW_SETTINGS.LNBITS_DEFAULT_BGIMAGE || ''
|
||||
),
|
||||
ads: WINDOW_SETTINGS.AD_SPACE.split(',').map(ad => ad.split(';')),
|
||||
denomination: WINDOW_SETTINGS.LNBITS_DENOMINATION,
|
||||
isSatsDenomination: WINDOW_SETTINGS.LNBITS_DENOMINATION == 'sats',
|
||||
// cookie vars
|
||||
isUserAuthorized: !!Quasar.Cookies.get('is_lnbits_user_authorized'),
|
||||
isUserImpersonated: !!Quasar.Cookies.get('is_lnbits_user_impersonated'),
|
||||
// frontend vars
|
||||
errorCode: null,
|
||||
errorMessage: null,
|
||||
user: null,
|
||||
wallet: null,
|
||||
isPublicPage: true,
|
||||
offline: !navigator.onLine,
|
||||
hasCamera: false,
|
||||
visibleDrawer: false,
|
||||
fiatBalance: 0,
|
||||
exchangeRate: 0,
|
||||
fiatTracking: false,
|
||||
payments: [],
|
||||
walletEventListeners: [],
|
||||
updatePayments: false, // used for updating the lnbits-payment-list
|
||||
updatePaymentsHash: false, // used for closing the receive dialog
|
||||
scanner: null,
|
||||
newWalletType: null
|
||||
})
|
||||
|
||||
@@ -157,8 +157,7 @@ window.app.mixin({
|
||||
return {
|
||||
api: window._lnbitsApi,
|
||||
utils: window._lnbitsUtils,
|
||||
g: window.g,
|
||||
...WINDOW_SETTINGS
|
||||
g: window.g
|
||||
}
|
||||
},
|
||||
// backwards compatibility for extensions, should not be used in the future
|
||||
|
||||
@@ -711,7 +711,7 @@ window.PageAccount = {
|
||||
await this.getUserAssets()
|
||||
// filter out themes that are not allowed
|
||||
this.themeOptions = this.themeOptions.filter(theme =>
|
||||
this.LNBITS_THEME_OPTIONS.includes(theme.name)
|
||||
this.g.settings.themeOptions.includes(theme.name)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,7 @@ window.PageAdmin = {
|
||||
lnbits_exchange_rate_providers: [],
|
||||
lnbits_audit_exclude_paths: [],
|
||||
lnbits_audit_include_paths: [],
|
||||
lnbits_audit_http_response_codes: [],
|
||||
lnbits_route_access_whitelist: [],
|
||||
lnbits_route_access_blacklist: []
|
||||
lnbits_audit_http_response_codes: []
|
||||
},
|
||||
isSuperUser: false,
|
||||
needsRestart: false
|
||||
@@ -71,25 +69,6 @@ window.PageAdmin = {
|
||||
.catch(LNbits.utils.notifyApiError)
|
||||
},
|
||||
updateSettings() {
|
||||
if (this.shouldConfirmRouteAccess()) {
|
||||
LNbits.utils
|
||||
.confirmDialog(this.$t('route_access_save_confirm'))
|
||||
.onOk(() => this.persistSettings())
|
||||
return
|
||||
}
|
||||
this.persistSettings()
|
||||
},
|
||||
shouldConfirmRouteAccess() {
|
||||
const fields = [
|
||||
'lnbits_route_access_control_enabled',
|
||||
'lnbits_route_access_whitelist',
|
||||
'lnbits_route_access_blacklist'
|
||||
]
|
||||
return fields.some(
|
||||
field => !_.isEqual(this.settings[field], this.formData[field])
|
||||
)
|
||||
},
|
||||
persistSettings() {
|
||||
const data = _.omit(this.formData, [
|
||||
'is_super_user',
|
||||
'lnbits_allowed_funding_sources',
|
||||
|
||||
@@ -842,8 +842,8 @@ window.PageExtensions = {
|
||||
},
|
||||
async created() {
|
||||
this.extensions = await this.fetchAllExtensions()
|
||||
this.extbuilderEnabled = this.g.user.admin || this.LNBITS_EXT_BUILDER
|
||||
this.reviewsUrl = this.LNBITS_EXTENSIONS_REVIEWS_URL
|
||||
this.extbuilderEnabled = this.g.user.admin || this.g.settings.extBuilder
|
||||
this.reviewsUrl = this.g.settings.extensionsReviewsUrl
|
||||
|
||||
if (this.g.user.extensions.length === 0) {
|
||||
this.tab = 'all'
|
||||
|
||||
@@ -7,7 +7,8 @@ window.PageFirstInstall = {
|
||||
isPwdRepeat: true,
|
||||
username: '',
|
||||
password: '',
|
||||
passwordRepeat: ''
|
||||
passwordRepeat: '',
|
||||
firstInstallToken: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -17,20 +18,25 @@ window.PageFirstInstall = {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async setPassword() {
|
||||
try {
|
||||
await LNbits.api.request('PUT', '/api/v1/auth/first_install', null, {
|
||||
setPassword() {
|
||||
LNbits.api
|
||||
.request('PUT', `/api/v1/auth/first_install`, null, {
|
||||
username: this.loginData.username,
|
||||
password: this.loginData.password,
|
||||
password_repeat: this.loginData.passwordRepeat
|
||||
password_repeat: this.loginData.passwordRepeat,
|
||||
first_install_token: this.loginData.firstInstallToken
|
||||
})
|
||||
window.location.href = '/admin'
|
||||
} catch (e) {
|
||||
LNbits.utils.notifyApiError(e)
|
||||
}
|
||||
.then(async () => {
|
||||
const res = await LNbits.api.getAuthUser()
|
||||
this.g.user = LNbits.map.user(res.data)
|
||||
this.g.isPublicPage = false
|
||||
this.$router.push('/admin')
|
||||
})
|
||||
.catch(this.utils.notifyApiError)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
document.title = 'First Install - LNbits'
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
this.loginData.firstInstallToken = params.get('token') || ''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,40 +19,15 @@ window.PageHome = {
|
||||
showClaimLnurl() {
|
||||
return (
|
||||
this.lnurl !== '' &&
|
||||
this.allowRegister &&
|
||||
'user-id-only' in this.LNBITS_AUTH_METHODS
|
||||
this.g.settings.allowRegister &&
|
||||
'user-id-only' in this.g.settings.authMethods
|
||||
)
|
||||
},
|
||||
formatDescription() {
|
||||
return LNbits.utils.convertMarkdown(this.SITE_DESCRIPTION)
|
||||
return LNbits.utils.convertMarkdown(this.g.settings.siteDescription)
|
||||
},
|
||||
isAccessTokenExpired() {
|
||||
return this.$q.cookies.get('is_access_token_expired')
|
||||
},
|
||||
allowRegister() {
|
||||
return this.LNBITS_NEW_ACCOUNTS_ALLOWED
|
||||
},
|
||||
// TODO: this makes no sense
|
||||
hasCustomImage() {
|
||||
return this.LNBITS_CUSTOM_IMAGE
|
||||
},
|
||||
showHomepageElements() {
|
||||
return this.LNBITS_SHOW_HOME_PAGE_ELEMENTS === true
|
||||
},
|
||||
siteTitle() {
|
||||
return this.SITE_TITLE || ''
|
||||
},
|
||||
siteTagline() {
|
||||
return this.SITE_TAGLINE || ''
|
||||
},
|
||||
adsEnabled() {
|
||||
return this.AD_SPACE_ENABLED && this.AD_SPACE && this.AD_SPACE.length > 0
|
||||
},
|
||||
adsTitle() {
|
||||
return this.AD_SPACE_TITLE || ''
|
||||
},
|
||||
ads() {
|
||||
return this.AD_SPACE.map(ad => ad.split(';'))
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
window._lnbitsUtils = {
|
||||
url_for(url) {
|
||||
const _url = new URL(url, window.location.origin)
|
||||
_url.searchParams.set('v', WINDOW_SETTINGS.CACHE_KEY)
|
||||
_url.searchParams.set('v', window.g.settings.cacheKey)
|
||||
return _url.toString()
|
||||
},
|
||||
loadScript(src) {
|
||||
|
||||
+5
-1
@@ -178,7 +178,11 @@ async def invoice_callback_dispatcher(checking_id: str, is_internal: bool = Fals
|
||||
logger.warning(f"Payment '{checking_id}' is not incoming, skipping.")
|
||||
return
|
||||
|
||||
status = await payment.check_status(skip_internal_payment_notifications=True)
|
||||
from lnbits.core.services.payments import check_payment_status
|
||||
|
||||
status = await check_payment_status(
|
||||
payment, skip_internal_payment_notifications=True
|
||||
)
|
||||
payment.fee = status.fee_msat or payment.fee
|
||||
# only overwrite preimage if status.preimage provides it
|
||||
payment.preimage = status.preimage or payment.preimage
|
||||
|
||||
@@ -70,10 +70,10 @@
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
const RENDERED_ROUTE = '{{ normalize_path(request.path) }}'
|
||||
const WINDOW_SETTINGS = {{ WINDOW_SETTINGS | tojson }}
|
||||
Object.keys(WINDOW_SETTINGS).forEach(key => {
|
||||
window[key] = WINDOW_SETTINGS[key]
|
||||
})
|
||||
const SETTINGS = {{ SETTINGS | tojson | safe }}
|
||||
const CURRENCIES = {{ CURRENCIES | tojson | safe }}
|
||||
// backwards compatibility for extensions (tpos)
|
||||
const LNBITS_DENOMINATION = {{ LNBITS_DENOMINATION | tojson | safe }}
|
||||
</script>
|
||||
<!-- vue templates -->
|
||||
{% include('components.vue') %} {% include('pages.vue') %}
|
||||
|
||||
@@ -36,7 +36,7 @@ include('components/lnbits-error.vue') %}
|
||||
<q-list v-if="g.user" dense class="lnbits-drawer__q-list">
|
||||
<q-item-label header v-text="$t('manage')"></q-item-label>
|
||||
<div v-if="g.user.admin">
|
||||
<q-item v-if="showAdmin" to="/admin">
|
||||
<q-item v-if="g.settings.showAdmin" to="/admin">
|
||||
<q-item-section side>
|
||||
<q-icon
|
||||
name="settings"
|
||||
@@ -51,7 +51,7 @@ include('components/lnbits-error.vue') %}
|
||||
<q-icon name="chevron_right" color="grey-5" size="md"></q-icon>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item v-if="showNode" to="/node">
|
||||
<q-item v-if="g.settings.showNodemanager" to="/node">
|
||||
<q-item-section side>
|
||||
<q-icon
|
||||
name="developer_board"
|
||||
@@ -66,7 +66,7 @@ include('components/lnbits-error.vue') %}
|
||||
<q-icon name="chevron_right" color="grey-5" size="md"></q-icon>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item v-if="showUsers" to="/users">
|
||||
<q-item v-if="g.settings.showAdmin" to="/users">
|
||||
<q-item-section side>
|
||||
<q-icon
|
||||
name="groups"
|
||||
@@ -81,7 +81,7 @@ include('components/lnbits-error.vue') %}
|
||||
<q-icon name="chevron_right" color="grey-5" size="md"></q-icon>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item v-if="showAudit" to="/audit">
|
||||
<q-item v-if="g.settings.showAudit" to="/audit">
|
||||
<q-item-section side>
|
||||
<q-icon
|
||||
name="playlist_add_check_circle"
|
||||
@@ -112,7 +112,7 @@ include('components/lnbits-error.vue') %}
|
||||
<q-icon name="chevron_right" color="grey-5" size="md"></q-icon>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item v-if="showExtensions" to="/extensions">
|
||||
<q-item v-if="g.settings.showExtensions" to="/extensions">
|
||||
<q-item-section side>
|
||||
<q-icon
|
||||
name="extension"
|
||||
@@ -686,7 +686,7 @@ include('components/lnbits-error.vue') %}
|
||||
dense
|
||||
filled
|
||||
v-model="walletName"
|
||||
:label="$t('name_your_wallet', {name: SITE_TITLE + ' *'})"
|
||||
:label="$t('name_your_wallet', {name: g.settings.siteTitle + ' *'})"
|
||||
></q-input>
|
||||
<q-card-actions vertical align="center" class="q-pa-none">
|
||||
<q-btn
|
||||
@@ -960,9 +960,9 @@ include('components/lnbits-error.vue') %}
|
||||
<q-avatar size="32px" class="q-mr-md">
|
||||
<q-img
|
||||
:src="
|
||||
keycloakIcon
|
||||
? keycloakIcon
|
||||
: 'lnbits/static/images/keycloak-logo.png'
|
||||
g.settings.keycloakIcon
|
||||
? g.settings.keycloakIcon
|
||||
: utils.url_for('lnbits/static/images/keycloak-logo.png')
|
||||
"
|
||||
></q-img>
|
||||
</q-avatar>
|
||||
@@ -970,7 +970,7 @@ include('components/lnbits-error.vue') %}
|
||||
<span
|
||||
v-text="
|
||||
$t('signin_with_custom_org', {
|
||||
custom_org: keycloakOrg
|
||||
custom_org: g.settings.keycloakOrg || 'Keycloak'
|
||||
})
|
||||
"
|
||||
></span>
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
<br />
|
||||
</div>
|
||||
<div class="col">
|
||||
<div v-if="LNBITS_NODE_UI">
|
||||
<div v-if="g.settings.hasNodemanager">
|
||||
<p>
|
||||
<span v-text="$t('node_management')"></span>
|
||||
</p>
|
||||
@@ -70,7 +70,7 @@
|
||||
v-model="formData.lnbits_node_ui_transactions"
|
||||
></q-toggle>
|
||||
</div>
|
||||
<p v-if="!LNBITS_NODE_UI">
|
||||
<p v-if="!g.settings.hasNodemanager">
|
||||
<span v-text="$t('node_management_not_supported')"></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -296,56 +296,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-12">
|
||||
<p v-text="$t('route_access_control')"></p>
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12">
|
||||
<q-toggle
|
||||
v-model="formData.lnbits_route_access_control_enabled"
|
||||
:label="$t('route_access_control_enable')"
|
||||
></q-toggle>
|
||||
<div
|
||||
class="text-caption text-grey-6 q-mt-xs"
|
||||
v-text="$t('route_access_control_hint')"
|
||||
></div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<q-select
|
||||
filled
|
||||
multiple
|
||||
use-chips
|
||||
use-input
|
||||
input-debounce="0"
|
||||
new-value-mode="add-unique"
|
||||
@new-value="addRouteOption"
|
||||
:options="routeOptionsFiltered"
|
||||
:loading="routeOptionsLoading"
|
||||
@filter="filterRouteOptions"
|
||||
v-model="formData.lnbits_route_access_whitelist"
|
||||
:label="$t('route_access_whitelist_label')"
|
||||
:hint="$t('route_access_whitelist_hint')"
|
||||
></q-select>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<q-select
|
||||
filled
|
||||
multiple
|
||||
use-chips
|
||||
use-input
|
||||
input-debounce="0"
|
||||
new-value-mode="add-unique"
|
||||
@new-value="addRouteOption"
|
||||
:options="routeOptionsFiltered"
|
||||
:loading="routeOptionsLoading"
|
||||
@filter="filterRouteOptions"
|
||||
v-model="formData.lnbits_route_access_blacklist"
|
||||
:label="$t('route_access_blacklist_label')"
|
||||
:hint="$t('route_access_blacklist_hint')"
|
||||
></q-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-12">
|
||||
<p v-text="$t('rate_limiter')"></p>
|
||||
<div class="row q-col-gutter-md">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template id="lnbits-footer">
|
||||
<q-footer
|
||||
v-if="showFooter"
|
||||
v-if="g.settings.showHomePageElements"
|
||||
class="bg-transparent q-px-lg q-py-md"
|
||||
:class="{'text-dark': !$q.dark.isActive}"
|
||||
>
|
||||
@@ -9,7 +9,7 @@
|
||||
<q-toolbar-title class="text-caption">
|
||||
<span v-text="title"></span>
|
||||
<br />
|
||||
<small v-text="$t('lnbits_version') + ': ' + version"></small>
|
||||
<small v-text="version"></small>
|
||||
</q-toolbar-title>
|
||||
<q-space></q-space>
|
||||
<q-btn
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<template id="lnbits-header">
|
||||
<q-header bordered class="bg-marginal-bg">
|
||||
<q-banner v-if="showVoidwallet" class="bg-warning text-white" dense>
|
||||
<q-banner
|
||||
v-if="g.settings.showVoidwallet"
|
||||
class="bg-warning text-white"
|
||||
dense
|
||||
>
|
||||
<template v-slot:avatar>
|
||||
<q-icon name="warning" color="white" />
|
||||
</template>
|
||||
@@ -24,13 +28,15 @@
|
||||
<q-toolbar-title>
|
||||
<q-btn flat no-caps dense class="q-mr-sm" size="lg" type="a" href="/">
|
||||
<q-img
|
||||
v-if="customLogoUrl"
|
||||
v-if="customLogo"
|
||||
height="30px"
|
||||
alt="Logo"
|
||||
:src="customLogoUrl"
|
||||
:src="customLogo"
|
||||
></q-img>
|
||||
<span v-else-if="!titleIsLnbits"><strong>LN</strong>bits</span>
|
||||
<span v-else v-text="title"></span>
|
||||
<span v-else-if="g.settings.siteTitle == 'LNbits'"
|
||||
><strong>LN</strong>bits</span
|
||||
>
|
||||
<span v-else v-text="g.settings.siteTitle"></span>
|
||||
</q-btn>
|
||||
<q-badge v-if="g.user && g.user.super_user">Super User</q-badge>
|
||||
<q-badge v-else-if="g.user && g.user.admin">Admin User</q-badge>
|
||||
@@ -38,31 +44,31 @@
|
||||
<q-badge
|
||||
class="q-mr-md"
|
||||
v-show="$q.screen.gt.sm"
|
||||
v-if="hasCustomBadge"
|
||||
:label="customBadge"
|
||||
:color="customBadgeColor"
|
||||
v-if="g.settings.customBadge"
|
||||
:label="g.settings.customBadge"
|
||||
:color="g.settings.customBadgeColor"
|
||||
>
|
||||
</q-badge>
|
||||
|
||||
<q-badge
|
||||
v-show="$q.screen.gt.sm"
|
||||
v-if="hasServiceFee"
|
||||
v-if="g.user && g.settings.serviceFee > 0"
|
||||
color="green"
|
||||
class="q-mr-md"
|
||||
>
|
||||
<span
|
||||
v-if="hasServiceFeeMax"
|
||||
v-if="g.user && g.settings.serviceFeeMax > 0"
|
||||
v-text="
|
||||
$t('service_fee_max_badge', {
|
||||
amount: serviceFee,
|
||||
max: serviceFeeMax,
|
||||
amount: g.settings.serviceFee,
|
||||
max: g.settings.serviceFeeMax,
|
||||
denom: g.denomination
|
||||
})
|
||||
"
|
||||
></span>
|
||||
<span
|
||||
v-else
|
||||
v-text="$t('service_fee_badge', {amount: serviceFee})"
|
||||
v-text="$t('service_fee_badge', {amount: g.settings.serviceFee})"
|
||||
></span>
|
||||
<q-tooltip><span v-text="$t('service_fee_tooltip')"></span></q-tooltip>
|
||||
</q-badge>
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
</q-list>
|
||||
</q-card-section>
|
||||
<q-expansion-item
|
||||
v-if="!HIDE_API"
|
||||
v-if="!g.settings.hideApi"
|
||||
group="api"
|
||||
dense
|
||||
expand-separator
|
||||
@@ -152,7 +152,7 @@
|
||||
</q-expansion-item>
|
||||
|
||||
<q-expansion-item
|
||||
v-if="!HIDE_API"
|
||||
v-if="!g.settings.hideApi"
|
||||
group="api"
|
||||
dense
|
||||
expand-separator
|
||||
@@ -197,7 +197,7 @@
|
||||
</q-card>
|
||||
</q-expansion-item>
|
||||
<q-expansion-item
|
||||
v-if="!HIDE_API"
|
||||
v-if="!g.settings.hideApi"
|
||||
group="api"
|
||||
dense
|
||||
expand-separator
|
||||
@@ -244,7 +244,7 @@
|
||||
</q-expansion-item>
|
||||
|
||||
<q-expansion-item
|
||||
v-if="!HIDE_API"
|
||||
v-if="!g.settings.hideApi"
|
||||
group="api"
|
||||
dense
|
||||
expand-separator
|
||||
@@ -273,7 +273,7 @@
|
||||
</q-card>
|
||||
</q-expansion-item>
|
||||
<q-expansion-item
|
||||
v-if="!HIDE_API"
|
||||
v-if="!g.settings.hideApi"
|
||||
group="api"
|
||||
dense
|
||||
expand-separator
|
||||
|
||||
@@ -258,15 +258,20 @@
|
||||
class="q-mb-md"
|
||||
>
|
||||
</q-input>
|
||||
<div v-if="!g.user.email" class="row"></div>
|
||||
<div v-if="!g.user.email" class="row">
|
||||
{% if "google-auth" in LNBITS_AUTH_METHODS or
|
||||
"github-auth" in LNBITS_AUTH_METHODS %}
|
||||
<div class="col q-pa-sm text-h6">
|
||||
<div
|
||||
v-if="
|
||||
'google-auth' in g.settings.authMethods ||
|
||||
'github-auth' in g.settings.authMethods
|
||||
"
|
||||
class="col q-pa-sm text-h6"
|
||||
>
|
||||
<span v-text="$t('verify_email')"></span>:
|
||||
</div>
|
||||
{%endif%} {% if "google-auth" in LNBITS_AUTH_METHODS %}
|
||||
<div class="col q-pa-sm">
|
||||
<div
|
||||
v-if="'google-auth' in g.settings.authMethods"
|
||||
class="col q-pa-sm"
|
||||
>
|
||||
<q-btn
|
||||
:href="`/api/v1/auth/google?user_id=${g.user.id}`"
|
||||
type="a"
|
||||
@@ -284,8 +289,10 @@
|
||||
<div>Google</div>
|
||||
</q-btn>
|
||||
</div>
|
||||
{%endif%} {% if "github-auth" in LNBITS_AUTH_METHODS %}
|
||||
<div class="col q-pa-sm">
|
||||
<div
|
||||
v-if="'github-auth' in g.settings.authMethods"
|
||||
class="col q-pa-sm"
|
||||
>
|
||||
<q-btn
|
||||
:href="`/api/v1/auth/github?user_id=${g.user.id}`"
|
||||
type="a"
|
||||
@@ -303,7 +310,6 @@
|
||||
<div>GitHub</div>
|
||||
</q-btn>
|
||||
</div>
|
||||
{%endif%}
|
||||
</div>
|
||||
</q-card-section>
|
||||
|
||||
@@ -508,7 +514,7 @@
|
||||
<div class="col-8">
|
||||
<lnbits-notifications-btn
|
||||
v-if="g.user"
|
||||
pubkey="{{ WEBPUSH_PUBKEY }}"
|
||||
pubkey="g.settings.webpushPubkey"
|
||||
></lnbits-notifications-btn>
|
||||
</div>
|
||||
</div>
|
||||
@@ -552,7 +558,7 @@
|
||||
></span>
|
||||
<br />
|
||||
<q-badge
|
||||
v-if="!LNBITS_NOSTR_CONFIGURED"
|
||||
v-if="!g.settings.nostrConfigured"
|
||||
v-text="$t('not_connected')"
|
||||
></q-badge>
|
||||
</div>
|
||||
@@ -572,7 +578,7 @@
|
||||
<span v-text="$t('notifications_chat_id')"></span>
|
||||
<br />
|
||||
<q-badge
|
||||
v-if="!LNBITS_TELEGRAM_CONFIGURED"
|
||||
v-if="!g.settings.telegramConfigured"
|
||||
v-text="$t('not_connected')"
|
||||
></q-badge>
|
||||
</div>
|
||||
|
||||
@@ -53,6 +53,18 @@
|
||||
@click="loginData.isPwdRepeat = !loginData.isPwdRepeat"
|
||||
/> </template
|
||||
></q-input>
|
||||
<q-input
|
||||
filled
|
||||
v-model.trim="loginData.firstInstallToken"
|
||||
:type="loginData.isPwd ? 'password' : 'text'"
|
||||
:label="$t('first_install_token')"
|
||||
><template v-slot:append>
|
||||
<q-icon
|
||||
:name="loginData.isPwd ? 'visibility_off' : 'visibility'"
|
||||
class="cursor-pointer"
|
||||
@click="loginData.isPwd = !loginData.isPwd"
|
||||
/> </template
|
||||
></q-input>
|
||||
<q-btn
|
||||
@click="setPassword()"
|
||||
unelevated
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
<div class="home row justify-center items-center">
|
||||
<div
|
||||
class="full-width content-center"
|
||||
:style="`max-width: ${hasCustomImage ? '850' : '600'}px; min-height: 55vh;`"
|
||||
:style="`max-width: ${g.settings.customImage ? '850' : '600'}px; min-height: 55vh;`"
|
||||
>
|
||||
<div v-if="showHomepageElements" class="row q-mb-md">
|
||||
<div v-if="g.settings.showHomepageElements" class="row q-mb-md">
|
||||
<div class="col-12">
|
||||
<div>
|
||||
<h5 v-text="siteTitle" class="q-my-none"></h5>
|
||||
<h5 v-text="g.settings.siteTitle" class="q-my-none"></h5>
|
||||
<template v-if="$q.screen.gt.sm">
|
||||
<h6 class="q-my-sm" v-text="siteTagline"></h6>
|
||||
<h6 class="q-my-sm" v-text="g.settings.siteTagline"></h6>
|
||||
<p class="q-my-sm" v-html="formatDescription"></p>
|
||||
</template>
|
||||
</div>
|
||||
@@ -31,7 +31,10 @@
|
||||
<div class="row">
|
||||
<div
|
||||
class="col-12"
|
||||
:class="{'col-sm-7': hasCustomImage, 'col-lg-6': hasCustomImage}"
|
||||
:class="{
|
||||
'col-sm-7': g.settings.customImage,
|
||||
'col-lg-6': g.settings.customImage
|
||||
}"
|
||||
>
|
||||
<div v-if="showClaimLnurl">
|
||||
<q-card-section>
|
||||
@@ -54,8 +57,8 @@
|
||||
<div v-else>
|
||||
<username-password
|
||||
v-if="authMethod != 'user-id-only'"
|
||||
:allowed_new_users="allowRegister"
|
||||
:auth-methods="LNBITS_AUTH_METHODS"
|
||||
:allowed_new_users="g.settings.allowRegister"
|
||||
:auth-methods="g.settings.authMethods"
|
||||
:auth-action="authAction"
|
||||
v-model:user-name="username"
|
||||
v-model:password_1="password"
|
||||
@@ -70,7 +73,7 @@
|
||||
v-if="authAction !== 'reset'"
|
||||
>
|
||||
<p
|
||||
v-if="authAction === 'login' && allowRegister"
|
||||
v-if="authAction === 'login' && g.settings.allowRegister"
|
||||
class="q-mb-none"
|
||||
>
|
||||
Not registered?
|
||||
@@ -82,7 +85,9 @@
|
||||
>
|
||||
</p>
|
||||
<p
|
||||
v-else-if="authAction === 'login' && !allowRegister"
|
||||
v-else-if="
|
||||
authAction === 'login' && !g.settings.allowRegister
|
||||
"
|
||||
class="q-mb-none"
|
||||
>
|
||||
<span v-text="$t('new_user_not_allowed')"></span>
|
||||
@@ -101,7 +106,7 @@
|
||||
</username-password>
|
||||
<user-id-only
|
||||
v-if="authMethod == 'user-id-only'"
|
||||
:allowed_new_users="allowRegister"
|
||||
:allowed_new_users="g.settings.allowRegister"
|
||||
v-model:usr="usr"
|
||||
v-model:wallet="walletName"
|
||||
:auth-action="authAction"
|
||||
@@ -114,16 +119,20 @@
|
||||
</user-id-only>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="hasCustomImage" class="col-sm-5 col-lg-6 gt-xs">
|
||||
<div v-if="g.settings.customImage" class="col-sm-5 col-lg-6 gt-xs">
|
||||
<div class="full-height flex flex-center q-pa-lg">
|
||||
<q-img :src="hasCustomImage" :ratio="1" width="250px"></q-img>
|
||||
<q-img
|
||||
:src="g.settings.customImage"
|
||||
:ratio="1"
|
||||
width="250px"
|
||||
></q-img>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</q-card>
|
||||
</div>
|
||||
|
||||
<div v-if="showHomepageElements">
|
||||
<div v-if="g.settings.showHomepageElements">
|
||||
<div class="flex flex-center q-gutter-md q-py-md">
|
||||
<q-btn
|
||||
outline
|
||||
@@ -148,10 +157,14 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="adsEnabled"
|
||||
v-if="g.settings.showAdSpace"
|
||||
class="justify-center col-10 q-my-lg row q-col-gutter-sm"
|
||||
>
|
||||
<a :href="ad[0]" v-for="ad in g.ads" class="lnbits-ad col-12 col-md-4">
|
||||
<a
|
||||
:href="ad[0]"
|
||||
v-for="ad in g.settings.adSpace"
|
||||
class="lnbits-ad col-12 col-md-4"
|
||||
>
|
||||
<q-img v-if="$q.dark.isActive" :src="ad[1]"></q-img>
|
||||
<q-img v-else :src="ad[2]"></q-img>
|
||||
</a>
|
||||
|
||||
@@ -172,15 +172,15 @@
|
||||
></q-tooltip>
|
||||
</q-btn>
|
||||
<div
|
||||
v-if="WALLET_FEATURED_BUTTON_URL"
|
||||
v-if="g.settings.walletFeaturedButtonUrl"
|
||||
class="float-right q-mt-sm q-ml-sm"
|
||||
>
|
||||
<q-btn
|
||||
color="primary"
|
||||
:label="WALLET_FEATURED_BUTTON_LABEL"
|
||||
:icon="WALLET_FEATURED_BUTTON_ICON || undefined"
|
||||
:label="g.settings.walletFeaturedButtonLabel"
|
||||
:icon="g.settings.walletFeaturedButtonIcon || undefined"
|
||||
size="sm"
|
||||
:to="WALLET_FEATURED_BUTTON_URL"
|
||||
:to="g.settings.walletFeaturedButtonUrl"
|
||||
>
|
||||
</q-btn>
|
||||
</div>
|
||||
@@ -212,10 +212,10 @@
|
||||
@send-lnurl="handleSendLnurl"
|
||||
:chart-config="chartConfig"
|
||||
></lnbits-wallet-extra>
|
||||
<q-card class="lnbits-wallet-ads" v-if="AD_SPACE_ENABLED">
|
||||
<q-card class="lnbits-wallet-ads" v-if="g.settings.showAdSpace">
|
||||
<q-card-section class="text-subtitle1">
|
||||
<span v-text="AD_SPACE_TITLE"></span>
|
||||
<a :href="ad[0]" class="lnbits-ad" v-for="ad in g.ads">
|
||||
<span v-text="g.settings.adSpaceTitle"></span>
|
||||
<a :href="ad[0]" class="lnbits-ad" v-for="ad in g.settings.adSpace">
|
||||
<q-img class="q-mb-xs" v-if="$q.dark.isActive" :src="ad[1]"></q-img>
|
||||
<q-img class="q-mb-xs" v-else :src="ad[2]"></q-img>
|
||||
</a>
|
||||
@@ -289,7 +289,7 @@
|
||||
></q-input>
|
||||
</div>
|
||||
<q-input
|
||||
v-if="has_holdinvoice"
|
||||
v-if="g.settings.hasHoldinvoice"
|
||||
filled
|
||||
dense
|
||||
v-model="receive.data.payment_hash"
|
||||
|
||||
@@ -45,10 +45,8 @@
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
const WINDOW_SETTINGS = {{ WINDOW_SETTINGS | tojson }}
|
||||
Object.keys(WINDOW_SETTINGS).forEach(key => {
|
||||
window[key] = WINDOW_SETTINGS[key]
|
||||
})
|
||||
const SETTINGS = {{ SETTINGS | tojson | safe }}
|
||||
const CURRENCIES = {{ CURRENCIES | tojson | safe }}
|
||||
</script>
|
||||
{% include('components.vue') %}{% block vue_templates %}{% endblock %} {%
|
||||
for url in INCLUDED_JS %}
|
||||
|
||||
@@ -14,6 +14,13 @@ from lnbits.settings import settings
|
||||
|
||||
def log_server_info():
|
||||
logger.info("LNbits Info")
|
||||
if settings.first_install:
|
||||
logger.success("This is a fresh install of LNbits.")
|
||||
if settings.first_install_token:
|
||||
logger.success(
|
||||
f"FIRST_INSTALL_TOKEN: `{settings.first_install_token}`. "
|
||||
"Please provide this token on /first_install."
|
||||
)
|
||||
logger.info(f"Version: {settings.version}")
|
||||
logger.info(f"Baseurl: {settings.lnbits_baseurl}")
|
||||
logger.info(f"Host: {settings.host}")
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "lnbits"
|
||||
version = "1.4.1"
|
||||
version = "1.4.2"
|
||||
requires-python = ">=3.10,<3.13"
|
||||
description = "LNbits, free and open-source Lightning wallet and accounts system."
|
||||
authors = [{ name = "Alan Bits", email = "alan@lnbits.com" }]
|
||||
|
||||
@@ -7,7 +7,11 @@ from lnbits import bolt11
|
||||
from lnbits.core.crud import get_standalone_payment, update_payment
|
||||
from lnbits.core.crud.wallets import create_wallet, get_wallet
|
||||
from lnbits.core.models import CreateInvoice, Payment, PaymentState
|
||||
from lnbits.core.services import fee_reserve_total, get_balance_delta
|
||||
from lnbits.core.services import (
|
||||
check_payment_status,
|
||||
fee_reserve_total,
|
||||
get_balance_delta,
|
||||
)
|
||||
from lnbits.core.services.payments import pay_invoice, update_wallet_balance
|
||||
from lnbits.core.services.users import create_user_account
|
||||
from lnbits.exceptions import PaymentError
|
||||
@@ -355,7 +359,7 @@ async def test_pay_hold_invoice_check_pending_and_fail_cancel_payment_task_in_me
|
||||
assert payment_db_after_settlement is not None
|
||||
|
||||
# payment is failed
|
||||
status = await payment_db_after_settlement.check_status()
|
||||
status = await check_payment_status(payment_db_after_settlement)
|
||||
assert not status.paid
|
||||
assert status.failed
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ from lnbits.core.crud.wallets import create_wallet
|
||||
from lnbits.core.models.payments import CreateInvoice, PaymentState
|
||||
from lnbits.core.models.users import User
|
||||
from lnbits.core.models.wallets import Wallet
|
||||
from lnbits.core.services import payments
|
||||
from lnbits.core.services import check_payment_status, payments
|
||||
from lnbits.core.services.fiat_providers import (
|
||||
check_stripe_signature,
|
||||
handle_fiat_payment_confirmation,
|
||||
@@ -221,7 +221,7 @@ async def test_create_wallet_fiat_invoice_success(
|
||||
assert payment.checking_id.startswith("fiat_stripe_")
|
||||
assert payment.fee <= 0
|
||||
|
||||
status = await payment.check_status()
|
||||
status = await check_payment_status(payment)
|
||||
assert status.success is False
|
||||
assert status.pending is True
|
||||
|
||||
@@ -230,7 +230,7 @@ async def test_create_wallet_fiat_invoice_success(
|
||||
"lnbits.fiat.StripeWallet.get_invoice_status",
|
||||
AsyncMock(return_value=fiat_mock_status),
|
||||
)
|
||||
status = await payment.check_status()
|
||||
status = await check_payment_status(payment)
|
||||
assert status.paid is True
|
||||
assert status.success is True
|
||||
|
||||
|
||||
Reference in New Issue
Block a user