not needed
This commit is contained in:
+1
-2
@@ -71,11 +71,10 @@ FORWARDED_ALLOW_IPS="*"
|
|||||||
# LNBITS_EXTENSIONS_PATH="/path/to/some/dir"
|
# LNBITS_EXTENSIONS_PATH="/path/to/some/dir"
|
||||||
|
|
||||||
# === WASM Extensions Sandbox Limits ===
|
# === WASM Extensions Sandbox Limits ===
|
||||||
# LNBITS_WASM_TIMEOUT_SECONDS=1.0
|
# LNBITS_WASM_TIMEOUT_SECONDS=3.0
|
||||||
# LNBITS_WASM_FUEL=50000
|
# LNBITS_WASM_FUEL=50000
|
||||||
# LNBITS_WASM_MAX_MODULE_BYTES=1000000
|
# LNBITS_WASM_MAX_MODULE_BYTES=1000000
|
||||||
# LNBITS_WASM_MAX_DB_OPS_PER_MIN=120
|
# LNBITS_WASM_MAX_DB_OPS_PER_MIN=120
|
||||||
# LNBITS_WASM_MAX_INVOICE_OPS_PER_MIN=30
|
|
||||||
|
|
||||||
# ID of the super user. The user ID must exist.
|
# ID of the super user. The user ID must exist.
|
||||||
# SUPER_USER=""
|
# SUPER_USER=""
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query, Request, WebSocket
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||||
from fastapi.responses import HTMLResponse, Response
|
from fastapi.responses import HTMLResponse, Response
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
||||||
from lnbits.core.crud.extensions import get_user_extension
|
from lnbits.core.crud.extensions import get_user_extension
|
||||||
from lnbits.core.crud.wallets import get_wallet_for_key, get_wallets_ids
|
|
||||||
from lnbits.core.models import User
|
from lnbits.core.models import User
|
||||||
from lnbits.core.services import create_invoice, pay_invoice, websocket_updater
|
from lnbits.core.services import websocket_updater
|
||||||
from lnbits.db import Database
|
from lnbits.db import Database
|
||||||
from lnbits.decorators import check_user_exists
|
from lnbits.decorators import check_user_exists
|
||||||
from lnbits.helpers import template_renderer
|
from lnbits.helpers import template_renderer
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
from lnbits.tasks import register_invoice_listener, unregister_invoice_listener
|
|
||||||
|
|
||||||
from .service import WasmExecutionError, wasm_call
|
from .service import WasmExecutionError, wasm_call
|
||||||
|
|
||||||
@@ -87,39 +84,6 @@ async def _require_permission(user_id: str, ext_id: str, permission: str) -> Non
|
|||||||
raise HTTPException(403, f"Missing permission: {permission}")
|
raise HTTPException(403, f"Missing permission: {permission}")
|
||||||
|
|
||||||
|
|
||||||
async def _require_wallet_access(user_id: str, wallet_id: str) -> None:
|
|
||||||
wallet_ids = await get_wallets_ids(user_id, deleted=False)
|
|
||||||
if wallet_id not in wallet_ids:
|
|
||||||
raise HTTPException(403, "Wallet does not belong to user.")
|
|
||||||
|
|
||||||
|
|
||||||
async def _wait_for_increment_payment(
|
|
||||||
ext_id: str, payment_hash: str, db: Database, upgrade_hash: str | None
|
|
||||||
) -> None:
|
|
||||||
queue_name = f"wasm:{ext_id}:{payment_hash}:{time.time()}"
|
|
||||||
invoice_queue: asyncio.Queue = asyncio.Queue()
|
|
||||||
register_invoice_listener(invoice_queue, queue_name)
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
payment = await asyncio.wait_for(invoice_queue.get(), timeout=3600)
|
|
||||||
if payment.payment_hash != payment_hash:
|
|
||||||
continue
|
|
||||||
if payment.pending is False:
|
|
||||||
try:
|
|
||||||
new_value = await wasm_call(
|
|
||||||
ext_id, "increment_counter", [], upgrade_hash=upgrade_hash
|
|
||||||
)
|
|
||||||
except WasmExecutionError:
|
|
||||||
return
|
|
||||||
await _kv_set(db, ext_id, "counter", str(new_value))
|
|
||||||
await websocket_updater(f"{ext_id}:counter", str(new_value))
|
|
||||||
return
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
return
|
|
||||||
finally:
|
|
||||||
unregister_invoice_listener(queue_name)
|
|
||||||
|
|
||||||
|
|
||||||
def register_wasm_ext_routes(app, ext) -> None:
|
def register_wasm_ext_routes(app, ext) -> None:
|
||||||
ext_id = ext.code
|
ext_id = ext.code
|
||||||
db = Database(f"ext_{ext_id}")
|
db = Database(f"ext_{ext_id}")
|
||||||
@@ -191,94 +155,6 @@ def register_wasm_ext_routes(app, ext) -> None:
|
|||||||
await websocket_updater(f"{ext_id}:{key}", str(new_value))
|
await websocket_updater(f"{ext_id}:{key}", str(new_value))
|
||||||
return {"key": key, "value": new_value}
|
return {"key": key, "value": new_value}
|
||||||
|
|
||||||
@router.post("/api/v1/invoices")
|
|
||||||
async def api_create_invoice(
|
|
||||||
payload: dict, user: User = Depends(check_user_exists)
|
|
||||||
):
|
|
||||||
await _require_permission(user.id, ext_id, "lnbits.invoice.create")
|
|
||||||
_check_quota(
|
|
||||||
user.id, ext_id, "invoice", settings.lnbits_wasm_max_invoice_ops_per_min
|
|
||||||
)
|
|
||||||
wallet_id = payload.get("wallet_id")
|
|
||||||
amount = payload.get("amount")
|
|
||||||
memo = payload.get("memo") or f"{ext_id} invoice"
|
|
||||||
if not wallet_id or not amount:
|
|
||||||
raise HTTPException(400, "Missing wallet_id or amount")
|
|
||||||
await _require_wallet_access(user.id, wallet_id)
|
|
||||||
|
|
||||||
try:
|
|
||||||
amount = int(amount)
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
raise HTTPException(400, "Invalid amount")
|
|
||||||
|
|
||||||
payment = await create_invoice(wallet_id=wallet_id, amount=amount, memo=memo)
|
|
||||||
return {
|
|
||||||
"payment_hash": payment.payment_hash,
|
|
||||||
"payment_request": payment.bolt11,
|
|
||||||
"amount": payment.amount,
|
|
||||||
}
|
|
||||||
|
|
||||||
@router.post("/api/v1/invoices/increment")
|
|
||||||
async def api_create_increment_invoice(
|
|
||||||
payload: dict, user: User = Depends(check_user_exists)
|
|
||||||
):
|
|
||||||
await _require_permission(user.id, ext_id, "ext.db.read_write")
|
|
||||||
await _require_permission(user.id, ext_id, "lnbits.invoice.create")
|
|
||||||
await _require_permission(user.id, ext_id, "lnbits.payments.subscribe")
|
|
||||||
_check_quota(
|
|
||||||
user.id, ext_id, "invoice", settings.lnbits_wasm_max_invoice_ops_per_min
|
|
||||||
)
|
|
||||||
wallet_id = payload.get("wallet_id")
|
|
||||||
memo = payload.get("memo") or f"{ext_id} increment"
|
|
||||||
if not wallet_id:
|
|
||||||
raise HTTPException(400, "Missing wallet_id")
|
|
||||||
await _require_wallet_access(user.id, wallet_id)
|
|
||||||
|
|
||||||
try:
|
|
||||||
amount = await wasm_call(
|
|
||||||
ext_id, "get_increment_amount", [], upgrade_hash=ext.upgrade_hash
|
|
||||||
)
|
|
||||||
except WasmExecutionError as exc:
|
|
||||||
raise HTTPException(500, str(exc)) from exc
|
|
||||||
if not amount or amount < 0:
|
|
||||||
raise HTTPException(400, "Invalid amount")
|
|
||||||
|
|
||||||
payment = await create_invoice(wallet_id=wallet_id, amount=amount, memo=memo)
|
|
||||||
asyncio.create_task(
|
|
||||||
_wait_for_increment_payment(
|
|
||||||
ext_id, payment.payment_hash, db, ext.upgrade_hash
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
"payment_hash": payment.payment_hash,
|
|
||||||
"payment_request": payment.bolt11,
|
|
||||||
"amount": payment.amount,
|
|
||||||
}
|
|
||||||
|
|
||||||
@router.post("/api/v1/invoices/pay")
|
|
||||||
async def api_pay_invoice(payload: dict, user: User = Depends(check_user_exists)):
|
|
||||||
await _require_permission(user.id, ext_id, "lnbits.invoice.pay")
|
|
||||||
_check_quota(
|
|
||||||
user.id, ext_id, "invoice", settings.lnbits_wasm_max_invoice_ops_per_min
|
|
||||||
)
|
|
||||||
wallet_id = payload.get("wallet_id")
|
|
||||||
payment_request = payload.get("payment_request")
|
|
||||||
if not wallet_id or not payment_request:
|
|
||||||
raise HTTPException(400, "Missing wallet_id or payment_request")
|
|
||||||
await _require_wallet_access(user.id, wallet_id)
|
|
||||||
|
|
||||||
payment = await pay_invoice(
|
|
||||||
wallet_id=wallet_id,
|
|
||||||
payment_request=payment_request,
|
|
||||||
description=f"{ext_id} payment",
|
|
||||||
tag=ext_id,
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
"payment_hash": payment.payment_hash,
|
|
||||||
"amount_msat": payment.amount_msat,
|
|
||||||
"status": payment.status,
|
|
||||||
}
|
|
||||||
|
|
||||||
@router.post("/api/v1/proxy")
|
@router.post("/api/v1/proxy")
|
||||||
async def api_proxy(payload: dict, req: Request, user: User = Depends(check_user_exists)):
|
async def api_proxy(payload: dict, req: Request, user: User = Depends(check_user_exists)):
|
||||||
method = str(payload.get("method", "GET")).upper()
|
method = str(payload.get("method", "GET")).upper()
|
||||||
@@ -310,34 +186,6 @@ def register_wasm_ext_routes(app, ext) -> None:
|
|||||||
media_type=resp.headers.get("content-type"),
|
media_type=resp.headers.get("content-type"),
|
||||||
)
|
)
|
||||||
|
|
||||||
@router.websocket("/api/v1/events/ws")
|
|
||||||
async def events_ws(websocket: WebSocket, api_key: str = Query(default="")):
|
|
||||||
await websocket.accept()
|
|
||||||
wallet = await get_wallet_for_key(api_key) if api_key else None
|
|
||||||
if not wallet:
|
|
||||||
await websocket.close(code=1008)
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
await _require_permission(wallet.user, ext_id, "lnbits.payments.subscribe")
|
|
||||||
except HTTPException:
|
|
||||||
await websocket.close(code=1008)
|
|
||||||
return
|
|
||||||
|
|
||||||
wallet_ids = await get_wallets_ids(wallet.user, deleted=False)
|
|
||||||
queue_name = f"wasm:{ext_id}:{wallet.user}:{id(websocket)}"
|
|
||||||
invoice_queue: asyncio.Queue = asyncio.Queue()
|
|
||||||
register_invoice_listener(invoice_queue, queue_name)
|
|
||||||
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
payment = await invoice_queue.get()
|
|
||||||
if payment.wallet_id in wallet_ids:
|
|
||||||
await websocket.send_json(payment.dict())
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
unregister_invoice_listener(queue_name)
|
|
||||||
|
|
||||||
static_dir = _ext_static_dir(ext_id, ext.upgrade_hash)
|
static_dir = _ext_static_dir(ext_id, ext.upgrade_hash)
|
||||||
if static_dir.is_dir():
|
if static_dir.is_dir():
|
||||||
app.mount(
|
app.mount(
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ class ExtensionsSettings(LNbitsSettings):
|
|||||||
lnbits_wasm_fuel: int = Field(default=50_000, ge=1_000)
|
lnbits_wasm_fuel: int = Field(default=50_000, ge=1_000)
|
||||||
lnbits_wasm_max_module_bytes: int = Field(default=1_000_000, ge=0)
|
lnbits_wasm_max_module_bytes: int = Field(default=1_000_000, ge=0)
|
||||||
lnbits_wasm_max_db_ops_per_min: int = Field(default=120, ge=0)
|
lnbits_wasm_max_db_ops_per_min: int = Field(default=120, ge=0)
|
||||||
lnbits_wasm_max_invoice_ops_per_min: int = Field(default=30, ge=0)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extension_builder_working_dir_path(self) -> Path:
|
def extension_builder_working_dir_path(self) -> Path:
|
||||||
|
|||||||
@@ -191,15 +191,6 @@
|
|||||||
hint="Per user per extension"
|
hint="Per user per extension"
|
||||||
></q-input>
|
></q-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-4">
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
type="number"
|
|
||||||
v-model.number="formData.lnbits_wasm_max_invoice_ops_per_min"
|
|
||||||
label="Max invoice ops per minute"
|
|
||||||
hint="Per user per extension"
|
|
||||||
></q-input>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user