I want them to turn black

This commit is contained in:
benarc 2021-10-17 18:33:29 +01:00
parent 70facdaa93
commit 1d3bb016a2
84 changed files with 899 additions and 1008 deletions

View File

@ -4,8 +4,15 @@ import uvloop
from starlette.requests import Request from starlette.requests import Request
from .commands import bundle_vendored, migrate_databases, transpile_scss from .commands import bundle_vendored, migrate_databases, transpile_scss
from .settings import (DEBUG, LNBITS_COMMIT, LNBITS_DATA_FOLDER, from .settings import (
LNBITS_SITE_TITLE, PORT, SERVICE_FEE, WALLET) DEBUG,
LNBITS_COMMIT,
LNBITS_DATA_FOLDER,
LNBITS_SITE_TITLE,
PORT,
SERVICE_FEE,
WALLET,
)
uvloop.install() uvloop.install()

View File

@ -16,12 +16,23 @@ import lnbits.settings
from .commands import db_migrate, handle_assets from .commands import db_migrate, handle_assets
from .core import core_app from .core import core_app
from .core.views.generic import core_html_routes from .core.views.generic import core_html_routes
from .helpers import (get_css_vendored, get_js_vendored, get_valid_extensions, from .helpers import (
template_renderer, url_for_vendored) get_css_vendored,
get_js_vendored,
get_valid_extensions,
template_renderer,
url_for_vendored,
)
from .requestvars import g from .requestvars import g
from .settings import WALLET from .settings import WALLET
from .tasks import (catch_everything_and_restart, check_pending_payments, internal_invoice_listener, from .tasks import (
invoice_listener, run_deferred_async, webhook_handler) catch_everything_and_restart,
check_pending_payments,
internal_invoice_listener,
invoice_listener,
run_deferred_async,
webhook_handler,
)
def create_app(config_object="lnbits.settings") -> FastAPI: def create_app(config_object="lnbits.settings") -> FastAPI:
@ -30,12 +41,11 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
""" """
app = FastAPI() app = FastAPI()
app.mount("/static", StaticFiles(directory="lnbits/static"), name="static") app.mount("/static", StaticFiles(directory="lnbits/static"), name="static")
app.mount("/core/static", StaticFiles(directory="lnbits/core/static"), name="core_static") app.mount(
"/core/static", StaticFiles(directory="lnbits/core/static"), name="core_static"
)
origins = [ origins = ["http://localhost", "http://localhost:5000"]
"http://localhost",
"http://localhost:5000",
]
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
@ -44,14 +54,19 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
allow_methods=["*"], allow_methods=["*"],
allow_headers=["*"], allow_headers=["*"],
) )
g().config = lnbits.settings g().config = lnbits.settings
g().base_url = f"http://{lnbits.settings.HOST}:{lnbits.settings.PORT}" g().base_url = f"http://{lnbits.settings.HOST}:{lnbits.settings.PORT}"
@app.exception_handler(RequestValidationError) @app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError): async def validation_exception_handler(
return template_renderer().TemplateResponse("error.html", {"request": request, "err": f"`{exc.errors()}` is not a valid UUID."}) request: Request, exc: RequestValidationError
):
return template_renderer().TemplateResponse(
"error.html",
{"request": request, "err": f"`{exc.errors()}` is not a valid UUID."},
)
# return HTMLResponse( # return HTMLResponse(
# status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, # status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
# content=jsonable_encoder({"detail": exc.errors(), "body": exc.body}), # content=jsonable_encoder({"detail": exc.errors(), "body": exc.body}),
@ -69,6 +84,7 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
return app return app
def check_funding_source(app: FastAPI) -> None: def check_funding_source(app: FastAPI) -> None:
@app.on_event("startup") @app.on_event("startup")
async def check_wallet_status(): async def check_wallet_status():
@ -95,7 +111,7 @@ def register_routes(app: FastAPI) -> None:
try: try:
ext_module = importlib.import_module(f"lnbits.extensions.{ext.code}") ext_module = importlib.import_module(f"lnbits.extensions.{ext.code}")
ext_route = getattr(ext_module, f"{ext.code}_ext") ext_route = getattr(ext_module, f"{ext.code}_ext")
if hasattr(ext_module, f"{ext.code}_start"): if hasattr(ext_module, f"{ext.code}_start"):
ext_start_func = getattr(ext_module, f"{ext.code}_start") ext_start_func = getattr(ext_module, f"{ext.code}_start")
ext_start_func() ext_start_func()
@ -150,6 +166,7 @@ def register_async_tasks(app):
async def stop_listeners(): async def stop_listeners():
pass pass
def register_exception_handlers(app: FastAPI): def register_exception_handlers(app: FastAPI):
@app.exception_handler(Exception) @app.exception_handler(Exception)
async def basic_error(request: Request, err): async def basic_error(request: Request, err):
@ -157,5 +174,6 @@ def register_exception_handlers(app: FastAPI):
etype, _, tb = sys.exc_info() etype, _, tb = sys.exc_info()
traceback.print_exception(etype, err, tb) traceback.print_exception(etype, err, tb)
exc = traceback.format_exc() exc = traceback.format_exc()
return template_renderer().TemplateResponse("error.html", {"request": request, "err": err}) return template_renderer().TemplateResponse(
"error.html", {"request": request, "err": err}
)

View File

@ -8,7 +8,6 @@ from fastapi.security.api_key import APIKeyQuery, APIKeyCookie, APIKeyHeader, AP
from fastapi.security.base import SecurityBase from fastapi.security.base import SecurityBase
API_KEY = "usr" API_KEY = "usr"
API_KEY_NAME = "X-API-key" API_KEY_NAME = "X-API-key"
@ -16,12 +15,11 @@ api_key_query = APIKeyQuery(name=API_KEY_NAME, auto_error=False)
api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False) api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False)
class AuthBearer(SecurityBase): class AuthBearer(SecurityBase):
def __init__(self, scheme_name: str = None, auto_error: bool = True): def __init__(self, scheme_name: str = None, auto_error: bool = True):
self.scheme_name = scheme_name or self.__class__.__name__ self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error self.auto_error = auto_error
async def __call__(self, request: Request): async def __call__(self, request: Request):
key = await self.get_api_key() key = await self.get_api_key()
print(key) print(key)
@ -37,7 +35,9 @@ class AuthBearer(SecurityBase):
# else: # else:
# raise HTTPException( # raise HTTPException(
# status_code=403, detail="Invalid authorization code.") # status_code=403, detail="Invalid authorization code.")
async def get_api_key(self,
async def get_api_key(
self,
api_key_query: str = Security(api_key_query), api_key_query: str = Security(api_key_query),
api_key_header: str = Security(api_key_header), api_key_header: str = Security(api_key_header),
): ):
@ -46,4 +46,6 @@ class AuthBearer(SecurityBase):
elif api_key_header == API_KEY: elif api_key_header == API_KEY:
return api_key_header return api_key_header
else: else:
raise HTTPException(status_code=403, detail="Could not validate credentials") raise HTTPException(
status_code=403, detail="Could not validate credentials"
)

View File

@ -125,12 +125,7 @@ def _unshorten_amount(amount: str) -> int:
# * `u` (micro): multiply by 0.000001 # * `u` (micro): multiply by 0.000001
# * `n` (nano): multiply by 0.000000001 # * `n` (nano): multiply by 0.000000001
# * `p` (pico): multiply by 0.000000000001 # * `p` (pico): multiply by 0.000000000001
units = { units = {"p": 10 ** 12, "n": 10 ** 9, "u": 10 ** 6, "m": 10 ** 3}
"p": 10 ** 12,
"n": 10 ** 9,
"u": 10 ** 6,
"m": 10 ** 3,
}
unit = str(amount)[-1] unit = str(amount)[-1]
# BOLT #11: # BOLT #11:
@ -161,9 +156,9 @@ def _trim_to_bytes(barr):
def _readable_scid(short_channel_id: int) -> str: def _readable_scid(short_channel_id: int) -> str:
return "{blockheight}x{transactionindex}x{outputindex}".format( return "{blockheight}x{transactionindex}x{outputindex}".format(
blockheight=((short_channel_id >> 40) & 0xffffff), blockheight=((short_channel_id >> 40) & 0xFFFFFF),
transactionindex=((short_channel_id >> 16) & 0xffffff), transactionindex=((short_channel_id >> 16) & 0xFFFFFF),
outputindex=(short_channel_id & 0xffff), outputindex=(short_channel_id & 0xFFFF),
) )

View File

@ -9,5 +9,3 @@ core_app: APIRouter = APIRouter()
from .views.api import * # noqa from .views.api import * # noqa
from .views.generic import * # noqa from .views.generic import * # noqa
from .views.public_api import * # noqa from .views.public_api import * # noqa

View File

@ -58,10 +58,11 @@ async def get_user(user_id: str, conn: Optional[Connection] = None) -> Optional[
return None return None
return User( return User(
id = user['id'], id=user["id"],
email = user['email'], email=user["email"],
extensions = [e[0] for e in extensions], extensions=[e[0] for e in extensions],
wallets = [Wallet(**w) for w in wallets]) wallets=[Wallet(**w) for w in wallets],
)
async def update_user_extension( async def update_user_extension(
@ -106,6 +107,7 @@ async def create_wallet(
return new_wallet return new_wallet
async def update_wallet( async def update_wallet(
wallet_id: str, new_name: str, conn: Optional[Connection] = None wallet_id: str, new_name: str, conn: Optional[Connection] = None
) -> Optional[Wallet]: ) -> Optional[Wallet]:
@ -115,7 +117,7 @@ async def update_wallet(
name = ? name = ?
WHERE id = ? WHERE id = ?
""", """,
(new_name, wallet_id) (new_name, wallet_id),
) )
@ -276,9 +278,7 @@ async def get_payments(
return [Payment.from_row(row) for row in rows] return [Payment.from_row(row) for row in rows]
async def delete_expired_invoices( async def delete_expired_invoices(conn: Optional[Connection] = None,) -> None:
conn: Optional[Connection] = None,
) -> None:
# first we delete all invoices older than one month # first we delete all invoices older than one month
await (conn or db).execute( await (conn or db).execute(
f""" f"""
@ -367,31 +367,22 @@ async def create_payment(
async def update_payment_status( async def update_payment_status(
checking_id: str, checking_id: str, pending: bool, conn: Optional[Connection] = None
pending: bool,
conn: Optional[Connection] = None,
) -> None: ) -> None:
await (conn or db).execute( await (conn or db).execute(
"UPDATE apipayments SET pending = ? WHERE checking_id = ?", "UPDATE apipayments SET pending = ? WHERE checking_id = ?",
( (pending, checking_id),
pending,
checking_id,
),
) )
async def delete_payment( async def delete_payment(checking_id: str, conn: Optional[Connection] = None) -> None:
checking_id: str,
conn: Optional[Connection] = None,
) -> None:
await (conn or db).execute( await (conn or db).execute(
"DELETE FROM apipayments WHERE checking_id = ?", (checking_id,) "DELETE FROM apipayments WHERE checking_id = ?", (checking_id,)
) )
async def check_internal( async def check_internal(
payment_hash: str, payment_hash: str, conn: Optional[Connection] = None
conn: Optional[Connection] = None,
) -> Optional[str]: ) -> Optional[str]:
row = await (conn or db).fetchone( row = await (conn or db).fetchone(
""" """
@ -411,9 +402,7 @@ async def check_internal(
async def save_balance_check( async def save_balance_check(
wallet_id: str, wallet_id: str, url: str, conn: Optional[Connection] = None
url: str,
conn: Optional[Connection] = None,
): ):
domain = urlparse(url).netloc domain = urlparse(url).netloc
@ -427,9 +416,7 @@ async def save_balance_check(
async def get_balance_check( async def get_balance_check(
wallet_id: str, wallet_id: str, domain: str, conn: Optional[Connection] = None
domain: str,
conn: Optional[Connection] = None,
) -> Optional[BalanceCheck]: ) -> Optional[BalanceCheck]:
row = await (conn or db).fetchone( row = await (conn or db).fetchone(
""" """
@ -452,9 +439,7 @@ async def get_balance_checks(conn: Optional[Connection] = None) -> List[BalanceC
async def save_balance_notify( async def save_balance_notify(
wallet_id: str, wallet_id: str, url: str, conn: Optional[Connection] = None
url: str,
conn: Optional[Connection] = None,
): ):
await (conn or db).execute( await (conn or db).execute(
""" """
@ -466,8 +451,7 @@ async def save_balance_notify(
async def get_balance_notify( async def get_balance_notify(
wallet_id: str, wallet_id: str, conn: Optional[Connection] = None
conn: Optional[Connection] = None,
) -> Optional[str]: ) -> Optional[str]:
row = await (conn or db).fetchone( row = await (conn or db).fetchone(
""" """

View File

@ -30,13 +30,8 @@ class Wallet(BaseModel):
@property @property
def lnurlwithdraw_full(self) -> str: def lnurlwithdraw_full(self) -> str:
url = url_for( url = url_for("/withdraw", external=True, usr=self.user, wal=self.id)
"/withdraw",
external=True,
usr=self.user,
wal=self.id,
)
try: try:
return lnurl_encode(url) return lnurl_encode(url)
except: except:
@ -47,9 +42,7 @@ class Wallet(BaseModel):
linking_key = hmac.digest(hashing_key, domain.encode("utf-8"), "sha256") linking_key = hmac.digest(hashing_key, domain.encode("utf-8"), "sha256")
return SigningKey.from_string( return SigningKey.from_string(
linking_key, linking_key, curve=SECP256k1, hashfunc=hashlib.sha256
curve=SECP256k1,
hashfunc=hashlib.sha256,
) )
async def get_payment(self, payment_hash: str) -> Optional["Payment"]: async def get_payment(self, payment_hash: str) -> Optional["Payment"]:

View File

@ -147,9 +147,7 @@ async def pay_invoice(
# so the other side only has access to his new money when we are sure # so the other side only has access to his new money when we are sure
# the payer has enough to deduct from # the payer has enough to deduct from
await update_payment_status( await update_payment_status(
checking_id=internal_checking_id, checking_id=internal_checking_id, pending=False, conn=conn
pending=False,
conn=conn,
) )
# notify receiver asynchronously # notify receiver asynchronously
@ -213,10 +211,7 @@ async def redeem_lnurl_withdraw(
if wait_seconds: if wait_seconds:
await asyncio.sleep(wait_seconds) await asyncio.sleep(wait_seconds)
params = { params = {"k1": res["k1"], "pr": payment_request}
"k1": res["k1"],
"pr": payment_request,
}
try: try:
params["balanceNotify"] = url_for( params["balanceNotify"] = url_for(
@ -235,8 +230,7 @@ async def redeem_lnurl_withdraw(
async def perform_lnurlauth( async def perform_lnurlauth(
callback: str, callback: str, conn: Optional[Connection] = None
conn: Optional[Connection] = None,
) -> Optional[LnurlErrorResponse]: ) -> Optional[LnurlErrorResponse]:
cb = urlparse(callback) cb = urlparse(callback)
@ -304,14 +298,12 @@ async def perform_lnurlauth(
return LnurlErrorResponse(reason=resp["reason"]) return LnurlErrorResponse(reason=resp["reason"])
except (KeyError, json.decoder.JSONDecodeError): except (KeyError, json.decoder.JSONDecodeError):
return LnurlErrorResponse( return LnurlErrorResponse(
reason=r.text[:200] + "..." if len(r.text) > 200 else r.text, reason=r.text[:200] + "..." if len(r.text) > 200 else r.text
) )
async def check_invoice_status( async def check_invoice_status(
wallet_id: str, wallet_id: str, payment_hash: str, conn: Optional[Connection] = None
payment_hash: str,
conn: Optional[Connection] = None,
) -> PaymentStatus: ) -> PaymentStatus:
payment = await get_wallet_payment(wallet_id, payment_hash, conn=conn) payment = await get_wallet_payment(wallet_id, payment_hash, conn=conn)
if not payment: if not payment:

View File

@ -33,10 +33,7 @@ async def wait_for_paid_invoices(invoice_paid_queue: asyncio.Queue):
if url: if url:
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
try: try:
r = await client.post( r = await client.post(url, timeout=4)
url,
timeout=4,
)
await mark_webhook_sent(payment, r.status_code) await mark_webhook_sent(payment, r.status_code)
except (httpx.ConnectError, httpx.RequestError): except (httpx.ConnectError, httpx.RequestError):
pass pass
@ -55,11 +52,7 @@ async def dispatch_webhook(payment: Payment):
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
data = payment._asdict() data = payment._asdict()
try: try:
r = await client.post( r = await client.post(payment.webhook, json=data, timeout=40)
payment.webhook,
json=data,
timeout=40,
)
await mark_webhook_sent(payment, r.status_code) await mark_webhook_sent(payment, r.status_code)
except (httpx.ConnectError, httpx.RequestError): except (httpx.ConnectError, httpx.RequestError):
await mark_webhook_sent(payment, -1) await mark_webhook_sent(payment, -1)

View File

@ -16,53 +16,67 @@ from pydantic import BaseModel
from lnbits import bolt11, lnurl from lnbits import bolt11, lnurl
from lnbits.core.models import Payment, Wallet from lnbits.core.models import Payment, Wallet
from lnbits.decorators import (WalletAdminKeyChecker, WalletInvoiceKeyChecker, from lnbits.decorators import (
WalletTypeInfo, get_key_type) WalletAdminKeyChecker,
WalletInvoiceKeyChecker,
WalletTypeInfo,
get_key_type,
)
from lnbits.helpers import url_for from lnbits.helpers import url_for
from lnbits.requestvars import g from lnbits.requestvars import g
from lnbits.utils.exchange_rates import currencies, fiat_amount_as_satoshis from lnbits.utils.exchange_rates import currencies, fiat_amount_as_satoshis
from .. import core_app, db from .. import core_app, db
from ..crud import get_payments, save_balance_check, update_wallet from ..crud import get_payments, save_balance_check, update_wallet
from ..services import (InvoiceFailure, PaymentFailure, create_invoice, from ..services import (
pay_invoice, perform_lnurlauth) InvoiceFailure,
PaymentFailure,
create_invoice,
pay_invoice,
perform_lnurlauth,
)
from ..tasks import api_invoice_listeners from ..tasks import api_invoice_listeners
@core_app.get("/api/v1/wallet") @core_app.get("/api/v1/wallet")
async def api_wallet(wallet: WalletTypeInfo = Depends(get_key_type)): async def api_wallet(wallet: WalletTypeInfo = Depends(get_key_type)):
return {"id": wallet.wallet.id, "name": wallet.wallet.name, "balance": wallet.wallet.balance_msat} return {
"id": wallet.wallet.id,
"name": wallet.wallet.name,
"balance": wallet.wallet.balance_msat,
}
@core_app.put("/api/v1/wallet/{new_name}") @core_app.put("/api/v1/wallet/{new_name}")
async def api_update_wallet(new_name: str, wallet: WalletTypeInfo = Depends(get_key_type)): async def api_update_wallet(
new_name: str, wallet: WalletTypeInfo = Depends(get_key_type)
):
await update_wallet(wallet.wallet.id, new_name) await update_wallet(wallet.wallet.id, new_name)
return { return {
"id": wallet.wallet.id, "id": wallet.wallet.id,
"name": wallet.wallet.name, "name": wallet.wallet.name,
"balance": wallet.wallet.balance_msat, "balance": wallet.wallet.balance_msat,
} }
@core_app.get("/api/v1/payments") @core_app.get("/api/v1/payments")
async def api_payments(wallet: WalletTypeInfo = Depends(get_key_type)): async def api_payments(wallet: WalletTypeInfo = Depends(get_key_type)):
return await get_payments(wallet_id=wallet.wallet.id, pending=True, complete=True) return await get_payments(wallet_id=wallet.wallet.id, pending=True, complete=True)
class CreateInvoiceData(BaseModel): class CreateInvoiceData(BaseModel):
out: Optional[bool] = True out: Optional[bool] = True
amount: int = Query(None, ge=1) amount: int = Query(None, ge=1)
memo: str = None memo: str = None
unit: Optional[str] = None unit: Optional[str] = None
description_hash: str = None description_hash: str = None
lnurl_callback: Optional[str] = None lnurl_callback: Optional[str] = None
lnurl_balance_check: Optional[str] = None lnurl_balance_check: Optional[str] = None
extra: Optional[dict] = None extra: Optional[dict] = None
webhook: Optional[str] = None webhook: Optional[str] = None
bolt11: Optional[str] = None bolt11: Optional[str] = None
async def api_payments_create_invoice(data: CreateInvoiceData, wallet: Wallet): async def api_payments_create_invoice(data: CreateInvoiceData, wallet: Wallet):
if "description_hash" in data: if "description_hash" in data:
description_hash = unhexlify(data.description_hash) description_hash = unhexlify(data.description_hash)
@ -89,7 +103,7 @@ async def api_payments_create_invoice(data: CreateInvoiceData, wallet: Wallet):
conn=conn, conn=conn,
) )
except InvoiceFailure as e: except InvoiceFailure as e:
raise HTTPException(status_code=520, detail=str(e)) raise HTTPException(status_code=520, detail=str(e))
except Exception as exc: except Exception as exc:
raise exc raise exc
@ -132,31 +146,17 @@ async def api_payments_create_invoice(data: CreateInvoiceData, wallet: Wallet):
"checking_id": invoice.payment_hash, "checking_id": invoice.payment_hash,
"lnurl_response": lnurl_response, "lnurl_response": lnurl_response,
} }
async def api_payments_pay_invoice(bolt11: str, wallet: Wallet): async def api_payments_pay_invoice(bolt11: str, wallet: Wallet):
try: try:
payment_hash = await pay_invoice( payment_hash = await pay_invoice(wallet_id=wallet.id, payment_request=bolt11)
wallet_id=wallet.id,
payment_request=bolt11,
)
except ValueError as e: except ValueError as e:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e))
status_code=HTTPStatus.BAD_REQUEST,
detail=str(e)
)
except PermissionError as e: except PermissionError as e:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail=str(e))
status_code=HTTPStatus.FORBIDDEN,
detail=str(e)
)
except PaymentFailure as e: except PaymentFailure as e:
raise HTTPException( raise HTTPException(status_code=520, detail=str(e))
status_code=520,
detail=str(e)
)
except Exception as exc: except Exception as exc:
raise exc raise exc
@ -165,34 +165,46 @@ async def api_payments_pay_invoice(bolt11: str, wallet: Wallet):
# maintain backwards compatibility with API clients: # maintain backwards compatibility with API clients:
"checking_id": payment_hash, "checking_id": payment_hash,
} }
@core_app.post("/api/v1/payments", deprecated=True, @core_app.post(
description="DEPRECATED. Use /api/v2/TBD and /api/v2/TBD instead", "/api/v1/payments",
status_code=HTTPStatus.CREATED) deprecated=True,
async def api_payments_create(wallet: WalletTypeInfo = Depends(get_key_type), description="DEPRECATED. Use /api/v2/TBD and /api/v2/TBD instead",
invoiceData: CreateInvoiceData = Body(...)): status_code=HTTPStatus.CREATED,
)
async def api_payments_create(
wallet: WalletTypeInfo = Depends(get_key_type),
invoiceData: CreateInvoiceData = Body(...),
):
if wallet.wallet_type < 0 or wallet.wallet_type > 2: if wallet.wallet_type < 0 or wallet.wallet_type > 2:
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Key is invalid") raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Key is invalid")
if invoiceData.out is True and wallet.wallet_type == 0: if invoiceData.out is True and wallet.wallet_type == 0:
if not invoiceData.bolt11: if not invoiceData.bolt11:
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="BOLT11 string is invalid or not given") raise HTTPException(
return await api_payments_pay_invoice(invoiceData.bolt11, wallet.wallet) # admin key status_code=HTTPStatus.BAD_REQUEST,
return await api_payments_create_invoice(invoiceData, wallet.wallet) # invoice key detail="BOLT11 string is invalid or not given",
)
return await api_payments_pay_invoice(
invoiceData.bolt11, wallet.wallet
) # admin key
return await api_payments_create_invoice(invoiceData, wallet.wallet) # invoice key
class CreateLNURLData(BaseModel): class CreateLNURLData(BaseModel):
description_hash: str description_hash: str
callback: str callback: str
amount: int amount: int
comment: Optional[str] = None comment: Optional[str] = None
description: Optional[str] = None description: Optional[str] = None
@core_app.post("/api/v1/payments/lnurl") @core_app.post("/api/v1/payments/lnurl")
async def api_payments_pay_lnurl(data: CreateLNURLData, async def api_payments_pay_lnurl(
wallet: WalletTypeInfo = Depends(get_key_type)): data: CreateLNURLData, wallet: WalletTypeInfo = Depends(get_key_type)
):
domain = urlparse(data.callback).netloc domain = urlparse(data.callback).netloc
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
@ -207,30 +219,28 @@ async def api_payments_pay_lnurl(data: CreateLNURLData,
except (httpx.ConnectError, httpx.RequestError): except (httpx.ConnectError, httpx.RequestError):
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, status_code=HTTPStatus.BAD_REQUEST,
detail=f"Failed to connect to {domain}." detail=f"Failed to connect to {domain}.",
) )
params = json.loads(r.text) params = json.loads(r.text)
if params.get("status") == "ERROR": if params.get("status") == "ERROR":
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, status_code=HTTPStatus.BAD_REQUEST,
detail=f"{domain} said: '{params.get('reason', '')}'" detail=f"{domain} said: '{params.get('reason', '')}'",
) )
invoice = bolt11.decode(params["pr"]) invoice = bolt11.decode(params["pr"])
if invoice.amount_msat != data.amount: if invoice.amount_msat != data.amount:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, status_code=HTTPStatus.BAD_REQUEST,
detail=f"{domain} returned an invalid invoice. Expected {data['amount']} msat, got {invoice.amount_msat}." detail=f"{domain} returned an invalid invoice. Expected {data['amount']} msat, got {invoice.amount_msat}.",
) )
if invoice.description_hash != data.description_hash: if invoice.description_hash != data.description_hash:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, status_code=HTTPStatus.BAD_REQUEST,
detail=f"{domain} returned an invalid invoice. Expected description_hash == {data['description_hash']}, got {invoice.description_hash}." detail=f"{domain} returned an invalid invoice. Expected description_hash == {data['description_hash']}, got {invoice.description_hash}.",
) )
extra = {} extra = {}
@ -252,7 +262,7 @@ async def api_payments_pay_lnurl(data: CreateLNURLData,
# maintain backwards compatibility with API clients: # maintain backwards compatibility with API clients:
"checking_id": payment_hash, "checking_id": payment_hash,
} }
async def subscribe(request: Request, wallet: Wallet): async def subscribe(request: Request, wallet: Wallet):
this_wallet_id = wallet.wallet.id this_wallet_id = wallet.wallet.id
@ -278,7 +288,7 @@ async def subscribe(request: Request, wallet: Wallet):
if data: if data:
jdata = json.dumps(dict(data.dict(), pending=False)) jdata = json.dumps(dict(data.dict(), pending=False))
# yield dict(id=1, event="this", data="1234") # yield dict(id=1, event="this", data="1234")
# await asyncio.sleep(2) # await asyncio.sleep(2)
yield dict(data=jdata, event=typ) yield dict(data=jdata, event=typ)
@ -288,8 +298,12 @@ async def subscribe(request: Request, wallet: Wallet):
@core_app.get("/api/v1/payments/sse") @core_app.get("/api/v1/payments/sse")
async def api_payments_sse(request: Request, wallet: WalletTypeInfo = Depends(get_key_type)): async def api_payments_sse(
return EventSourceResponse(subscribe(request, wallet), ping=20, media_type="text/event-stream") request: Request, wallet: WalletTypeInfo = Depends(get_key_type)
):
return EventSourceResponse(
subscribe(request, wallet), ping=20, media_type="text/event-stream"
)
@core_app.get("/api/v1/payments/{payment_hash}") @core_app.get("/api/v1/payments/{payment_hash}")
@ -307,9 +321,11 @@ async def api_payment(payment_hash, wallet: WalletTypeInfo = Depends(get_key_typ
return {"paid": False} return {"paid": False}
return {"paid": not payment.pending, "preimage": payment.preimage} return {"paid": not payment.pending, "preimage": payment.preimage}
@core_app.get("/api/v1/lnurlscan/{code}", dependencies=[Depends(WalletInvoiceKeyChecker())])
@core_app.get(
"/api/v1/lnurlscan/{code}", dependencies=[Depends(WalletInvoiceKeyChecker())]
)
async def api_lnurlscan(code: str): async def api_lnurlscan(code: str):
try: try:
url = lnurl.decode(code) url = lnurl.decode(code)
@ -327,7 +343,9 @@ async def api_lnurlscan(code: str):
) )
# will proceed with these values # will proceed with these values
else: else:
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="invalid lnurl") raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail="invalid lnurl"
)
# params is what will be returned to the client # params is what will be returned to the client
params: Dict = {"domain": domain} params: Dict = {"domain": domain}
@ -343,24 +361,31 @@ async def api_lnurlscan(code: str):
r = await client.get(url, timeout=5) r = await client.get(url, timeout=5)
if r.is_error: if r.is_error:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.SERVICE_UNAVAILABLE, status_code=HTTPStatus.SERVICE_UNAVAILABLE,
detail={"domain": domain, "message": "failed to get parameters"} detail={"domain": domain, "message": "failed to get parameters"},
) )
try: try:
data = json.loads(r.text) data = json.loads(r.text)
except json.decoder.JSONDecodeError: except json.decoder.JSONDecodeError:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.SERVICE_UNAVAILABLE, status_code=HTTPStatus.SERVICE_UNAVAILABLE,
detail={"domain": domain, "message": f"got invalid response '{r.text[:200]}'"} detail={
"domain": domain,
"message": f"got invalid response '{r.text[:200]}'",
},
) )
try: try:
tag = data["tag"] tag = data["tag"]
if tag == "channelRequest": if tag == "channelRequest":
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, status_code=HTTPStatus.BAD_REQUEST,
detail={"domain": domain, "kind": "channel", "message": "unsupported"} detail={
"domain": domain,
"kind": "channel",
"message": "unsupported",
},
) )
params.update(**data) params.update(**data)
@ -410,8 +435,9 @@ async def api_lnurlscan(code: str):
detail={ detail={
"domain": domain, "domain": domain,
"message": f"lnurl JSON response invalid: {exc}", "message": f"lnurl JSON response invalid: {exc}",
}) },
)
return params return params
@ -419,8 +445,10 @@ async def api_lnurlscan(code: str):
async def api_perform_lnurlauth(callback: str): async def api_perform_lnurlauth(callback: str):
err = await perform_lnurlauth(callback) err = await perform_lnurlauth(callback)
if err: if err:
raise HTTPException(status_code=HTTPStatus.SERVICE_UNAVAILABLE, detail=err.reason) raise HTTPException(
status_code=HTTPStatus.SERVICE_UNAVAILABLE, detail=err.reason
)
return "" return ""

View File

@ -17,38 +17,48 @@ from lnbits.helpers import template_renderer, url_for
from lnbits.requestvars import g from lnbits.requestvars import g
from lnbits.core.models import User from lnbits.core.models import User
from lnbits.decorators import check_user_exists from lnbits.decorators import check_user_exists
from lnbits.settings import (LNBITS_ALLOWED_USERS, LNBITS_SITE_TITLE, from lnbits.settings import LNBITS_ALLOWED_USERS, LNBITS_SITE_TITLE, SERVICE_FEE
SERVICE_FEE)
from ..crud import (create_account, create_wallet, delete_wallet, from ..crud import (
get_balance_check, get_user, save_balance_notify, create_account,
update_user_extension) create_wallet,
delete_wallet,
get_balance_check,
get_user,
save_balance_notify,
update_user_extension,
)
from ..services import pay_invoice, redeem_lnurl_withdraw from ..services import pay_invoice, redeem_lnurl_withdraw
core_html_routes: APIRouter = APIRouter(tags=["Core NON-API Website Routes"]) core_html_routes: APIRouter = APIRouter(tags=["Core NON-API Website Routes"])
@core_html_routes.get("/favicon.ico") @core_html_routes.get("/favicon.ico")
async def favicon(): async def favicon():
return FileResponse("lnbits/core/static/favicon.ico") return FileResponse("lnbits/core/static/favicon.ico")
@core_html_routes.get("/", response_class=HTMLResponse) @core_html_routes.get("/", response_class=HTMLResponse)
async def home(request: Request, lightning: str = None): async def home(request: Request, lightning: str = None):
return template_renderer().TemplateResponse("core/index.html", {"request": request, "lnurl": lightning}) return template_renderer().TemplateResponse(
"core/index.html", {"request": request, "lnurl": lightning}
)
@core_html_routes.get("/extensions", name="core.extensions") @core_html_routes.get("/extensions", name="core.extensions")
async def extensions( async def extensions(
request: Request, request: Request,
user: User = Depends(check_user_exists), user: User = Depends(check_user_exists),
enable: str= Query(None), enable: str = Query(None),
disable: str = Query(None) disable: str = Query(None),
): ):
extension_to_enable = enable extension_to_enable = enable
extension_to_disable = disable extension_to_disable = disable
if extension_to_enable and extension_to_disable: if extension_to_enable and extension_to_disable:
raise HTTPException(HTTPStatus.BAD_REQUEST, "You can either `enable` or `disable` an extension.") raise HTTPException(
HTTPStatus.BAD_REQUEST, "You can either `enable` or `disable` an extension."
)
if extension_to_enable: if extension_to_enable:
await update_user_extension( await update_user_extension(
@ -63,14 +73,20 @@ async def extensions(
if extension_to_enable or extension_to_disable: if extension_to_enable or extension_to_disable:
user = await get_user(user.id) user = await get_user(user.id)
return template_renderer().TemplateResponse("core/extensions.html", {"request": request, "user": user.dict()}) return template_renderer().TemplateResponse(
"core/extensions.html", {"request": request, "user": user.dict()}
)
@core_html_routes.get("/wallet", response_class=HTMLResponse) @core_html_routes.get("/wallet", response_class=HTMLResponse)
#Not sure how to validate # Not sure how to validate
# @validate_uuids(["usr", "nme"]) # @validate_uuids(["usr", "nme"])
async def wallet(request: Request = Query(None), nme: Optional[str] = Query(None), async def wallet(
usr: Optional[UUID4] = Query(None), wal: Optional[UUID4] = Query(None)): request: Request = Query(None),
nme: Optional[str] = Query(None),
usr: Optional[UUID4] = Query(None),
wal: Optional[UUID4] = Query(None),
):
user_id = usr.hex if usr else None user_id = usr.hex if usr else None
wallet_id = wal.hex if wal else None wallet_id = wal.hex if wal else None
wallet_name = nme wallet_name = nme
@ -87,23 +103,38 @@ async def wallet(request: Request = Query(None), nme: Optional[str] = Query(None
else: else:
user = await get_user(user_id) user = await get_user(user_id)
if not user: if not user:
return template_renderer().TemplateResponse("error.html", {"request": request, "err": "User does not exist."}) return template_renderer().TemplateResponse(
"error.html", {"request": request, "err": "User does not exist."}
)
if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS: if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS:
return template_renderer().TemplateResponse("error.html", {"request": request, "err": "User not authorized."}) return template_renderer().TemplateResponse(
"error.html", {"request": request, "err": "User not authorized."}
)
if not wallet_id: if not wallet_id:
if user.wallets and not wallet_name: if user.wallets and not wallet_name:
wallet = user.wallets[0] wallet = user.wallets[0]
else: else:
wallet = await create_wallet(user_id=user.id, wallet_name=wallet_name) wallet = await create_wallet(user_id=user.id, wallet_name=wallet_name)
return RedirectResponse(f"/wallet?usr={user.id}&wal={wallet.id}", status_code=status.HTTP_307_TEMPORARY_REDIRECT) return RedirectResponse(
f"/wallet?usr={user.id}&wal={wallet.id}",
status_code=status.HTTP_307_TEMPORARY_REDIRECT,
)
wallet = user.get_wallet(wallet_id) wallet = user.get_wallet(wallet_id)
if not wallet: if not wallet:
return template_renderer().TemplateResponse("error.html", {"request": request, "err": "Wallet not found"}) return template_renderer().TemplateResponse(
"error.html", {"request": request, "err": "Wallet not found"}
)
return template_renderer().TemplateResponse( return template_renderer().TemplateResponse(
"core/wallet.html", {"request":request,"user":user.dict(), "wallet":wallet.dict(), "service_fee":service_fee} "core/wallet.html",
{
"request": request,
"user": user.dict(),
"wallet": wallet.dict(),
"service_fee": service_fee,
},
) )
@ -116,22 +147,17 @@ async def lnurl_full_withdraw(request: Request):
wallet = user.get_wallet(request.args.get("wal")) wallet = user.get_wallet(request.args.get("wal"))
if not wallet: if not wallet:
return{"status": "ERROR", "reason": "Wallet does not exist."} return {"status": "ERROR", "reason": "Wallet does not exist."}
return { return {
"tag": "withdrawRequest", "tag": "withdrawRequest",
"callback": url_for( "callback": url_for("/withdraw/cb", external=True, usr=user.id, wal=wallet.id),
"/withdraw/cb", "k1": "0",
external=True, "minWithdrawable": 1000 if wallet.withdrawable_balance else 0,
usr=user.id, "maxWithdrawable": wallet.withdrawable_balance,
wal=wallet.id, "defaultDescription": f"{LNBITS_SITE_TITLE} balance withdraw from {wallet.id[0:5]}",
), "balanceCheck": url_for("/withdraw", external=True, usr=user.id, wal=wallet.id),
"k1": "0", }
"minWithdrawable": 1000 if wallet.withdrawable_balance else 0,
"maxWithdrawable": wallet.withdrawable_balance,
"defaultDescription": f"{LNBITS_SITE_TITLE} balance withdraw from {wallet.id[0:5]}",
"balanceCheck": url_for("/withdraw", external=True, usr=user.id, wal=wallet.id),
}
@core_html_routes.get("/withdraw/cb") @core_html_routes.get("/withdraw/cb")
@ -176,10 +202,14 @@ async def deletewallet(request: Request):
user_wallet_ids.remove(wallet_id) user_wallet_ids.remove(wallet_id)
if user_wallet_ids: if user_wallet_ids:
return RedirectResponse(url_for("/wallet", usr=g().user.id, wal=user_wallet_ids[0]), return RedirectResponse(
status_code=status.HTTP_307_TEMPORARY_REDIRECT) url_for("/wallet", usr=g().user.id, wal=user_wallet_ids[0]),
status_code=status.HTTP_307_TEMPORARY_REDIRECT,
)
return RedirectResponse(url_for("/"), status_code=status.HTTP_307_TEMPORARY_REDIRECT) return RedirectResponse(
url_for("/"), status_code=status.HTTP_307_TEMPORARY_REDIRECT
)
@core_html_routes.get("/withdraw/notify/{service}") @core_html_routes.get("/withdraw/notify/{service}")
@ -203,11 +233,14 @@ async def lnurlwallet(request: Request):
request.args.get("lightning"), request.args.get("lightning"),
"LNbits initial funding: voucher redeem.", "LNbits initial funding: voucher redeem.",
{"tag": "lnurlwallet"}, {"tag": "lnurlwallet"},
5 # wait 5 seconds before sending the invoice to the service 5, # wait 5 seconds before sending the invoice to the service
) )
) )
return RedirectResponse(f"/wallet?usr={user.id}&wal={wallet.id}", status_code=status.HTTP_307_TEMPORARY_REDIRECT) return RedirectResponse(
f"/wallet?usr={user.id}&wal={wallet.id}",
status_code=status.HTTP_307_TEMPORARY_REDIRECT,
)
@core_html_routes.get("/manifest/{usr}.webmanifest") @core_html_routes.get("/manifest/{usr}.webmanifest")
@ -217,27 +250,28 @@ async def manifest(usr: str):
raise HTTPException(status_code=HTTPStatus.NOT_FOUND) raise HTTPException(status_code=HTTPStatus.NOT_FOUND)
return { return {
"short_name": "LNbits", "short_name": "LNbits",
"name": "LNbits Wallet", "name": "LNbits Wallet",
"icons": [ "icons": [
{ {
"src": "https://cdn.jsdelivr.net/gh/lnbits/lnbits@0.3.0/docs/logos/lnbits.png", "src": "https://cdn.jsdelivr.net/gh/lnbits/lnbits@0.3.0/docs/logos/lnbits.png",
"type": "image/png", "type": "image/png",
"sizes": "900x900", "sizes": "900x900",
} }
], ],
"start_url": "/wallet?usr=" + usr, "start_url": "/wallet?usr=" + usr,
"background_color": "#3367D6", "background_color": "#3367D6",
"description": "Weather forecast information", "description": "Weather forecast information",
"display": "standalone", "display": "standalone",
"scope": "/", "scope": "/",
"theme_color": "#3367D6", "theme_color": "#3367D6",
"shortcuts": [ "shortcuts": [
{ {
"name": wallet.name, "name": wallet.name,
"short_name": wallet.name, "short_name": wallet.name,
"description": wallet.name, "description": wallet.name,
"url": "/wallet?usr=" + usr + "&wal=" + wallet.id, "url": "/wallet?usr=" + usr + "&wal=" + wallet.id,
} }
for wallet in user.wallets for wallet in user.wallets
],} ],
}

View File

@ -15,8 +15,7 @@ async def api_public_payment_longpolling(payment_hash):
if not payment: if not payment:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Payment does not exist."
detail="Payment does not exist."
) )
elif not payment.pending: elif not payment.pending:
return {"status": "paid"} return {"status": "paid"}
@ -28,8 +27,7 @@ async def api_public_payment_longpolling(payment_hash):
return {"status": "expired"} return {"status": "expired"}
except: except:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, status_code=HTTPStatus.BAD_REQUEST, detail="Invalid bolt11 invoice."
detail="Invalid bolt11 invoice."
) )
payment_queue = asyncio.Queue(0) payment_queue = asyncio.Queue(0)
@ -50,14 +48,10 @@ async def api_public_payment_longpolling(payment_hash):
await asyncio.sleep(45) await asyncio.sleep(45)
cancel_scope.cancel() cancel_scope.cancel()
asyncio.create_task(payment_info_receiver()) asyncio.create_task(payment_info_receiver())
asyncio.create_task(timeouter()) asyncio.create_task(timeouter())
if response: if response:
return response return response
else: else:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.REQUEST_TIMEOUT, detail="timeout")
status_code=HTTPStatus.REQUEST_TIMEOUT,
detail="timeout"
)

View File

@ -24,34 +24,49 @@ from lnbits.settings import LNBITS_ALLOWED_USERS
class KeyChecker(SecurityBase): class KeyChecker(SecurityBase):
def __init__(self, scheme_name: str = None, auto_error: bool = True, api_key: str = None): def __init__(
self, scheme_name: str = None, auto_error: bool = True, api_key: str = None
):
self.scheme_name = scheme_name or self.__class__.__name__ self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error self.auto_error = auto_error
self._key_type = "invoice" self._key_type = "invoice"
self._api_key = api_key self._api_key = api_key
if api_key: if api_key:
self.model: APIKey= APIKey( self.model: APIKey = APIKey(
**{"in": APIKeyIn.query}, name="X-API-KEY", description="Wallet API Key - QUERY" **{"in": APIKeyIn.query},
name="X-API-KEY",
description="Wallet API Key - QUERY",
) )
else: else:
self.model: APIKey= APIKey( self.model: APIKey = APIKey(
**{"in": APIKeyIn.header}, name="X-API-KEY", description="Wallet API Key - HEADER" **{"in": APIKeyIn.header},
name="X-API-KEY",
description="Wallet API Key - HEADER",
) )
self.wallet = None self.wallet = None
async def __call__(self, request: Request) -> Wallet: async def __call__(self, request: Request) -> Wallet:
try: try:
key_value = self._api_key if self._api_key else request.headers.get("X-API-KEY") or request.query_params["api-key"] key_value = (
self._api_key
if self._api_key
else request.headers.get("X-API-KEY") or request.query_params["api-key"]
)
# FIXME: Find another way to validate the key. A fetch from DB should be avoided here. # FIXME: Find another way to validate the key. A fetch from DB should be avoided here.
# Also, we should not return the wallet here - thats silly. # Also, we should not return the wallet here - thats silly.
# Possibly store it in a Redis DB # Possibly store it in a Redis DB
self.wallet = await get_wallet_for_key(key_value, self._key_type) self.wallet = await get_wallet_for_key(key_value, self._key_type)
if not self.wallet: if not self.wallet:
raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="Invalid key or expired key.") raise HTTPException(
status_code=HTTPStatus.UNAUTHORIZED,
detail="Invalid key or expired key.",
)
except KeyError: except KeyError:
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, raise HTTPException(
detail="`X-API-KEY` header missing.") status_code=HTTPStatus.BAD_REQUEST, detail="`X-API-KEY` header missing."
)
class WalletInvoiceKeyChecker(KeyChecker): class WalletInvoiceKeyChecker(KeyChecker):
""" """
@ -61,10 +76,14 @@ class WalletInvoiceKeyChecker(KeyChecker):
The checker will raise an HTTPException when the key is wrong in some ways. The checker will raise an HTTPException when the key is wrong in some ways.
""" """
def __init__(self, scheme_name: str = None, auto_error: bool = True, api_key: str = None):
def __init__(
self, scheme_name: str = None, auto_error: bool = True, api_key: str = None
):
super().__init__(scheme_name, auto_error, api_key) super().__init__(scheme_name, auto_error, api_key)
self._key_type = "invoice" self._key_type = "invoice"
class WalletAdminKeyChecker(KeyChecker): class WalletAdminKeyChecker(KeyChecker):
""" """
WalletAdminKeyChecker will ensure that the provided admin WalletAdminKeyChecker will ensure that the provided admin
@ -73,11 +92,15 @@ class WalletAdminKeyChecker(KeyChecker):
The checker will raise an HTTPException when the key is wrong in some ways. The checker will raise an HTTPException when the key is wrong in some ways.
""" """
def __init__(self, scheme_name: str = None, auto_error: bool = True, api_key: str = None):
def __init__(
self, scheme_name: str = None, auto_error: bool = True, api_key: str = None
):
super().__init__(scheme_name, auto_error, api_key) super().__init__(scheme_name, auto_error, api_key)
self._key_type = "admin" self._key_type = "admin"
class WalletTypeInfo():
class WalletTypeInfo:
wallet_type: int wallet_type: int
wallet: Wallet wallet: Wallet
@ -85,20 +108,33 @@ class WalletTypeInfo():
self.wallet_type = wallet_type self.wallet_type = wallet_type
self.wallet = wallet self.wallet = wallet
api_key_header = APIKeyHeader(name="X-API-KEY", auto_error=False, description="Admin or Invoice key for wallet API's")
api_key_query = APIKeyQuery(name="api-key", auto_error=False, description="Admin or Invoice key for wallet API's") api_key_header = APIKeyHeader(
async def get_key_type(r: Request, name="X-API-KEY",
api_key_header: str = Security(api_key_header), auto_error=False,
api_key_query: str = Security(api_key_query)) -> WalletTypeInfo: description="Admin or Invoice key for wallet API's",
)
api_key_query = APIKeyQuery(
name="api-key",
auto_error=False,
description="Admin or Invoice key for wallet API's",
)
async def get_key_type(
r: Request,
api_key_header: str = Security(api_key_header),
api_key_query: str = Security(api_key_query),
) -> WalletTypeInfo:
# 0: admin # 0: admin
# 1: invoice # 1: invoice
# 2: invalid # 2: invalid
if not api_key_header and not api_key_query: if not api_key_header and not api_key_query:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST) raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
token = api_key_header if api_key_header else api_key_query token = api_key_header if api_key_header else api_key_query
try: try:
checker = WalletAdminKeyChecker(api_key=token) checker = WalletAdminKeyChecker(api_key=token)
await checker.__call__(r) await checker.__call__(r)
@ -122,6 +158,8 @@ async def get_key_type(r: Request,
return WalletTypeInfo(2, None) return WalletTypeInfo(2, None)
except: except:
raise raise
# api_key_header = APIKeyHeader(name="X-API-KEY", auto_error=False, description="Admin or Invoice key for wallet API's") # api_key_header = APIKeyHeader(name="X-API-KEY", auto_error=False, description="Admin or Invoice key for wallet API's")
# api_key_query = APIKeyQuery(name="api-key", auto_error=False, description="Admin or Invoice key for wallet API's") # api_key_query = APIKeyQuery(name="api-key", auto_error=False, description="Admin or Invoice key for wallet API's")
# oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") # oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@ -159,6 +197,7 @@ async def get_key_type(r: Request,
# except: # except:
# raise # raise
def api_validate_post_request(*, schema: dict): def api_validate_post_request(*, schema: dict):
def wrap(view): def wrap(view):
@wraps(view) @wraps(view)
@ -166,7 +205,9 @@ def api_validate_post_request(*, schema: dict):
if "application/json" not in request.headers["Content-Type"]: if "application/json" not in request.headers["Content-Type"]:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, status_code=HTTPStatus.BAD_REQUEST,
detail=jsonify({"message": "Content-Type must be `application/json`."}) detail=jsonify(
{"message": "Content-Type must be `application/json`."}
),
) )
v = Validator(schema) v = Validator(schema)
@ -176,10 +217,9 @@ def api_validate_post_request(*, schema: dict):
if not v.validate(g().data): if not v.validate(g().data):
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, status_code=HTTPStatus.BAD_REQUEST,
detail=jsonify({"message": f"Errors in request data: {v.errors}"}) detail=jsonify({"message": f"Errors in request data: {v.errors}"}),
) )
return await view(**kwargs) return await view(**kwargs)
return wrapped_view return wrapped_view
@ -191,14 +231,12 @@ async def check_user_exists(usr: UUID4) -> User:
g().user = await get_user(usr.hex) g().user = await get_user(usr.hex)
if not g().user: if not g().user:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="User does not exist."
detail="User does not exist."
) )
if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS: if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.UNAUTHORIZED, status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
detail="User not authorized."
) )
return g().user return g().user

View File

@ -19,11 +19,7 @@ copilot_ext: APIRouter = APIRouter(prefix="/copilot", tags=["copilot"])
def copilot_renderer(): def copilot_renderer():
return template_renderer( return template_renderer(["lnbits/extensions/copilot/templates"])
[
"lnbits/extensions/copilot/templates",
]
)
from .views_api import * # noqa from .views_api import * # noqa

View File

@ -8,7 +8,11 @@ from http import HTTPStatus
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from starlette.responses import HTMLResponse, JSONResponse # type: ignore from starlette.responses import HTMLResponse, JSONResponse # type: ignore
import base64 import base64
from lnurl import LnurlPayResponse, LnurlPayActionResponse, LnurlErrorResponse # type: ignore from lnurl import (
LnurlPayResponse,
LnurlPayActionResponse,
LnurlErrorResponse,
) # type: ignore
from lnurl.types import LnurlPayMetadata from lnurl.types import LnurlPayMetadata
from lnbits.core.services import create_invoice from lnbits.core.services import create_invoice
from .models import Copilots, CreateCopilotData from .models import Copilots, CreateCopilotData
@ -24,8 +28,7 @@ async def lnurl_response(req: Request, cp_id: str = Query(None)):
cp = await get_copilot(cp_id) cp = await get_copilot(cp_id)
if not cp: if not cp:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Copilot not found"
detail="Copilot not found",
) )
resp = LnurlPayResponse( resp = LnurlPayResponse(
@ -49,8 +52,7 @@ async def lnurl_callback(
cp = await get_copilot(cp_id) cp = await get_copilot(cp_id)
if not cp: if not cp:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Copilot not found"
detail="Copilot not found",
) )
amount_received = int(amount) amount_received = int(amount)
@ -84,9 +86,6 @@ async def lnurl_callback(
extra={"tag": "copilot", "copilot": cp.id, "comment": comment}, extra={"tag": "copilot", "copilot": cp.id, "comment": comment},
) )
resp = LnurlPayActionResponse( resp = LnurlPayActionResponse(
pr=payment_request, pr=payment_request, success_action=None, disposable=False, routes=[]
success_action=None,
disposable=False,
routes=[],
) )
return resp.dict() return resp.dict()

View File

@ -38,8 +38,7 @@ async def on_invoice_paid(payment: Payment) -> None:
if not copilot: if not copilot:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Copilot does not exist"
detail="Copilot does not exist",
) )
if copilot.animation1threshold: if copilot.animation1threshold:
if int(payment.amount / 1000) >= copilot.animation1threshold: if int(payment.amount / 1000) >= copilot.animation1threshold:

View File

@ -44,10 +44,7 @@ async def api_copilots_retrieve(
try: try:
return copilots return copilots
except: except:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.NO_CONTENT, detail="No copilots")
status_code=HTTPStatus.NO_CONTENT,
detail="No copilots",
)
@copilot_ext.get("/api/v1/copilot/{copilot_id}") @copilot_ext.get("/api/v1/copilot/{copilot_id}")
@ -57,8 +54,7 @@ async def api_copilot_retrieve(
copilot = await get_copilot(copilot_id) copilot = await get_copilot(copilot_id)
if not copilot: if not copilot:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Copilot not found"
detail="Copilot not found",
) )
if not copilot.lnurl_toggle: if not copilot.lnurl_toggle:
return copilot.dict() return copilot.dict()
@ -83,15 +79,13 @@ async def api_copilot_create_or_update(
@copilot_ext.delete("/api/v1/copilot/{copilot_id}") @copilot_ext.delete("/api/v1/copilot/{copilot_id}")
async def api_copilot_delete( async def api_copilot_delete(
copilot_id: str = Query(None), copilot_id: str = Query(None), wallet: WalletTypeInfo = Depends(get_key_type)
wallet: WalletTypeInfo = Depends(get_key_type),
): ):
copilot = await get_copilot(copilot_id) copilot = await get_copilot(copilot_id)
if not copilot: if not copilot:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Copilot does not exist"
detail="Copilot does not exist",
) )
await delete_copilot(copilot_id) await delete_copilot(copilot_id)
@ -101,22 +95,16 @@ async def api_copilot_delete(
@copilot_ext.get("/api/v1/copilot/ws/{copilot_id}/{comment}/{data}") @copilot_ext.get("/api/v1/copilot/ws/{copilot_id}/{comment}/{data}")
async def api_copilot_ws_relay( async def api_copilot_ws_relay(
copilot_id: str = Query(None), copilot_id: str = Query(None), comment: str = Query(None), data: str = Query(None)
comment: str = Query(None),
data: str = Query(None),
): ):
copilot = await get_copilot(copilot_id) copilot = await get_copilot(copilot_id)
print(copilot) print(copilot)
if not copilot: if not copilot:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Copilot does not exist"
detail="Copilot does not exist",
) )
try: try:
await updater(copilot_id, data, comment) await updater(copilot_id, data, comment)
except: except:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your copilot")
status_code=HTTPStatus.FORBIDDEN,
detail="Not your copilot",
)
return "" return ""

View File

@ -20,11 +20,7 @@ jukebox_ext: APIRouter = APIRouter(prefix="/jukebox", tags=["jukebox"])
def jukebox_renderer(): def jukebox_renderer():
return template_renderer( return template_renderer(["lnbits/extensions/jukebox/templates"])
[
"lnbits/extensions/jukebox/templates",
]
)
from .views_api import * # noqa from .views_api import * # noqa

View File

@ -6,8 +6,7 @@ from lnbits.helpers import urlsafe_short_hash
async def create_jukebox( async def create_jukebox(
data: CreateJukeLinkData, data: CreateJukeLinkData, inkey: Optional[str] = ""
inkey: Optional[str] = "",
) -> Jukebox: ) -> Jukebox:
juke_id = urlsafe_short_hash() juke_id = urlsafe_short_hash()
result = await db.execute( result = await db.execute(
@ -87,12 +86,7 @@ async def create_jukebox_payment(data: CreateJukeboxPayment) -> JukeboxPayment:
INSERT INTO jukebox.jukebox_payment (payment_hash, juke_id, song_id, paid) INSERT INTO jukebox.jukebox_payment (payment_hash, juke_id, song_id, paid)
VALUES (?, ?, ?, ?) VALUES (?, ?, ?, ?)
""", """,
( (data.payment_hash, data.juke_id, data.song_id, False),
data.payment_hash,
data.juke_id,
data.song_id,
False,
),
) )
jukebox_payment = await get_jukebox_payment(data.payment_hash) jukebox_payment = await get_jukebox_payment(data.payment_hash)
assert jukebox_payment, "Newly created Jukebox Payment couldn't be retrieved" assert jukebox_payment, "Newly created Jukebox Payment couldn't be retrieved"

View File

@ -44,10 +44,7 @@ async def api_get_jukeboxs(
return jukeboxs return jukeboxs
except: except:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.NO_CONTENT, detail="No Jukeboxes")
status_code=HTTPStatus.NO_CONTENT,
detail="No Jukeboxes",
)
##################SPOTIFY AUTH##################### ##################SPOTIFY AUTH#####################
@ -102,18 +99,12 @@ async def api_create_update_jukebox(
@jukebox_ext.delete("/api/v1/jukebox/{juke_id}") @jukebox_ext.delete("/api/v1/jukebox/{juke_id}")
async def api_delete_item( async def api_delete_item(juke_id=None, wallet: WalletTypeInfo = Depends(get_key_type)):
juke_id=None,
wallet: WalletTypeInfo = Depends(get_key_type),
):
await delete_jukebox(juke_id) await delete_jukebox(juke_id)
try: try:
return [{**jukebox} for jukebox in await get_jukeboxs(wallet.wallet.user)] return [{**jukebox} for jukebox in await get_jukeboxs(wallet.wallet.user)]
except: except:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.NO_CONTENT, detail="No Jukebox")
status_code=HTTPStatus.NO_CONTENT,
detail="No Jukebox",
)
################JUKEBOX ENDPOINTS################## ################JUKEBOX ENDPOINTS##################
@ -130,10 +121,7 @@ async def api_get_jukebox_song(
try: try:
jukebox = await get_jukebox(juke_id) jukebox = await get_jukebox(juke_id)
except: except:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="No Jukeboxes")
status_code=HTTPStatus.FORBIDDEN,
detail="No Jukeboxes",
)
tracks = [] tracks = []
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
try: try:
@ -176,10 +164,7 @@ async def api_get_token(juke_id=None):
try: try:
jukebox = await get_jukebox(juke_id) jukebox = await get_jukebox(juke_id)
except: except:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="No Jukeboxes")
status_code=HTTPStatus.FORBIDDEN,
detail="No Jukeboxes",
)
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
try: try:
@ -215,16 +200,12 @@ async def api_get_token(juke_id=None):
@jukebox_ext.get("/api/v1/jukebox/jb/{juke_id}") @jukebox_ext.get("/api/v1/jukebox/jb/{juke_id}")
async def api_get_jukebox_device_check( async def api_get_jukebox_device_check(
juke_id: str = Query(None), juke_id: str = Query(None), retry: bool = Query(False)
retry: bool = Query(False),
): ):
try: try:
jukebox = await get_jukebox(juke_id) jukebox = await get_jukebox(juke_id)
except: except:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="No Jukeboxes")
status_code=HTTPStatus.FORBIDDEN,
detail="No Jukeboxes",
)
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
rDevice = await client.get( rDevice = await client.get(
"https://api.spotify.com/v1/me/player/devices", "https://api.spotify.com/v1/me/player/devices",
@ -237,20 +218,17 @@ async def api_get_jukebox_device_check(
token = await api_get_token(juke_id) token = await api_get_token(juke_id)
if token == False: if token == False:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, status_code=HTTPStatus.FORBIDDEN, detail="No devices connected"
detail="No devices connected",
) )
elif retry: elif retry:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, status_code=HTTPStatus.FORBIDDEN, detail="Failed to get auth"
detail="Failed to get auth",
) )
else: else:
return api_get_jukebox_device_check(juke_id, retry=True) return api_get_jukebox_device_check(juke_id, retry=True)
else: else:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, status_code=HTTPStatus.FORBIDDEN, detail="No device connected"
detail="No device connected",
) )
@ -263,10 +241,7 @@ async def api_get_jukebox_invoice(juke_id, song_id):
jukebox = await get_jukebox(juke_id) jukebox = await get_jukebox(juke_id)
print(jukebox) print(jukebox)
except: except:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="No jukebox")
status_code=HTTPStatus.FORBIDDEN,
detail="No jukebox",
)
try: try:
devices = await api_get_jukebox_device_check(juke_id) devices = await api_get_jukebox_device_check(juke_id)
@ -276,13 +251,11 @@ async def api_get_jukebox_invoice(juke_id, song_id):
deviceConnected = True deviceConnected = True
if not deviceConnected: if not deviceConnected:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="No device connected"
detail="No device connected",
) )
except: except:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="No device connected"
detail="No device connected",
) )
invoice = await create_invoice( invoice = await create_invoice(
@ -304,16 +277,12 @@ async def api_get_jukebox_invoice(juke_id, song_id):
@jukebox_ext.get("/api/v1/jukebox/jb/checkinvoice/{pay_hash}/{juke_id}") @jukebox_ext.get("/api/v1/jukebox/jb/checkinvoice/{pay_hash}/{juke_id}")
async def api_get_jukebox_invoice_check( async def api_get_jukebox_invoice_check(
pay_hash: str = Query(None), pay_hash: str = Query(None), juke_id: str = Query(None)
juke_id: str = Query(None),
): ):
try: try:
jukebox = await get_jukebox(juke_id) jukebox = await get_jukebox(juke_id)
except: except:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="No jukebox")
status_code=HTTPStatus.FORBIDDEN,
detail="No jukebox",
)
try: try:
status = await check_invoice_status(jukebox.wallet, pay_hash) status = await check_invoice_status(jukebox.wallet, pay_hash)
is_paid = not status.pending is_paid = not status.pending
@ -338,10 +307,7 @@ async def api_get_jukebox_invoice_paid(
try: try:
jukebox = await get_jukebox(juke_id) jukebox = await get_jukebox(juke_id)
except: except:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="No jukebox")
status_code=HTTPStatus.FORBIDDEN,
detail="No jukebox",
)
await api_get_jukebox_invoice_check(pay_hash, juke_id) await api_get_jukebox_invoice_check(pay_hash, juke_id)
jukebox_payment = await get_jukebox_payment(pay_hash) jukebox_payment = await get_jukebox_payment(pay_hash)
if jukebox_payment.paid: if jukebox_payment.paid:
@ -390,8 +356,7 @@ async def api_get_jukebox_invoice_paid(
) )
else: else:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, status_code=HTTPStatus.FORBIDDEN, detail="Invoice not paid"
detail="Invoice not paid",
) )
elif r.status_code == 200: elif r.status_code == 200:
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
@ -424,29 +389,23 @@ async def api_get_jukebox_invoice_paid(
) )
else: else:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.OK, status_code=HTTPStatus.OK, detail="Invoice not paid"
detail="Invoice not paid",
) )
elif r.status_code == 401 or r.status_code == 403: elif r.status_code == 401 or r.status_code == 403:
token = await api_get_token(juke_id) token = await api_get_token(juke_id)
if token == False: if token == False:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.OK, status_code=HTTPStatus.OK, detail="Invoice not paid"
detail="Invoice not paid",
) )
elif retry: elif retry:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, status_code=HTTPStatus.FORBIDDEN, detail="Failed to get auth"
detail="Failed to get auth",
) )
else: else:
return await api_get_jukebox_invoice_paid( return await api_get_jukebox_invoice_paid(
song_id, juke_id, pay_hash song_id, juke_id, pay_hash
) )
raise HTTPException( raise HTTPException(status_code=HTTPStatus.OK, detail="Invoice not paid")
status_code=HTTPStatus.OK,
detail="Invoice not paid",
)
############################GET TRACKS ############################GET TRACKS
@ -454,16 +413,12 @@ async def api_get_jukebox_invoice_paid(
@jukebox_ext.get("/api/v1/jukebox/jb/currently/{juke_id}") @jukebox_ext.get("/api/v1/jukebox/jb/currently/{juke_id}")
async def api_get_jukebox_currently( async def api_get_jukebox_currently(
retry: bool = Query(False), retry: bool = Query(False), juke_id: str = Query(None)
juke_id: str = Query(None),
): ):
try: try:
jukebox = await get_jukebox(juke_id) jukebox = await get_jukebox(juke_id)
except: except:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="No jukebox")
status_code=HTTPStatus.FORBIDDEN,
detail="No jukebox",
)
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
try: try:
r = await client.get( r = await client.get(
@ -472,10 +427,7 @@ async def api_get_jukebox_currently(
headers={"Authorization": "Bearer " + jukebox.sp_access_token}, headers={"Authorization": "Bearer " + jukebox.sp_access_token},
) )
if r.status_code == 204: if r.status_code == 204:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.OK, detail="Nothing")
status_code=HTTPStatus.OK,
detail="Nothing",
)
elif r.status_code == 200: elif r.status_code == 200:
try: try:
response = r.json() response = r.json()
@ -490,31 +442,26 @@ async def api_get_jukebox_currently(
return track return track
except: except:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Something went wrong"
detail="Something went wrong",
) )
elif r.status_code == 401: elif r.status_code == 401:
token = await api_get_token(juke_id) token = await api_get_token(juke_id)
if token == False: if token == False:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, status_code=HTTPStatus.FORBIDDEN, detail="INvoice not paid"
detail="INvoice not paid",
) )
elif retry: elif retry:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, status_code=HTTPStatus.FORBIDDEN, detail="Failed to get auth"
detail="Failed to get auth",
) )
else: else:
return await api_get_jukebox_currently(retry=True, juke_id=juke_id) return await api_get_jukebox_currently(retry=True, juke_id=juke_id)
else: else:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Something went wrong"
detail="Something went wrong",
) )
except: except:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Something went wrong"
detail="Something went wrong",
) )

View File

@ -12,11 +12,7 @@ lndhub_ext: APIRouter = APIRouter(prefix="/lndhub", tags=["lndhub"])
def lndhub_renderer(): def lndhub_renderer():
return template_renderer( return template_renderer(["lnbits/extensions/lndhub/templates"])
[
"lnbits/extensions/lndhub/templates",
]
)
from .views_api import * # noqa from .views_api import * # noqa

View File

@ -12,12 +12,19 @@ from starlette.responses import HTMLResponse, JSONResponse
from lnbits.decorators import WalletTypeInfo, get_key_type # type: ignore from lnbits.decorators import WalletTypeInfo, get_key_type # type: ignore
api_key_header_auth = APIKeyHeader(name="AUTHORIZATION", auto_error=False, description="Admin or Invoice key for LNDHub API's") api_key_header_auth = APIKeyHeader(
async def check_wallet(r: Request, api_key_header_auth: str = Security(api_key_header_auth)) -> WalletTypeInfo: name="AUTHORIZATION",
auto_error=False,
description="Admin or Invoice key for LNDHub API's",
)
async def check_wallet(
r: Request, api_key_header_auth: str = Security(api_key_header_auth)
) -> WalletTypeInfo:
if not api_key_header_auth: if not api_key_header_auth:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid auth key"
detail="Invalid auth key"
) )
t = api_key_header_auth.split(" ")[1] t = api_key_header_auth.split(" ")[1]
@ -26,14 +33,15 @@ async def check_wallet(r: Request, api_key_header_auth: str = Security(api_key_h
return await get_key_type(r, api_key_header=token) return await get_key_type(r, api_key_header=token)
async def require_admin_key(r: Request, api_key_header_auth: str = Security(api_key_header_auth)): async def require_admin_key(
r: Request, api_key_header_auth: str = Security(api_key_header_auth)
):
wallet = await check_wallet(r, api_key_header_auth) wallet = await check_wallet(r, api_key_header_auth)
if wallet.wallet_type != 0: if wallet.wallet_type != 0:
# If wallet type is not admin then return the unauthorized status # If wallet type is not admin then return the unauthorized status
# This also covers when the user passes an invalid key type # This also covers when the user passes an invalid key type
raise HTTPException( raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED, detail="Admin key required."
detail="Admin key required.",
) )
else: else:
return wallet return wallet

View File

@ -23,10 +23,8 @@ from fastapi.security import OAuth2PasswordBearer
@lndhub_ext.get("/ext/getinfo") @lndhub_ext.get("/ext/getinfo")
async def lndhub_getinfo(): async def lndhub_getinfo():
raise HTTPException( raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="bad auth")
status_code=HTTPStatus.UNAUTHORIZED,
detail="bad auth",
)
class AuthData(BaseModel): class AuthData(BaseModel):
login: str = Query(None) login: str = Query(None)
@ -35,16 +33,17 @@ class AuthData(BaseModel):
@lndhub_ext.post("/ext/auth") @lndhub_ext.post("/ext/auth")
async def lndhub_auth( async def lndhub_auth(data: AuthData):
data: AuthData
):
token = ( token = (
data.refresh_token data.refresh_token
if data.refresh_token if data.refresh_token
else urlsafe_b64encode((data.login + ":" + data.password).encode("utf-8")).decode("ascii") else urlsafe_b64encode(
(data.login + ":" + data.password).encode("utf-8")
).decode("ascii")
) )
return {"refresh_token": token, "access_token": token} return {"refresh_token": token, "access_token": token}
class AddInvoice(BaseModel): class AddInvoice(BaseModel):
amt: str = Query(None) amt: str = Query(None)
memo: str = Query(None) memo: str = Query(None)
@ -53,8 +52,7 @@ class AddInvoice(BaseModel):
@lndhub_ext.post("/ext/addinvoice") @lndhub_ext.post("/ext/addinvoice")
async def lndhub_addinvoice( async def lndhub_addinvoice(
data: AddInvoice, data: AddInvoice, wallet: WalletTypeInfo = Depends(check_wallet)
wallet: WalletTypeInfo = Depends(check_wallet)
): ):
try: try:
_, pr = await create_invoice( _, pr = await create_invoice(
@ -65,8 +63,7 @@ async def lndhub_addinvoice(
) )
except: except:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Failed to create invoice"
detail="Failed to create invoice",
) )
invoice = bolt11.decode(pr) invoice = bolt11.decode(pr)
@ -78,9 +75,11 @@ async def lndhub_addinvoice(
"hash": invoice.payment_hash, "hash": invoice.payment_hash,
} }
class Invoice(BaseModel): class Invoice(BaseModel):
invoice: str invoice: str
@lndhub_ext.post("/ext/payinvoice") @lndhub_ext.post("/ext/payinvoice")
async def lndhub_payinvoice( async def lndhub_payinvoice(
r_invoice: Invoice, wallet: WalletTypeInfo = Depends(require_admin_key) r_invoice: Invoice, wallet: WalletTypeInfo = Depends(require_admin_key)
@ -92,10 +91,7 @@ async def lndhub_payinvoice(
extra={"tag": "lndhub"}, extra={"tag": "lndhub"},
) )
except: except:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Payment failed")
status_code=HTTPStatus.NOT_FOUND,
detail="Payment failed",
)
invoice: bolt11.Invoice = bolt11.decode(r_invoice.invoice) invoice: bolt11.Invoice = bolt11.decode(r_invoice.invoice)
print("INV2", invoice) print("INV2", invoice)
@ -116,9 +112,7 @@ async def lndhub_payinvoice(
@lndhub_ext.get("/ext/balance") @lndhub_ext.get("/ext/balance")
# @check_wallet() # @check_wallet()
async def lndhub_balance( async def lndhub_balance(wallet: WalletTypeInfo = Depends(check_wallet),):
wallet: WalletTypeInfo = Depends(check_wallet),
):
return {"BTC": {"AvailableBalance": wallet.wallet.balance}} return {"BTC": {"AvailableBalance": wallet.wallet.balance}}

View File

@ -14,12 +14,9 @@ lnticket_ext: APIRouter = APIRouter(
# "lnticket", __name__, static_folder="static", template_folder="templates" # "lnticket", __name__, static_folder="static", template_folder="templates"
) )
def lnticket_renderer(): def lnticket_renderer():
return template_renderer( return template_renderer(["lnbits/extensions/lnticket/templates"])
[
"lnbits/extensions/lnticket/templates",
]
)
from .views_api import * # noqa from .views_api import * # noqa
@ -30,4 +27,3 @@ from .tasks import wait_for_paid_invoices
def lnticket_start(): def lnticket_start():
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
loop.create_task(catch_everything_and_restart(wait_for_paid_invoices)) loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))

View File

@ -9,16 +9,23 @@ import httpx
async def create_ticket( async def create_ticket(
payment_hash: str, payment_hash: str, wallet: str, data: CreateTicketData
wallet: str,
data: CreateTicketData
) -> Tickets: ) -> Tickets:
await db.execute( await db.execute(
""" """
INSERT INTO lnticket.ticket (id, form, email, ltext, name, wallet, sats, paid) INSERT INTO lnticket.ticket (id, form, email, ltext, name, wallet, sats, paid)
VALUES (?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""", """,
(payment_hash, data.form, data.email, data.ltext, data.name, wallet, data.sats, False), (
payment_hash,
data.form,
data.email,
data.ltext,
data.name,
wallet,
data.sats,
False,
),
) )
ticket = await get_ticket(payment_hash) ticket = await get_ticket(payment_hash)
@ -99,17 +106,23 @@ async def delete_ticket(ticket_id: str) -> None:
# FORMS # FORMS
async def create_form( async def create_form(data: CreateFormData, wallet: Wallet) -> Forms:
data: CreateFormData,
wallet: Wallet,
) -> Forms:
form_id = urlsafe_short_hash() form_id = urlsafe_short_hash()
await db.execute( await db.execute(
""" """
INSERT INTO lnticket.form2 (id, wallet, name, webhook, description, flatrate, amount, amountmade) INSERT INTO lnticket.form2 (id, wallet, name, webhook, description, flatrate, amount, amountmade)
VALUES (?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""", """,
(form_id, wallet.id, wallet.name, data.webhook, data.description, data.flatrate, data.amount, 0), (
form_id,
wallet.id,
wallet.name,
data.webhook,
data.description,
data.flatrate,
data.amount,
0,
),
) )
form = await get_form(form_id) form = await get_form(form_id)

View File

@ -79,16 +79,7 @@ async def m002_changed(db):
) )
VALUES (?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""", """,
( (row[0], row[1], row[2], row[3], row[4], row[5], row[6], True),
row[0],
row[1],
row[2],
row[3],
row[4],
row[5],
row[6],
True,
),
) )
await db.execute("DROP TABLE lnticket.tickets") await db.execute("DROP TABLE lnticket.tickets")
@ -134,15 +125,7 @@ async def m003_changed(db):
) )
VALUES (?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?)
""", """,
( (row[0], row[1], row[2], row[3], row[4], row[5], row[6]),
row[0],
row[1],
row[2],
row[3],
row[4],
row[5],
row[6],
),
) )
await db.execute("DROP TABLE lnticket.forms") await db.execute("DROP TABLE lnticket.forms")
@ -189,14 +172,6 @@ async def m004_changed(db):
) )
VALUES (?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?)
""", """,
( (row[0], row[1], row[2], row[3], row[4], row[5], row[6]),
row[0],
row[1],
row[2],
row[3],
row[4],
row[5],
row[6],
),
) )
await db.execute("DROP TABLE lnticket.form") await db.execute("DROP TABLE lnticket.form")

View File

@ -2,6 +2,7 @@ from typing import Optional
from fastapi.param_functions import Query from fastapi.param_functions import Query
from pydantic import BaseModel from pydantic import BaseModel
class CreateFormData(BaseModel): class CreateFormData(BaseModel):
name: str = Query(...) name: str = Query(...)
webhook: str = Query(None) webhook: str = Query(None)
@ -9,6 +10,7 @@ class CreateFormData(BaseModel):
amount: int = Query(..., ge=0) amount: int = Query(..., ge=0)
flatrate: int = Query(...) flatrate: int = Query(...)
class CreateTicketData(BaseModel): class CreateTicketData(BaseModel):
form: str = Query(...) form: str = Query(...)
name: str = Query(...) name: str = Query(...)
@ -16,6 +18,7 @@ class CreateTicketData(BaseModel):
ltext: str = Query(...) ltext: str = Query(...)
sats: int = Query(..., ge=0) sats: int = Query(..., ge=0)
class Forms(BaseModel): class Forms(BaseModel):
id: str id: str
wallet: str wallet: str

View File

@ -14,13 +14,16 @@ from fastapi.templating import Jinja2Templates
templates = Jinja2Templates(directory="templates") templates = Jinja2Templates(directory="templates")
@lnticket_ext.get("/", response_class=HTMLResponse) @lnticket_ext.get("/", response_class=HTMLResponse)
# not needed as we automatically get the user with the given ID # not needed as we automatically get the user with the given ID
# If no user with this ID is found, an error is raised # If no user with this ID is found, an error is raised
# @validate_uuids(["usr"], required=True) # @validate_uuids(["usr"], required=True)
# @check_user_exists() # @check_user_exists()
async def index(request: Request, user: User = Depends(check_user_exists)): async def index(request: Request, user: User = Depends(check_user_exists)):
return lnticket_renderer().TemplateResponse("lnticket/index.html", {"request": request,"user": user.dict()}) return lnticket_renderer().TemplateResponse(
"lnticket/index.html", {"request": request, "user": user.dict()}
)
@lnticket_ext.get("/{form_id}") @lnticket_ext.get("/{form_id}")
@ -28,8 +31,7 @@ async def display(request: Request, form_id):
form = await get_form(form_id) form = await get_form(form_id)
if not form: if not form:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="LNTicket does not exist."
detail="LNTicket does not exist."
) )
# abort(HTTPStatus.NOT_FOUND, "LNTicket does not exist.") # abort(HTTPStatus.NOT_FOUND, "LNTicket does not exist.")
@ -37,11 +39,13 @@ async def display(request: Request, form_id):
return lnticket_renderer().TemplateResponse( return lnticket_renderer().TemplateResponse(
"lnticket/display.html", "lnticket/display.html",
{"request": request, {
"form_id":form.id, "request": request,
"form_name":form.name, "form_id": form.id,
"form_desc":form.description, "form_name": form.name,
"form_amount":form.amount, "form_desc": form.description,
"form_flatrate":form.flatrate, "form_amount": form.amount,
"form_wallet":wallet.inkey} "form_flatrate": form.flatrate,
"form_wallet": wallet.inkey,
},
) )

View File

@ -34,7 +34,11 @@ from .crud import (
@lnticket_ext.get("/api/v1/forms") @lnticket_ext.get("/api/v1/forms")
async def api_forms_get(r: Request, all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)): async def api_forms_get(
r: Request,
all_wallets: bool = Query(False),
wallet: WalletTypeInfo = Depends(get_key_type),
):
wallet_ids = [wallet.wallet.id] wallet_ids = [wallet.wallet.id]
if all_wallets: if all_wallets:
@ -42,6 +46,7 @@ async def api_forms_get(r: Request, all_wallets: bool = Query(False), wallet: Wa
return [form.dict() for form in await get_forms(wallet_ids)] return [form.dict() for form in await get_forms(wallet_ids)]
@lnticket_ext.post("/api/v1/forms", status_code=HTTPStatus.CREATED) @lnticket_ext.post("/api/v1/forms", status_code=HTTPStatus.CREATED)
@lnticket_ext.put("/api/v1/forms/{form_id}") @lnticket_ext.put("/api/v1/forms/{form_id}")
# @api_check_wallet_key("invoice") # @api_check_wallet_key("invoice")
@ -55,21 +60,21 @@ async def api_forms_get(r: Request, all_wallets: bool = Query(False), wallet: Wa
# "flatrate": {"type": "integer", "required": True}, # "flatrate": {"type": "integer", "required": True},
# } # }
# ) # )
async def api_form_create(data: CreateFormData, form_id=None, wallet: WalletTypeInfo = Depends(get_key_type)): async def api_form_create(
data: CreateFormData, form_id=None, wallet: WalletTypeInfo = Depends(get_key_type)
):
if form_id: if form_id:
form = await get_form(form_id) form = await get_form(form_id)
if not form: if not form:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail=f"Form does not exist."
detail=f"Form does not exist."
) )
# return {"message": "Form does not exist."}, HTTPStatus.NOT_FOUND # return {"message": "Form does not exist."}, HTTPStatus.NOT_FOUND
if form.wallet != wallet.wallet.id: if form.wallet != wallet.wallet.id:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, status_code=HTTPStatus.FORBIDDEN, detail=f"Not your form."
detail=f"Not your form."
) )
# return {"message": "Not your form."}, HTTPStatus.FORBIDDEN # return {"message": "Not your form."}, HTTPStatus.FORBIDDEN
@ -86,16 +91,12 @@ async def api_form_delete(form_id, wallet: WalletTypeInfo = Depends(get_key_type
if not form: if not form:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail=f"Form does not exist."
detail=f"Form does not exist."
) )
# return {"message": "Form does not exist."}, HTTPStatus.NOT_FOUND # return {"message": "Form does not exist."}, HTTPStatus.NOT_FOUND
if form.wallet != wallet.wallet.id: if form.wallet != wallet.wallet.id:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail=f"Not your form.")
status_code=HTTPStatus.FORBIDDEN,
detail=f"Not your form."
)
# return {"message": "Not your form."}, HTTPStatus.FORBIDDEN # return {"message": "Not your form."}, HTTPStatus.FORBIDDEN
await delete_form(form_id) await delete_form(form_id)
@ -109,7 +110,9 @@ async def api_form_delete(form_id, wallet: WalletTypeInfo = Depends(get_key_type
@lnticket_ext.get("/api/v1/tickets") @lnticket_ext.get("/api/v1/tickets")
# @api_check_wallet_key("invoice") # @api_check_wallet_key("invoice")
async def api_tickets(all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)): async def api_tickets(
all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)
):
wallet_ids = [wallet.wallet.id] wallet_ids = [wallet.wallet.id]
if all_wallets: if all_wallets:
@ -117,6 +120,7 @@ async def api_tickets(all_wallets: bool = Query(False), wallet: WalletTypeInfo =
return [form.dict() for form in await get_tickets(wallet_ids)] return [form.dict() for form in await get_tickets(wallet_ids)]
@lnticket_ext.post("/api/v1/tickets/{form_id}", status_code=HTTPStatus.CREATED) @lnticket_ext.post("/api/v1/tickets/{form_id}", status_code=HTTPStatus.CREATED)
# @api_validate_post_request( # @api_validate_post_request(
# schema={ # schema={
@ -131,8 +135,7 @@ async def api_ticket_make_ticket(data: CreateTicketData, form_id):
form = await get_form(form_id) form = await get_form(form_id)
if not form: if not form:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail=f"LNTicket does not exist."
detail=f"LNTicket does not exist."
) )
# return {"message": "LNTicket does not exist."}, HTTPStatus.NOT_FOUND # return {"message": "LNTicket does not exist."}, HTTPStatus.NOT_FOUND
@ -146,10 +149,7 @@ async def api_ticket_make_ticket(data: CreateTicketData, form_id):
extra={"tag": "lnticket"}, extra={"tag": "lnticket"},
) )
except Exception as e: except Exception as e:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail=str(e)
)
# return {"message": str(e)}, HTTPStatus.INTERNAL_SERVER_ERROR # return {"message": str(e)}, HTTPStatus.INTERNAL_SERVER_ERROR
ticket = await create_ticket( ticket = await create_ticket(
@ -158,18 +158,14 @@ async def api_ticket_make_ticket(data: CreateTicketData, form_id):
if not ticket: if not ticket:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="LNTicket could not be fetched."
detail="LNTicket could not be fetched."
) )
# return ( # return (
# {"message": "LNTicket could not be fetched."}, # {"message": "LNTicket could not be fetched."},
# HTTPStatus.NOT_FOUND, # HTTPStatus.NOT_FOUND,
# ) # )
return { return {"payment_hash": payment_hash, "payment_request": payment_request}
"payment_hash": payment_hash,
"payment_request": payment_request
}
@lnticket_ext.get("/api/v1/tickets/{payment_hash}", status_code=HTTPStatus.OK) @lnticket_ext.get("/api/v1/tickets/{payment_hash}", status_code=HTTPStatus.OK)
@ -198,16 +194,12 @@ async def api_ticket_delete(ticket_id, wallet: WalletTypeInfo = Depends(get_key_
if not ticket: if not ticket:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail=f"LNTicket does not exist."
detail=f"LNTicket does not exist."
) )
# return {"message": "Paywall does not exist."}, HTTPStatus.NOT_FOUND # return {"message": "Paywall does not exist."}, HTTPStatus.NOT_FOUND
if ticket.wallet != wallet.wallet.id: if ticket.wallet != wallet.wallet.id:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your ticket.")
status_code=HTTPStatus.FORBIDDEN,
detail="Not your ticket."
)
# return {"message": "Not your ticket."}, HTTPStatus.FORBIDDEN # return {"message": "Not your ticket."}, HTTPStatus.FORBIDDEN
await delete_ticket(ticket_id) await delete_ticket(ticket_id)

View File

@ -26,11 +26,7 @@ lnurlp_ext: APIRouter = APIRouter(
def lnurlp_renderer(): def lnurlp_renderer():
return template_renderer( return template_renderer(["lnbits/extensions/lnurlp/templates"])
[
"lnbits/extensions/lnurlp/templates",
]
)
from .views_api import * # noqa from .views_api import * # noqa

View File

@ -5,10 +5,7 @@ from . import db
from .models import PayLink, CreatePayLinkData from .models import PayLink, CreatePayLinkData
async def create_pay_link( async def create_pay_link(data: CreatePayLinkData, wallet_id: str) -> PayLink:
data: CreatePayLinkData,
wallet_id: str
) -> PayLink:
returning = "" if db.type == SQLITE else "RETURNING ID" returning = "" if db.type == SQLITE else "RETURNING ID"
method = db.execute if db.type == SQLITE else db.fetchone method = db.execute if db.type == SQLITE else db.fetchone

View File

@ -3,7 +3,11 @@ import math
from http import HTTPStatus from http import HTTPStatus
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from lnurl import LnurlPayResponse, LnurlPayActionResponse, LnurlErrorResponse # type: ignore from lnurl import (
LnurlPayResponse,
LnurlPayActionResponse,
LnurlErrorResponse,
) # type: ignore
from lnbits.core.services import create_invoice from lnbits.core.services import create_invoice
from lnbits.utils.exchange_rates import get_fiat_rate_satoshis from lnbits.utils.exchange_rates import get_fiat_rate_satoshis
@ -12,13 +16,16 @@ from . import lnurlp_ext
from .crud import increment_pay_link from .crud import increment_pay_link
@lnurlp_ext.get("/api/v1/lnurl/{link_id}", status_code=HTTPStatus.OK, name="lnurlp.api_lnurl_response") @lnurlp_ext.get(
"/api/v1/lnurl/{link_id}",
status_code=HTTPStatus.OK,
name="lnurlp.api_lnurl_response",
)
async def api_lnurl_response(request: Request, link_id): async def api_lnurl_response(request: Request, link_id):
link = await increment_pay_link(link_id, served_meta=1) link = await increment_pay_link(link_id, served_meta=1)
if not link: if not link:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Pay link does not exist."
detail="Pay link does not exist."
) )
rate = await get_fiat_rate_satoshis(link.currency) if link.currency else 1 rate = await get_fiat_rate_satoshis(link.currency) if link.currency else 1
@ -37,13 +44,16 @@ async def api_lnurl_response(request: Request, link_id):
return params return params
@lnurlp_ext.get("/api/v1/lnurl/cb/{link_id}", status_code=HTTPStatus.OK, name="lnurlp.api_lnurl_callback") @lnurlp_ext.get(
"/api/v1/lnurl/cb/{link_id}",
status_code=HTTPStatus.OK,
name="lnurlp.api_lnurl_callback",
)
async def api_lnurl_callback(request: Request, link_id): async def api_lnurl_callback(request: Request, link_id):
link = await increment_pay_link(link_id, served_pr=1) link = await increment_pay_link(link_id, served_pr=1)
if not link: if not link:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Pay link does not exist."
detail="Pay link does not exist."
) )
min, max = link.min, link.max min, max = link.min, link.max
rate = await get_fiat_rate_satoshis(link.currency) if link.currency else 1 rate = await get_fiat_rate_satoshis(link.currency) if link.currency else 1
@ -55,23 +65,22 @@ async def api_lnurl_callback(request: Request, link_id):
min = link.min * 1000 min = link.min * 1000
max = link.max * 1000 max = link.max * 1000
amount_received = int(request.query_params.get('amount') or 0) amount_received = int(request.query_params.get("amount") or 0)
if amount_received < min: if amount_received < min:
return LnurlErrorResponse( return LnurlErrorResponse(
reason=f"Amount {amount_received} is smaller than minimum {min}." reason=f"Amount {amount_received} is smaller than minimum {min}."
).dict() ).dict()
elif amount_received > max: elif amount_received > max:
return LnurlErrorResponse( return LnurlErrorResponse(
reason=f"Amount {amount_received} is greater than maximum {max}." reason=f"Amount {amount_received} is greater than maximum {max}."
).dict() ).dict()
comment = request.query_params.get("comment") comment = request.query_params.get("comment")
if len(comment or "") > link.comment_chars: if len(comment or "") > link.comment_chars:
return LnurlErrorResponse( return LnurlErrorResponse(
reason=f"Got a comment with {len(comment)} characters, but can only accept {link.comment_chars}" reason=f"Got a comment with {len(comment)} characters, but can only accept {link.comment_chars}"
).dict() ).dict()
payment_hash, payment_request = await create_invoice( payment_hash, payment_request = await create_invoice(
wallet_id=link.wallet, wallet_id=link.wallet,
@ -80,20 +89,20 @@ async def api_lnurl_callback(request: Request, link_id):
description_hash=hashlib.sha256( description_hash=hashlib.sha256(
link.lnurlpay_metadata.encode("utf-8") link.lnurlpay_metadata.encode("utf-8")
).digest(), ).digest(),
extra={"tag": "lnurlp", "link": link.id, "comment": comment, 'extra': request.query_params.get('amount')}, extra={
"tag": "lnurlp",
"link": link.id,
"comment": comment,
"extra": request.query_params.get("amount"),
},
) )
success_action = link.success_action(payment_hash) success_action = link.success_action(payment_hash)
if success_action: if success_action:
resp = LnurlPayActionResponse( resp = LnurlPayActionResponse(
pr=payment_request, pr=payment_request, success_action=success_action, routes=[]
success_action=success_action,
routes=[],
) )
else: else:
resp = LnurlPayActionResponse( resp = LnurlPayActionResponse(pr=payment_request, routes=[])
pr=payment_request,
routes=[],
)
return resp.dict() return resp.dict()

View File

@ -8,15 +8,17 @@ from lnurl.types import LnurlPayMetadata # type: ignore
from sqlite3 import Row from sqlite3 import Row
from pydantic import BaseModel from pydantic import BaseModel
class CreatePayLinkData(BaseModel): class CreatePayLinkData(BaseModel):
description: str description: str
min: int = Query(0.01, ge=0.01) min: int = Query(0.01, ge=0.01)
max: int = Query(0.01, ge=0.01) max: int = Query(0.01, ge=0.01)
currency: str = Query(None) currency: str = Query(None)
comment_chars: int = Query(0, ge=0, lt=800) comment_chars: int = Query(0, ge=0, lt=800)
webhook_url: str = Query(None) webhook_url: str = Query(None)
success_text: str = Query(None) success_text: str = Query(None)
success_url: str = Query(None) success_url: str = Query(None)
class PayLink(BaseModel): class PayLink(BaseModel):
id: int id: int
@ -37,7 +39,6 @@ class PayLink(BaseModel):
data = dict(row) data = dict(row)
return cls(**data) return cls(**data)
def lnurl(self, req: Request) -> str: def lnurl(self, req: Request) -> str:
url = req.url_for("lnurlp.api_lnurl_response", link_id=self.id) url = req.url_for("lnurlp.api_lnurl_response", link_id=self.id)
return lnurl_encode(url) return lnurl_encode(url)
@ -58,9 +59,6 @@ class PayLink(BaseModel):
"url": urlunparse(url), "url": urlunparse(url),
} }
elif self.success_text: elif self.success_text:
return { return {"tag": "message", "message": self.success_text}
"tag": "message",
"message": self.success_text,
}
else: else:
return None return None

View File

@ -17,6 +17,7 @@ async def wait_for_paid_invoices():
payment = await invoice_queue.get() payment = await invoice_queue.get()
await on_invoice_paid(payment) await on_invoice_paid(payment)
async def on_invoice_paid(payment: Payment) -> None: async def on_invoice_paid(payment: Payment) -> None:
if "lnurlp" != payment.extra.get("tag"): if "lnurlp" != payment.extra.get("tag"):
# not an lnurlp invoice # not an lnurlp invoice

View File

@ -14,34 +14,35 @@ from lnbits.core.models import User
templates = Jinja2Templates(directory="templates") templates = Jinja2Templates(directory="templates")
@lnurlp_ext.get("/", response_class=HTMLResponse) @lnurlp_ext.get("/", response_class=HTMLResponse)
# @validate_uuids(["usr"], required=True) # @validate_uuids(["usr"], required=True)
# @check_user_exists() # @check_user_exists()
async def index(request: Request, user: User = Depends(check_user_exists)): async def index(request: Request, user: User = Depends(check_user_exists)):
return lnurlp_renderer().TemplateResponse("lnurlp/index.html", {"request": request, "user": user.dict()}) return lnurlp_renderer().TemplateResponse(
"lnurlp/index.html", {"request": request, "user": user.dict()}
)
@lnurlp_ext.get("/{link_id}", response_class=HTMLResponse) @lnurlp_ext.get("/{link_id}", response_class=HTMLResponse)
async def display(request: Request,link_id): async def display(request: Request, link_id):
link = await get_pay_link(link_id) link = await get_pay_link(link_id)
if not link: if not link:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Pay link does not exist."
detail="Pay link does not exist."
) )
# abort(HTTPStatus.NOT_FOUND, "Pay link does not exist.") # abort(HTTPStatus.NOT_FOUND, "Pay link does not exist.")
ctx = {"request": request, "lnurl":link.lnurl(req=request)} ctx = {"request": request, "lnurl": link.lnurl(req=request)}
return lnurlp_renderer().TemplateResponse("lnurlp/display.html", ctx) return lnurlp_renderer().TemplateResponse("lnurlp/display.html", ctx)
@lnurlp_ext.get("/print/{link_id}", response_class=HTMLResponse) @lnurlp_ext.get("/print/{link_id}", response_class=HTMLResponse)
async def print_qr(request: Request,link_id): async def print_qr(request: Request, link_id):
link = await get_pay_link(link_id) link = await get_pay_link(link_id)
if not link: if not link:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Pay link does not exist."
detail="Pay link does not exist."
) )
# abort(HTTPStatus.NOT_FOUND, "Pay link does not exist.") # abort(HTTPStatus.NOT_FOUND, "Pay link does not exist.")
ctx = {"request": request, "lnurl":link.lnurl(req=request)} ctx = {"request": request, "lnurl": link.lnurl(req=request)}
return lnurlp_renderer().TemplateResponse("lnurlp/print_qr.html", ctx) return lnurlp_renderer().TemplateResponse("lnurlp/print_qr.html", ctx)

View File

@ -29,11 +29,7 @@ offlineshop_ext: APIRouter = APIRouter(
def offlineshop_renderer(): def offlineshop_renderer():
return template_renderer( return template_renderer(["lnbits/extensions/offlineshop/templates"])
[
"lnbits/extensions/offlineshop/templates",
]
)
from .lnurl import * # noqa from .lnurl import * # noqa

View File

@ -51,12 +51,7 @@ async def set_method(shop: int, method: str, wordlist: str = "") -> Optional[Sho
async def add_item( async def add_item(
shop: int, shop: int, name: str, description: str, image: Optional[str], price: int, unit: str
name: str,
description: str,
image: Optional[str],
price: int,
unit: str,
) -> int: ) -> int:
result = await db.execute( result = await db.execute(
""" """

View File

@ -8,8 +8,8 @@ def hotp(key, counter, digits=6, digest="sha1"):
key = base64.b32decode(key.upper() + "=" * ((8 - len(key)) % 8)) key = base64.b32decode(key.upper() + "=" * ((8 - len(key)) % 8))
counter = struct.pack(">Q", counter) counter = struct.pack(">Q", counter)
mac = hmac.new(key, counter, digest).digest() mac = hmac.new(key, counter, digest).digest()
offset = mac[-1] & 0x0f offset = mac[-1] & 0x0F
binary = struct.unpack(">L", mac[offset : offset + 4])[0] & 0x7fffffff binary = struct.unpack(">L", mac[offset : offset + 4])[0] & 0x7FFFFFFF
return str(binary)[-digits:].zfill(digits) return str(binary)[-digits:].zfill(digits)

View File

@ -4,7 +4,11 @@ from fastapi.params import Query
from starlette.requests import Request from starlette.requests import Request
from lnbits.helpers import url_for from lnbits.helpers import url_for
from lnurl import LnurlPayResponse, LnurlPayActionResponse, LnurlErrorResponse # type: ignore from lnurl import (
LnurlPayResponse,
LnurlPayActionResponse,
LnurlErrorResponse,
) # type: ignore
from lnbits.core.services import create_invoice from lnbits.core.services import create_invoice
from lnbits.utils.exchange_rates import fiat_amount_as_satoshis from lnbits.utils.exchange_rates import fiat_amount_as_satoshis
@ -15,7 +19,7 @@ from .crud import get_shop, get_item
@offlineshop_ext.get("/lnurl/{item_id}", name="offlineshop.lnurl_response") @offlineshop_ext.get("/lnurl/{item_id}", name="offlineshop.lnurl_response")
async def lnurl_response(req: Request, item_id: int = Query(...)): async def lnurl_response(req: Request, item_id: int = Query(...)):
item = await get_item(item_id) # type: Item item = await get_item(item_id) # type: Item
if not item: if not item:
return {"status": "ERROR", "reason": "Item not found."} return {"status": "ERROR", "reason": "Item not found."}
@ -40,7 +44,7 @@ async def lnurl_response(req: Request, item_id: int = Query(...)):
@offlineshop_ext.get("/lnurl/cb/{item_id}", name="offlineshop.lnurl_callback") @offlineshop_ext.get("/lnurl/cb/{item_id}", name="offlineshop.lnurl_callback")
async def lnurl_callback(request: Request, item_id: int): async def lnurl_callback(request: Request, item_id: int):
item = await get_item(item_id) # type: Item item = await get_item(item_id) # type: Item
if not item: if not item:
return {"status": "ERROR", "reason": "Couldn't find item."} return {"status": "ERROR", "reason": "Couldn't find item."}
@ -80,7 +84,9 @@ async def lnurl_callback(request: Request, item_id: int):
resp = LnurlPayActionResponse( resp = LnurlPayActionResponse(
pr=payment_request, pr=payment_request,
success_action=item.success_action(shop, payment_hash, request) if shop.method else None, success_action=item.success_action(shop, payment_hash, request)
if shop.method
else None,
routes=[], routes=[],
) )

View File

@ -14,7 +14,7 @@ from .helpers import totp
shop_counters: Dict = {} shop_counters: Dict = {}
class ShopCounter(): class ShopCounter:
wordlist: List[str] wordlist: List[str]
fulfilled_payments: OrderedDict fulfilled_payments: OrderedDict
counter: int counter: int
@ -66,7 +66,7 @@ class Shop(BaseModel):
def otp_key(self) -> str: def otp_key(self) -> str:
return base64.b32encode( return base64.b32encode(
hashlib.sha256( hashlib.sha256(
("otpkey" + str(self.id) + self.wallet).encode("ascii"), ("otpkey" + str(self.id) + self.wallet).encode("ascii")
).digest() ).digest()
).decode("ascii") ).decode("ascii")
@ -90,9 +90,7 @@ class Item(BaseModel):
unit: str unit: str
def lnurl(self, req: Request) -> str: def lnurl(self, req: Request) -> str:
return lnurl_encode( return lnurl_encode(req.url_for("offlineshop.lnurl_response", item_id=self.id))
req.url_for("offlineshop.lnurl_response", item_id=self.id)
)
def values(self, req: Request): def values(self, req: Request):
values = self.dict() values = self.dict()
@ -116,8 +114,6 @@ class Item(BaseModel):
return None return None
return UrlAction( return UrlAction(
url=req.url_for( url=req.url_for("offlineshop.confirmation_code", p=payment_hash),
"offlineshop.confirmation_code", p=payment_hash
),
description="Open to get the confirmation code for your purchase.", description="Open to get the confirmation code for your purchase.",
) )

View File

@ -18,14 +18,16 @@ from fastapi import Request, HTTPException
@offlineshop_ext.get("/", response_class=HTMLResponse) @offlineshop_ext.get("/", response_class=HTMLResponse)
async def index(request: Request, user: User = Depends(check_user_exists)): async def index(request: Request, user: User = Depends(check_user_exists)):
return offlineshop_renderer().TemplateResponse("offlineshop/index.html", {"request": request, "user": user.dict()}) return offlineshop_renderer().TemplateResponse(
"offlineshop/index.html", {"request": request, "user": user.dict()}
)
@offlineshop_ext.get("/print", response_class=HTMLResponse) @offlineshop_ext.get("/print", response_class=HTMLResponse)
async def print_qr_codes(request: Request, items: List[int] = None): async def print_qr_codes(request: Request, items: List[int] = None):
items = [] items = []
for item_id in request.query_params.get("items").split(","): for item_id in request.query_params.get("items").split(","):
item = await get_item(item_id) # type: Item item = await get_item(item_id) # type: Item
if item: if item:
items.append( items.append(
{ {
@ -35,11 +37,16 @@ async def print_qr_codes(request: Request, items: List[int] = None):
} }
) )
return offlineshop_renderer().TemplateResponse("offlineshop/print.html", {"request": request,"items":items}) return offlineshop_renderer().TemplateResponse(
"offlineshop/print.html", {"request": request, "items": items}
)
@offlineshop_ext.get("/confirmation/{p}", name="offlineshop.confirmation_code", @offlineshop_ext.get(
response_class=HTMLResponse) "/confirmation/{p}",
name="offlineshop.confirmation_code",
response_class=HTMLResponse,
)
async def confirmation_code(p: str = Query(...)): async def confirmation_code(p: str = Query(...)):
style = "<style>* { font-size: 100px}</style>" style = "<style>* { font-size: 100px}</style>"
@ -48,20 +55,20 @@ async def confirmation_code(p: str = Query(...)):
if not payment: if not payment:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND,
detail=f"Couldn't find the payment {payment_hash}." + style detail=f"Couldn't find the payment {payment_hash}." + style,
) )
if payment.pending: if payment.pending:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.PAYMENT_REQUIRED, status_code=HTTPStatus.PAYMENT_REQUIRED,
detail=f"Payment {payment_hash} wasn't received yet. Please try again in a minute." + style detail=f"Payment {payment_hash} wasn't received yet. Please try again in a minute."
+ style,
) )
if payment.time + 60 * 15 < time.time(): if payment.time + 60 * 15 < time.time():
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.REQUEST_TIMEOUT, status_code=HTTPStatus.REQUEST_TIMEOUT,
detail="Too much time has passed." + style detail="Too much time has passed." + style,
) )
item = await get_item(payment.extra.get("item")) item = await get_item(payment.extra.get("item"))
shop = await get_shop(item.shop) shop = await get_shop(item.shop)

View File

@ -33,17 +33,16 @@ async def api_list_currencies_available():
@offlineshop_ext.get("/api/v1/offlineshop") @offlineshop_ext.get("/api/v1/offlineshop")
# @api_check_wallet_key("invoice") # @api_check_wallet_key("invoice")
async def api_shop_from_wallet(r: Request, wallet: WalletTypeInfo = Depends(get_key_type)): async def api_shop_from_wallet(
r: Request, wallet: WalletTypeInfo = Depends(get_key_type)
):
shop = await get_or_create_shop_by_wallet(wallet.wallet.id) shop = await get_or_create_shop_by_wallet(wallet.wallet.id)
items = await get_items(shop.id) items = await get_items(shop.id)
try: try:
return { return {
**shop.dict(), **shop.dict(),
**{ **{"otp_key": shop.otp_key, "items": [item.values(r) for item in items]},
"otp_key": shop.otp_key,
"items": [item.values(r) for item in items],
},
} }
except LnurlInvalidUrl: except LnurlInvalidUrl:
raise HTTPException( raise HTTPException(
@ -63,18 +62,15 @@ class CreateItemsData(BaseModel):
@offlineshop_ext.post("/api/v1/offlineshop/items") @offlineshop_ext.post("/api/v1/offlineshop/items")
@offlineshop_ext.put("/api/v1/offlineshop/items/{item_id}") @offlineshop_ext.put("/api/v1/offlineshop/items/{item_id}")
# @api_check_wallet_key("invoice") # @api_check_wallet_key("invoice")
async def api_add_or_update_item(data: CreateItemsData, item_id=None, wallet: WalletTypeInfo = Depends(get_key_type)): async def api_add_or_update_item(
data: CreateItemsData, item_id=None, wallet: WalletTypeInfo = Depends(get_key_type)
):
shop = await get_or_create_shop_by_wallet(wallet.wallet.id) shop = await get_or_create_shop_by_wallet(wallet.wallet.id)
if item_id == None: if item_id == None:
await add_item( await add_item(
shop.id, shop.id, data.name, data.description, data.image, data.price, data.unit
data.name,
data.description,
data.image,
data.price,
data.unit,
) )
return HTMLResponse(status_code=HTTPStatus.CREATED) return HTMLResponse(status_code=HTTPStatus.CREATED)
else: else:
await update_item( await update_item(
shop.id, shop.id,
@ -102,7 +98,9 @@ class CreateMethodData(BaseModel):
@offlineshop_ext.put("/api/v1/offlineshop/method") @offlineshop_ext.put("/api/v1/offlineshop/method")
# @api_check_wallet_key("invoice") # @api_check_wallet_key("invoice")
async def api_set_method(data: CreateMethodData, wallet: WalletTypeInfo = Depends(get_key_type)): async def api_set_method(
data: CreateMethodData, wallet: WalletTypeInfo = Depends(get_key_type)
):
method = data.method method = data.method
wordlist = data.wordlist.split("\n") if data.wordlist else None wordlist = data.wordlist.split("\n") if data.wordlist else None

View File

@ -12,11 +12,7 @@ satsdice_ext: APIRouter = APIRouter(prefix="/satsdice", tags=["satsdice"])
def satsdice_renderer(): def satsdice_renderer():
return template_renderer( return template_renderer(["lnbits/extensions/satsdice/templates"])
[
"lnbits/extensions/satsdice/templates",
]
)
from .views_api import * # noqa from .views_api import * # noqa

View File

@ -15,9 +15,7 @@ from .models import (
from lnbits.helpers import urlsafe_short_hash from lnbits.helpers import urlsafe_short_hash
async def create_satsdice_pay( async def create_satsdice_pay(data: CreateSatsDiceLink,) -> satsdiceLink:
data: CreateSatsDiceLink,
) -> satsdiceLink:
satsdice_id = urlsafe_short_hash() satsdice_id = urlsafe_short_hash()
await db.execute( await db.execute(
""" """
@ -124,13 +122,7 @@ async def create_satsdice_payment(data: CreateSatsDicePayment) -> satsdicePaymen
) )
VALUES (?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?)
""", """,
( (data.payment_hash, data.satsdice_pay, data.value, False, False),
data.payment_hash,
data.satsdice_pay,
data.value,
False,
False,
),
) )
payment = await get_satsdice_payment(payment_hash) payment = await get_satsdice_payment(payment_hash)
assert payment, "Newly created withdraw couldn't be retrieved" assert payment, "Newly created withdraw couldn't be retrieved"
@ -211,8 +203,7 @@ async def get_satsdice_withdraw_by_hash(
unique_hash: str, num=0 unique_hash: str, num=0
) -> Optional[satsdiceWithdraw]: ) -> Optional[satsdiceWithdraw]:
row = await db.fetchone( row = await db.fetchone(
"SELECT * FROM satsdice.satsdice_withdraw WHERE unique_hash = ?", "SELECT * FROM satsdice.satsdice_withdraw WHERE unique_hash = ?", (unique_hash,)
(unique_hash,),
) )
if not row: if not row:
return None return None
@ -259,10 +250,7 @@ async def delete_satsdice_withdraw(withdraw_id: str) -> None:
) )
async def create_withdraw_hash_check( async def create_withdraw_hash_check(the_hash: str, lnurl_id: str) -> HashCheck:
the_hash: str,
lnurl_id: str,
) -> HashCheck:
await db.execute( await db.execute(
""" """
INSERT INTO satsdice.hash_checkw ( INSERT INTO satsdice.hash_checkw (
@ -271,10 +259,7 @@ async def create_withdraw_hash_check(
) )
VALUES (?, ?) VALUES (?, ?)
""", """,
( (the_hash, lnurl_id),
the_hash,
lnurl_id,
),
) )
hashCheck = await get_withdraw_hash_checkw(the_hash, lnurl_id) hashCheck = await get_withdraw_hash_checkw(the_hash, lnurl_id)
return hashCheck return hashCheck

View File

@ -19,7 +19,11 @@ from .crud import (
get_satsdice_pay, get_satsdice_pay,
create_satsdice_payment, create_satsdice_payment,
) )
from lnurl import LnurlPayResponse, LnurlPayActionResponse, LnurlErrorResponse # type: ignore from lnurl import (
LnurlPayResponse,
LnurlPayActionResponse,
LnurlErrorResponse,
) # type: ignore
##############LNURLP STUFF ##############LNURLP STUFF
@ -30,8 +34,7 @@ async def api_lnurlp_response(req: Request, link_id: str = Query(None)):
link = await get_satsdice_pay(link_id) link = await get_satsdice_pay(link_id)
if not link: if not link:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="LNURL-pay not found."
detail="LNURL-pay not found.",
) )
resp = LnurlPayResponse( resp = LnurlPayResponse(
callback=req.url_for( callback=req.url_for(
@ -51,8 +54,7 @@ async def api_lnurlp_callback(link_id: str = Query(None), amount: str = Query(No
link = await get_satsdice_pay(link_id) link = await get_satsdice_pay(link_id)
if not link: if not link:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="LNURL-pay not found."
detail="LNURL-pay not found.",
) )
min, max = link.min_bet, link.max_bet min, max = link.min_bet, link.max_bet
@ -87,15 +89,10 @@ async def api_lnurlp_callback(link_id: str = Query(None), amount: str = Query(No
link = await create_satsdice_payment(data) link = await create_satsdice_payment(data)
if success_action: if success_action:
resp = LnurlPayActionResponse( resp = LnurlPayActionResponse(
pr=payment_request, pr=payment_request, success_action=success_action, routes=[]
success_action=success_action,
routes=[],
) )
else: else:
resp = LnurlPayActionResponse( resp = LnurlPayActionResponse(pr=payment_request, routes=[])
pr=payment_request,
routes=[],
)
return resp.dict() return resp.dict()
@ -109,15 +106,11 @@ async def api_lnurlw_response(unique_hash: str = Query(None)):
if not link: if not link:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="LNURL-satsdice not found."
detail="LNURL-satsdice not found.",
) )
if link.used: if link.used:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.OK, detail="satsdice is spent.")
status_code=HTTPStatus.OK,
detail="satsdice is spent.",
)
return link.lnurl_response.dict() return link.lnurl_response.dict()
@ -136,21 +129,14 @@ async def api_lnurlw_callback(
if not link: if not link:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="LNURL-satsdice not found."
detail="LNURL-satsdice not found.",
) )
if link.used: if link.used:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.OK, detail="satsdice is spent.")
status_code=HTTPStatus.OK,
detail="satsdice is spent.",
)
if link.k1 != k1: if link.k1 != k1:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Bad request..")
status_code=HTTPStatus.BAD_REQUEST,
detail="Bad request..",
)
if now < link.open_time: if now < link.open_time:
return {"status": "ERROR", "reason": f"Wait {link.open_time - now} seconds."} return {"status": "ERROR", "reason": f"Wait {link.open_time - now} seconds."}

View File

@ -51,11 +51,7 @@ class satsdiceLink(BaseModel):
# qs: Dict = parse_qs(url.query) # qs: Dict = parse_qs(url.query)
# qs["payment_hash"] = payment_hash # qs["payment_hash"] = payment_hash
# url = url._replace(query=urlencode(qs, doseq=True)) # url = url._replace(query=urlencode(qs, doseq=True))
return { return {"tag": "url", "description": "Check the attached link", "url": url}
"tag": "url",
"description": "Check the attached link",
"url": url,
}
class satsdicePayment(BaseModel): class satsdicePayment(BaseModel):
@ -78,9 +74,7 @@ class satsdiceWithdraw(BaseModel):
def lnurl(self, req: Request) -> Lnurl: def lnurl(self, req: Request) -> Lnurl:
return lnurl_encode( return lnurl_encode(
req.url_for( req.url_for(
"satsdice.lnurlw_response", "satsdice.lnurlw_response", unique_hash=self.unique_hash, _external=True
unique_hash=self.unique_hash,
_external=True,
) )
) )
@ -91,9 +85,7 @@ class satsdiceWithdraw(BaseModel):
@property @property
def lnurl_response(self, req: Request) -> LnurlWithdrawResponse: def lnurl_response(self, req: Request) -> LnurlWithdrawResponse:
url = req.url_for( url = req.url_for(
"satsdice.api_lnurlw_callback", "satsdice.api_lnurlw_callback", unique_hash=self.unique_hash, _external=True
unique_hash=self.unique_hash,
_external=True,
) )
return LnurlWithdrawResponse( return LnurlWithdrawResponse(
callback=url, callback=url,

View File

@ -15,9 +15,7 @@ from lnbits.core.crud import (
delete_expired_invoices, delete_expired_invoices,
get_balance_checks, get_balance_checks,
) )
from lnbits.core.services import ( from lnbits.core.services import check_invoice_status
check_invoice_status,
)
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from fastapi.params import Depends from fastapi.params import Depends
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates

View File

@ -67,14 +67,12 @@ async def api_link_retrieve(
if not link: if not link:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Pay link does not exist."
detail="Pay link does not exist.",
) )
if link.wallet != wallet.wallet.id: if link.wallet != wallet.wallet.id:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, status_code=HTTPStatus.FORBIDDEN, detail="Not your pay link."
detail="Not your pay link.",
) )
return {**link._asdict(), **{"lnurl": link.lnurl}} return {**link._asdict(), **{"lnurl": link.lnurl}}
@ -88,17 +86,13 @@ async def api_link_create_or_update(
link_id: str = Query(None), link_id: str = Query(None),
): ):
if data.min_bet > data.max_bet: if data.min_bet > data.max_bet:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Bad request")
status_code=HTTPStatus.BAD_REQUEST,
detail="Bad request",
)
if link_id: if link_id:
link = await get_satsdice_pay(link_id) link = await get_satsdice_pay(link_id)
if not link: if not link:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Satsdice does not exist"
detail="Satsdice does not exist",
) )
if link.wallet != wallet.wallet.id: if link.wallet != wallet.wallet.id:
@ -123,14 +117,12 @@ async def api_link_delete(
if not link: if not link:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Pay link does not exist."
detail="Pay link does not exist.",
) )
if link.wallet != g.wallet.id: if link.wallet != g.wallet.id:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, status_code=HTTPStatus.FORBIDDEN, detail="Not your pay link."
detail="Not your pay link.",
) )
await delete_satsdice_pay(link_id) await delete_satsdice_pay(link_id)
@ -153,10 +145,7 @@ async def api_withdraws(
return ( return (
jsonify( jsonify(
[ [
{ {**withdraw._asdict(), **{"lnurl": withdraw.lnurl}}
**withdraw._asdict(),
**{"lnurl": withdraw.lnurl},
}
for withdraw in await get_satsdice_withdraws(wallet_ids) for withdraw in await get_satsdice_withdraws(wallet_ids)
] ]
), ),
@ -177,14 +166,12 @@ async def api_withdraw_retrieve(
if not withdraw: if not withdraw:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="satsdice withdraw does not exist."
detail="satsdice withdraw does not exist.",
) )
if withdraw.wallet != wallet.wallet.id: if withdraw.wallet != wallet.wallet.id:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, status_code=HTTPStatus.FORBIDDEN, detail="Not your satsdice withdraw."
detail="Not your satsdice withdraw.",
) )
return {**withdraw._asdict(), **{"lnurl": withdraw.lnurl}}, HTTPStatus.OK return {**withdraw._asdict(), **{"lnurl": withdraw.lnurl}}, HTTPStatus.OK
@ -220,8 +207,7 @@ async def api_withdraw_create_or_update(
) )
if withdraw.wallet != wallet.wallet.id: if withdraw.wallet != wallet.wallet.id:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, status_code=HTTPStatus.FORBIDDEN, detail="Not your satsdice withdraw."
detail="Not your satsdice withdraw.",
) )
withdraw = await update_satsdice_withdraw( withdraw = await update_satsdice_withdraw(
@ -245,14 +231,12 @@ async def api_withdraw_delete(
if not withdraw: if not withdraw:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="satsdice withdraw does not exist."
detail="satsdice withdraw does not exist.",
) )
if withdraw.wallet != wallet.wallet.id: if withdraw.wallet != wallet.wallet.id:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, status_code=HTTPStatus.FORBIDDEN, detail="Not your satsdice withdraw."
detail="Not your satsdice withdraw.",
) )
await delete_satsdice_withdraw(withdraw_id) await delete_satsdice_withdraw(withdraw_id)

View File

@ -8,17 +8,11 @@ from lnbits.helpers import template_renderer
db = Database("ext_satspay") db = Database("ext_satspay")
satspay_ext: APIRouter = APIRouter( satspay_ext: APIRouter = APIRouter(prefix="/satspay", tags=["satspay"])
prefix="/satspay",
tags=["satspay"]
)
def satspay_renderer(): def satspay_renderer():
return template_renderer( return template_renderer(["lnbits/extensions/satspay/templates"])
[
"lnbits/extensions/satspay/templates",
]
)
from .views_api import * # noqa from .views_api import * # noqa

View File

@ -14,10 +14,7 @@ from ..watchonly.crud import get_watch_wallet, get_fresh_address, get_mempool
###############CHARGES########################## ###############CHARGES##########################
async def create_charge( async def create_charge(user: str, data: CreateCharge) -> Charges:
user: str,
data: CreateCharge
) -> Charges:
charge_id = urlsafe_short_hash() charge_id = urlsafe_short_hash()
if data.onchainwallet: if data.onchainwallet:
wallet = await get_watch_wallet(data.onchainwallet) wallet = await get_watch_wallet(data.onchainwallet)

View File

@ -4,6 +4,7 @@ from fastapi.param_functions import Query
from pydantic import BaseModel from pydantic import BaseModel
import time import time
class CreateCharge(BaseModel): class CreateCharge(BaseModel):
onchainwallet: str = Query(None) onchainwallet: str = Query(None)
lnbitswallet: str = Query(None) lnbitswallet: str = Query(None)
@ -14,6 +15,7 @@ class CreateCharge(BaseModel):
time: int = Query(..., ge=1) time: int = Query(..., ge=1)
amount: int = Query(..., ge=1) amount: int = Query(..., ge=1)
class Charges(BaseModel): class Charges(BaseModel):
id: str id: str
user: str user: str

View File

@ -14,9 +14,12 @@ from .crud import get_charge
templates = Jinja2Templates(directory="templates") templates = Jinja2Templates(directory="templates")
@satspay_ext.get("/", response_class=HTMLResponse) @satspay_ext.get("/", response_class=HTMLResponse)
async def index(request: Request, user: User = Depends(check_user_exists)): async def index(request: Request, user: User = Depends(check_user_exists)):
return satspay_renderer().TemplateResponse("satspay/index.html", {"request": request,"user": user.dict()}) return satspay_renderer().TemplateResponse(
"satspay/index.html", {"request": request, "user": user.dict()}
)
@satspay_ext.get("/{charge_id}", response_class=HTMLResponse) @satspay_ext.get("/{charge_id}", response_class=HTMLResponse)
@ -24,7 +27,8 @@ async def display(request: Request, charge_id):
charge = await get_charge(charge_id) charge = await get_charge(charge_id)
if not charge: if not charge:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Charge link does not exist."
detail="Charge link does not exist."
) )
return satspay_renderer().TemplateResponse("satspay/display.html", {"request": request, "charge": charge}) return satspay_renderer().TemplateResponse(
"satspay/display.html", {"request": request, "charge": charge}
)

View File

@ -30,8 +30,9 @@ from .crud import (
@satspay_ext.post("/api/v1/charge") @satspay_ext.post("/api/v1/charge")
@satspay_ext.put("/api/v1/charge/{charge_id}") @satspay_ext.put("/api/v1/charge/{charge_id}")
async def api_charge_create_or_update(
async def api_charge_create_or_update(data: CreateCharge, wallet: WalletTypeInfo = Depends(get_key_type), charge_id=None): data: CreateCharge, wallet: WalletTypeInfo = Depends(get_key_type), charge_id=None
):
if not charge_id: if not charge_id:
charge = await create_charge(user=wallet.wallet.user, data=data) charge = await create_charge(user=wallet.wallet.user, data=data)
return charge.dict() return charge.dict()
@ -44,32 +45,33 @@ async def api_charge_create_or_update(data: CreateCharge, wallet: WalletTypeInfo
async def api_charges_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)): async def api_charges_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
try: try:
return [ return [
{ {
**charge.dict(), **charge.dict(),
**{"time_elapsed": charge.time_elapsed}, **{"time_elapsed": charge.time_elapsed},
**{"paid": charge.paid}, **{"paid": charge.paid},
} }
for charge in await get_charges(wallet.wallet.user) for charge in await get_charges(wallet.wallet.user)
] ]
except: except:
return "" return ""
@satspay_ext.get("/api/v1/charge/{charge_id}") @satspay_ext.get("/api/v1/charge/{charge_id}")
async def api_charge_retrieve(charge_id, wallet: WalletTypeInfo = Depends(get_key_type)): async def api_charge_retrieve(
charge_id, wallet: WalletTypeInfo = Depends(get_key_type)
):
charge = await get_charge(charge_id) charge = await get_charge(charge_id)
if not charge: if not charge:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Charge does not exist."
detail="Charge does not exist."
) )
return { return {
**charge.dict(), **charge.dict(),
**{"time_elapsed": charge.time_elapsed}, **{"time_elapsed": charge.time_elapsed},
**{"paid": charge.paid}, **{"paid": charge.paid},
} }
@satspay_ext.delete("/api/v1/charge/{charge_id}") @satspay_ext.delete("/api/v1/charge/{charge_id}")
@ -78,8 +80,7 @@ async def api_charge_delete(charge_id, wallet: WalletTypeInfo = Depends(get_key_
if not charge: if not charge:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Charge does not exist."
detail="Charge does not exist."
) )
await delete_charge(charge_id) await delete_charge(charge_id)
@ -96,8 +97,7 @@ async def api_charges_balance(charge_id):
if not charge: if not charge:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Charge does not exist."
detail="Charge does not exist."
) )
if charge.paid and charge.webhook: if charge.paid and charge.webhook:
@ -129,7 +129,9 @@ async def api_charges_balance(charge_id):
@satspay_ext.put("/api/v1/mempool") @satspay_ext.put("/api/v1/mempool")
async def api_update_mempool(endpoint: str = Query(...), wallet: WalletTypeInfo = Depends(get_key_type)): async def api_update_mempool(
endpoint: str = Query(...), wallet: WalletTypeInfo = Depends(get_key_type)
):
mempool = await update_mempool(endpoint, user=wallet.wallet.user) mempool = await update_mempool(endpoint, user=wallet.wallet.user)
return mempool.dict() return mempool.dict()

View File

@ -14,12 +14,9 @@ tpos_ext: APIRouter = APIRouter(
# "tpos", __name__, static_folder="static", template_folder="templates" # "tpos", __name__, static_folder="static", template_folder="templates"
) )
def tpos_renderer(): def tpos_renderer():
return template_renderer( return template_renderer(["lnbits/extensions/tpos/templates"])
[
"lnbits/extensions/tpos/templates",
]
)
from .views_api import * # noqa from .views_api import * # noqa

View File

@ -7,6 +7,7 @@ class CreateTposData(BaseModel):
name: str name: str
currency: str currency: str
class TPoS(BaseModel): class TPoS(BaseModel):
id: str id: str
wallet: str wallet: str

View File

@ -13,11 +13,14 @@ from fastapi.templating import Jinja2Templates
templates = Jinja2Templates(directory="templates") templates = Jinja2Templates(directory="templates")
@tpos_ext.get("/", response_class=HTMLResponse) @tpos_ext.get("/", response_class=HTMLResponse)
# @validate_uuids(["usr"], required=True) # @validate_uuids(["usr"], required=True)
# @check_user_exists() # @check_user_exists()
async def index(request: Request, user: User = Depends(check_user_exists)): async def index(request: Request, user: User = Depends(check_user_exists)):
return tpos_renderer().TemplateResponse("tpos/index.html", {"request": request,"user": user.dict()}) return tpos_renderer().TemplateResponse(
"tpos/index.html", {"request": request, "user": user.dict()}
)
@tpos_ext.get("/{tpos_id}") @tpos_ext.get("/{tpos_id}")
@ -25,9 +28,10 @@ async def tpos(request: Request, tpos_id):
tpos = await get_tpos(tpos_id) tpos = await get_tpos(tpos_id)
if not tpos: if not tpos:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist."
detail="TPoS does not exist."
) )
# abort(HTTPStatus.NOT_FOUND, "TPoS does not exist.") # abort(HTTPStatus.NOT_FOUND, "TPoS does not exist.")
return tpos_renderer().TemplateResponse("tpos/tpos.html", {"request": request, "tpos": tpos}) return tpos_renderer().TemplateResponse(
"tpos/tpos.html", {"request": request, "tpos": tpos}
)

View File

@ -19,18 +19,19 @@ from .models import TPoS, CreateTposData
@tpos_ext.get("/api/v1/tposs", status_code=HTTPStatus.OK) @tpos_ext.get("/api/v1/tposs", status_code=HTTPStatus.OK)
async def api_tposs( async def api_tposs(
all_wallets: bool = Query(None), all_wallets: bool = Query(None), wallet: WalletTypeInfo = Depends(get_key_type)
wallet: WalletTypeInfo = Depends(get_key_type) ):
):
wallet_ids = [wallet.wallet.id] wallet_ids = [wallet.wallet.id]
if all_wallets: if all_wallets:
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
return [tpos.dict() for tpos in await get_tposs(wallet_ids)] return [tpos.dict() for tpos in await get_tposs(wallet_ids)]
@tpos_ext.post("/api/v1/tposs", status_code=HTTPStatus.CREATED) @tpos_ext.post("/api/v1/tposs", status_code=HTTPStatus.CREATED)
async def api_tpos_create(data: CreateTposData, wallet: WalletTypeInfo = Depends(get_key_type)): async def api_tpos_create(
data: CreateTposData, wallet: WalletTypeInfo = Depends(get_key_type)
):
tpos = await create_tpos(wallet_id=wallet.wallet.id, data=data) tpos = await create_tpos(wallet_id=wallet.wallet.id, data=data)
return tpos.dict() return tpos.dict()
@ -41,30 +42,26 @@ async def api_tpos_delete(tpos_id: str, wallet: WalletTypeInfo = Depends(get_key
if not tpos: if not tpos:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist."
detail="TPoS does not exist."
) )
# return {"message": "TPoS does not exist."}, HTTPStatus.NOT_FOUND # return {"message": "TPoS does not exist."}, HTTPStatus.NOT_FOUND
if tpos.wallet != wallet.wallet.id: if tpos.wallet != wallet.wallet.id:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your TPoS.")
status_code=HTTPStatus.FORBIDDEN,
detail="Not your TPoS."
)
# return {"message": "Not your TPoS."}, HTTPStatus.FORBIDDEN # return {"message": "Not your TPoS."}, HTTPStatus.FORBIDDEN
await delete_tpos(tpos_id) await delete_tpos(tpos_id)
raise HTTPException(status_code=HTTPStatus.NO_CONTENT) raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
# return "", HTTPStatus.NO_CONTENT # return "", HTTPStatus.NO_CONTENT
@tpos_ext.post("/api/v1/tposs/{tpos_id}/invoices", status_code=HTTPStatus.CREATED) @tpos_ext.post("/api/v1/tposs/{tpos_id}/invoices", status_code=HTTPStatus.CREATED)
async def api_tpos_create_invoice(amount: int = Query(..., ge=1), tpos_id: str = None): async def api_tpos_create_invoice(amount: int = Query(..., ge=1), tpos_id: str = None):
tpos = await get_tpos(tpos_id) tpos = await get_tpos(tpos_id)
if not tpos: if not tpos:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist."
detail="TPoS does not exist."
) )
# return {"message": "TPoS does not exist."}, HTTPStatus.NOT_FOUND # return {"message": "TPoS does not exist."}, HTTPStatus.NOT_FOUND
@ -76,22 +73,20 @@ async def api_tpos_create_invoice(amount: int = Query(..., ge=1), tpos_id: str =
extra={"tag": "tpos"}, extra={"tag": "tpos"},
) )
except Exception as e: except Exception as e:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail=str(e)
)
# return {"message": str(e)}, HTTPStatus.INTERNAL_SERVER_ERROR # return {"message": str(e)}, HTTPStatus.INTERNAL_SERVER_ERROR
return {"payment_hash": payment_hash, "payment_request": payment_request} return {"payment_hash": payment_hash, "payment_request": payment_request}
@tpos_ext.get("/api/v1/tposs/{tpos_id}/invoices/{payment_hash}", status_code=HTTPStatus.OK) @tpos_ext.get(
"/api/v1/tposs/{tpos_id}/invoices/{payment_hash}", status_code=HTTPStatus.OK
)
async def api_tpos_check_invoice(tpos_id: str, payment_hash: str): async def api_tpos_check_invoice(tpos_id: str, payment_hash: str):
tpos = await get_tpos(tpos_id) tpos = await get_tpos(tpos_id)
if not tpos: if not tpos:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist."
detail="TPoS does not exist."
) )
# return {"message": "TPoS does not exist."}, HTTPStatus.NOT_FOUND # return {"message": "TPoS does not exist."}, HTTPStatus.NOT_FOUND

View File

@ -10,15 +10,12 @@ db = Database("ext_usermanager")
usermanager_ext: APIRouter = APIRouter( usermanager_ext: APIRouter = APIRouter(
prefix="/usermanager", prefix="/usermanager",
tags=["usermanager"] tags=["usermanager"]
#"usermanager", __name__, static_folder="static", template_folder="templates" # "usermanager", __name__, static_folder="static", template_folder="templates"
) )
def usermanager_renderer(): def usermanager_renderer():
return template_renderer( return template_renderer(["lnbits/extensions/usermanager/templates"])
[
"lnbits/extensions/usermanager/templates",
]
)
from .views_api import * # noqa from .views_api import * # noqa

View File

@ -16,9 +16,7 @@ from .models import Users, Wallets, CreateUserData
### Users ### Users
async def create_usermanager_user( async def create_usermanager_user(data: CreateUserData) -> Users:
data: CreateUserData
) -> Users:
account = await create_account() account = await create_account()
user = await get_user(account.id) user = await get_user(account.id)
assert user, "Newly created user couldn't be retrieved" assert user, "Newly created user couldn't be retrieved"
@ -38,7 +36,14 @@ async def create_usermanager_user(
INSERT INTO usermanager.wallets (id, admin, name, "user", adminkey, inkey) INSERT INTO usermanager.wallets (id, admin, name, "user", adminkey, inkey)
VALUES (?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?)
""", """,
(wallet.id, data.admin_id, data.wallet_name, user.id, wallet.adminkey, wallet.inkey), (
wallet.id,
data.admin_id,
data.wallet_name,
user.id,
wallet.adminkey,
wallet.inkey,
),
) )
user_created = await get_usermanager_user(user.id) user_created = await get_usermanager_user(user.id)
@ -55,7 +60,7 @@ async def get_usermanager_users(user_id: str) -> List[Users]:
rows = await db.fetchall( rows = await db.fetchall(
"SELECT * FROM usermanager.users WHERE admin = ?", (user_id,) "SELECT * FROM usermanager.users WHERE admin = ?", (user_id,)
) )
return [Users(**row) for row in rows] return [Users(**row) for row in rows]

View File

@ -2,6 +2,7 @@ from pydantic import BaseModel
from fastapi.param_functions import Query from fastapi.param_functions import Query
from sqlite3 import Row from sqlite3 import Row
class CreateUserData(BaseModel): class CreateUserData(BaseModel):
user_name: str = Query(...) user_name: str = Query(...)
wallet_name: str = Query(...) wallet_name: str = Query(...)

View File

@ -8,6 +8,9 @@ from lnbits.decorators import check_user_exists
from . import usermanager_ext, usermanager_renderer from . import usermanager_ext, usermanager_renderer
@usermanager_ext.get("/", response_class=HTMLResponse) @usermanager_ext.get("/", response_class=HTMLResponse)
async def index(request: Request, user: User = Depends(check_user_exists)): async def index(request: Request, user: User = Depends(check_user_exists)):
return usermanager_renderer().TemplateResponse("usermanager/index.html", {"request": request,"user": user.dict()}) return usermanager_renderer().TemplateResponse(
"usermanager/index.html", {"request": request, "user": user.dict()}
)

View File

@ -49,20 +49,25 @@ async def api_usermanager_user(user_id, wallet: WalletTypeInfo = Depends(get_key
# "password": {"type": "string", "required": False}, # "password": {"type": "string", "required": False},
# } # }
# ) # )
async def api_usermanager_users_create(data: CreateUserData, wallet: WalletTypeInfo = Depends(get_key_type)): async def api_usermanager_users_create(
data: CreateUserData, wallet: WalletTypeInfo = Depends(get_key_type)
):
user = await create_usermanager_user(data) user = await create_usermanager_user(data)
full = user.dict() full = user.dict()
full["wallets"] = [wallet.dict() for wallet in await get_usermanager_users_wallets(user.id)] full["wallets"] = [
wallet.dict() for wallet in await get_usermanager_users_wallets(user.id)
]
return full return full
@usermanager_ext.delete("/api/v1/users/{user_id}") @usermanager_ext.delete("/api/v1/users/{user_id}")
async def api_usermanager_users_delete(user_id, wallet: WalletTypeInfo = Depends(get_key_type)): async def api_usermanager_users_delete(
user_id, wallet: WalletTypeInfo = Depends(get_key_type)
):
user = await get_usermanager_user(user_id) user = await get_usermanager_user(user_id)
if not user: if not user:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="User does not exist."
detail="User does not exist."
) )
await delete_usermanager_user(user_id) await delete_usermanager_user(user_id)
raise HTTPException(status_code=HTTPStatus.NO_CONTENT) raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
@ -72,16 +77,15 @@ async def api_usermanager_users_delete(user_id, wallet: WalletTypeInfo = Depends
@usermanager_ext.post("/api/v1/extensions") @usermanager_ext.post("/api/v1/extensions")
async def api_usermanager_activate_extension(extension: str = Query(...), userid: str = Query(...), active: bool = Query(...)): async def api_usermanager_activate_extension(
extension: str = Query(...), userid: str = Query(...), active: bool = Query(...)
):
user = await get_user(userid) user = await get_user(userid)
if not user: if not user:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="User does not exist."
detail="User does not exist."
) )
update_user_extension( update_user_extension(user_id=userid, extension=extension, active=active)
user_id=userid, extension=extension, active=active
)
return {"extension": "updated"} return {"extension": "updated"}
@ -93,11 +97,9 @@ async def api_usermanager_wallets_create(
wallet: WalletTypeInfo = Depends(get_key_type), wallet: WalletTypeInfo = Depends(get_key_type),
user_id: str = Query(...), user_id: str = Query(...),
wallet_name: str = Query(...), wallet_name: str = Query(...),
admin_id: str = Query(...) admin_id: str = Query(...),
): ):
user = await create_usermanager_wallet( user = await create_usermanager_wallet(user_id, wallet_name, admin_id)
user_id, wallet_name, admin_id
)
return user.dict() return user.dict()
@ -108,23 +110,30 @@ async def api_usermanager_wallets(wallet: WalletTypeInfo = Depends(get_key_type)
@usermanager_ext.get("/api/v1/wallets/{wallet_id}") @usermanager_ext.get("/api/v1/wallets/{wallet_id}")
async def api_usermanager_wallet_transactions(wallet_id, wallet: WalletTypeInfo = Depends(get_key_type)): async def api_usermanager_wallet_transactions(
wallet_id, wallet: WalletTypeInfo = Depends(get_key_type)
):
return await get_usermanager_wallet_transactions(wallet_id) return await get_usermanager_wallet_transactions(wallet_id)
@usermanager_ext.get("/api/v1/wallets/{user_id}") @usermanager_ext.get("/api/v1/wallets/{user_id}")
async def api_usermanager_users_wallets(user_id, wallet: WalletTypeInfo = Depends(get_key_type)): async def api_usermanager_users_wallets(
user_id, wallet: WalletTypeInfo = Depends(get_key_type)
):
# wallet = await get_usermanager_users_wallets(user_id) # wallet = await get_usermanager_users_wallets(user_id)
return [s_wallet.dict() for s_wallet in await get_usermanager_users_wallets(user_id)] return [
s_wallet.dict() for s_wallet in await get_usermanager_users_wallets(user_id)
]
@usermanager_ext.delete("/api/v1/wallets/{wallet_id}") @usermanager_ext.delete("/api/v1/wallets/{wallet_id}")
async def api_usermanager_wallets_delete(wallet_id, wallet: WalletTypeInfo = Depends(get_key_type)): async def api_usermanager_wallets_delete(
wallet_id, wallet: WalletTypeInfo = Depends(get_key_type)
):
get_wallet = await get_usermanager_wallet(wallet_id) get_wallet = await get_usermanager_wallet(wallet_id)
if not get_wallet: if not get_wallet:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Wallet does not exist."
detail="Wallet does not exist."
) )
await delete_usermanager_wallet(wallet_id, get_wallet.user) await delete_usermanager_wallet(wallet_id, get_wallet.user)
raise HTTPException(status_code=HTTPStatus.NO_CONTENT) raise HTTPException(status_code=HTTPStatus.NO_CONTENT)

View File

@ -8,17 +8,11 @@ from lnbits.helpers import template_renderer
db = Database("ext_watchonly") db = Database("ext_watchonly")
watchonly_ext: APIRouter = APIRouter( watchonly_ext: APIRouter = APIRouter(prefix="/watchonly", tags=["watchonly"])
prefix="/watchonly",
tags=["watchonly"]
)
def watchonly_renderer(): def watchonly_renderer():
return template_renderer( return template_renderer(["lnbits/extensions/watchonly/templates"])
[
"lnbits/extensions/watchonly/templates",
]
)
from .views_api import * # noqa from .views_api import * # noqa

View File

@ -2,10 +2,12 @@ from sqlite3 import Row
from fastapi.param_functions import Query from fastapi.param_functions import Query
from pydantic import BaseModel from pydantic import BaseModel
class CreateWallet(BaseModel): class CreateWallet(BaseModel):
masterpub: str = Query("") masterpub: str = Query("")
title: str = Query("") title: str = Query("")
class Wallets(BaseModel): class Wallets(BaseModel):
id: str id: str
user: str user: str

View File

@ -8,6 +8,7 @@ from lnbits.core.models import User
from lnbits.decorators import check_user_exists from lnbits.decorators import check_user_exists
from . import watchonly_ext, watchonly_renderer from . import watchonly_ext, watchonly_renderer
# from .crud import get_payment # from .crud import get_payment
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
@ -17,7 +18,9 @@ templates = Jinja2Templates(directory="templates")
@watchonly_ext.get("/", response_class=HTMLResponse) @watchonly_ext.get("/", response_class=HTMLResponse)
async def index(request: Request, user: User = Depends(check_user_exists)): async def index(request: Request, user: User = Depends(check_user_exists)):
return watchonly_renderer().TemplateResponse("watchonly/index.html", {"request": request,"user": user.dict()}) return watchonly_renderer().TemplateResponse(
"watchonly/index.html", {"request": request, "user": user.dict()}
)
# @watchonly_ext.get("/{charge_id}", response_class=HTMLResponse) # @watchonly_ext.get("/{charge_id}", response_class=HTMLResponse)

View File

@ -38,29 +38,29 @@ async def api_wallets_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
@watchonly_ext.get("/api/v1/wallet/{wallet_id}") @watchonly_ext.get("/api/v1/wallet/{wallet_id}")
async def api_wallet_retrieve(wallet_id, wallet: WalletTypeInfo = Depends(get_key_type)): async def api_wallet_retrieve(
wallet_id, wallet: WalletTypeInfo = Depends(get_key_type)
):
w_wallet = await get_watch_wallet(wallet_id) w_wallet = await get_watch_wallet(wallet_id)
if not w_wallet: if not w_wallet:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Wallet does not exist."
detail="Wallet does not exist."
) )
return w_wallet.dict() return w_wallet.dict()
@watchonly_ext.post("/api/v1/wallet") @watchonly_ext.post("/api/v1/wallet")
async def api_wallet_create_or_update(data: CreateWallet, wallet_id=None, w: WalletTypeInfo = Depends(get_key_type)): async def api_wallet_create_or_update(
data: CreateWallet, wallet_id=None, w: WalletTypeInfo = Depends(get_key_type)
):
try: try:
wallet = await create_watch_wallet( wallet = await create_watch_wallet(
user=w.wallet.user, masterpub=data.masterpub, title=data.title user=w.wallet.user, masterpub=data.masterpub, title=data.title
) )
except Exception as e: except Exception as e:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e))
status_code=HTTPStatus.BAD_REQUEST,
detail=str(e)
)
mempool = await get_mempool(w.wallet.user) mempool = await get_mempool(w.wallet.user)
if not mempool: if not mempool:
@ -74,8 +74,7 @@ async def api_wallet_delete(wallet_id, w: WalletTypeInfo = Depends(get_key_type)
if not wallet: if not wallet:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Wallet does not exist."
detail="Wallet does not exist."
) )
await delete_watch_wallet(wallet_id) await delete_watch_wallet(wallet_id)
@ -96,14 +95,12 @@ async def api_fresh_address(wallet_id, w: WalletTypeInfo = Depends(get_key_type)
@watchonly_ext.get("/api/v1/addresses/{wallet_id}") @watchonly_ext.get("/api/v1/addresses/{wallet_id}")
async def api_get_addresses(wallet_id, w: WalletTypeInfo = Depends(get_key_type)): async def api_get_addresses(wallet_id, w: WalletTypeInfo = Depends(get_key_type)):
wallet = await get_watch_wallet(wallet_id) wallet = await get_watch_wallet(wallet_id)
if not wallet: if not wallet:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Wallet does not exist."
detail="Wallet does not exist."
) )
addresses = await get_addresses(wallet_id) addresses = await get_addresses(wallet_id)
@ -119,7 +116,9 @@ async def api_get_addresses(wallet_id, w: WalletTypeInfo = Depends(get_key_type)
@watchonly_ext.put("/api/v1/mempool") @watchonly_ext.put("/api/v1/mempool")
async def api_update_mempool(endpoint: str = Query(...), w: WalletTypeInfo = Depends(get_key_type)): async def api_update_mempool(
endpoint: str = Query(...), w: WalletTypeInfo = Depends(get_key_type)
):
mempool = await update_mempool(endpoint, user=w.wallet.user) mempool = await update_mempool(endpoint, user=w.wallet.user)
return mempool.dict() return mempool.dict()

View File

@ -21,12 +21,9 @@ withdraw_ext: APIRouter = APIRouter(
# "withdraw", __name__, static_folder="static", template_folder="templates" # "withdraw", __name__, static_folder="static", template_folder="templates"
) )
def withdraw_renderer(): def withdraw_renderer():
return template_renderer( return template_renderer(["lnbits/extensions/withdraw/templates"])
[
"lnbits/extensions/withdraw/templates",
]
)
from .views_api import * # noqa from .views_api import * # noqa

View File

@ -7,9 +7,7 @@ from .models import WithdrawLink, HashCheck, CreateWithdrawData
async def create_withdraw_link( async def create_withdraw_link(
data: CreateWithdrawData, data: CreateWithdrawData, wallet_id: str, usescsv: str
wallet_id: str,
usescsv: str,
) -> WithdrawLink: ) -> WithdrawLink:
link_id = urlsafe_short_hash() link_id = urlsafe_short_hash()
await db.execute( await db.execute(
@ -115,10 +113,7 @@ def chunks(lst, n):
yield lst[i : i + n] yield lst[i : i + n]
async def create_hash_check( async def create_hash_check(the_hash: str, lnurl_id: str) -> HashCheck:
the_hash: str,
lnurl_id: str,
) -> HashCheck:
await db.execute( await db.execute(
""" """
INSERT INTO withdraw.hash_check ( INSERT INTO withdraw.hash_check (
@ -127,10 +122,7 @@ async def create_hash_check(
) )
VALUES (?, ?) VALUES (?, ?)
""", """,
( (the_hash, lnurl_id),
the_hash,
lnurl_id,
),
) )
hashCheck = await get_hash_check(the_hash, lnurl_id) hashCheck = await get_hash_check(the_hash, lnurl_id)
return hashCheck return hashCheck

View File

@ -14,14 +14,17 @@ from .crud import get_withdraw_link_by_hash, update_withdraw_link
# FOR LNURLs WHICH ARE NOT UNIQUE # FOR LNURLs WHICH ARE NOT UNIQUE
@withdraw_ext.get("/api/v1/lnurl/{unique_hash}", status_code=HTTPStatus.OK, name="withdraw.api_lnurl_response") @withdraw_ext.get(
"/api/v1/lnurl/{unique_hash}",
status_code=HTTPStatus.OK,
name="withdraw.api_lnurl_response",
)
async def api_lnurl_response(request: Request, unique_hash): async def api_lnurl_response(request: Request, unique_hash):
link = await get_withdraw_link_by_hash(unique_hash) link = await get_withdraw_link_by_hash(unique_hash)
if not link: if not link:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Withdraw link does not exist."
detail="Withdraw link does not exist."
) )
# return ({"status": "ERROR", "reason": "LNURL-withdraw not found."}, # return ({"status": "ERROR", "reason": "LNURL-withdraw not found."},
# HTTPStatus.OK, # HTTPStatus.OK,
@ -39,23 +42,22 @@ async def api_lnurl_response(request: Request, unique_hash):
return link.lnurl_response(request).dict() return link.lnurl_response(request).dict()
# CALLBACK # CALLBACK
@withdraw_ext.get("/api/v1/lnurl/cb/{unique_hash}", name="withdraw.api_lnurl_callback") @withdraw_ext.get("/api/v1/lnurl/cb/{unique_hash}", name="withdraw.api_lnurl_callback")
async def api_lnurl_callback(request: Request, async def api_lnurl_callback(
unique_hash: str=Query(...), request: Request,
k1: str = Query(...), unique_hash: str = Query(...),
payment_request: str = Query(..., alias="pr") k1: str = Query(...),
): payment_request: str = Query(..., alias="pr"),
):
link = await get_withdraw_link_by_hash(unique_hash) link = await get_withdraw_link_by_hash(unique_hash)
now = int(datetime.now().timestamp()) now = int(datetime.now().timestamp())
if not link: if not link:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="LNURL-withdraw not found."
detail="LNURL-withdraw not found."
) )
# return ( # return (
# {"status": "ERROR", "reason": "LNURL-withdraw not found."}, # {"status": "ERROR", "reason": "LNURL-withdraw not found."},
@ -73,10 +75,7 @@ async def api_lnurl_callback(request: Request,
# ) # )
if link.k1 != k1: if link.k1 != k1:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Bad request.")
status_code=HTTPStatus.BAD_REQUEST,
detail="Bad request."
)
# return {"status": "ERROR", "reason": "Bad request."}, HTTPStatus.OK # return {"status": "ERROR", "reason": "Bad request."}, HTTPStatus.OK
if now < link.open_time: if now < link.open_time:
@ -123,17 +122,21 @@ async def api_lnurl_callback(request: Request,
return {"status": "OK"} return {"status": "OK"}
# FOR LNURLs WHICH ARE UNIQUE # FOR LNURLs WHICH ARE UNIQUE
@withdraw_ext.get("/api/v1/lnurl/{unique_hash}/{id_unique_hash}", status_code=HTTPStatus.OK, name="withdraw.api_lnurl_multi_response") @withdraw_ext.get(
"/api/v1/lnurl/{unique_hash}/{id_unique_hash}",
status_code=HTTPStatus.OK,
name="withdraw.api_lnurl_multi_response",
)
async def api_lnurl_multi_response(request: Request, unique_hash, id_unique_hash): async def api_lnurl_multi_response(request: Request, unique_hash, id_unique_hash):
link = await get_withdraw_link_by_hash(unique_hash) link = await get_withdraw_link_by_hash(unique_hash)
if not link: if not link:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="LNURL-withdraw not found."
detail="LNURL-withdraw not found."
) )
# return ( # return (
# {"status": "ERROR", "reason": "LNURL-withdraw not found."}, # {"status": "ERROR", "reason": "LNURL-withdraw not found."},
@ -158,8 +161,7 @@ async def api_lnurl_multi_response(request: Request, unique_hash, id_unique_hash
found = True found = True
if not found: if not found:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="LNURL-withdraw not found."
detail="LNURL-withdraw not found."
) )
# return ( # return (
# {"status": "ERROR", "reason": "LNURL-withdraw not found."}, # {"status": "ERROR", "reason": "LNURL-withdraw not found."},

View File

@ -5,13 +5,14 @@ from sqlite3 import Row
from pydantic import BaseModel from pydantic import BaseModel
import shortuuid # type: ignore import shortuuid # type: ignore
class CreateWithdrawData(BaseModel): class CreateWithdrawData(BaseModel):
title: str = Query(...) title: str = Query(...)
min_withdrawable: int = Query(..., ge=1) min_withdrawable: int = Query(..., ge=1)
max_withdrawable: int = Query(..., ge=1) max_withdrawable: int = Query(..., ge=1)
uses: int = Query(..., ge=1) uses: int = Query(..., ge=1)
wait_time: int = Query(..., ge=1) wait_time: int = Query(..., ge=1)
is_unique: bool is_unique: bool
class WithdrawLink(BaseModel): class WithdrawLink(BaseModel):
@ -49,17 +50,15 @@ class WithdrawLink(BaseModel):
url = req.url_for( url = req.url_for(
"withdraw.api_lnurl_multi_response", "withdraw.api_lnurl_multi_response",
unique_hash=self.unique_hash, unique_hash=self.unique_hash,
id_unique_hash=multihash id_unique_hash=multihash,
) )
else: else:
url = req.url_for( url = req.url_for(
"withdraw.api_lnurl_response", "withdraw.api_lnurl_response", unique_hash=self.unique_hash
unique_hash=self.unique_hash
) )
return lnurl_encode(url) return lnurl_encode(url)
def lnurl_response(self, req: Request) -> LnurlWithdrawResponse: def lnurl_response(self, req: Request) -> LnurlWithdrawResponse:
url = req.url_for( url = req.url_for(
name="withdraw.api_lnurl_callback", unique_hash=self.unique_hash name="withdraw.api_lnurl_callback", unique_hash=self.unique_hash

View File

@ -15,11 +15,14 @@ from lnbits.core.models import User
templates = Jinja2Templates(directory="templates") templates = Jinja2Templates(directory="templates")
@withdraw_ext.get("/", response_class=HTMLResponse) @withdraw_ext.get("/", response_class=HTMLResponse)
# @validate_uuids(["usr"], required=True) # @validate_uuids(["usr"], required=True)
# @check_user_exists() # @check_user_exists()
async def index(request: Request, user: User = Depends(check_user_exists)): async def index(request: Request, user: User = Depends(check_user_exists)):
return withdraw_renderer().TemplateResponse("withdraw/index.html", {"request":request,"user": user.dict()}) return withdraw_renderer().TemplateResponse(
"withdraw/index.html", {"request": request, "user": user.dict()}
)
@withdraw_ext.get("/{link_id}", response_class=HTMLResponse) @withdraw_ext.get("/{link_id}", response_class=HTMLResponse)
@ -28,13 +31,20 @@ async def display(request: Request, link_id):
if not link: if not link:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Withdraw link does not exist."
detail="Withdraw link does not exist."
) )
# response.status_code = HTTPStatus.NOT_FOUND # response.status_code = HTTPStatus.NOT_FOUND
# return "Withdraw link does not exist." #probably here is where we should return the 404?? # return "Withdraw link does not exist." #probably here is where we should return the 404??
print("LINK", link) print("LINK", link)
return withdraw_renderer().TemplateResponse("withdraw/display.html", {"request":request,"link":link.dict(), "lnurl": link.lnurl(req=request), "unique":True}) return withdraw_renderer().TemplateResponse(
"withdraw/display.html",
{
"request": request,
"link": link.dict(),
"lnurl": link.lnurl(req=request),
"unique": True,
},
)
@withdraw_ext.get("/img/{link_id}", response_class=StreamingResponse) @withdraw_ext.get("/img/{link_id}", response_class=StreamingResponse)
@ -42,8 +52,7 @@ async def img(request: Request, link_id):
link = await get_withdraw_link(link_id, 0) link = await get_withdraw_link(link_id, 0)
if not link: if not link:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Withdraw link does not exist."
detail="Withdraw link does not exist."
) )
# response.status_code = HTTPStatus.NOT_FOUND # response.status_code = HTTPStatus.NOT_FOUND
# return "Withdraw link does not exist." # return "Withdraw link does not exist."
@ -63,7 +72,8 @@ async def img(request: Request, link_id):
"Cache-Control": "no-cache, no-store, must-revalidate", "Cache-Control": "no-cache, no-store, must-revalidate",
"Pragma": "no-cache", "Pragma": "no-cache",
"Expires": "0", "Expires": "0",
}) },
)
@withdraw_ext.get("/print/{link_id}", response_class=HTMLResponse) @withdraw_ext.get("/print/{link_id}", response_class=HTMLResponse)
@ -71,15 +81,17 @@ async def print_qr(request: Request, link_id):
link = await get_withdraw_link(link_id) link = await get_withdraw_link(link_id)
if not link: if not link:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Withdraw link does not exist."
detail="Withdraw link does not exist."
) )
# response.status_code = HTTPStatus.NOT_FOUND # response.status_code = HTTPStatus.NOT_FOUND
# return "Withdraw link does not exist." # return "Withdraw link does not exist."
if link.uses == 0: if link.uses == 0:
return withdraw_renderer().TemplateResponse("withdraw/print_qr.html", {"request":request,"link":link.dict(), unique:False}) return withdraw_renderer().TemplateResponse(
"withdraw/print_qr.html",
{"request": request, "link": link.dict(), unique: False},
)
links = [] links = []
count = 0 count = 0
@ -87,8 +99,7 @@ async def print_qr(request: Request, link_id):
linkk = await get_withdraw_link(link_id, count) linkk = await get_withdraw_link(link_id, count)
if not linkk: if not linkk:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, status_code=HTTPStatus.NOT_FOUND, detail="Withdraw link does not exist."
detail="Withdraw link does not exist."
) )
# response.status_code = HTTPStatus.NOT_FOUND # response.status_code = HTTPStatus.NOT_FOUND
# return "Withdraw link does not exist." # return "Withdraw link does not exist."
@ -97,4 +108,6 @@ async def print_qr(request: Request, link_id):
page_link = list(chunks(links, 2)) page_link = list(chunks(links, 2))
linked = list(chunks(page_link, 5)) linked = list(chunks(page_link, 5))
print("LINKED", linked) print("LINKED", linked)
return withdraw_renderer().TemplateResponse("withdraw/print_qr.html", {"request":request,"link":linked, "unique":True}) return withdraw_renderer().TemplateResponse(
"withdraw/print_qr.html", {"request": request, "link": linked, "unique": True}
)

View File

@ -28,7 +28,11 @@ from .crud import (
@withdraw_ext.get("/api/v1/links", status_code=HTTPStatus.OK) @withdraw_ext.get("/api/v1/links", status_code=HTTPStatus.OK)
# @api_check_wallet_key("invoice") # @api_check_wallet_key("invoice")
async def api_links(req: Request, wallet: WalletTypeInfo = Depends(get_key_type), all_wallets: bool = Query(False)): async def api_links(
req: Request,
wallet: WalletTypeInfo = Depends(get_key_type),
all_wallets: bool = Query(False),
):
wallet_ids = [wallet.wallet.id] wallet_ids = [wallet.wallet.id]
if all_wallets: if all_wallets:
@ -36,12 +40,9 @@ async def api_links(req: Request, wallet: WalletTypeInfo = Depends(get_key_type)
try: try:
return [ return [
{ {**link.dict(), **{"lnurl": link.lnurl(req)}}
**link.dict(), for link in await get_withdraw_links(wallet_ids)
**{"lnurl": link.lnurl(req)}, ]
}
for link in await get_withdraw_links(wallet_ids)
]
except LnurlInvalidUrl: except LnurlInvalidUrl:
raise HTTPException( raise HTTPException(
@ -59,21 +60,20 @@ async def api_link_retrieve(link_id, wallet: WalletTypeInfo = Depends(get_key_ty
if not link: if not link:
raise HTTPException( raise HTTPException(
detail="Withdraw link does not exist.", detail="Withdraw link does not exist.", status_code=HTTPStatus.NOT_FOUND
status_code=HTTPStatus.NOT_FOUND
) )
# response.status_code = HTTPStatus.NOT_FOUND # response.status_code = HTTPStatus.NOT_FOUND
# return {"message": "Withdraw link does not exist."} # return {"message": "Withdraw link does not exist."}
if link.wallet != wallet.wallet.id: if link.wallet != wallet.wallet.id:
raise HTTPException( raise HTTPException(
detail="Not your withdraw link.", detail="Not your withdraw link.", status_code=HTTPStatus.FORBIDDEN
status_code=HTTPStatus.FORBIDDEN
) )
# response.status_code = HTTPStatus.FORBIDDEN # response.status_code = HTTPStatus.FORBIDDEN
# return {"message": "Not your withdraw link."} # return {"message": "Not your withdraw link."}
return {**link, **{"lnurl": link.lnurl(request)}} return {**link, **{"lnurl": link.lnurl(request)}}
# class CreateData(BaseModel): # class CreateData(BaseModel):
# title: str = Query(...) # title: str = Query(...)
# min_withdrawable: int = Query(..., ge=1) # min_withdrawable: int = Query(..., ge=1)
@ -82,14 +82,20 @@ async def api_link_retrieve(link_id, wallet: WalletTypeInfo = Depends(get_key_ty
# wait_time: int = Query(..., ge=1) # wait_time: int = Query(..., ge=1)
# is_unique: bool # is_unique: bool
@withdraw_ext.post("/api/v1/links", status_code=HTTPStatus.CREATED) @withdraw_ext.post("/api/v1/links", status_code=HTTPStatus.CREATED)
@withdraw_ext.put("/api/v1/links/{link_id}", status_code=HTTPStatus.OK) @withdraw_ext.put("/api/v1/links/{link_id}", status_code=HTTPStatus.OK)
# @api_check_wallet_key("admin") # @api_check_wallet_key("admin")
async def api_link_create_or_update(req: Request, data: CreateWithdrawData, link_id: str = None, wallet: WalletTypeInfo = Depends(get_key_type)): async def api_link_create_or_update(
req: Request,
data: CreateWithdrawData,
link_id: str = None,
wallet: WalletTypeInfo = Depends(get_key_type),
):
if data.max_withdrawable < data.min_withdrawable: if data.max_withdrawable < data.min_withdrawable:
raise HTTPException( raise HTTPException(
detail="`max_withdrawable` needs to be at least `min_withdrawable`.", detail="`max_withdrawable` needs to be at least `min_withdrawable`.",
status_code=HTTPStatus.BAD_REQUEST status_code=HTTPStatus.BAD_REQUEST,
) )
# response.status_code = HTTPStatus.BAD_REQUEST # response.status_code = HTTPStatus.BAD_REQUEST
# return { # return {
@ -108,15 +114,13 @@ async def api_link_create_or_update(req: Request, data: CreateWithdrawData, link
link = await get_withdraw_link(link_id, 0) link = await get_withdraw_link(link_id, 0)
if not link: if not link:
raise HTTPException( raise HTTPException(
detail="Withdraw link does not exist.", detail="Withdraw link does not exist.", status_code=HTTPStatus.NOT_FOUND
status_code=HTTPStatus.NOT_FOUND
) )
# response.status_code = HTTPStatus.NOT_FOUND # response.status_code = HTTPStatus.NOT_FOUND
# return {"message": "Withdraw link does not exist."} # return {"message": "Withdraw link does not exist."}
if link.wallet != wallet.wallet.id: if link.wallet != wallet.wallet.id:
raise HTTPException( raise HTTPException(
detail="Not your withdraw link.", detail="Not your withdraw link.", status_code=HTTPStatus.FORBIDDEN
status_code=HTTPStatus.FORBIDDEN
) )
# response.status_code = HTTPStatus.FORBIDDEN # response.status_code = HTTPStatus.FORBIDDEN
# return {"message": "Not your withdraw link."} # return {"message": "Not your withdraw link."}
@ -137,16 +141,14 @@ async def api_link_delete(link_id, wallet: WalletTypeInfo = Depends(get_key_type
if not link: if not link:
raise HTTPException( raise HTTPException(
detail="Withdraw link does not exist.", detail="Withdraw link does not exist.", status_code=HTTPStatus.NOT_FOUND
status_code=HTTPStatus.NOT_FOUND
) )
# response.status_code = HTTPStatus.NOT_FOUND # response.status_code = HTTPStatus.NOT_FOUND
# return {"message": "Withdraw link does not exist."} # return {"message": "Withdraw link does not exist."}
if link.wallet != wallet.wallet.id: if link.wallet != wallet.wallet.id:
raise HTTPException( raise HTTPException(
detail="Not your withdraw link.", detail="Not your withdraw link.", status_code=HTTPStatus.FORBIDDEN
status_code=HTTPStatus.FORBIDDEN
) )
# response.status_code = HTTPStatus.FORBIDDEN # response.status_code = HTTPStatus.FORBIDDEN
# return {"message": "Not your withdraw link."} # return {"message": "Not your withdraw link."}
@ -158,6 +160,8 @@ async def api_link_delete(link_id, wallet: WalletTypeInfo = Depends(get_key_type
@withdraw_ext.get("/api/v1/links/{the_hash}/{lnurl_id}", status_code=HTTPStatus.OK) @withdraw_ext.get("/api/v1/links/{the_hash}/{lnurl_id}", status_code=HTTPStatus.OK)
# @api_check_wallet_key("invoice") # @api_check_wallet_key("invoice")
async def api_hash_retrieve(the_hash, lnurl_id, wallet: WalletTypeInfo = Depends(get_key_type)): async def api_hash_retrieve(
the_hash, lnurl_id, wallet: WalletTypeInfo = Depends(get_key_type)
):
hashCheck = await get_hash_check(the_hash, lnurl_id) hashCheck = await get_hash_check(the_hash, lnurl_id)
return hashCheck return hashCheck

View File

@ -41,7 +41,9 @@ class ExtensionManager:
]: ]:
try: try:
with open( with open(
os.path.join(settings.LNBITS_PATH, "extensions", extension, "config.json") os.path.join(
settings.LNBITS_PATH, "extensions", extension, "config.json"
)
) as json_file: ) as json_file:
config = json.load(json_file) config = json.load(json_file)
is_valid = True is_valid = True
@ -137,11 +139,8 @@ def get_vendored(ext: str, prefer_minified: bool = False) -> List[str]:
def url_for_vendored(abspath: str) -> str: def url_for_vendored(abspath: str) -> str:
return "/" + os.path.relpath(abspath, settings.LNBITS_PATH) return "/" + os.path.relpath(abspath, settings.LNBITS_PATH)
def url_for(
endpoint: str, def url_for(endpoint: str, external: Optional[bool] = False, **params: Any) -> str:
external: Optional[bool] = False,
**params: Any,
) -> str:
base = g().base_url if external else "" base = g().base_url if external else ""
url_params = "?" url_params = "?"
for key in params: for key in params:
@ -149,9 +148,12 @@ def url_for(
url = f"{base}{endpoint}{url_params}" url = f"{base}{endpoint}{url_params}"
return url return url
def template_renderer(additional_folders: List = []) -> Jinja2Templates: def template_renderer(additional_folders: List = []) -> Jinja2Templates:
t = Jinja2Templates( t = Jinja2Templates(
loader=jinja2.FileSystemLoader(["lnbits/templates", "lnbits/core/templates", *additional_folders]), loader=jinja2.FileSystemLoader(
["lnbits/templates", "lnbits/core/templates", *additional_folders]
)
) )
t.env.globals["SITE_TITLE"] = settings.LNBITS_SITE_TITLE t.env.globals["SITE_TITLE"] = settings.LNBITS_SITE_TITLE
t.env.globals["SITE_TAGLINE"] = settings.LNBITS_SITE_TAGLINE t.env.globals["SITE_TAGLINE"] = settings.LNBITS_SITE_TAGLINE
@ -159,7 +161,7 @@ def template_renderer(additional_folders: List = []) -> Jinja2Templates:
t.env.globals["LNBITS_THEME_OPTIONS"] = settings.LNBITS_THEME_OPTIONS t.env.globals["LNBITS_THEME_OPTIONS"] = settings.LNBITS_THEME_OPTIONS
t.env.globals["LNBITS_VERSION"] = settings.LNBITS_COMMIT t.env.globals["LNBITS_VERSION"] = settings.LNBITS_COMMIT
t.env.globals["EXTENSIONS"] = get_valid_extensions() t.env.globals["EXTENSIONS"] = get_valid_extensions()
if settings.DEBUG: if settings.DEBUG:
t.env.globals["VENDORED_JS"] = map(url_for_vendored, get_js_vendored()) t.env.globals["VENDORED_JS"] = map(url_for_vendored, get_js_vendored())
t.env.globals["VENDORED_CSS"] = map(url_for_vendored, get_css_vendored()) t.env.globals["VENDORED_CSS"] = map(url_for_vendored, get_css_vendored())

View File

@ -1,4 +1,4 @@
# Borrowed from the excellent accent-starlette # Borrowed from the excellent accent-starlette
# https://github.com/accent-starlette/starlette-core/blob/master/starlette_core/templating.py # https://github.com/accent-starlette/starlette-core/blob/master/starlette_core/templating.py
import typing import typing
@ -23,7 +23,7 @@ class Jinja2Templates(templating.Jinja2Templates):
def get_environment(self, loader: "jinja2.BaseLoader") -> "jinja2.Environment": def get_environment(self, loader: "jinja2.BaseLoader") -> "jinja2.Environment":
@jinja2.contextfunction @jinja2.contextfunction
def url_for(context: dict, name: str, **path_params: typing.Any) -> str: def url_for(context: dict, name: str, **path_params: typing.Any) -> str:
request: Request = context["request"] # type: starlette.requests.Request request: Request = context["request"] # type: starlette.requests.Request
return request.app.url_path_for(name, **path_params) return request.app.url_path_for(name, **path_params)
def url_params_update(init: QueryParams, **new: typing.Any) -> QueryParams: def url_params_update(init: QueryParams, **new: typing.Any) -> QueryParams:

View File

@ -1,8 +1,9 @@
import contextvars import contextvars
import types import types
request_global = contextvars.ContextVar("request_global", request_global = contextvars.ContextVar(
default=types.SimpleNamespace()) "request_global", default=types.SimpleNamespace()
)
def g() -> types.SimpleNamespace: def g() -> types.SimpleNamespace:

View File

@ -52,8 +52,7 @@ SERVICE_FEE = env.float("LNBITS_SERVICE_FEE", default=0.0)
try: try:
LNBITS_COMMIT = ( LNBITS_COMMIT = (
subprocess.check_output( subprocess.check_output(
["git", "-C", LNBITS_PATH, "rev-parse", "HEAD"], ["git", "-C", LNBITS_PATH, "rev-parse", "HEAD"], stderr=subprocess.DEVNULL
stderr=subprocess.DEVNULL,
) )
.strip() .strip()
.decode("ascii") .decode("ascii")

View File

@ -35,7 +35,7 @@ class CLightningWallet(Wallet):
try: try:
answer = self.ln.help("invoicewithdescriptionhash") answer = self.ln.help("invoicewithdescriptionhash")
if answer["help"][0]["command"].startswith( if answer["help"][0]["command"].startswith(
"invoicewithdescriptionhash msatoshi label description_hash", "invoicewithdescriptionhash msatoshi label description_hash"
): ):
self.supports_description_hash = True self.supports_description_hash = True
except: except:
@ -53,8 +53,7 @@ class CLightningWallet(Wallet):
try: try:
funds = self.ln.listfunds() funds = self.ln.listfunds()
return StatusResponse( return StatusResponse(
None, None, sum([ch["channel_sat"] * 1000 for ch in funds["channels"]])
sum([ch["channel_sat"] * 1000 for ch in funds["channels"]]),
) )
except RpcError as exc: except RpcError as exc:
error_message = f"lightningd '{exc.method}' failed with '{exc.error}'." error_message = f"lightningd '{exc.method}' failed with '{exc.error}'."
@ -121,11 +120,7 @@ class CLightningWallet(Wallet):
i = 0 i = 0
while True: while True:
call = json.dumps( call = json.dumps(
{ {"method": "waitanyinvoice", "id": 0, "params": [self.last_pay_index]}
"method": "waitanyinvoice",
"id": 0,
"params": [self.last_pay_index],
}
) )
await stream.send_all(call.encode("utf-8")) await stream.send_all(call.encode("utf-8"))

View File

@ -30,9 +30,7 @@ class LNbitsWallet(Wallet):
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
try: try:
r = await client.get( r = await client.get(
url=f"{self.endpoint}/api/v1/wallet", url=f"{self.endpoint}/api/v1/wallet", headers=self.key, timeout=15
headers=self.key,
timeout=15,
) )
except Exception as exc: except Exception as exc:
return StatusResponse( return StatusResponse(
@ -65,9 +63,7 @@ class LNbitsWallet(Wallet):
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
r = await client.post( r = await client.post(
url=f"{self.endpoint}/api/v1/payments", url=f"{self.endpoint}/api/v1/payments", headers=self.key, json=data
headers=self.key,
json=data,
) )
ok, checking_id, payment_request, error_message = ( ok, checking_id, payment_request, error_message = (
not r.is_error, not r.is_error,

View File

@ -64,19 +64,11 @@ def load_macaroon(macaroon_path: str):
def parse_checking_id(checking_id: str) -> bytes: def parse_checking_id(checking_id: str) -> bytes:
return base64.b64decode( return base64.b64decode(checking_id.replace("_", "/"))
checking_id.replace("_", "/"),
)
def stringify_checking_id(r_hash: bytes) -> str: def stringify_checking_id(r_hash: bytes) -> str:
return ( return base64.b64encode(r_hash).decode("utf-8").replace("/", "_")
base64.b64encode(
r_hash,
)
.decode("utf-8")
.replace("/", "_")
)
class LndWallet(Wallet): class LndWallet(Wallet):
@ -177,28 +169,23 @@ class LndWallet(Wallet):
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
async with purerpc.secure_channel( async with purerpc.secure_channel(
self.endpoint, self.endpoint, self.port, get_ssl_context(self.cert_path)
self.port,
get_ssl_context(self.cert_path),
) as channel: ) as channel:
client = purerpc.Client("lnrpc.Lightning", channel) client = purerpc.Client("lnrpc.Lightning", channel)
subscribe_invoices = client.get_method_stub( subscribe_invoices = client.get_method_stub(
"SubscribeInvoices", "SubscribeInvoices",
purerpc.RPCSignature( purerpc.RPCSignature(
purerpc.Cardinality.UNARY_STREAM, purerpc.Cardinality.UNARY_STREAM, ln.InvoiceSubscription, ln.Invoice
ln.InvoiceSubscription,
ln.Invoice,
), ),
) )
if self.macaroon_path.split('.')[-1] == 'macaroon': if self.macaroon_path.split(".")[-1] == "macaroon":
macaroon = load_macaroon(self.macaroon_path) macaroon = load_macaroon(self.macaroon_path)
else: else:
macaroon = self.macaroon_path macaroon = self.macaroon_path
async for inv in subscribe_invoices( async for inv in subscribe_invoices(
ln.InvoiceSubscription(), ln.InvoiceSubscription(), metadata=[("macaroon", macaroon)]
metadata=[("macaroon", macaroon)],
): ):
if not inv.settled: if not inv.settled:
continue continue

View File

@ -39,8 +39,7 @@ class LndRestWallet(Wallet):
try: try:
async with httpx.AsyncClient(verify=self.cert) as client: async with httpx.AsyncClient(verify=self.cert) as client:
r = await client.get( r = await client.get(
f"{self.endpoint}/v1/balance/channels", f"{self.endpoint}/v1/balance/channels", headers=self.auth
headers=self.auth,
) )
except (httpx.ConnectError, httpx.RequestError): except (httpx.ConnectError, httpx.RequestError):
return StatusResponse(f"Unable to connect to {self.endpoint}.", 0) return StatusResponse(f"Unable to connect to {self.endpoint}.", 0)
@ -60,10 +59,7 @@ class LndRestWallet(Wallet):
memo: Optional[str] = None, memo: Optional[str] = None,
description_hash: Optional[bytes] = None, description_hash: Optional[bytes] = None,
) -> InvoiceResponse: ) -> InvoiceResponse:
data: Dict = { data: Dict = {"value": amount, "private": True}
"value": amount,
"private": True,
}
if description_hash: if description_hash:
data["description_hash"] = base64.b64encode(description_hash).decode( data["description_hash"] = base64.b64encode(description_hash).decode(
"ascii" "ascii"
@ -73,9 +69,7 @@ class LndRestWallet(Wallet):
async with httpx.AsyncClient(verify=self.cert) as client: async with httpx.AsyncClient(verify=self.cert) as client:
r = await client.post( r = await client.post(
url=f"{self.endpoint}/v1/invoices", url=f"{self.endpoint}/v1/invoices", headers=self.auth, json=data
headers=self.auth,
json=data,
) )
if r.is_error: if r.is_error:
@ -117,8 +111,7 @@ class LndRestWallet(Wallet):
async with httpx.AsyncClient(verify=self.cert) as client: async with httpx.AsyncClient(verify=self.cert) as client:
r = await client.get( r = await client.get(
url=f"{self.endpoint}/v1/invoice/{checking_id}", url=f"{self.endpoint}/v1/invoice/{checking_id}", headers=self.auth
headers=self.auth,
) )
if r.is_error or not r.json().get("settled"): if r.is_error or not r.json().get("settled"):
@ -164,9 +157,7 @@ class LndRestWallet(Wallet):
while True: while True:
try: try:
async with httpx.AsyncClient( async with httpx.AsyncClient(
timeout=None, timeout=None, headers=self.auth, verify=self.cert
headers=self.auth,
verify=self.cert,
) as client: ) as client:
async with client.stream("GET", url) as r: async with client.stream("GET", url) as r:
async for line in r.aiter_lines(): async for line in r.aiter_lines():

View File

@ -139,12 +139,10 @@ class LNPayWallet(Wallet):
lntx_id = data["data"]["wtx"]["lnTx"]["id"] lntx_id = data["data"]["wtx"]["lnTx"]["id"]
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
r = await client.get( r = await client.get(
f"{self.endpoint}/lntx/{lntx_id}?fields=settled", f"{self.endpoint}/lntx/{lntx_id}?fields=settled", headers=self.auth
headers=self.auth,
) )
data = r.json() data = r.json()
if data["settled"]: if data["settled"]:
await self.queue.put(lntx_id) await self.queue.put(lntx_id)
raise HTTPException(status_code=HTTPStatus.NO_CONTENT) raise HTTPException(status_code=HTTPStatus.NO_CONTENT)

View File

@ -30,9 +30,7 @@ class LntxbotWallet(Wallet):
async def status(self) -> StatusResponse: async def status(self) -> StatusResponse:
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
r = await client.get( r = await client.get(
f"{self.endpoint}/balance", f"{self.endpoint}/balance", headers=self.auth, timeout=40
headers=self.auth,
timeout=40,
) )
try: try:
data = r.json() data = r.json()
@ -60,10 +58,7 @@ class LntxbotWallet(Wallet):
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
r = await client.post( r = await client.post(
f"{self.endpoint}/addinvoice", f"{self.endpoint}/addinvoice", headers=self.auth, json=data, timeout=40
headers=self.auth,
json=data,
timeout=40,
) )
if r.is_error: if r.is_error:
@ -123,8 +118,7 @@ class LntxbotWallet(Wallet):
async def get_payment_status(self, checking_id: str) -> PaymentStatus: async def get_payment_status(self, checking_id: str) -> PaymentStatus:
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
r = await client.post( r = await client.post(
url=f"{self.endpoint}/paymentstatus/{checking_id}", url=f"{self.endpoint}/paymentstatus/{checking_id}", headers=self.auth
headers=self.auth,
) )
data = r.json() data = r.json()

View File

@ -36,9 +36,7 @@ class OpenNodeWallet(Wallet):
try: try:
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
r = await client.get( r = await client.get(
f"{self.endpoint}/v1/account/balance", f"{self.endpoint}/v1/account/balance", headers=self.auth, timeout=40
headers=self.auth,
timeout=40,
) )
except (httpx.ConnectError, httpx.RequestError): except (httpx.ConnectError, httpx.RequestError):
return StatusResponse(f"Unable to connect to '{self.endpoint}'", 0) return StatusResponse(f"Unable to connect to '{self.endpoint}'", 0)
@ -137,7 +135,6 @@ class OpenNodeWallet(Wallet):
if "status" not in data or data["status"] != "paid": if "status" not in data or data["status"] != "paid":
raise HTTPException(status_code=HTTPStatus.NO_CONTENT) raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
charge_id = data["id"] charge_id = data["id"]
x = hmac.new(self.auth["Authorization"].encode("ascii"), digestmod="sha256") x = hmac.new(self.auth["Authorization"].encode("ascii"), digestmod="sha256")
x.update(charge_id.encode("ascii")) x.update(charge_id.encode("ascii"))
@ -147,4 +144,3 @@ class OpenNodeWallet(Wallet):
await self.queue.put(charge_id) await self.queue.put(charge_id)
raise HTTPException(status_code=HTTPStatus.NO_CONTENT) raise HTTPException(status_code=HTTPStatus.NO_CONTENT)

View File

@ -75,8 +75,7 @@ class SparkWallet(Wallet):
return StatusResponse(str(e), 0) return StatusResponse(str(e), 0)
return StatusResponse( return StatusResponse(
None, None, sum([ch["channel_sat"] * 1000 for ch in funds["channels"]])
sum([ch["channel_sat"] * 1000 for ch in funds["channels"]]),
) )
async def create_invoice( async def create_invoice(