feat(lnurl): access-control verify endpoint
Some checks failed
lint.yml / feat(lnurl): access-control verify endpoint (push) Failing after 0s
Some checks failed
lint.yml / feat(lnurl): access-control verify endpoint (push) Failing after 0s
GET /api/v1/verify/{external_id}?p=&c= — side-effect-light SUN check for
access control (doors via the aiolabs extension): confirms a genuine,
non-replayed tap and returns the card identity (external_id, card_name),
WITHOUT /scan's spend semantics (no withdrawRequest, no daily-limit, no hit).
It still advances the SUN counter, so a captured p/c can't be replayed.
Named 'verify' because /auth is already the card-programming OTP endpoint.
config.json → 1.1.1-aio.2.
This commit is contained in:
parent
6d240eaa7b
commit
6893f409be
2 changed files with 39 additions and 1 deletions
|
|
@ -394,3 +394,41 @@ async def pay_callback(
|
|||
action = MessageAction(message=Max144Str("Topped up!"))
|
||||
invoice = parse_obj_as(LightningInvoice, payment.bolt11)
|
||||
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