Pretty much works

This commit is contained in:
ben 2022-08-26 19:22:03 +01:00
parent c3870e65e3
commit 330a026379
6 changed files with 54 additions and 29 deletions

View file

@ -8,30 +8,32 @@ from .models import Card, CreateCardData, Hit, Refund
async def create_card(data: CreateCardData, wallet_id: str) -> Card:
card_id = urlsafe_short_hash()
card_id = urlsafe_short_hash().upper()
await db.execute(
"""
INSERT INTO boltcards.cards (
id,
uid,
wallet,
card_name,
uid,
counter,
withdraw,
tx_limit,
daily_limit,
k0,
k1,
k2,
otp
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
card_id,
data.uid.upper(),
wallet_id,
data.card_name,
data.uid.upper(),
data.counter,
data.withdraw,
data.tx_limit,
data.daily_limit,
data.k0,
data.k1,
data.k2,
@ -145,11 +147,16 @@ async def get_hits(cards_ids: Union[str, List[str]]) -> List[Hit]:
async def get_hits_today(card_id: Union[str, List[str]]) -> List[Hit]:
rows = await db.fetchall(
f"SELECT * FROM boltcards.hits WHERE card_id = ? AND timestamp >= DATE() AND timestamp < DATE() + INTERVAL ? DAY", (card_id, 1)
f"SELECT * FROM boltcards.hits WHERE card_id = ? AND time >= DATE('now') AND time < DATE('now', '+1 day')", (card_id,)
)
return [Hit(**row) for row in rows]
async def spend_hit(id: str):
await db.execute(
"UPDATE boltcards.hits SET spent = ? WHERE id = ?",
(True, id),
)
async def create_hit(card_id, ip, useragent, old_ctr, new_ctr) -> Hit:
hit_id = urlsafe_short_hash()
@ -159,19 +166,23 @@ async def create_hit(card_id, ip, useragent, old_ctr, new_ctr) -> Hit:
id,
card_id,
ip,
spent,
useragent,
old_ctr,
new_ctr
new_ctr,
amount
)
VALUES (?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""",
(
hit_id,
card_id,
ip,
False,
useragent,
old_ctr,
new_ctr,
0,
),
)
hit = await get_hit(hit_id)

View file

@ -1,10 +1,13 @@
import base64
import hashlib
import hmac
import json
from http import HTTPStatus
from io import BytesIO
from typing import Optional
from loguru import logger
from embit import bech32, compact
from fastapi import Request
from fastapi.param_functions import Query
@ -33,6 +36,7 @@ from .crud import (
get_card_by_uid,
get_hit,
get_hits_today,
spend_hit,
update_card,
update_card_counter,
update_card_otp,
@ -50,10 +54,10 @@ async def api_scan(p, c, request: Request, card_uid: str = None):
c = c.upper()
card = None
counter = b""
try:
card = await get_card_by_uid(card_uid)
card_uid, counter = decryptSUN(bytes.fromhex(p), bytes.fromhex(card.k1))
if card.uid.upper() != card_uid.hex().upper():
return {"status": "ERROR", "reason": "Card UID mis-match."}
except:
@ -67,8 +71,8 @@ async def api_scan(p, c, request: Request, card_uid: str = None):
ctr_int = int.from_bytes(counter, "little")
if ctr_int <= card.counter:
return {"status": "ERROR", "reason": "This link is already used."}
# if ctr_int <= card.counter:
# return {"status": "ERROR", "reason": "This link is already used."}
await update_card_counter(ctr_int, card.id)
@ -86,13 +90,13 @@ async def api_scan(p, c, request: Request, card_uid: str = None):
for hit in todays_hits:
hits_amount = hits_amount + hit.amount
if (hits_amount + card.tx_limit) > card.daily_limit:
return {"status": "ERROR", "reason": "Max daily liit spent."}
return {"status": "ERROR", "reason": "Max daily limit spent."}
hit = await create_hit(card.id, ip, agent, card.counter, ctr_int)
lnurlpay = lnurl_encode(request.url_for("boltcards.lnurlp_response", hit_id=hit.id))
return {
"tag": "withdrawRequest",
"callback": request.url_for(
"boltcards.lnurl_callback"
"boltcards.lnurl_callback", hitid=hit.id
),
"k1": hit.id,
"minWithdrawable": 1 * 1000,
@ -166,14 +170,15 @@ async def api_auth(a, request: Request):
)
async def lnurlp_response(req: Request, hit_id: str = Query(None)):
hit = await get_hit(hit_id)
card = await get_card(hit.card_id)
if not hit:
return {"status": "ERROR", "reason": f"LNURL-pay record not found."}
payResponse = {
"tag": "payRequest",
"callback": req.url_for("boltcards.lnurlp_callback", hit_id=hit_id),
"metadata": LnurlPayMetadata(json.dumps([["text/plain", "Refund"]])),
"minSendable": math.ceil(link.min_bet * 1) * 1000,
"maxSendable": round(link.max_bet * 1) * 1000,
"minSendable": 1 * 1000,
"maxSendable": card.tx_limit * 1000,
}
return json.dumps(payResponse)
@ -187,14 +192,15 @@ async def lnurlp_callback(
req: Request, hit_id: str = Query(None), amount: str = Query(None)
):
hit = await get_hit(hit_id)
card = await get_card(hit.card_id)
if not hit:
return {"status": "ERROR", "reason": f"LNURL-pay record not found."}
payment_hash, payment_request = await create_invoice(
wallet_id=link.wallet,
amount=int(amount / 1000),
wallet_id=card.wallet,
amount=int(amount) / 1000,
memo=f"Refund {hit_id}",
unhashed_description=LnurlPayMetadata(json.dumps([["text/plain", hit_id]])).encode("utf-8"),
unhashed_description=LnurlPayMetadata(json.dumps([["text/plain", "Refund"]])).encode("utf-8"),
extra={"refund": hit_id},
)

View file

@ -64,6 +64,7 @@ class Hit(BaseModel):
useragent: str
old_ctr: int
new_ctr: int
amount: int
time: int
def from_row(cls, row: Row) -> "Hit":

View file

@ -18,6 +18,7 @@ new Vue({
cards: [],
hits: [],
refunds: [],
lnurlLink: location.hostname + '/boltcards/api/v1/scan/',
cardDialog: {
show: false,
data: {
@ -43,10 +44,10 @@ new Vue({
field: 'counter'
},
{
name: 'withdraw',
name: 'uid',
align: 'left',
label: 'Withdraw ID',
field: 'withdraw'
label: 'Card ID',
field: 'uid'
}
],
pagination: {
@ -150,7 +151,6 @@ new Vue({
},
getHits: function () {
var self = this
LNbits.api
.request(
'GET',
@ -167,7 +167,6 @@ new Vue({
},
getRefunds: function () {
var self = this
LNbits.api
.request(
'GET',
@ -184,7 +183,6 @@ new Vue({
},
openQrCodeDialog(cardId) {
var card = _.findWhere(this.cards, {id: cardId})
this.qrCodeDialog.data = {
link: window.location.origin + '/boltcards/api/v1/auth?a=' + card.otp,
name: card.card_name,
@ -197,11 +195,9 @@ new Vue({
},
addCardOpen: function () {
this.cardDialog.show = true
var elem = this.$els.myBtn
elem.click()
this.generateKeys()
},
generateKeys: function () {
const genRanHex = size =>
[...Array(size)]
.map(() => Math.floor(Math.random() * 16).toString(16))

View file

@ -34,6 +34,7 @@
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<q-th auto-width>Base URL</q-th>
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label }}
</q-th>
@ -53,6 +54,14 @@
@click="openQrCodeDialog(props.row.id)"
></q-btn>
</q-td>
<q-td auto-width>
<q-btn
outline
color="grey"
@click="copyText(lnurlLink + props.row.uid)"
lnurlLink >lnurl://...<q-tooltip>Click to copy, then add to NFC card</q-tooltip>
</q-btn>
</q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }}
</q-td>
@ -193,7 +202,7 @@
filled
dense
emit-value
v-model.trim="cardDialog.data.trans_limit"
v-model.trim="cardDialog.data.tx_limit"
type="number"
label="Max transaction (sats)"
class="q-pr-sm"

View file

@ -26,6 +26,7 @@ from .crud import (
from .models import CreateCardData
from .nxp424 import decryptSUN, getSunMAC
from loguru import logger
@boltcards_ext.get("/api/v1/cards")
async def api_cards(
@ -47,6 +48,7 @@ async def api_card_create_or_update(
card_id: str = None,
wallet: WalletTypeInfo = Depends(require_admin_key),
):
logger.debug(len(bytes.fromhex(data.uid)))
try:
if len(bytes.fromhex(data.uid)) != 7:
raise HTTPException(