Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 2 additions & 86 deletions lnbits/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,25 @@
import os
import shutil
import sys
import traceback
from contextlib import asynccontextmanager
from http import HTTPStatus
from pathlib import Path
from typing import Callable, List, Optional

from fastapi import FastAPI, HTTPException, Request
from fastapi.exceptions import RequestValidationError
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import RedirectResponse
from fastapi.staticfiles import StaticFiles
from loguru import logger
from slowapi import Limiter
from slowapi.util import get_remote_address
from starlette.middleware.sessions import SessionMiddleware
from starlette.responses import JSONResponse

from lnbits.core.crud import get_dbversions, get_installed_extensions
from lnbits.core.helpers import migrate_extension_database
from lnbits.core.tasks import ( # watchdog_task
killswitch_task,
wait_for_paid_invoices,
)
from lnbits.exceptions import register_exception_handlers
from lnbits.settings import settings
from lnbits.tasks import (
cancel_all_tasks,
Expand All @@ -53,7 +49,6 @@
get_valid_extensions,
version_parse,
)
from .helpers import template_renderer
from .middleware import (
CustomGZipMiddleware,
ExtensionsRedirectMiddleware,
Expand Down Expand Up @@ -429,82 +424,3 @@ def register_async_tasks():
if settings.lnbits_admin_ui:
server_log_task = initialize_server_websocket_logger()
create_permanent_task(server_log_task)


def register_exception_handlers(app: FastAPI):
@app.exception_handler(Exception)
async def exception_handler(request: Request, exc: Exception):
etype, _, tb = sys.exc_info()
traceback.print_exception(etype, exc, tb)
logger.error(f"Exception: {exc!s}")
# Only the browser sends "text/html" request
# not fail proof, but everything else get's a JSON response
if (
request.headers
and "accept" in request.headers
and "text/html" in request.headers["accept"]
):
return template_renderer().TemplateResponse(
request, "error.html", {"err": f"Error: {exc!s}"}
)

return JSONResponse(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
content={"detail": str(exc)},
)

@app.exception_handler(RequestValidationError)
async def validation_exception_handler(
request: Request, exc: RequestValidationError
):
logger.error(f"RequestValidationError: {exc!s}")
# Only the browser sends "text/html" request
# not fail proof, but everything else get's a JSON response

if (
request.headers
and "accept" in request.headers
and "text/html" in request.headers["accept"]
):
return template_renderer().TemplateResponse(
request,
"error.html",
{"err": f"Error: {exc!s}"},
)

return JSONResponse(
status_code=HTTPStatus.BAD_REQUEST,
content={"detail": str(exc)},
)

@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
logger.error(f"HTTPException {exc.status_code}: {exc.detail}")
# Only the browser sends "text/html" request
# not fail proof, but everything else get's a JSON response

if (
request.headers
and "accept" in request.headers
and "text/html" in request.headers["accept"]
):
if exc.headers and "token-expired" in exc.headers:
response = RedirectResponse("/")
response.delete_cookie("cookie_access_token")
response.delete_cookie("is_lnbits_user_authorized")
response.set_cookie("is_access_token_expired", "true")
return response

return template_renderer().TemplateResponse(
request,
"error.html",
{
"request": request,
"err": f"HTTP Error {exc.status_code}: {exc.detail}",
},
)

return JSONResponse(
status_code=exc.status_code,
content={"detail": exc.detail},
)
90 changes: 36 additions & 54 deletions lnbits/core/views/payment_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,6 @@
update_pending_payments,
)
from ..services import (
InvoiceError,
PaymentError,
check_transaction_status,
create_invoice,
fee_reserve_total,
Expand Down Expand Up @@ -150,33 +148,25 @@ async def api_payments_create_invoice(data: CreateInvoice, wallet: Wallet):
memo = ""

async with db.connect() as conn:
try:
payment_hash, payment_request = await create_invoice(
wallet_id=wallet.id,
amount=data.amount,
memo=memo,
currency=data.unit,
description_hash=description_hash,
unhashed_description=unhashed_description,
expiry=data.expiry,
extra=data.extra,
webhook=data.webhook,
internal=data.internal,
conn=conn,
)
# NOTE: we get the checking_id with a seperate query because create_invoice
# does not return it and it would be a big hustle to change its return type
# (used across extensions)
payment_db = await get_standalone_payment(payment_hash, conn=conn)
assert payment_db is not None, "payment not found"
checking_id = payment_db.checking_id
except InvoiceError as exc:
return JSONResponse(
status_code=520,
content={"detail": exc.message, "status": exc.status},
)
except Exception as exc:
raise exc
payment_hash, payment_request = await create_invoice(
wallet_id=wallet.id,
amount=data.amount,
memo=memo,
currency=data.unit,
description_hash=description_hash,
unhashed_description=unhashed_description,
expiry=data.expiry,
extra=data.extra,
webhook=data.webhook,
internal=data.internal,
conn=conn,
)
# NOTE: we get the checking_id with a seperate query because create_invoice
# does not return it and it would be a big hustle to change its return type
# (used across extensions)
payment_db = await get_standalone_payment(payment_hash, conn=conn)
assert payment_db is not None, "payment not found"
checking_id = payment_db.checking_id

invoice = bolt11.decode(payment_request)

Expand Down Expand Up @@ -213,28 +203,6 @@ async def api_payments_create_invoice(data: CreateInvoice, wallet: Wallet):
}


async def api_payments_pay_invoice(
bolt11: str, wallet: Wallet, extra: Optional[dict] = None
):
try:
payment_hash = await pay_invoice(
wallet_id=wallet.id, payment_request=bolt11, extra=extra
)
except PaymentError as exc:
return JSONResponse(
status_code=520,
content={"detail": exc.message, "status": exc.status},
)
except Exception as exc:
raise exc

return {
"payment_hash": payment_hash,
# maintain backwards compatibility with API clients:
"checking_id": payment_hash,
}


@payment_router.post(
"",
summary="Create or pay an invoice",
Expand All @@ -247,6 +215,11 @@ async def api_payments_pay_invoice(
field to supply the BOLT11 invoice to be paid.
""",
status_code=HTTPStatus.CREATED,
responses={
400: {"description": "Invalid BOLT11 string or missing fields."},
401: {"description": "Invoice (or Admin) key required."},
520: {"description": "Payment or Invoice error."},
},
)
async def api_payments_create(
wallet: WalletTypeInfo = Depends(require_invoice_key),
Expand All @@ -258,9 +231,18 @@ async def api_payments_create(
status_code=HTTPStatus.BAD_REQUEST,
detail="BOLT11 string is invalid or not given",
)
return await api_payments_pay_invoice(
invoice_data.bolt11, wallet.wallet, invoice_data.extra
) # admin key

payment_hash = await pay_invoice(
wallet_id=wallet.wallet.id,
payment_request=invoice_data.bolt11,
extra=invoice_data.extra,
)
return {
"payment_hash": payment_hash,
# maintain backwards compatibility with API clients:
"checking_id": payment_hash,
}

elif not invoice_data.out:
# invoice key
return await api_payments_create_invoice(invoice_data, wallet.wallet)
Expand Down
101 changes: 101 additions & 0 deletions lnbits/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import sys
import traceback
from http import HTTPStatus
from typing import Optional

from fastapi import FastAPI, HTTPException, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse, RedirectResponse, Response
from loguru import logger

from lnbits.core.services import InvoiceError, PaymentError

from .helpers import template_renderer


def register_exception_handlers(app: FastAPI):
register_exception_handler(app)
register_request_validation_exception_handler(app)
register_http_exception_handler(app)
register_payment_error_handler(app)
register_invoice_error_handler(app)


def render_html_error(request: Request, exc: Exception) -> Optional[Response]:
# Only the browser sends "text/html" request
# not fail proof, but everything else get's a JSON response
if (
request.headers
and "accept" in request.headers
and "text/html" in request.headers["accept"]
):
if (
isinstance(exc, HTTPException)
and exc.headers
and "token-expired" in exc.headers
):
response = RedirectResponse("/")
response.delete_cookie("cookie_access_token")
response.delete_cookie("is_lnbits_user_authorized")
response.set_cookie("is_access_token_expired", "true")
return response

return template_renderer().TemplateResponse(
request, "error.html", {"err": f"Error: {exc!s}"}
)

return None


def register_exception_handler(app: FastAPI):
@app.exception_handler(Exception)
async def exception_handler(request: Request, exc: Exception):
etype, _, tb = sys.exc_info()
traceback.print_exception(etype, exc, tb)
logger.error(f"Exception: {exc!s}")
return render_html_error(request, exc) or JSONResponse(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
content={"detail": str(exc)},
)


def register_request_validation_exception_handler(app: FastAPI):
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(
request: Request, exc: RequestValidationError
):
logger.error(f"RequestValidationError: {exc!s}")
return render_html_error(request, exc) or JSONResponse(
status_code=HTTPStatus.BAD_REQUEST,
content={"detail": str(exc)},
)


def register_http_exception_handler(app: FastAPI):
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
logger.error(f"HTTPException {exc.status_code}: {exc.detail}")
return render_html_error(request, exc) or JSONResponse(
status_code=exc.status_code,
content={"detail": exc.detail},
)


def register_payment_error_handler(app: FastAPI):
@app.exception_handler(PaymentError)
async def payment_error_handler(request: Request, exc: PaymentError):
logger.error(f"PaymentError: {exc.message}, {exc.status}")
return JSONResponse(
status_code=520,
content={"detail": exc.message, "status": exc.status},
)


def register_invoice_error_handler(app: FastAPI):
@app.exception_handler(InvoiceError)
async def invoice_error_handler(request: Request, exc: InvoiceError):
logger.error(f"InvoiceError: {exc.message}, Status: {exc.status}")
return JSONResponse(
status_code=520,
content={"detail": exc.message, "status": exc.status},
)