From 939a7f242bf0a00b7600efd58bd76981f5f10b7c Mon Sep 17 00:00:00 2001 From: Arc Date: Thu, 21 May 2026 10:02:00 +0100 Subject: [PATCH] cleanup --- lnbits/core/views/extension_api.py | 51 ++++++++ lnbits/static/js/pages/extensions.js | 161 ++++++++++++-------------- lnbits/templates/pages/extensions.vue | 3 +- tests/api/test_extension_api.py | 33 ++++++ 4 files changed, 161 insertions(+), 87 deletions(-) diff --git a/lnbits/core/views/extension_api.py b/lnbits/core/views/extension_api.py index 142f15cda..d4e78e2ff 100644 --- a/lnbits/core/views/extension_api.py +++ b/lnbits/core/views/extension_api.py @@ -1,6 +1,8 @@ +import json import sys import traceback from http import HTTPStatus +from pathlib import Path import httpx from bolt11 import decode as bolt11_decode @@ -68,6 +70,53 @@ extension_router = APIRouter( ) +def _load_wasm_extension_config(ext_id: str) -> dict: + candidate_dirs = [ + Path(settings.lnbits_extensions_path, "extensions", ext_id), + Path(settings.lnbits_path, "lnbits", "extensions", ext_id), + ] + for base in candidate_dirs: + conf_path = base / "config.json" + if not conf_path.is_file(): + continue + try: + with open(conf_path) as json_file: + config = json.load(json_file) + return config if isinstance(config, dict) else {} + except Exception: + return {} + return {} + + +def _ensure_wasm_permissions_saved(ext_id: str, user_ext: UserExtension) -> None: + if get_extension_type(ext_id) != "wasm": + return + config = _load_wasm_extension_config(ext_id) + required = [ + permission.get("id") + for permission in config.get("permissions", []) + if isinstance(permission, dict) and permission.get("id") + ] + granted = user_ext.extra.granted_permissions if user_ext.extra else [] + missing = [ + permission for permission in required if permission not in (granted or []) + ] + if missing: + raise HTTPException( + HTTPStatus.BAD_REQUEST, + "Save WASM permissions before enabling this extension.", + ) + + payment_tags = config.get("payment_tags", []) + granted_tags = user_ext.extra.granted_payment_tags if user_ext.extra else [] + has_granted_payment_tag = any(tag in (granted_tags or []) for tag in payment_tags) + if payment_tags and not has_granted_payment_tag: + raise HTTPException( + HTTPStatus.BAD_REQUEST, + "Select at least one WASM payment tag before enabling this extension.", + ) + + @extension_router.post("", dependencies=[Depends(check_admin)]) async def api_install_extension(data: CreateExtension): release = await InstallableExtension.get_extension_release( @@ -198,6 +247,8 @@ async def api_enable_extension( user_ext = UserExtension(user=account_id.id, extension=ext_id, active=False) await create_user_extension(user_ext) + _ensure_wasm_permissions_saved(ext_id, user_ext) + if account_id.is_admin_id or not ext.requires_payment: user_ext.active = True await update_user_extension(user_ext) diff --git a/lnbits/static/js/pages/extensions.js b/lnbits/static/js/pages/extensions.js index 8763b9a11..fb9226d4d 100644 --- a/lnbits/static/js/pages/extensions.js +++ b/lnbits/static/js/pages/extensions.js @@ -262,39 +262,81 @@ window.PageExtensions = { extension.inProgress = false }) }, - async enableExtensionForUser(extension) { - if (extension.extensionType === 'wasm') { - const wasmHost = this.extensions.find(ext => ext.id === 'wasm') - if (!wasmHost || !wasmHost.isInstalled || !wasmHost.isActive) { - Quasar.Notify.create({ - type: 'warning', - message: - 'Enable the WASM! host extension before using this extension.' - }) - return - } - if (extension.permissions && extension.permissions.length) { - if (!extension._grantedPermissions) { - Quasar.Notify.create({ - type: 'warning', - message: 'Save permissions before enabling this extension.' - }) - return - } - } - if (extension.paymentTags && extension.paymentTags.length) { - if ( - !extension._grantedPaymentTags || - !extension._grantedPaymentTags.length - ) { - Quasar.Notify.create({ - type: 'warning', - message: 'Select payment tags before enabling this extension.' - }) - return - } - } + async loadWasmCapabilities(extension) { + const {data} = await LNbits.api.request( + 'GET', + `/wasm/api/v1/extensions/${extension.id}/capabilities`, + this.g.user.wallets[0].adminkey + ) + const capabilities = data || {} + extension.permissions = capabilities.permissions || [] + extension.paymentTags = capabilities.payment_tags || [] + extension.grantedPermissions = capabilities.granted_permissions || [] + extension.grantedPaymentTags = capabilities.granted_payment_tags || [] + extension._grantedPermissions = extension.grantedPermissions.slice() + extension._grantedPaymentTags = extension.grantedPaymentTags.slice() + return capabilities + }, + showWasmPermissionsDialog(extension, data = {}) { + this.permissionsDialog.extension = extension + this.permissionsDialog.checked = ( + data.granted_permissions || + extension._grantedPermissions || + extension.grantedPermissions || + [] + ).slice() + this.permissionsDialog.missing = data.missing_permissions || [] + this.permissionsDialog.tags = ( + data.granted_payment_tags || + extension._grantedPaymentTags || + extension.grantedPaymentTags || + [] + ).slice() + this.permissionsDialog.tagOptions = + data.payment_tags || extension.paymentTags || [] + this.permissionsDialog.show = true + }, + async ensureWasmPermissionsReady(extension) { + if (extension.extensionType !== 'wasm') return true + const wasmHost = this.extensions.find(ext => ext.id === 'wasm') + if (!wasmHost || !wasmHost.isInstalled || !wasmHost.isActive) { + Quasar.Notify.create({ + type: 'warning', + message: + 'Enable the WASM! host extension before using this extension.' + }) + return false } + let data = {} + try { + data = await this.loadWasmCapabilities(extension) + } catch (err) { + LNbits.utils.notifyApiError(err) + return false + } + const required = (data.permissions || []).map(p => p.id).filter(Boolean) + const missing = required.filter( + permission => !extension.grantedPermissions.includes(permission) + ) + const tags = data.payment_tags || [] + const missingTags = + tags.length && + !tags.some(tag => extension.grantedPaymentTags.includes(tag)) + if (!missing.length && !missingTags) return true + + Quasar.Notify.create({ + type: 'warning', + message: 'Save WASM permissions before using this extension.' + }) + this.showWasmPermissionsDialog(extension, data) + return false + }, + async openExtension(extension) { + if (!(await this.ensureWasmPermissionsReady(extension))) return + window.location.href = `${extension.id}/` + }, + async enableExtensionForUser(extension) { + if (!(await this.ensureWasmPermissionsReady(extension))) return if (extension.isPaymentRequired) { this.showPayToEnable(extension) return @@ -348,22 +390,6 @@ window.PageExtensions = { this.selectedExtension.payToEnable.showQRCode = false this.showPayToEnableDialog = true }, - openPermissionsDialog(extension) { - this.permissionsDialog.extension = extension - this.permissionsDialog.checked = extension._grantedPermissions - ? extension._grantedPermissions.slice() - : extension.grantedPermissions - ? extension.grantedPermissions.slice() - : [] - this.permissionsDialog.missing = [] - this.permissionsDialog.tags = extension._grantedPaymentTags - ? extension._grantedPaymentTags.slice() - : extension.grantedPaymentTags - ? extension.grantedPaymentTags.slice() - : [] - this.permissionsDialog.tagOptions = [] - this.permissionsDialog.show = true - }, cancelPermissionsDialog() { this.permissionsDialog.show = false this.permissionsDialog.extension = null @@ -380,47 +406,12 @@ window.PageExtensions = { }) return } - this.permissionsDialog.extension = extension - this.permissionsDialog.checked = extension._grantedPermissions - ? extension._grantedPermissions.slice() - : extension.grantedPermissions - ? extension.grantedPermissions.slice() - : [] - this.permissionsDialog.missing = [] - this.permissionsDialog.tags = extension._grantedPaymentTags - ? extension._grantedPaymentTags.slice() - : extension.grantedPaymentTags - ? extension.grantedPaymentTags.slice() - : [] - this.permissionsDialog.tagOptions = [] try { - const {data} = await LNbits.api.request( - 'GET', - `/wasm/api/v1/extensions/${extension.id}/capabilities`, - this.g.user.wallets[0].adminkey - ) - if (data && Array.isArray(data.permissions)) { - extension.permissions = data.permissions - } - if (data && Array.isArray(data.missing_permissions)) { - this.permissionsDialog.missing = data.missing_permissions - } - if (data && Array.isArray(data.payment_tags)) { - extension.paymentTags = data.payment_tags - this.permissionsDialog.tagOptions = data.payment_tags - } - if (data && Array.isArray(data.granted_permissions)) { - extension.grantedPermissions = data.granted_permissions - this.permissionsDialog.checked = data.granted_permissions.slice() - } - if (data && Array.isArray(data.granted_payment_tags)) { - extension.grantedPaymentTags = data.granted_payment_tags - this.permissionsDialog.tags = data.granted_payment_tags.slice() - } + const data = await this.loadWasmCapabilities(extension) + this.showWasmPermissionsDialog(extension, data) } catch (err) { LNbits.utils.notifyApiError(err) } - this.permissionsDialog.show = true }, async confirmPermissionsDialog() { const ext = this.permissionsDialog.extension diff --git a/lnbits/templates/pages/extensions.vue b/lnbits/templates/pages/extensions.vue index 228817dda..c7b733dee 100644 --- a/lnbits/templates/pages/extensions.vue +++ b/lnbits/templates/pages/extensions.vue @@ -291,8 +291,7 @@ " flat color="primary" - type="a" - :href="extension.id + '/'" + @click="openExtension(extension)" :label="$t('open')" >