lnbits-legend/lnbits/extensions/boltcards/views_api.py

172 lines
5.1 KiB
Python
Raw Normal View History

2022-06-13 21:08:06 +02:00
# views_api.py is for you API endpoints that could be hit by another service
# add your dependencies here
# import httpx
# (use httpx just like requests, except instead of response.ok there's only the
# response.is_error that is its inverse)
2022-08-14 23:52:55 +02:00
import secrets
2022-06-13 21:08:06 +02:00
from http import HTTPStatus
from fastapi.params import Depends, Query
from starlette.exceptions import HTTPException
from starlette.requests import Request
from lnbits.core.crud import get_user
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
from lnbits.extensions.withdraw import get_withdraw_link
from . import boltcards_ext
from .crud import (
2022-07-15 16:43:06 +02:00
create_card,
2022-06-21 18:03:20 +02:00
create_hit,
2022-07-15 16:43:06 +02:00
delete_card,
2022-06-13 21:08:06 +02:00
get_all_cards,
get_card,
2022-08-14 23:52:55 +02:00
get_card_by_otp,
2022-07-15 16:43:06 +02:00
get_cards,
2022-06-21 22:04:43 +02:00
get_hits,
2022-06-13 21:08:06 +02:00
update_card,
2022-07-15 16:43:06 +02:00
update_card_counter,
2022-08-14 23:52:55 +02:00
update_card_otp,
2022-06-13 21:08:06 +02:00
)
from .models import CreateCardData
2022-07-15 16:43:06 +02:00
from .nxp424 import decryptSUN, getSunMAC
2022-06-13 21:08:06 +02:00
@boltcards_ext.get("/api/v1/cards")
async def api_cards(
g: WalletTypeInfo = Depends(get_key_type), all_wallets: bool = Query(False)
):
wallet_ids = [g.wallet.id]
if all_wallets:
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
return [card.dict() for card in await get_cards(wallet_ids)]
2022-07-15 16:43:06 +02:00
2022-06-13 21:08:06 +02:00
@boltcards_ext.post("/api/v1/cards", status_code=HTTPStatus.CREATED)
@boltcards_ext.put("/api/v1/cards/{card_id}", status_code=HTTPStatus.OK)
2022-08-14 23:52:55 +02:00
async def api_card_create_or_update(
2022-07-15 16:43:06 +02:00
# req: Request,
2022-06-13 21:08:06 +02:00
data: CreateCardData,
card_id: str = None,
wallet: WalletTypeInfo = Depends(require_admin_key),
):
if card_id:
card = await get_card(card_id)
if not card:
raise HTTPException(
detail="Card does not exist.", status_code=HTTPStatus.NOT_FOUND
)
if card.wallet != wallet.wallet.id:
raise HTTPException(
detail="Not your card.", status_code=HTTPStatus.FORBIDDEN
)
2022-07-15 16:43:06 +02:00
card = await update_card(card_id, **data.dict())
2022-06-13 21:08:06 +02:00
else:
2022-07-15 16:43:06 +02:00
card = await create_card(wallet_id=wallet.wallet.id, data=data)
2022-06-13 21:08:06 +02:00
return card.dict()
2022-07-15 16:43:06 +02:00
2022-06-13 21:08:06 +02:00
@boltcards_ext.delete("/api/v1/cards/{card_id}")
2022-08-14 23:52:55 +02:00
async def api_card_delete(card_id, wallet: WalletTypeInfo = Depends(require_admin_key)):
2022-06-13 21:08:06 +02:00
card = await get_card(card_id)
if not card:
raise HTTPException(
detail="Card does not exist.", status_code=HTTPStatus.NOT_FOUND
)
if card.wallet != wallet.wallet.id:
2022-07-15 16:43:06 +02:00
raise HTTPException(detail="Not your card.", status_code=HTTPStatus.FORBIDDEN)
2022-06-13 21:08:06 +02:00
await delete_card(card_id)
raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
2022-07-15 16:43:06 +02:00
2022-06-21 22:04:43 +02:00
@boltcards_ext.get("/api/v1/hits")
async def api_hits(
g: WalletTypeInfo = Depends(get_key_type), all_wallets: bool = Query(False)
):
wallet_ids = [g.wallet.id]
if all_wallets:
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
cards = await get_cards(wallet_ids)
cards_ids = []
for card in cards:
cards_ids.append(card.id)
return [hit.dict() for hit in await get_hits(cards_ids)]
2022-07-15 16:43:06 +02:00
2022-08-14 23:52:55 +02:00
# /boltcards/api/v1/scan?p=00000000000000000000000000000000&c=0000000000000000
@boltcards_ext.get("/api/v1/scan")
async def api_scane(p, c, request: Request):
2022-06-13 21:08:06 +02:00
card = None
2022-07-15 16:43:06 +02:00
counter = b""
2022-06-13 21:08:06 +02:00
2022-06-21 23:41:08 +02:00
# since this route is common to all cards I don't know whitch 'meta key' to use
# so I try one by one until decrypted uid matches
2022-06-13 21:08:06 +02:00
for cand in await get_all_cards():
2022-08-14 23:52:55 +02:00
if cand.k1:
card_uid, counter = decryptSUN(bytes.fromhex(p), bytes.fromhex(cand.k1))
2022-06-13 21:08:06 +02:00
if card_uid.hex().upper() == cand.uid:
card = cand
break
if card == None:
return {"status": "ERROR", "reason": "Unknown card."}
2022-08-14 23:52:55 +02:00
if c != getSunMAC(card_uid, counter, bytes.fromhex(card.k2)).hex().upper():
2022-06-13 21:08:06 +02:00
return {"status": "ERROR", "reason": "CMAC does not check."}
2022-06-21 22:04:43 +02:00
ctr_int = int.from_bytes(counter, "little")
if ctr_int <= card.counter:
2022-06-13 21:08:06 +02:00
return {"status": "ERROR", "reason": "This link is already used."}
2022-07-15 16:43:06 +02:00
2022-06-21 23:41:08 +02:00
await update_card_counter(ctr_int, card.id)
2022-06-13 21:08:06 +02:00
2022-06-21 23:41:08 +02:00
# gathering some info for hit record
2022-06-21 18:03:20 +02:00
ip = request.client.host
2022-07-15 16:43:06 +02:00
if "x-real-ip" in request.headers:
ip = request.headers["x-real-ip"]
elif "x-forwarded-for" in request.headers:
ip = request.headers["x-forwarded-for"]
2022-06-21 18:03:20 +02:00
2022-07-15 16:43:06 +02:00
agent = request.headers["user-agent"] if "user-agent" in request.headers else ""
2022-06-21 22:04:43 +02:00
await create_hit(card.id, ip, agent, card.counter, ctr_int)
2022-06-21 18:03:20 +02:00
2022-06-13 21:08:06 +02:00
link = await get_withdraw_link(card.withdraw, 0)
return link.lnurl_response(request)
2022-08-14 23:52:55 +02:00
# /boltcards/api/v1/auth?a=00000000000000000000000000000000
@boltcards_ext.get("/api/v1/auth")
async def api_auth(a, request: Request):
if a == "00000000000000000000000000000000":
response = {"k0": "0" * 32, "k1": "1" * 32, "k2": "2" * 32}
return response
card = await get_card_by_otp(a)
if not card:
raise HTTPException(
detail="Card does not exist.", status_code=HTTPStatus.NOT_FOUND
)
new_otp = secrets.token_hex(16)
print(card.otp)
print(new_otp)
await update_card_otp(new_otp, card.id)
response = {"k0": card.k0, "k1": card.k1, "k2": card.k2}
return response