fix: import lnbits resources
This commit is contained in:
@@ -1,17 +1,20 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Annotated, Any
|
from typing import Annotated, Any, NoReturn
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from fastapi import Depends, FastAPI, HTTPException, Request
|
from fastapi import Depends, FastAPI, HTTPException, Request
|
||||||
from fastapi.responses import FileResponse
|
from fastapi.responses import FileResponse, Response
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from pydantic import UUID4
|
from pydantic import UUID4
|
||||||
|
from starlette.staticfiles import PathLike as StaticFilesPathLike
|
||||||
|
from starlette.types import Scope
|
||||||
|
|
||||||
from lnbits.decorators import (
|
from lnbits.decorators import (
|
||||||
check_access_token,
|
check_access_token,
|
||||||
@@ -24,6 +27,19 @@ from lnbits.settings import settings
|
|||||||
from lnbits.utils.cache import cache
|
from lnbits.utils.cache import cache
|
||||||
|
|
||||||
WASM_FRAME_TOKEN_EXPIRY_SECONDS = 60
|
WASM_FRAME_TOKEN_EXPIRY_SECONDS = 60
|
||||||
|
WASM_EXTENSION_CORE_ASSET_PREFIX = "_lnbits"
|
||||||
|
WASM_EXTENSION_CORE_STATIC_ASSETS = {
|
||||||
|
"bundle.min.css": ("static/bundle.min.css", "text/css; charset=utf-8"),
|
||||||
|
"quasar.css": ("static/vendor/quasar.css", "text/css; charset=utf-8"),
|
||||||
|
"quasar.umd.prod.js": (
|
||||||
|
"static/vendor/quasar.umd.prod.js",
|
||||||
|
"text/javascript; charset=utf-8",
|
||||||
|
),
|
||||||
|
"vue.global.prod.js": (
|
||||||
|
"static/vendor/vue.global.prod.js",
|
||||||
|
"text/javascript; charset=utf-8",
|
||||||
|
),
|
||||||
|
}
|
||||||
WASM_EXTENSION_STATIC_MIME_TYPES = {
|
WASM_EXTENSION_STATIC_MIME_TYPES = {
|
||||||
".css": "text/css; charset=utf-8",
|
".css": "text/css; charset=utf-8",
|
||||||
".gif": "image/gif",
|
".gif": "image/gif",
|
||||||
@@ -55,18 +71,20 @@ class WasmExtension:
|
|||||||
|
|
||||||
|
|
||||||
class GuardedWasmExtensionStaticFiles(StaticFiles):
|
class GuardedWasmExtensionStaticFiles(StaticFiles):
|
||||||
async def get_response(self, path: str, scope: dict[str, Any]) -> Any:
|
async def get_response(self, path: str, scope: Scope) -> Response:
|
||||||
|
if path.startswith(f"{WASM_EXTENSION_CORE_ASSET_PREFIX}/"):
|
||||||
|
return _wasm_extension_core_asset_response(path)
|
||||||
if Path(path).suffix.lower() not in WASM_EXTENSION_STATIC_MIME_TYPES:
|
if Path(path).suffix.lower() not in WASM_EXTENSION_STATIC_MIME_TYPES:
|
||||||
raise HTTPException(status_code=404)
|
raise HTTPException(status_code=404)
|
||||||
return await super().get_response(path, scope)
|
return await super().get_response(path, scope)
|
||||||
|
|
||||||
def file_response(
|
def file_response(
|
||||||
self,
|
self,
|
||||||
full_path: str,
|
full_path: StaticFilesPathLike,
|
||||||
stat_result: Any,
|
stat_result: os.stat_result,
|
||||||
scope: dict[str, Any],
|
scope: Scope,
|
||||||
status_code: int = 200,
|
status_code: int = 200,
|
||||||
) -> Any:
|
) -> Response:
|
||||||
suffix = Path(full_path).suffix.lower()
|
suffix = Path(full_path).suffix.lower()
|
||||||
if suffix in WASM_EXTENSION_TEXT_STATIC_EXTENSIONS:
|
if suffix in WASM_EXTENSION_TEXT_STATIC_EXTENSIONS:
|
||||||
_reject_html_like_wasm_static_asset(Path(full_path))
|
_reject_html_like_wasm_static_asset(Path(full_path))
|
||||||
@@ -145,8 +163,6 @@ def load_wasm_extension(ext_id: str) -> WasmExtension:
|
|||||||
|
|
||||||
def _mount_wasm_extension_static(app: FastAPI, extension: WasmExtension) -> None:
|
def _mount_wasm_extension_static(app: FastAPI, extension: WasmExtension) -> None:
|
||||||
static_path = extension.root_path / "static"
|
static_path = extension.root_path / "static"
|
||||||
if not static_path.is_dir():
|
|
||||||
return
|
|
||||||
|
|
||||||
mount_path = f"/ext-assets/{extension.id}"
|
mount_path = f"/ext-assets/{extension.id}"
|
||||||
if any(getattr(route, "path", None) == mount_path for route in app.routes):
|
if any(getattr(route, "path", None) == mount_path for route in app.routes):
|
||||||
@@ -154,7 +170,7 @@ def _mount_wasm_extension_static(app: FastAPI, extension: WasmExtension) -> None
|
|||||||
|
|
||||||
app.mount(
|
app.mount(
|
||||||
mount_path,
|
mount_path,
|
||||||
GuardedWasmExtensionStaticFiles(directory=static_path),
|
GuardedWasmExtensionStaticFiles(directory=static_path, check_dir=False),
|
||||||
name=f"{extension.id}-static",
|
name=f"{extension.id}-static",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -452,16 +468,15 @@ def _wasm_extension_wrapper_response(
|
|||||||
def _wasm_extension_frame_csp(request: Request, extension: WasmExtension) -> str:
|
def _wasm_extension_frame_csp(request: Request, extension: WasmExtension) -> str:
|
||||||
origin = str(request.base_url).rstrip("/")
|
origin = str(request.base_url).rstrip("/")
|
||||||
extension_assets = f"{origin}/ext-assets/{extension.id}/"
|
extension_assets = f"{origin}/ext-assets/{extension.id}/"
|
||||||
static_assets = f"{origin}/static/"
|
|
||||||
return (
|
return (
|
||||||
"sandbox allow-scripts; "
|
"sandbox allow-scripts; "
|
||||||
"default-src 'none'; "
|
"default-src 'none'; "
|
||||||
f"script-src {extension_assets}; "
|
f"script-src {extension_assets}; "
|
||||||
"script-src-attr 'none'; "
|
"script-src-attr 'none'; "
|
||||||
f"style-src {extension_assets} {static_assets}; "
|
f"style-src {extension_assets}; "
|
||||||
"style-src-attr 'none'; "
|
"style-src-attr 'none'; "
|
||||||
f"img-src {extension_assets} {static_assets}; "
|
f"img-src {extension_assets} data:; "
|
||||||
f"font-src {static_assets}; "
|
f"font-src {extension_assets}; "
|
||||||
"connect-src 'none'; "
|
"connect-src 'none'; "
|
||||||
"form-action 'none'; "
|
"form-action 'none'; "
|
||||||
"object-src 'none'; "
|
"object-src 'none'; "
|
||||||
@@ -535,7 +550,7 @@ def _raise_wasm_extension_frame_not_found(
|
|||||||
extension: WasmExtension,
|
extension: WasmExtension,
|
||||||
frame_path: str,
|
frame_path: str,
|
||||||
reason: str,
|
reason: str,
|
||||||
) -> None:
|
) -> NoReturn:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"WASM frame token {reason} for extension '{extension.id}' at '{frame_path}'."
|
f"WASM frame token {reason} for extension '{extension.id}' at '{frame_path}'."
|
||||||
)
|
)
|
||||||
@@ -670,6 +685,27 @@ def _reject_html_like_wasm_static_asset(path: Path) -> None:
|
|||||||
raise HTTPException(status_code=404)
|
raise HTTPException(status_code=404)
|
||||||
|
|
||||||
|
|
||||||
|
def _wasm_extension_core_asset_response(path: str) -> FileResponse:
|
||||||
|
asset_name = path.removeprefix(f"{WASM_EXTENSION_CORE_ASSET_PREFIX}/")
|
||||||
|
if not asset_name or "/" in asset_name or "\\" in asset_name:
|
||||||
|
raise HTTPException(status_code=404)
|
||||||
|
|
||||||
|
asset_config = WASM_EXTENSION_CORE_STATIC_ASSETS.get(asset_name)
|
||||||
|
if not asset_config:
|
||||||
|
raise HTTPException(status_code=404)
|
||||||
|
|
||||||
|
relative_path, content_type = asset_config
|
||||||
|
asset_path = Path(settings.lnbits_path, relative_path)
|
||||||
|
if not asset_path.is_file():
|
||||||
|
raise HTTPException(status_code=404)
|
||||||
|
|
||||||
|
response = FileResponse(asset_path)
|
||||||
|
response.headers["Content-Type"] = content_type
|
||||||
|
response.headers["X-Content-Type-Options"] = "nosniff"
|
||||||
|
response.headers["Cache-Control"] = "no-store"
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
def _check_wasm_module(path: Path) -> None:
|
def _check_wasm_module(path: Path) -> None:
|
||||||
if not path.is_file():
|
if not path.is_file():
|
||||||
raise FileNotFoundError(f"WASM module not found: {path}")
|
raise FileNotFoundError(f"WASM module not found: {path}")
|
||||||
|
|||||||
Reference in New Issue
Block a user