Compare commits
38
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0ea42fd70 | ||
|
|
cd58a0dc9b | ||
|
|
cac4e309f4 | ||
|
|
632cd257d7 | ||
|
|
a11274593c | ||
|
|
ae92965b69 | ||
|
|
3e1f75c4cc | ||
|
|
d668747ca2 | ||
|
|
dedfcd4818 | ||
|
|
f66f2fe6c2 | ||
|
|
f3ca37e846 | ||
|
|
d45709a80f | ||
|
|
e4b6dc84b5 | ||
|
|
67d1b4be3e | ||
|
|
bd514f1a46 | ||
|
|
03b302da69 | ||
|
|
b373e35915 | ||
|
|
0fd116e7ee | ||
|
|
2c84f9781d | ||
|
|
9fcf3121ac | ||
|
|
1df9fa831d | ||
|
|
42039070b9 | ||
|
|
2f9146fff3 | ||
|
|
5738795c8c | ||
|
|
3c77acf266 | ||
|
|
a4f2f55279 | ||
|
|
2ab7c17ceb | ||
|
|
7a2c509cd3 | ||
|
|
8a51ea53c8 | ||
|
|
19d3e9e64e | ||
|
|
9f9f14ead0 | ||
|
|
0a093d82ae | ||
|
|
17fd459fa7 | ||
|
|
fe9aa4d8c9 | ||
|
|
1af3b62739 | ||
|
|
e9cbc3492f | ||
|
|
ca2f78d25b | ||
|
|
9d9ab48345 |
+1
-9
@@ -7,13 +7,9 @@
|
|||||||
# They are NOT managed by the Admin UI and are not stored in the database.
|
# They are NOT managed by the Admin UI and are not stored in the database.
|
||||||
|
|
||||||
# === First Install Token ===
|
# === First Install Token ===
|
||||||
# If set the user is required to enter this token on the /first_install page
|
# if set the user is required to enter this token on the /first_install page
|
||||||
# FIRST_INSTALL_TOKEN="myaccesstoken"
|
# FIRST_INSTALL_TOKEN="myaccesstoken"
|
||||||
|
|
||||||
# === Security ===
|
|
||||||
# When enabled (recommended), auth cookies require HTTPS and SSO will reject insecure HTTP.
|
|
||||||
AUTH_HTTPS_ONLY=true
|
|
||||||
|
|
||||||
# === Logging and Development ===
|
# === Logging and Development ===
|
||||||
|
|
||||||
DEBUG=False
|
DEBUG=False
|
||||||
@@ -28,10 +24,6 @@ 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 ===
|
||||||
|
|
||||||
|
|||||||
@@ -69,10 +69,7 @@ jobs:
|
|||||||
--onefile \
|
--onefile \
|
||||||
--name lnbits \
|
--name lnbits \
|
||||||
--hidden-import=embit \
|
--hidden-import=embit \
|
||||||
--hidden-import=bitstring.bitstore_bitarray \
|
|
||||||
--collect-all embit \
|
--collect-all embit \
|
||||||
--collect-all bitstring \
|
|
||||||
--collect-all bitarray \
|
|
||||||
--collect-all lnbits \
|
--collect-all lnbits \
|
||||||
--collect-all sqlalchemy \
|
--collect-all sqlalchemy \
|
||||||
--collect-all breez_sdk \
|
--collect-all breez_sdk \
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
name: LNbits CI
|
name: LNbits CI
|
||||||
on:
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- dev
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
|
|
||||||
@@ -65,7 +69,7 @@ jobs:
|
|||||||
make: openapi
|
make: openapi
|
||||||
|
|
||||||
regtest:
|
regtest:
|
||||||
needs: [ lint ]
|
needs: [ lint, test-api, test-wallets, test-unit, migration, openapi ]
|
||||||
uses: ./.github/workflows/regtest.yml
|
uses: ./.github/workflows/regtest.yml
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ __pycache__
|
|||||||
*$py.class
|
*$py.class
|
||||||
.mypy_cache
|
.mypy_cache
|
||||||
.vscode
|
.vscode
|
||||||
.codex
|
|
||||||
*-lock.json
|
*-lock.json
|
||||||
.python-version
|
.python-version
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -43,4 +43,4 @@ ENV LNBITS_HOST="0.0.0.0"
|
|||||||
|
|
||||||
EXPOSE 5000
|
EXPOSE 5000
|
||||||
|
|
||||||
CMD ["sh", "-c", "uv --offline run lnbits --port $LNBITS_PORT --host $LNBITS_HOST --forwarded-allow-ips='*'"]
|
CMD ["sh", "-c", "uv run lnbits --port $LNBITS_PORT --host $LNBITS_HOST --forwarded-allow-ips='*'"]
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<a href="https://lnbits.com" target="_blank" rel="noopener noreferrer">
|
<a href="https://lnbits.com" target="_blank" rel="noopener noreferrer">
|
||||||
<picture>
|
<picture>
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="docs/logos/lnbits-full-inverse.svg">
|
<source media="(prefers-color-scheme: dark)" srcset="https://i.imgur.com/QE6SIrs.png">
|
||||||
<img src="docs/logos/lnbits-full.svg" alt="LNbits" style="width:300px">
|
<img src="https://i.imgur.com/fyKPgVT.png" alt="LNbits" style="width:300px">
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
 [![license-badge]](LICENSE) [![docs-badge]][docs]  [](https://extensions.lnbits.com/) [](https://shop.lnbits.com/) [<img src="https://img.shields.io/badge/community_chat-Telegram-24A1DE">](https://t.me/lnbits) [<img src="https://img.shields.io/badge/supported_by-%3E__OpenSats-f97316">](https://opensats.org)
|
 [![license-badge]](LICENSE) [![docs-badge]][docs]  [](https://extensions.lnbits.com/) [](https://shop.lnbits.com/) [<img src="https://img.shields.io/badge/community_chat-Telegram-24A1DE">](https://t.me/lnbits) [<img src="https://img.shields.io/badge/supported_by-%3E__OpenSats-f97316">](https://opensats.org)
|
||||||
<img alt="lnbits_head" src="docs/assets/header.jpg" />
|
<img width="2000" height="203" alt="lnbits_head" src="https://github.com/user-attachments/assets/77669718-ac10-43c7-ae95-6ce236c77401" />
|
||||||
[](https://demo.lnbits.com/tipjar/DwaUiE4kBX6mUW6pj3X5Kg)
|
[](https://demo.lnbits.com/tipjar/DwaUiE4kBX6mUW6pj3X5Kg)
|
||||||
|
|
||||||
# LNbits — The most powerful Bitcoin & Lightning toolkit
|
# LNbits — The most powerful Bitcoin & Lightning toolkit
|
||||||
@@ -46,7 +46,7 @@ Get yourself familiar and test on our demo server [demo.lnbits.com](https://demo
|
|||||||
|
|
||||||
LNbits is packaged with tools to help manage funds, such as a table of transactions, line chart of spending, export to csv. Each wallet also comes with its own API keys, to help partition the exposure of your funding source.
|
LNbits is packaged with tools to help manage funds, such as a table of transactions, line chart of spending, export to csv. Each wallet also comes with its own API keys, to help partition the exposure of your funding source.
|
||||||
|
|
||||||
<img alt="lnbits_wallet" src="docs/assets/wallet.jpg" />
|
<img src="https://i.imgur.com/w8jdGpF.png" style="width:800px">
|
||||||
|
|
||||||
## LNbits extension universe
|
## LNbits extension universe
|
||||||
|
|
||||||
@@ -54,25 +54,25 @@ Extend YOUR LNbits to meet YOUR needs.
|
|||||||
|
|
||||||
All non-core features are installed as extensions, reducing your code base and making your LNbits unique to you. Extend your LNbits install in any direction, and even create and share your own extensions.
|
All non-core features are installed as extensions, reducing your code base and making your LNbits unique to you. Extend your LNbits install in any direction, and even create and share your own extensions.
|
||||||
|
|
||||||
<img alt="lnbits_extensions" src="docs/assets/extensions.jpg" />
|
<img src="https://i.imgur.com/aEBpwJF.png" style="width:800px">
|
||||||
|
|
||||||
## LNbits API
|
## LNbits API
|
||||||
|
|
||||||
LNbits has a powerful API, many projects use LNbits to do the heavy lifting for their bitcoin/lightning services.
|
LNbits has a powerful API, many projects use LNbits to do the heavy lifting for their bitcoin/lightning services.
|
||||||
|
|
||||||
<img alt="lnbits_api" src="docs/assets/api.jpg" />
|
<img src="https://i.imgur.com/V742sb9.png" style="width:800px">
|
||||||
|
|
||||||
## LNbits node manager
|
## LNbits node manager
|
||||||
|
|
||||||
LNbits comes packaged with a light node management UI, to make running your node that much easier.
|
LNbits comes packaged with a light node management UI, to make running your node that much easier.
|
||||||
|
|
||||||
<img alt="lnbits_api" src="docs/assets/lightning_node.jpg" />
|
<img src="https://i.imgur.com/TYqIK60.png" style="width:800px">
|
||||||
|
|
||||||
## LNbits merchant tools
|
## LNbits across all your devices
|
||||||
|
|
||||||
The LNbits stack can process both bitcoin and fiat payments, making it a turnkey, all-in-one solution for merchants. With orders and inventory shared across extensions, and built-in notifications for Nostr, Telegram, and email, LNbits keeps everything in sync, freeing merchants to focus on their business.
|
As well as working great in a browser, LNbits has native IoS and Android apps as well as a chrome extension. So you can enjoy the same UI across ALL your devices.
|
||||||
|
|
||||||
<img alt="lnbits_merchants" src="docs/assets/merchants_small.webp" />
|
<img src="https://i.imgur.com/J96EbRf.png" style="width:800px">
|
||||||
|
|
||||||
## Powered by LNbits
|
## Powered by LNbits
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 180 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 317 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 139 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 157 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 305 KiB |
@@ -12,7 +12,6 @@ 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,
|
||||||
@@ -59,7 +58,6 @@ 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,
|
||||||
@@ -119,13 +117,11 @@ __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,13 +90,6 @@ 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:
|
||||||
|
|||||||
@@ -105,8 +105,7 @@ async def get_settings_field(
|
|||||||
)
|
)
|
||||||
if not row:
|
if not row:
|
||||||
return None
|
return None
|
||||||
value = json.loads(row["value"]) if row["value"] else None
|
return SettingsField(id=row["id"], value=json.loads(row["value"]), tag=row["tag"])
|
||||||
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"):
|
||||||
|
|||||||
@@ -37,13 +37,6 @@ 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,7 +9,6 @@ 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 (
|
||||||
@@ -39,8 +38,6 @@ 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()
|
||||||
|
|
||||||
@@ -66,15 +63,6 @@ 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)
|
||||||
|
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ from lnbits.core.crud.payments import get_daily_stats
|
|||||||
from lnbits.core.db import db
|
from lnbits.core.db import db
|
||||||
from lnbits.core.models import PaymentDailyStats, PaymentFilters
|
from lnbits.core.models import PaymentDailyStats, PaymentFilters
|
||||||
from lnbits.core.models.payments import CreateInvoice
|
from lnbits.core.models.payments import CreateInvoice
|
||||||
from lnbits.core.services.fiat_providers import handle_fiat_payment_confirmation
|
|
||||||
from lnbits.db import Connection, Filters
|
from lnbits.db import Connection, Filters
|
||||||
from lnbits.decorators import check_user_extension_access
|
from lnbits.decorators import check_user_extension_access
|
||||||
from lnbits.exceptions import InvoiceError, PaymentError, UnsupportedError
|
from lnbits.exceptions import InvoiceError, PaymentError, UnsupportedError
|
||||||
from lnbits.fiat import get_fiat_provider
|
from lnbits.fiat import get_fiat_provider
|
||||||
from lnbits.helpers import check_callback_url
|
from lnbits.helpers import check_callback_url
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
|
from lnbits.tasks import create_task, internal_invoice_queue_put
|
||||||
from lnbits.utils.crypto import fake_privkey, random_secret_and_hash, verify_preimage
|
from lnbits.utils.crypto import fake_privkey, random_secret_and_hash, verify_preimage
|
||||||
from lnbits.utils.exchange_rates import fiat_amount_as_satoshis, satoshis_amount_as_fiat
|
from lnbits.utils.exchange_rates import fiat_amount_as_satoshis, satoshis_amount_as_fiat
|
||||||
from lnbits.wallets import fake_wallet, get_funding_source
|
from lnbits.wallets import fake_wallet, get_funding_source
|
||||||
@@ -509,8 +509,6 @@ async def update_wallet_balance(
|
|||||||
)
|
)
|
||||||
payment.status = PaymentState.SUCCESS
|
payment.status = PaymentState.SUCCESS
|
||||||
await update_payment(payment, conn=conn)
|
await update_payment(payment, conn=conn)
|
||||||
from lnbits.tasks import internal_invoice_queue_put
|
|
||||||
|
|
||||||
await internal_invoice_queue_put(payment.checking_id)
|
await internal_invoice_queue_put(payment.checking_id)
|
||||||
|
|
||||||
|
|
||||||
@@ -821,8 +819,6 @@ async def _pay_external_invoice(
|
|||||||
|
|
||||||
fee_reserve_msat = fee_reserve(amount_msat, internal=False)
|
fee_reserve_msat = fee_reserve(amount_msat, internal=False)
|
||||||
|
|
||||||
from lnbits.tasks import create_task
|
|
||||||
|
|
||||||
task = create_task(
|
task = create_task(
|
||||||
_fundingsource_pay_invoice(checking_id, payment.bolt11, fee_reserve_msat)
|
_fundingsource_pay_invoice(checking_id, payment.bolt11, fee_reserve_msat)
|
||||||
)
|
)
|
||||||
@@ -1072,30 +1068,3 @@ async def _send_payment_notification_in_background(
|
|||||||
if not wallet:
|
if not wallet:
|
||||||
raise PaymentError(f"Could not fetch wallet '{wallet_id}'.", status="failed")
|
raise PaymentError(f"Could not fetch wallet '{wallet_id}'.", status="failed")
|
||||||
send_payment_notification_in_background(wallet, payment)
|
send_payment_notification_in_background(wallet, payment)
|
||||||
|
|
||||||
|
|
||||||
async def update_invoice_callback(checking_id: str) -> Payment | None:
|
|
||||||
"""
|
|
||||||
Takes a checking_id of an incoming payment, from either paid_invoices_stream()
|
|
||||||
or internal_invoice_queue. Checks its status, updates and returns it.
|
|
||||||
returns None if no payment was found or it not and incoming payment.
|
|
||||||
"""
|
|
||||||
payment = await get_standalone_payment(checking_id, incoming=True)
|
|
||||||
if not payment:
|
|
||||||
logger.warning(f"No payment found for '{checking_id}'.")
|
|
||||||
return None
|
|
||||||
if not payment.is_in:
|
|
||||||
logger.warning(f"Payment '{checking_id}' is not incoming, skipping.")
|
|
||||||
return None
|
|
||||||
|
|
||||||
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
|
|
||||||
payment.status = PaymentState.SUCCESS
|
|
||||||
await update_payment(payment)
|
|
||||||
if payment.fiat_provider:
|
|
||||||
await handle_fiat_payment_confirmation(payment)
|
|
||||||
return payment
|
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ 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,
|
||||||
@@ -56,8 +55,6 @@ 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(
|
||||||
@@ -98,15 +95,6 @@ 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()
|
||||||
|
|
||||||
@@ -185,12 +173,6 @@ 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,7 +12,6 @@ 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,
|
||||||
@@ -549,13 +548,6 @@ 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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -98,16 +98,12 @@ 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, ValueError))
|
if isinstance(exc, AssertionError)
|
||||||
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=(
|
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||||
HTTPStatus.BAD_REQUEST
|
|
||||||
if isinstance(exc, (AssertionError, ValueError))
|
|
||||||
else HTTPStatus.INTERNAL_SERVER_ERROR
|
|
||||||
),
|
|
||||||
detail=detail,
|
detail=detail,
|
||||||
) from exc
|
) from exc
|
||||||
|
|
||||||
|
|||||||
+2
-14
@@ -324,8 +324,7 @@ class AssetSettings(LNbitsSettings):
|
|||||||
"heif",
|
"heif",
|
||||||
"heics",
|
"heics",
|
||||||
"text/plain",
|
"text/plain",
|
||||||
"text/json",
|
"text/json" "text/xml",
|
||||||
"text/xml",
|
|
||||||
"application/json",
|
"application/json",
|
||||||
"application/pdf",
|
"application/pdf",
|
||||||
]
|
]
|
||||||
@@ -448,7 +447,6 @@ 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 (
|
||||||
@@ -589,8 +587,6 @@ class ZBDFundingSource(LNbitsSettings):
|
|||||||
class PhoenixdFundingSource(LNbitsSettings):
|
class PhoenixdFundingSource(LNbitsSettings):
|
||||||
phoenixd_api_endpoint: str | None = Field(default="http://localhost:9740/")
|
phoenixd_api_endpoint: str | None = Field(default="http://localhost:9740/")
|
||||||
phoenixd_api_password: str | None = Field(default=None)
|
phoenixd_api_password: str | None = Field(default=None)
|
||||||
phoenixd_data_dir: str | None = Field(default=None)
|
|
||||||
phoenixd_mnemonic: str | None = Field(default=None)
|
|
||||||
|
|
||||||
|
|
||||||
class AlbyFundingSource(LNbitsSettings):
|
class AlbyFundingSource(LNbitsSettings):
|
||||||
@@ -1010,6 +1006,7 @@ class EnvSettings(LNbitsSettings):
|
|||||||
debug_database: bool = Field(default=False)
|
debug_database: bool = Field(default=False)
|
||||||
bundle_assets: bool = Field(default=True)
|
bundle_assets: bool = Field(default=True)
|
||||||
# When enabled, auth cookies require HTTPS and SSO will reject insecure HTTP.
|
# When enabled, auth cookies require HTTPS and SSO will reject insecure HTTP.
|
||||||
|
# Set to false for local/dev environments that run without TLS.
|
||||||
auth_https_only: bool = Field(default=True)
|
auth_https_only: bool = Field(default=True)
|
||||||
host: str = Field(default="127.0.0.1")
|
host: str = Field(default="127.0.0.1")
|
||||||
port: int = Field(default=5000, gt=0)
|
port: int = Field(default=5000, gt=0)
|
||||||
@@ -1028,20 +1025,11 @@ 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:
|
||||||
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
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+11
-11
File diff suppressed because one or more lines are too long
@@ -275,8 +275,6 @@ window.localisation.en = {
|
|||||||
requires_server_restart:
|
requires_server_restart:
|
||||||
'Changing these settings requires a server restart to take effect.',
|
'Changing these settings requires a server restart to take effect.',
|
||||||
funding_source_info: 'Select the active funding wallet',
|
funding_source_info: 'Select the active funding wallet',
|
||||||
phoenixd_warning:
|
|
||||||
"Phoenixd mnemonic is only available if phoenixd data-dir is specified and is readable by LNbits. It's not indicative of phoenixd not running. It just means LNbits cannot access the mnemonic to display it here.",
|
|
||||||
latest_update: 'You are on the latest version {version}.',
|
latest_update: 'You are on the latest version {version}.',
|
||||||
notifications: 'Notifications',
|
notifications: 'Notifications',
|
||||||
notifications_configure: 'Configure Notifications',
|
notifications_configure: 'Configure Notifications',
|
||||||
|
|||||||
@@ -202,18 +202,7 @@ window.app.component('lnbits-admin-funding-sources', {
|
|||||||
'Phoenixd',
|
'Phoenixd',
|
||||||
{
|
{
|
||||||
phoenixd_api_endpoint: 'Endpoint',
|
phoenixd_api_endpoint: 'Endpoint',
|
||||||
phoenixd_api_password: 'Key',
|
phoenixd_api_password: 'Key'
|
||||||
phoenixd_data_dir: {
|
|
||||||
label: 'Data Directory',
|
|
||||||
hint: 'Directory where phoenixd stores its data, including the seed phrase.'
|
|
||||||
},
|
|
||||||
phoenixd_mnemonic: {
|
|
||||||
label: 'Phoenixd Seed Phrase',
|
|
||||||
hint: 'Only available if phoenixd data-dir is specified',
|
|
||||||
readonly: true,
|
|
||||||
copy: true,
|
|
||||||
qrcode: true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -41,37 +41,5 @@ window.app.component('lnbits-admin-site-customisation', {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {}
|
||||||
onBackgroundImageInput(e) {
|
|
||||||
const file = e.target.files[0]
|
|
||||||
if (file) {
|
|
||||||
this.uploadBackgroundImage(file)
|
|
||||||
}
|
|
||||||
e.target.value = null
|
|
||||||
},
|
|
||||||
async uploadBackgroundImage(file) {
|
|
||||||
const formData = new FormData()
|
|
||||||
formData.append('file', file)
|
|
||||||
try {
|
|
||||||
const {data} = await LNbits.api.request(
|
|
||||||
'POST',
|
|
||||||
'/api/v1/assets?public_asset=true',
|
|
||||||
null,
|
|
||||||
formData,
|
|
||||||
{
|
|
||||||
headers: {'Content-Type': 'multipart/form-data'}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const assetUrl = `${window.location.origin}/api/v1/assets/${data.id}/thumbnail`
|
|
||||||
this.formData.lnbits_default_bgimage = assetUrl
|
|
||||||
Quasar.Notify.create({
|
|
||||||
type: 'positive',
|
|
||||||
message: 'Background image uploaded.',
|
|
||||||
icon: null
|
|
||||||
})
|
|
||||||
} catch (e) {
|
|
||||||
LNbits.utils.notifyApiError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -165,26 +165,5 @@ window.app.component('lnbits-qrcode', {
|
|||||||
this.$refs.qrCode.$el.style.maxWidth = this.maxWidth + 'px'
|
this.$refs.qrCode.$el.style.maxWidth = this.maxWidth + 'px'
|
||||||
this.$refs.qrCode.$el.setAttribute('width', '100%')
|
this.$refs.qrCode.$el.setAttribute('width', '100%')
|
||||||
this.$refs.qrCode.$el.removeAttribute('height')
|
this.$refs.qrCode.$el.removeAttribute('height')
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
optimizedValue() {
|
|
||||||
const separatorIndex = this.value.indexOf(':')
|
|
||||||
const type =
|
|
||||||
separatorIndex === -1 ? '' : this.value.substring(0, separatorIndex)
|
|
||||||
const value =
|
|
||||||
separatorIndex === -1
|
|
||||||
? this.value
|
|
||||||
: this.value.substring(separatorIndex + 1)
|
|
||||||
|
|
||||||
if (this.utils.isValidBech32(value)) {
|
|
||||||
const normalizedValue = value.toUpperCase()
|
|
||||||
if (type) {
|
|
||||||
return `${type.toUpperCase()}:${normalizedValue}`
|
|
||||||
}
|
|
||||||
return normalizedValue
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -545,56 +545,31 @@ window.PageAccount = {
|
|||||||
if (file) {
|
if (file) {
|
||||||
this.uploadAsset(file)
|
this.uploadAsset(file)
|
||||||
}
|
}
|
||||||
e.target.value = null
|
|
||||||
},
|
},
|
||||||
onBackgroundImageInput(e) {
|
async uploadAsset(file) {
|
||||||
const file = e.target.files[0]
|
|
||||||
if (file) {
|
|
||||||
this.uploadBackgroundImage(file)
|
|
||||||
}
|
|
||||||
e.target.value = null
|
|
||||||
},
|
|
||||||
async uploadAsset(
|
|
||||||
file,
|
|
||||||
{isPublic = this.assetsUploadToPublic, notifySuccess = true} = {}
|
|
||||||
) {
|
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('file', file)
|
formData.append('file', file)
|
||||||
try {
|
try {
|
||||||
const {data} = await LNbits.api.request(
|
await LNbits.api.request(
|
||||||
'POST',
|
'POST',
|
||||||
`/api/v1/assets?public_asset=${isPublic}`,
|
`/api/v1/assets?public_asset=${this.assetsUploadToPublic}`,
|
||||||
null,
|
null,
|
||||||
formData,
|
formData,
|
||||||
{
|
{
|
||||||
headers: {'Content-Type': 'multipart/form-data'}
|
headers: {'Content-Type': 'multipart/form-data'}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if (notifySuccess) {
|
this.$q.notify({
|
||||||
this.$q.notify({
|
type: 'positive',
|
||||||
type: 'positive',
|
message: 'Upload successful!',
|
||||||
message: 'Upload successful!',
|
icon: null
|
||||||
icon: null
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
await this.getUserAssets()
|
await this.getUserAssets()
|
||||||
return data
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e)
|
console.warn(e)
|
||||||
LNbits.utils.notifyApiError(e)
|
LNbits.utils.notifyApiError(e)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async uploadBackgroundImage(file) {
|
|
||||||
const asset = await this.uploadAsset(file, {
|
|
||||||
isPublic: false,
|
|
||||||
notifySuccess: false
|
|
||||||
})
|
|
||||||
if (!asset) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const assetUrl = `${window.location.origin}/api/v1/assets/${asset.id}/thumbnail`
|
|
||||||
await this.siteCustomisationChanged({bgimageChoice: assetUrl})
|
|
||||||
},
|
|
||||||
async deleteAsset(asset) {
|
async deleteAsset(asset) {
|
||||||
LNbits.utils
|
LNbits.utils
|
||||||
.confirmDialog('Are you sure you want to delete this asset?')
|
.confirmDialog('Are you sure you want to delete this asset?')
|
||||||
|
|||||||
@@ -160,46 +160,6 @@ window._lnbitsUtils = {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isValidBech32(value) {
|
|
||||||
if (typeof value !== 'string') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const candidate = value.trim()
|
|
||||||
if (
|
|
||||||
!candidate ||
|
|
||||||
(candidate !== candidate.toLowerCase() &&
|
|
||||||
candidate !== candidate.toUpperCase())
|
|
||||||
) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalized = candidate.toLowerCase()
|
|
||||||
const splitPosition = normalized.lastIndexOf('1')
|
|
||||||
if (splitPosition <= 0) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const humanReadablePart = normalized.substring(0, splitPosition)
|
|
||||||
const data = normalized.substring(splitPosition + 1)
|
|
||||||
if (data.length < 6) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
typeof bech32ToFiveBitArray !== 'function' ||
|
|
||||||
typeof verify_checksum !== 'function'
|
|
||||||
) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const words = bech32ToFiveBitArray(data)
|
|
||||||
if (words.some(word => word < 0)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return verify_checksum(humanReadablePart, words)
|
|
||||||
},
|
|
||||||
async notifyApiError(error) {
|
async notifyApiError(error) {
|
||||||
if (!error.response) {
|
if (!error.response) {
|
||||||
return console.error(error)
|
return console.error(error)
|
||||||
|
|||||||
Vendored
+153
-294
File diff suppressed because it is too large
Load Diff
+35
-11
@@ -5,8 +5,12 @@ from collections.abc import Callable, Coroutine
|
|||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from lnbits.core.models import Payment
|
from lnbits.core.crud import (
|
||||||
from lnbits.core.services.payments import update_invoice_callback
|
get_standalone_payment,
|
||||||
|
update_payment,
|
||||||
|
)
|
||||||
|
from lnbits.core.models import Payment, PaymentState
|
||||||
|
from lnbits.core.services.fiat_providers import handle_fiat_payment_confirmation
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
from lnbits.wallets import get_funding_source
|
from lnbits.wallets import get_funding_source
|
||||||
|
|
||||||
@@ -112,10 +116,7 @@ async def internal_invoice_listener() -> None:
|
|||||||
while settings.lnbits_running:
|
while settings.lnbits_running:
|
||||||
checking_id = await internal_invoice_queue.get()
|
checking_id = await internal_invoice_queue.get()
|
||||||
logger.info(f"got an internal payment notification {checking_id}")
|
logger.info(f"got an internal payment notification {checking_id}")
|
||||||
payment = await update_invoice_callback(checking_id)
|
await invoice_callback_dispatcher(checking_id, is_internal=True)
|
||||||
if payment:
|
|
||||||
logger.success(f"internal invoice {checking_id} settled")
|
|
||||||
await invoice_callback_dispatcher(payment)
|
|
||||||
|
|
||||||
|
|
||||||
async def invoice_listener() -> None:
|
async def invoice_listener() -> None:
|
||||||
@@ -128,10 +129,7 @@ async def invoice_listener() -> None:
|
|||||||
funding_source = get_funding_source()
|
funding_source = get_funding_source()
|
||||||
async for checking_id in funding_source.paid_invoices_stream():
|
async for checking_id in funding_source.paid_invoices_stream():
|
||||||
logger.info(f"got a payment notification {checking_id}")
|
logger.info(f"got a payment notification {checking_id}")
|
||||||
payment = await update_invoice_callback(checking_id)
|
await invoice_callback_dispatcher(checking_id)
|
||||||
if payment:
|
|
||||||
logger.success(f"fundingsource invoice {checking_id} settled")
|
|
||||||
await invoice_callback_dispatcher(payment)
|
|
||||||
|
|
||||||
|
|
||||||
def wait_for_paid_invoices(
|
def wait_for_paid_invoices(
|
||||||
@@ -167,7 +165,33 @@ def run_interval(
|
|||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
async def invoice_callback_dispatcher(payment: Payment):
|
async def invoice_callback_dispatcher(checking_id: str, is_internal: bool = False):
|
||||||
|
"""
|
||||||
|
Takes an incoming payment, checks its status, and dispatches it to
|
||||||
|
invoice_listeners from core and extensions.
|
||||||
|
"""
|
||||||
|
payment = await get_standalone_payment(checking_id, incoming=True)
|
||||||
|
if not payment:
|
||||||
|
logger.warning(f"No payment found for '{checking_id}'.")
|
||||||
|
return
|
||||||
|
if not payment.is_in:
|
||||||
|
logger.warning(f"Payment '{checking_id}' is not incoming, skipping.")
|
||||||
|
return
|
||||||
|
|
||||||
|
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
|
||||||
|
payment.status = PaymentState.SUCCESS
|
||||||
|
await update_payment(payment)
|
||||||
|
if payment.fiat_provider:
|
||||||
|
await handle_fiat_payment_confirmation(payment)
|
||||||
|
internal = "internal" if is_internal else ""
|
||||||
|
logger.success(f"{internal} invoice {checking_id} settled")
|
||||||
for name, send_chan in invoice_listeners.items():
|
for name, send_chan in invoice_listeners.items():
|
||||||
logger.trace(f"invoice listeners: sending to `{name}`")
|
logger.trace(f"invoice listeners: sending to `{name}`")
|
||||||
await send_chan.put(payment)
|
await send_chan.put(payment)
|
||||||
|
|||||||
@@ -75,35 +75,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isSuperUser">
|
<div class="row q-col-gutter-md">
|
||||||
<lnbits-admin-funding-sources
|
|
||||||
:form-data="formData"
|
|
||||||
:allowed-funding-sources="settings.lnbits_allowed_funding_sources"
|
|
||||||
/>
|
|
||||||
<div class="row q-col-gutter-md q-my-md">
|
|
||||||
<div class="col-12 col-sm-8">
|
|
||||||
<q-item tag="div">
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label
|
|
||||||
v-text="$t('funding_source_retries')"
|
|
||||||
></q-item-label>
|
|
||||||
<q-item-label
|
|
||||||
caption
|
|
||||||
v-text="$t('funding_source_retries_desc')"
|
|
||||||
></q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
v-model="formData.funding_source_max_retries"
|
|
||||||
type="number"
|
|
||||||
/>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row q-col-gutter-md q-mt-lg">
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<h6 class="q-my-none">
|
<h6 class="q-my-none">
|
||||||
<span v-text="$t('routing_fee_reserve_calculations')"></span>
|
<span v-text="$t('routing_fee_reserve_calculations')"></span>
|
||||||
@@ -210,6 +182,34 @@
|
|||||||
></q-input>
|
></q-input>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="isSuperUser">
|
||||||
|
<lnbits-admin-funding-sources
|
||||||
|
:form-data="formData"
|
||||||
|
:allowed-funding-sources="settings.lnbits_allowed_funding_sources"
|
||||||
|
/>
|
||||||
|
<div class="row q-col-gutter-md q-my-md">
|
||||||
|
<div class="col-12 col-sm-8">
|
||||||
|
<q-item tag="div">
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label
|
||||||
|
v-text="$t('funding_source_retries')"
|
||||||
|
></q-item-label>
|
||||||
|
<q-item-label
|
||||||
|
caption
|
||||||
|
v-text="$t('funding_source_retries_desc')"
|
||||||
|
></q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
v-model="formData.funding_source_max_retries"
|
||||||
|
type="number"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<q-separator></q-separator>
|
<q-separator></q-separator>
|
||||||
<h6 class="q-mt-lg q-mb-sm">
|
<h6 class="q-mt-lg q-mb-sm">
|
||||||
<p v-text="$t('watchdog')"></p>
|
<p v-text="$t('watchdog')"></p>
|
||||||
|
|||||||
@@ -52,7 +52,6 @@
|
|||||||
:label="prop.label"
|
:label="prop.label"
|
||||||
:hint="prop.hint"
|
:hint="prop.hint"
|
||||||
:value="prop.value"
|
:value="prop.value"
|
||||||
:readonly="prop.readonly || false"
|
|
||||||
>
|
>
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="prop.copy"
|
v-if="prop.copy"
|
||||||
@@ -74,15 +73,6 @@
|
|||||||
></q-btn>
|
></q-btn>
|
||||||
</q-input>
|
</q-input>
|
||||||
</div>
|
</div>
|
||||||
<p
|
|
||||||
v-if="fund === 'PhoenixdWallet' && key === 'phoenixd_mnemonic'"
|
|
||||||
class="col-12 q-my-md"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
<q-icon name="warning" color="orange" size="xs"></q-icon>
|
|
||||||
<span v-text="$t('phoenixd_warning')"></span>
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<q-expansion-item
|
<q-expansion-item
|
||||||
v-if="
|
v-if="
|
||||||
|
|||||||
@@ -251,27 +251,10 @@
|
|||||||
type="text"
|
type="text"
|
||||||
v-model="formData.lnbits_default_bgimage"
|
v-model="formData.lnbits_default_bgimage"
|
||||||
label="Background Image"
|
label="Background Image"
|
||||||
|
@update:model-value="applyGlobalBgimage"
|
||||||
hint="This must be a trusted source. It can change the content and it can log your IP address."
|
hint="This must be a trusted source. It can change the content and it can log your IP address."
|
||||||
>
|
>
|
||||||
<template v-slot:append>
|
|
||||||
<q-btn
|
|
||||||
dense
|
|
||||||
flat
|
|
||||||
round
|
|
||||||
icon="upload"
|
|
||||||
@click="$refs.adminBackgroundImageInput.click()"
|
|
||||||
>
|
|
||||||
<q-tooltip>Upload background image</q-tooltip>
|
|
||||||
</q-btn>
|
|
||||||
</template>
|
|
||||||
</q-input>
|
</q-input>
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
ref="adminBackgroundImageInput"
|
|
||||||
accept="image/*"
|
|
||||||
style="display: none"
|
|
||||||
@change="onBackgroundImageInput"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row q-col-gutter-md q-mb-md">
|
<div class="row q-col-gutter-md q-mb-md">
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
>
|
>
|
||||||
<qrcode-vue
|
<qrcode-vue
|
||||||
ref="qrCode"
|
ref="qrCode"
|
||||||
:value="optimizedValue"
|
:value="value"
|
||||||
:margin="margin"
|
:margin="margin"
|
||||||
:size="size"
|
:size="size"
|
||||||
level="Q"
|
level="Q"
|
||||||
|
|||||||
@@ -492,28 +492,10 @@
|
|||||||
siteCustomisationChanged({bgimageChoice: $event})
|
siteCustomisationChanged({bgimageChoice: $event})
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<template v-slot:append>
|
|
||||||
<q-btn
|
|
||||||
dense
|
|
||||||
flat
|
|
||||||
round
|
|
||||||
icon="upload"
|
|
||||||
@click="$refs.backgroundImageInput.click()"
|
|
||||||
>
|
|
||||||
<q-tooltip>Upload background image</q-tooltip>
|
|
||||||
</q-btn>
|
|
||||||
</template>
|
|
||||||
<q-tooltip
|
<q-tooltip
|
||||||
><span v-text="$t('background_image')"></span
|
><span v-text="$t('background_image')"></span
|
||||||
></q-tooltip>
|
></q-tooltip>
|
||||||
</q-input>
|
</q-input>
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
ref="backgroundImageInput"
|
|
||||||
accept="image/*"
|
|
||||||
style="display: none"
|
|
||||||
@change="onBackgroundImageInput"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row q-mb-md">
|
<div class="row q-mb-md">
|
||||||
@@ -1126,130 +1108,82 @@
|
|||||||
<q-separator></q-separator>
|
<q-separator></q-separator>
|
||||||
|
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<div
|
<q-btn-dropdown
|
||||||
class="row items-center no-wrap q-col-gutter-sm"
|
color="grey"
|
||||||
|
dense
|
||||||
|
outline
|
||||||
|
no-caps
|
||||||
|
:label="props.row.name"
|
||||||
|
:icon="props.row.is_public ? 'public' : ''"
|
||||||
>
|
>
|
||||||
<div class="col">
|
<q-list>
|
||||||
<q-btn-dropdown
|
<q-item
|
||||||
color="grey"
|
clickable
|
||||||
dense
|
v-close-popup
|
||||||
outline
|
@click="copyAssetLinkToClipboard(props.row)"
|
||||||
no-caps
|
|
||||||
class="full-width"
|
|
||||||
:label="props.row.name"
|
|
||||||
:icon="props.row.is_public ? 'public' : ''"
|
|
||||||
>
|
>
|
||||||
<q-list>
|
<q-item-section avatar>
|
||||||
<q-item
|
<q-avatar icon="content_copy" />
|
||||||
clickable
|
</q-item-section>
|
||||||
v-close-popup
|
<q-item-section>
|
||||||
@click="
|
<q-item-label>Copy Link</q-item-label>
|
||||||
copyAssetLinkToClipboard(props.row)
|
<q-item-label caption
|
||||||
"
|
>Copy asset link to
|
||||||
|
clipboard</q-item-label
|
||||||
>
|
>
|
||||||
<q-item-section avatar>
|
</q-item-section>
|
||||||
<q-avatar icon="content_copy" />
|
</q-item>
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>Copy Link</q-item-label>
|
|
||||||
<q-item-label caption
|
|
||||||
>Copy asset link to
|
|
||||||
clipboard</q-item-label
|
|
||||||
>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
|
|
||||||
<q-item
|
<q-item
|
||||||
clickable
|
clickable
|
||||||
v-close-popup
|
v-close-popup
|
||||||
@click="
|
@click="toggleAssetPublicAccess(props.row)"
|
||||||
toggleAssetPublicAccess(props.row)
|
>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-avatar
|
||||||
|
:icon="
|
||||||
|
props.row.is_public
|
||||||
|
? 'public_off'
|
||||||
|
: 'public'
|
||||||
"
|
"
|
||||||
|
text-color="primary"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section v-if="props.row.is_public">
|
||||||
|
<q-item-label>Unpublish</q-item-label>
|
||||||
|
<q-item-label caption
|
||||||
|
>Make this asset private</q-item-label
|
||||||
>
|
>
|
||||||
<q-item-section avatar>
|
</q-item-section>
|
||||||
<q-avatar
|
<q-item-section v-else>
|
||||||
:icon="
|
<q-item-label>Publish</q-item-label>
|
||||||
props.row.is_public
|
<q-item-label caption
|
||||||
? 'public_off'
|
>Make this asset public</q-item-label
|
||||||
: 'public'
|
>
|
||||||
"
|
</q-item-section>
|
||||||
text-color="primary"
|
</q-item>
|
||||||
/>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section
|
|
||||||
v-if="props.row.is_public"
|
|
||||||
>
|
|
||||||
<q-item-label>Unpublish</q-item-label>
|
|
||||||
<q-item-label caption
|
|
||||||
>Make this asset
|
|
||||||
private</q-item-label
|
|
||||||
>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section v-else>
|
|
||||||
<q-item-label>Publish</q-item-label>
|
|
||||||
<q-item-label caption
|
|
||||||
>Make this asset
|
|
||||||
public</q-item-label
|
|
||||||
>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
|
|
||||||
<q-item
|
<q-item
|
||||||
clickable
|
clickable
|
||||||
v-close-popup
|
v-close-popup
|
||||||
@click="deleteAsset(props.row)"
|
@click="deleteAsset(props.row)"
|
||||||
|
>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-avatar
|
||||||
|
icon="delete"
|
||||||
|
text-color="negative"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>Delete</q-item-label>
|
||||||
|
<q-item-label caption
|
||||||
|
>Permanently delete this
|
||||||
|
asset</q-item-label
|
||||||
>
|
>
|
||||||
<q-item-section avatar>
|
</q-item-section>
|
||||||
<q-avatar
|
</q-item>
|
||||||
icon="delete"
|
</q-list>
|
||||||
text-color="negative"
|
</q-btn-dropdown>
|
||||||
/>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>Delete</q-item-label>
|
|
||||||
<q-item-label caption
|
|
||||||
>Permanently delete this
|
|
||||||
asset</q-item-label
|
|
||||||
>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</q-list>
|
|
||||||
</q-btn-dropdown>
|
|
||||||
</div>
|
|
||||||
<div class="col-auto">
|
|
||||||
<q-btn
|
|
||||||
type="a"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
color="primary"
|
|
||||||
dense
|
|
||||||
flat
|
|
||||||
round
|
|
||||||
icon="image"
|
|
||||||
:href="`/api/v1/assets/${props.row.id}/data`"
|
|
||||||
>
|
|
||||||
<q-tooltip>Full image</q-tooltip>
|
|
||||||
</q-btn>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="col-auto"
|
|
||||||
v-if="props.row.thumbnail_base64"
|
|
||||||
>
|
|
||||||
<q-btn
|
|
||||||
type="a"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
color="secondary"
|
|
||||||
dense
|
|
||||||
flat
|
|
||||||
round
|
|
||||||
icon="photo_size_select_small"
|
|
||||||
:href="`/api/v1/assets/${props.row.id}/thumbnail`"
|
|
||||||
>
|
|
||||||
<q-tooltip>Thumbnail</q-tooltip>
|
|
||||||
</q-btn>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,11 +15,7 @@ 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:
|
||||||
if settings.has_first_install_token_changed():
|
logger.success("This is a fresh install of LNbits.")
|
||||||
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}`. "
|
||||||
|
|||||||
@@ -4,11 +4,9 @@ import hashlib
|
|||||||
import json
|
import json
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from embit.bip39 import mnemonic_is_valid
|
|
||||||
from httpx import RequestError, TimeoutException
|
from httpx import RequestError, TimeoutException
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from websockets import connect
|
from websockets import connect
|
||||||
@@ -63,8 +61,6 @@ class PhoenixdWallet(Wallet):
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.client = httpx.AsyncClient(base_url=self.endpoint, headers=self.headers)
|
self.client = httpx.AsyncClient(base_url=self.endpoint, headers=self.headers)
|
||||||
self._seed_mnemonic_to_persist: str | None = None
|
|
||||||
self._load_mnemonic_from_seed_file()
|
|
||||||
|
|
||||||
async def cleanup(self):
|
async def cleanup(self):
|
||||||
try:
|
try:
|
||||||
@@ -73,7 +69,6 @@ class PhoenixdWallet(Wallet):
|
|||||||
logger.warning(f"Error closing wallet connection: {e}")
|
logger.warning(f"Error closing wallet connection: {e}")
|
||||||
|
|
||||||
async def status(self) -> StatusResponse:
|
async def status(self) -> StatusResponse:
|
||||||
await self._persist_loaded_mnemonic()
|
|
||||||
try:
|
try:
|
||||||
r = await self.client.get("/getinfo", timeout=10)
|
r = await self.client.get("/getinfo", timeout=10)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
@@ -106,6 +101,7 @@ class PhoenixdWallet(Wallet):
|
|||||||
unhashed_description: bytes | None = None,
|
unhashed_description: bytes | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> InvoiceResponse:
|
) -> InvoiceResponse:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
msats_amount = amount
|
msats_amount = amount
|
||||||
data: dict[str, Any] = {
|
data: dict[str, Any] = {
|
||||||
@@ -313,7 +309,7 @@ class PhoenixdWallet(Wallet):
|
|||||||
and message_json.get("type") == "payment_received"
|
and message_json.get("type") == "payment_received"
|
||||||
):
|
):
|
||||||
logger.info(
|
logger.info(
|
||||||
f"payment-received: {message_json['paymentHash']}"
|
f'payment-received: {message_json["paymentHash"]}'
|
||||||
)
|
)
|
||||||
yield message_json["paymentHash"]
|
yield message_json["paymentHash"]
|
||||||
|
|
||||||
@@ -323,49 +319,3 @@ class PhoenixdWallet(Wallet):
|
|||||||
"retrying in 5 seconds"
|
"retrying in 5 seconds"
|
||||||
)
|
)
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
|
|
||||||
def _load_mnemonic_from_seed_file(self):
|
|
||||||
data_dir = settings.phoenixd_data_dir
|
|
||||||
if not data_dir:
|
|
||||||
return
|
|
||||||
|
|
||||||
seed_path = Path(data_dir).expanduser() / "seed.dat"
|
|
||||||
if not seed_path.is_file():
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
mnemonic = seed_path.read_text(encoding="utf-8").strip()
|
|
||||||
if mnemonic == settings.phoenixd_mnemonic:
|
|
||||||
return
|
|
||||||
except OSError as exc:
|
|
||||||
logger.warning(f"Failed to read Phoenixd seed file '{seed_path}': {exc}")
|
|
||||||
return
|
|
||||||
|
|
||||||
if not mnemonic:
|
|
||||||
logger.warning(f"Phoenixd seed file '{seed_path}' is empty.")
|
|
||||||
return
|
|
||||||
|
|
||||||
if not mnemonic_is_valid(mnemonic):
|
|
||||||
logger.warning(
|
|
||||||
f"Phoenixd seed file '{seed_path}' does not contain a valid "
|
|
||||||
"BIP39 mnemonic."
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
settings.phoenixd_mnemonic = mnemonic
|
|
||||||
self._seed_mnemonic_to_persist = mnemonic
|
|
||||||
|
|
||||||
async def _persist_loaded_mnemonic(self):
|
|
||||||
if not self._seed_mnemonic_to_persist:
|
|
||||||
return
|
|
||||||
|
|
||||||
logger.info("Updating 'PHOENIXD_MNEMONIC' mnemonic settings.")
|
|
||||||
try:
|
|
||||||
from lnbits.core.crud.settings import set_settings_field
|
|
||||||
|
|
||||||
await set_settings_field(
|
|
||||||
"phoenixd_mnemonic", self._seed_mnemonic_to_persist
|
|
||||||
)
|
|
||||||
self._seed_mnemonic_to_persist = None
|
|
||||||
except Exception as exc:
|
|
||||||
logger.warning(f"Failed to persist Phoenixd mnemonic: {exc}")
|
|
||||||
|
|||||||
Generated
+11
-15
@@ -6,7 +6,7 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "lnbits",
|
"name": "lnbits",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.15.0",
|
"axios": "^1.13.5",
|
||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"nostr-tools": "^2.18.2",
|
"nostr-tools": "^2.18.2",
|
||||||
@@ -734,14 +734,14 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.15.0",
|
"version": "1.13.5",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
|
||||||
"integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==",
|
"integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.11",
|
"follow-redirects": "^1.15.11",
|
||||||
"form-data": "^4.0.5",
|
"form-data": "^4.0.5",
|
||||||
"proxy-from-env": "^2.1.0"
|
"proxy-from-env": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
@@ -1498,9 +1498,9 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
"version": "2.3.2",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -1555,13 +1555,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/proxy-from-env": {
|
"node_modules/proxy-from-env": {
|
||||||
"version": "2.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
"integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"node_modules/pyright": {
|
"node_modules/pyright": {
|
||||||
"version": "1.1.289",
|
"version": "1.1.289",
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@
|
|||||||
"terser": "^5.44.1"
|
"terser": "^5.44.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.15.0",
|
"axios": "^1.13.5",
|
||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"nostr-tools": "^2.18.2",
|
"nostr-tools": "^2.18.2",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "lnbits"
|
name = "lnbits"
|
||||||
version = "1.5.4"
|
version = "1.5.2"
|
||||||
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" }]
|
||||||
|
|||||||
@@ -135,8 +135,6 @@ async def test_auth_api_first_install_success_and_validation(
|
|||||||
)
|
)
|
||||||
assert success.status_code == 200
|
assert success.status_code == 200
|
||||||
assert success.json()["access_token"]
|
assert success.json()["access_token"]
|
||||||
assert "cookie_access_token=" in success.headers["set-cookie"]
|
|
||||||
assert "Secure" not in success.headers["set-cookie"]
|
|
||||||
|
|
||||||
updated_superuser = await get_account(settings.super_user, active_only=False)
|
updated_superuser = await get_account(settings.super_user, active_only=False)
|
||||||
assert updated_superuser is not None
|
assert updated_superuser is not None
|
||||||
@@ -161,43 +159,3 @@ async def test_auth_api_first_install_success_and_validation(
|
|||||||
await update_account(restored_superuser)
|
await update_account(restored_superuser)
|
||||||
settings.first_install = original_first_install
|
settings.first_install = original_first_install
|
||||||
settings.first_install_token = original_first_install_token
|
settings.first_install_token = original_first_install_token
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
|
||||||
async def test_auth_api_first_install_uses_secure_cookie_when_enabled(
|
|
||||||
http_client: AsyncClient, settings: Settings
|
|
||||||
):
|
|
||||||
superuser = await get_account(settings.super_user, active_only=False)
|
|
||||||
assert superuser is not None
|
|
||||||
|
|
||||||
original_username = superuser.username
|
|
||||||
original_password_hash = superuser.password_hash
|
|
||||||
original_first_install = settings.first_install
|
|
||||||
original_auth_https_only = settings.auth_https_only
|
|
||||||
|
|
||||||
new_username = f"secure_{uuid4().hex[:8]}"
|
|
||||||
|
|
||||||
try:
|
|
||||||
settings.first_install = True
|
|
||||||
settings.auth_https_only = True
|
|
||||||
|
|
||||||
success = await http_client.put(
|
|
||||||
"/api/v1/auth/first_install",
|
|
||||||
json={
|
|
||||||
"username": new_username,
|
|
||||||
"password": "secret1234",
|
|
||||||
"password_repeat": "secret1234",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
assert success.status_code == 200
|
|
||||||
assert "cookie_access_token=" in success.headers["set-cookie"]
|
|
||||||
assert "Secure" in success.headers["set-cookie"]
|
|
||||||
finally:
|
|
||||||
restored_superuser = await get_account(settings.super_user, active_only=False)
|
|
||||||
assert restored_superuser is not None
|
|
||||||
restored_superuser.username = original_username
|
|
||||||
restored_superuser.password_hash = original_password_hash
|
|
||||||
await update_account(restored_superuser)
|
|
||||||
settings.first_install = original_first_install
|
|
||||||
settings.auth_https_only = original_auth_https_only
|
|
||||||
|
|||||||
+1
-40
@@ -1,6 +1,4 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import copy
|
|
||||||
import inspect
|
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
@@ -25,12 +23,7 @@ from lnbits.core.services import create_user_account, update_wallet_balance
|
|||||||
from lnbits.core.services.payments import create_wallet_invoice
|
from lnbits.core.services.payments import create_wallet_invoice
|
||||||
from lnbits.core.views.auth_api import first_install
|
from lnbits.core.views.auth_api import first_install
|
||||||
from lnbits.db import DB_TYPE, SQLITE, Database
|
from lnbits.db import DB_TYPE, SQLITE, Database
|
||||||
from lnbits.settings import (
|
from lnbits.settings import AuthMethods, FiatProviderLimits, Settings
|
||||||
AuthMethods,
|
|
||||||
EditableSettings,
|
|
||||||
FiatProviderLimits,
|
|
||||||
Settings,
|
|
||||||
)
|
|
||||||
from lnbits.settings import settings as lnbits_settings
|
from lnbits.settings import settings as lnbits_settings
|
||||||
from lnbits.wallets.fake import FakeWallet
|
from lnbits.wallets.fake import FakeWallet
|
||||||
from tests.helpers import (
|
from tests.helpers import (
|
||||||
@@ -40,23 +33,6 @@ from tests.helpers import (
|
|||||||
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
||||||
|
|
||||||
ADMIN_USER_ID = uuid4().hex
|
ADMIN_USER_ID = uuid4().hex
|
||||||
# Snapshot the initialized module settings instead of a fresh Settings() instance.
|
|
||||||
# The module settings include runtime-populated values like `version`.
|
|
||||||
_PURE_SETTINGS = copy.deepcopy(lnbits_settings)
|
|
||||||
_PURE_SETTINGS_FIELDS = tuple(
|
|
||||||
sorted(
|
|
||||||
{
|
|
||||||
field_name
|
|
||||||
for field_name in Settings.readonly_fields()
|
|
||||||
if field_name != "super_user"
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
name
|
|
||||||
for name in inspect.signature(EditableSettings).parameters
|
|
||||||
if not name.startswith("_")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
@@ -96,7 +72,6 @@ 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,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -352,21 +327,7 @@ async def new_user(username: str | None = None) -> User:
|
|||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
def _restore_pure_settings(settings: Settings):
|
|
||||||
for field_name in _PURE_SETTINGS_FIELDS:
|
|
||||||
setattr(
|
|
||||||
settings, field_name, copy.deepcopy(getattr(_PURE_SETTINGS, field_name))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _settings_cleanup(settings: Settings):
|
def _settings_cleanup(settings: Settings):
|
||||||
_restore_pure_settings(settings)
|
|
||||||
settings.auth_https_only = False
|
|
||||||
settings.lnbits_data_folder = "./tests/data"
|
|
||||||
settings.bundle_assets = True
|
|
||||||
settings.lnbits_admin_ui = True
|
|
||||||
settings.lnbits_extensions_default_install = []
|
|
||||||
settings.lnbits_extensions_deactivate_all = True
|
|
||||||
settings.lnbits_allow_new_accounts = True
|
settings.lnbits_allow_new_accounts = True
|
||||||
settings.lnbits_allowed_users = []
|
settings.lnbits_allowed_users = []
|
||||||
settings.auth_allowed_methods = AuthMethods.all()
|
settings.auth_allowed_methods = AuthMethods.all()
|
||||||
|
|||||||
+1
-3
@@ -28,7 +28,7 @@ from lnbits.core.models.extensions_builder import (
|
|||||||
PublicPageFields,
|
PublicPageFields,
|
||||||
SettingsFields,
|
SettingsFields,
|
||||||
)
|
)
|
||||||
from lnbits.wallets import get_funding_source, set_funding_source
|
from lnbits.wallets import get_funding_source
|
||||||
|
|
||||||
|
|
||||||
class DbTestModel(BaseModel):
|
class DbTestModel(BaseModel):
|
||||||
@@ -182,8 +182,6 @@ def make_lnurl_pay_response(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
set_funding_source()
|
|
||||||
|
|
||||||
funding_source = get_funding_source()
|
funding_source = get_funding_source()
|
||||||
is_fake: bool = funding_source.__class__.__name__ == "FakeWallet"
|
is_fake: bool = funding_source.__class__.__name__ == "FakeWallet"
|
||||||
is_regtest: bool = not is_fake
|
is_regtest: bool = not is_fake
|
||||||
|
|||||||
@@ -9,7 +9,15 @@ from loguru import logger
|
|||||||
from lnbits.wallets import get_funding_source
|
from lnbits.wallets import get_funding_source
|
||||||
|
|
||||||
funding_source = get_funding_source()
|
funding_source = get_funding_source()
|
||||||
is_boltz_wallet = funding_source.__class__.__name__ == "BoltzWallet"
|
|
||||||
|
|
||||||
|
def is_boltz_wallet():
|
||||||
|
print(
|
||||||
|
"### funding_source.__class__.__name__ 2",
|
||||||
|
get_funding_source().__class__.__name__,
|
||||||
|
)
|
||||||
|
return get_funding_source().__class__.__name__ == "BoltzWallet"
|
||||||
|
|
||||||
|
|
||||||
docker_lightning_cli = [
|
docker_lightning_cli = [
|
||||||
"docker",
|
"docker",
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ async def test_create_invoice(from_wallet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# we cannot know the preimage of the swap yet
|
# we cannot know the preimage of the swap yet
|
||||||
if not is_boltz_wallet:
|
funding_source = get_funding_source()
|
||||||
|
if not is_boltz_wallet():
|
||||||
assert payment.preimage
|
assert payment.preimage
|
||||||
|
|
||||||
invoice = decode(payment.bolt11)
|
invoice = decode(payment.bolt11)
|
||||||
@@ -42,7 +43,8 @@ async def test_create_internal_invoice(from_wallet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# we cannot know the preimage of the swap yet
|
# we cannot know the preimage of the swap yet
|
||||||
if not is_boltz_wallet:
|
funding_source = get_funding_source()
|
||||||
|
if not is_boltz_wallet():
|
||||||
assert payment.preimage
|
assert payment.preimage
|
||||||
|
|
||||||
invoice = decode(payment.bolt11)
|
invoice = decode(payment.bolt11)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ async def test_services_pay_invoice(to_wallet, real_invoice):
|
|||||||
)
|
)
|
||||||
assert payment
|
assert payment
|
||||||
assert payment.memo == description
|
assert payment.memo == description
|
||||||
if not is_boltz_wallet:
|
if not is_boltz_wallet():
|
||||||
assert payment.status == PaymentState.SUCCESS
|
assert payment.status == PaymentState.SUCCESS
|
||||||
assert payment.preimage
|
assert payment.preimage
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,140 +0,0 @@
|
|||||||
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)
|
|
||||||
@@ -252,12 +252,7 @@ async def test_notification_for_internal_payment(
|
|||||||
assert _payment.bolt11 == payment.bolt11
|
assert _payment.bolt11 == payment.bolt11
|
||||||
assert _payment.amount == 123_000
|
assert _payment.amount == 123_000
|
||||||
updated_payment = await get_payment(_payment.checking_id)
|
updated_payment = await get_payment(_payment.checking_id)
|
||||||
assert (
|
assert updated_payment.webhook_status == "404"
|
||||||
updated_payment.webhook_status is not None
|
|
||||||
), "Webhook should have been called."
|
|
||||||
assert (
|
|
||||||
int(updated_payment.webhook_status) >= 400
|
|
||||||
), "Webhook should have been called and failed."
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ async def test_update_wallet_balance_validates_credit_and_debit(
|
|||||||
|
|
||||||
settings.lnbits_wallet_limit_max_balance = 0
|
settings.lnbits_wallet_limit_max_balance = 0
|
||||||
queue_mock = mocker.patch(
|
queue_mock = mocker.patch(
|
||||||
"lnbits.tasks.internal_invoice_queue_put",
|
"lnbits.core.services.payments.internal_invoice_queue_put",
|
||||||
mocker.AsyncMock(),
|
mocker.AsyncMock(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -671,48 +671,48 @@ toml = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cryptography"
|
name = "cryptography"
|
||||||
version = "46.0.6"
|
version = "46.0.5"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
||||||
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/a4/ba/04b1bd4218cbc58dc90ce967106d51582371b898690f3ae0402876cc4f34/cryptography-46.0.6.tar.gz", hash = "sha256:27550628a518c5c6c903d84f637fbecf287f6cb9ced3804838a1295dc1fd0759", size = 750542, upload-time = "2026-03-25T23:34:53.396Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064, upload-time = "2026-02-10T19:18:38.255Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/47/23/9285e15e3bc57325b0a72e592921983a701efc1ee8f91c06c5f0235d86d9/cryptography-46.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:64235194bad039a10bb6d2d930ab3323baaec67e2ce36215fd0952fad0930ca8", size = 7176401, upload-time = "2026-03-25T23:33:22.096Z" },
|
{ url = "https://files.pythonhosted.org/packages/f7/81/b0bb27f2ba931a65409c6b8a8b358a7f03c0e46eceacddff55f7c84b1f3b/cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad", size = 7176289, upload-time = "2026-02-10T19:17:08.274Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/60/f8/e61f8f13950ab6195b31913b42d39f0f9afc7d93f76710f299b5ec286ae6/cryptography-46.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:26031f1e5ca62fcb9d1fcb34b2b60b390d1aacaa15dc8b895a9ed00968b97b30", size = 4275275, upload-time = "2026-03-25T23:33:23.844Z" },
|
{ url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637, upload-time = "2026-02-10T19:17:10.53Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/19/69/732a736d12c2631e140be2348b4ad3d226302df63ef64d30dfdb8db7ad1c/cryptography-46.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9a693028b9cbe51b5a1136232ee8f2bc242e4e19d456ded3fa7c86e43c713b4a", size = 4425320, upload-time = "2026-03-25T23:33:25.703Z" },
|
{ url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742, upload-time = "2026-02-10T19:17:12.388Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d4/12/123be7292674abf76b21ac1fc0e1af50661f0e5b8f0ec8285faac18eb99e/cryptography-46.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:67177e8a9f421aa2d3a170c3e56eca4e0128883cf52a071a7cbf53297f18b175", size = 4278082, upload-time = "2026-03-25T23:33:27.423Z" },
|
{ url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528, upload-time = "2026-02-10T19:17:13.853Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/5b/ba/d5e27f8d68c24951b0a484924a84c7cdaed7502bac9f18601cd357f8b1d2/cryptography-46.0.6-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d9528b535a6c4f8ff37847144b8986a9a143585f0540fbcb1a98115b543aa463", size = 4926514, upload-time = "2026-03-25T23:33:29.206Z" },
|
{ url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993, upload-time = "2026-02-10T19:17:15.618Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/34/71/1ea5a7352ae516d5512d17babe7e1b87d9db5150b21f794b1377eac1edc0/cryptography-46.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:22259338084d6ae497a19bae5d4c66b7ca1387d3264d1c2c0e72d9e9b6a77b97", size = 4457766, upload-time = "2026-03-25T23:33:30.834Z" },
|
{ url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855, upload-time = "2026-02-10T19:17:17.221Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/01/59/562be1e653accee4fdad92c7a2e88fced26b3fdfce144047519bbebc299e/cryptography-46.0.6-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:760997a4b950ff00d418398ad73fbc91aa2894b5c1db7ccb45b4f68b42a63b3c", size = 3986535, upload-time = "2026-03-25T23:33:33.02Z" },
|
{ url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635, upload-time = "2026-02-10T19:17:18.792Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d6/8b/b1ebfeb788bf4624d36e45ed2662b8bd43a05ff62157093c1539c1288a18/cryptography-46.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3dfa6567f2e9e4c5dceb8ccb5a708158a2a871052fa75c8b78cb0977063f1507", size = 4277618, upload-time = "2026-03-25T23:33:34.567Z" },
|
{ url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038, upload-time = "2026-02-10T19:17:20.256Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/dd/52/a005f8eabdb28df57c20f84c44d397a755782d6ff6d455f05baa2785bd91/cryptography-46.0.6-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:cdcd3edcbc5d55757e5f5f3d330dd00007ae463a7e7aa5bf132d1f22a4b62b19", size = 4890802, upload-time = "2026-03-25T23:33:37.034Z" },
|
{ url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181, upload-time = "2026-02-10T19:17:21.825Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ec/4d/8e7d7245c79c617d08724e2efa397737715ca0ec830ecb3c91e547302555/cryptography-46.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:d4e4aadb7fc1f88687f47ca20bb7227981b03afaae69287029da08096853b738", size = 4457425, upload-time = "2026-03-25T23:33:38.904Z" },
|
{ url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482, upload-time = "2026-02-10T19:17:25.133Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/1d/5c/f6c3596a1430cec6f949085f0e1a970638d76f81c3ea56d93d564d04c340/cryptography-46.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2b417edbe8877cda9022dde3a008e2deb50be9c407eef034aeeb3a8b11d9db3c", size = 4405530, upload-time = "2026-03-25T23:33:40.842Z" },
|
{ url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497, upload-time = "2026-02-10T19:17:26.66Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/7e/c9/9f9cea13ee2dbde070424e0c4f621c091a91ffcc504ffea5e74f0e1daeff/cryptography-46.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:380343e0653b1c9d7e1f55b52aaa2dbb2fdf2730088d48c43ca1c7c0abb7cc2f", size = 4667896, upload-time = "2026-03-25T23:33:42.781Z" },
|
{ url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819, upload-time = "2026-02-10T19:17:28.569Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ad/b5/1895bc0821226f129bc74d00eccfc6a5969e2028f8617c09790bf89c185e/cryptography-46.0.6-cp311-abi3-win32.whl", hash = "sha256:bcb87663e1f7b075e48c3be3ecb5f0b46c8fc50b50a97cf264e7f60242dca3f2", size = 3026348, upload-time = "2026-03-25T23:33:45.021Z" },
|
{ url = "https://files.pythonhosted.org/packages/ea/ed/325d2a490c5e94038cdb0117da9397ece1f11201f425c4e9c57fe5b9f08b/cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48", size = 3028230, upload-time = "2026-02-10T19:17:30.518Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c3/f8/c9bcbf0d3e6ad288b9d9aa0b1dee04b063d19e8c4f871855a03ab3a297ab/cryptography-46.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:6739d56300662c468fddb0e5e291f9b4d084bead381667b9e654c7dd81705124", size = 3483896, upload-time = "2026-03-25T23:33:46.649Z" },
|
{ url = "https://files.pythonhosted.org/packages/e9/5a/ac0f49e48063ab4255d9e3b79f5def51697fce1a95ea1370f03dc9db76f6/cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4", size = 3480909, upload-time = "2026-02-10T19:17:32.083Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c4/cc/f330e982852403da79008552de9906804568ae9230da8432f7496ce02b71/cryptography-46.0.6-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:12cae594e9473bca1a7aceb90536060643128bb274fcea0fc459ab90f7d1ae7a", size = 7162776, upload-time = "2026-03-25T23:34:13.308Z" },
|
{ url = "https://files.pythonhosted.org/packages/e2/fa/a66aa722105ad6a458bebd64086ca2b72cdd361fed31763d20390f6f1389/cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31", size = 7170514, upload-time = "2026-02-10T19:17:56.267Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/49/b3/dc27efd8dcc4bff583b3f01d4a3943cd8b5821777a58b3a6a5f054d61b79/cryptography-46.0.6-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:639301950939d844a9e1c4464d7e07f902fe9a7f6b215bb0d4f28584729935d8", size = 4270529, upload-time = "2026-03-25T23:34:15.019Z" },
|
{ url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349, upload-time = "2026-02-10T19:17:58.419Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e6/05/e8d0e6eb4f0d83365b3cb0e00eb3c484f7348db0266652ccd84632a3d58d/cryptography-46.0.6-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ed3775295fb91f70b4027aeba878d79b3e55c0b3e97eaa4de71f8f23a9f2eb77", size = 4414827, upload-time = "2026-03-25T23:34:16.604Z" },
|
{ url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667, upload-time = "2026-02-10T19:18:00.619Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/2f/97/daba0f5d2dc6d855e2dcb70733c812558a7977a55dd4a6722756628c44d1/cryptography-46.0.6-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8927ccfbe967c7df312ade694f987e7e9e22b2425976ddbf28271d7e58845290", size = 4271265, upload-time = "2026-03-25T23:34:18.586Z" },
|
{ url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980, upload-time = "2026-02-10T19:18:02.379Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/89/06/fe1fce39a37ac452e58d04b43b0855261dac320a2ebf8f5260dd55b201a9/cryptography-46.0.6-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:b12c6b1e1651e42ab5de8b1e00dc3b6354fdfd778e7fa60541ddacc27cd21410", size = 4916800, upload-time = "2026-03-25T23:34:20.561Z" },
|
{ url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143, upload-time = "2026-02-10T19:18:03.964Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ff/8a/b14f3101fe9c3592603339eb5d94046c3ce5f7fc76d6512a2d40efd9724e/cryptography-46.0.6-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:063b67749f338ca9c5a0b7fe438a52c25f9526b851e24e6c9310e7195aad3b4d", size = 4448771, upload-time = "2026-03-25T23:34:22.406Z" },
|
{ url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674, upload-time = "2026-02-10T19:18:05.588Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/01/b3/0796998056a66d1973fd52ee89dc1bb3b6581960a91ad4ac705f182d398f/cryptography-46.0.6-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:02fad249cb0e090b574e30b276a3da6a149e04ee2f049725b1f69e7b8351ec70", size = 3978333, upload-time = "2026-03-25T23:34:24.281Z" },
|
{ url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801, upload-time = "2026-02-10T19:18:07.167Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c5/3d/db200af5a4ffd08918cd55c08399dc6c9c50b0bc72c00a3246e099d3a849/cryptography-46.0.6-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e6142674f2a9291463e5e150090b95a8519b2fb6e6aaec8917dd8d094ce750d", size = 4271069, upload-time = "2026-03-25T23:34:25.895Z" },
|
{ url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755, upload-time = "2026-02-10T19:18:09.813Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d7/18/61acfd5b414309d74ee838be321c636fe71815436f53c9f0334bf19064fa/cryptography-46.0.6-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:456b3215172aeefb9284550b162801d62f5f264a081049a3e94307fe20792cfa", size = 4878358, upload-time = "2026-03-25T23:34:27.67Z" },
|
{ url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539, upload-time = "2026-02-10T19:18:11.263Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/8b/65/5bf43286d566f8171917cae23ac6add941654ccf085d739195a4eacf1674/cryptography-46.0.6-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:341359d6c9e68834e204ceaf25936dffeafea3829ab80e9503860dcc4f4dac58", size = 4448061, upload-time = "2026-03-25T23:34:29.375Z" },
|
{ url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794, upload-time = "2026-02-10T19:18:12.914Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e0/25/7e49c0fa7205cf3597e525d156a6bce5b5c9de1fd7e8cb01120e459f205a/cryptography-46.0.6-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9a9c42a2723999a710445bc0d974e345c32adfd8d2fac6d8a251fa829ad31cfb", size = 4399103, upload-time = "2026-03-25T23:34:32.036Z" },
|
{ url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160, upload-time = "2026-02-10T19:18:14.375Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/44/46/466269e833f1c4718d6cd496ffe20c56c9c8d013486ff66b4f69c302a68d/cryptography-46.0.6-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6617f67b1606dfd9fe4dbfa354a9508d4a6d37afe30306fe6c101b7ce3274b72", size = 4659255, upload-time = "2026-03-25T23:34:33.679Z" },
|
{ url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/0a/09/ddc5f630cc32287d2c953fc5d32705e63ec73e37308e5120955316f53827/cryptography-46.0.6-cp38-abi3-win32.whl", hash = "sha256:7f6690b6c55e9c5332c0b59b9c8a3fb232ebf059094c17f9019a51e9827df91c", size = 3010660, upload-time = "2026-03-25T23:34:35.418Z" },
|
{ url = "https://files.pythonhosted.org/packages/45/2d/9c5f2926cb5300a8eefc3f4f0b3f3df39db7f7ce40c8365444c49363cbda/cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72", size = 3010220, upload-time = "2026-02-10T19:18:17.361Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/1b/82/ca4893968aeb2709aacfb57a30dec6fa2ab25b10fa9f064b8882ce33f599/cryptography-46.0.6-cp38-abi3-win_amd64.whl", hash = "sha256:79e865c642cfc5c0b3eb12af83c35c5aeff4fa5c672dc28c43721c2c9fdd2f0f", size = 3471160, upload-time = "2026-03-25T23:34:37.191Z" },
|
{ url = "https://files.pythonhosted.org/packages/48/ef/0c2f4a8e31018a986949d34a01115dd057bf536905dca38897bacd21fac3/cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", size = 3467050, upload-time = "2026-02-10T19:18:18.899Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/2e/84/7ccff00ced5bac74b775ce0beb7d1be4e8637536b522b5df9b73ada42da2/cryptography-46.0.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:2ea0f37e9a9cf0df2952893ad145fd9627d326a59daec9b0802480fa3bcd2ead", size = 3475444, upload-time = "2026-03-25T23:34:38.944Z" },
|
{ url = "https://files.pythonhosted.org/packages/eb/dd/2d9fdb07cebdf3d51179730afb7d5e576153c6744c3ff8fded23030c204e/cryptography-46.0.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:3b4995dc971c9fb83c25aa44cf45f02ba86f71ee600d81091c2f0cbae116b06c", size = 3476964, upload-time = "2026-02-10T19:18:20.687Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/bc/1f/4c926f50df7749f000f20eede0c896769509895e2648db5da0ed55db711d/cryptography-46.0.6-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a3e84d5ec9ba01f8fd03802b2147ba77f0c8f2617b2aff254cedd551844209c8", size = 4218227, upload-time = "2026-03-25T23:34:40.871Z" },
|
{ url = "https://files.pythonhosted.org/packages/e9/6f/6cc6cc9955caa6eaf83660b0da2b077c7fe8ff9950a3c5e45d605038d439/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bc84e875994c3b445871ea7181d424588171efec3e185dced958dad9e001950a", size = 4218321, upload-time = "2026-02-10T19:18:22.349Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c6/65/707be3ffbd5f786028665c3223e86e11c4cda86023adbc56bd72b1b6bab5/cryptography-46.0.6-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:12f0fa16cc247b13c43d56d7b35287ff1569b5b1f4c5e87e92cc4fcc00cd10c0", size = 4381399, upload-time = "2026-03-25T23:34:42.609Z" },
|
{ url = "https://files.pythonhosted.org/packages/3e/5d/c4da701939eeee699566a6c1367427ab91a8b7088cc2328c09dbee940415/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2ae6971afd6246710480e3f15824ed3029a60fc16991db250034efd0b9fb4356", size = 4381786, upload-time = "2026-02-10T19:18:24.529Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f3/6d/73557ed0ef7d73d04d9aba745d2c8e95218213687ee5e76b7d236a5030fc/cryptography-46.0.6-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:50575a76e2951fe7dbd1f56d181f8c5ceeeb075e9ff88e7ad997d2f42af06e7b", size = 4217595, upload-time = "2026-03-25T23:34:44.205Z" },
|
{ url = "https://files.pythonhosted.org/packages/ac/97/a538654732974a94ff96c1db621fa464f455c02d4bb7d2652f4edc21d600/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d861ee9e76ace6cf36a6a89b959ec08e7bc2493ee39d07ffe5acb23ef46d27da", size = 4217990, upload-time = "2026-02-10T19:18:25.957Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/9e/c5/e1594c4eec66a567c3ac4400008108a415808be2ce13dcb9a9045c92f1a0/cryptography-46.0.6-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:90e5f0a7b3be5f40c3a0a0eafb32c681d8d2c181fc2a1bdabe9b3f611d9f6b1a", size = 4380912, upload-time = "2026-03-25T23:34:46.328Z" },
|
{ url = "https://files.pythonhosted.org/packages/ae/11/7e500d2dd3ba891197b9efd2da5454b74336d64a7cc419aa7327ab74e5f6/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:2b7a67c9cd56372f3249b39699f2ad479f6991e62ea15800973b956f4b73e257", size = 4381252, upload-time = "2026-02-10T19:18:27.496Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/1a/89/843b53614b47f97fe1abc13f9a86efa5ec9e275292c457af1d4a60dc80e0/cryptography-46.0.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6728c49e3b2c180ef26f8e9f0a883a2c585638db64cf265b49c9ba10652d430e", size = 3409955, upload-time = "2026-03-25T23:34:48.465Z" },
|
{ url = "https://files.pythonhosted.org/packages/bc/58/6b3d24e6b9bc474a2dcdee65dfd1f008867015408a271562e4b690561a4d/cryptography-46.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8456928655f856c6e1533ff59d5be76578a7157224dbd9ce6872f25055ab9ab7", size = 3407605, upload-time = "2026-02-10T19:18:29.233Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1263,7 +1263,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lnbits"
|
name = "lnbits"
|
||||||
version = "1.5.4"
|
version = "1.5.2"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "aiosqlite" },
|
{ name = "aiosqlite" },
|
||||||
@@ -2022,11 +2022,11 @@ email = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pygments"
|
name = "pygments"
|
||||||
version = "2.20.0"
|
version = "2.19.2"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
|
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2114,7 +2114,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest"
|
name = "pytest"
|
||||||
version = "9.0.3"
|
version = "9.0.2"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||||
@@ -2125,9 +2125,9 @@ dependencies = [
|
|||||||
{ name = "pygments" },
|
{ name = "pygments" },
|
||||||
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" },
|
{ url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2213,11 +2213,11 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-multipart"
|
name = "python-multipart"
|
||||||
version = "0.0.26"
|
version = "0.0.22"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/88/71/b145a380824a960ebd60e1014256dbb7d2253f2316ff2d73dfd8928ec2c3/python_multipart-0.0.26.tar.gz", hash = "sha256:08fadc45918cd615e26846437f50c5d6d23304da32c341f289a617127b081f17", size = 43501, upload-time = "2026-04-10T14:09:59.473Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/94/01/979e98d542a70714b0cb2b6728ed0b7c46792b695e3eaec3e20711271ca3/python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58", size = 37612, upload-time = "2026-01-25T10:15:56.219Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/9a/22/f1925cdda983ab66fc8ec6ec8014b959262747e58bdca26a4e3d1da29d56/python_multipart-0.0.26-py3-none-any.whl", hash = "sha256:c0b169f8c4484c13b0dcf2ef0ec3a4adb255c4b7d18d8e420477d2b1dd03f185", size = 28847, upload-time = "2026-04-10T14:09:58.131Z" },
|
{ url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579, upload-time = "2026-01-25T10:15:54.811Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2312,7 +2312,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "requests"
|
name = "requests"
|
||||||
version = "2.33.0"
|
version = "2.32.5"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "certifi" },
|
{ name = "certifi" },
|
||||||
@@ -2320,9 +2320,9 @@ dependencies = [
|
|||||||
{ name = "idna" },
|
{ name = "idna" },
|
||||||
{ name = "urllib3" },
|
{ name = "urllib3" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/34/64/8860370b167a9721e8956ae116825caff829224fbca0ca6e7bf8ddef8430/requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652", size = 134232, upload-time = "2026-03-25T15:10:41.586Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017, upload-time = "2026-03-25T15:10:40.382Z" },
|
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
Reference in New Issue
Block a user