Compare commits
1 commit
v1.1.1-aio
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6893f409be |
2 changed files with 39 additions and 1 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "Bolt Cards",
|
"name": "Bolt Cards",
|
||||||
"short_description": "Self custody Bolt Cards with one time LNURLw",
|
"short_description": "Self custody Bolt Cards with one time LNURLw",
|
||||||
"tile": "/boltcards/static/image/boltcard.png",
|
"tile": "/boltcards/static/image/boltcard.png",
|
||||||
"version": "1.1.1-aio.1",
|
"version": "1.1.1-aio.2",
|
||||||
"min_lnbits_version": "1.3.0",
|
"min_lnbits_version": "1.3.0",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -394,3 +394,41 @@ async def pay_callback(
|
||||||
action = MessageAction(message=Max144Str("Topped up!"))
|
action = MessageAction(message=Max144Str("Topped up!"))
|
||||||
invoice = parse_obj_as(LightningInvoice, payment.bolt11)
|
invoice = parse_obj_as(LightningInvoice, payment.bolt11)
|
||||||
return LnurlPayActionResponse(pr=invoice, successAction=action)
|
return LnurlPayActionResponse(pr=invoice, successAction=action)
|
||||||
|
|
||||||
|
|
||||||
|
###############ACCESS-CONTROL VERIFY (doors)#################
|
||||||
|
# /boltcards/api/v1/verify/{external_id}?p=<32-hex>&c=<16-hex>
|
||||||
|
# Side-effect-light SUN check for access control (e.g. doors via the `access`
|
||||||
|
# extension): confirm the tap is a genuine, non-replayed card and return its
|
||||||
|
# identity — WITHOUT /scan's spend semantics (no withdrawRequest, no daily-limit
|
||||||
|
# check, no "hit" record). It DOES advance the SUN counter, exactly like /scan,
|
||||||
|
# so a captured p/c can't be replayed. Named `verify` because `/auth` is already
|
||||||
|
# the card-programming OTP endpoint.
|
||||||
|
@boltcards_lnurl_router.get("/api/v1/verify/{external_id}")
|
||||||
|
async def api_verify(p, c, external_id: str):
|
||||||
|
p = p.upper()
|
||||||
|
c = c.upper()
|
||||||
|
card = await get_card_by_external_id(external_id)
|
||||||
|
if not card:
|
||||||
|
return {"authenticated": False, "reason": "Card not found."}
|
||||||
|
if not card.enable:
|
||||||
|
return {"authenticated": False, "reason": "Card is disabled."}
|
||||||
|
try:
|
||||||
|
card_uid, counter = decrypt_sun(bytes.fromhex(p), bytes.fromhex(card.k1))
|
||||||
|
if card.uid.upper() != card_uid.hex().upper():
|
||||||
|
return {"authenticated": False, "reason": "Card UID mis-match."}
|
||||||
|
if c != get_sun_mac(card_uid, counter, bytes.fromhex(card.k2)).hex().upper():
|
||||||
|
return {"authenticated": False, "reason": "CMAC does not check."}
|
||||||
|
except Exception:
|
||||||
|
return {"authenticated": False, "reason": "Error decrypting card."}
|
||||||
|
|
||||||
|
ctr_int = int.from_bytes(counter, "little")
|
||||||
|
if ctr_int <= card.counter:
|
||||||
|
return {"authenticated": False, "reason": "This link is already used."}
|
||||||
|
await update_card_counter(ctr_int, card.id)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"authenticated": True,
|
||||||
|
"external_id": card.external_id,
|
||||||
|
"card_name": card.card_name,
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue