Black/prettier

This commit is contained in:
ben 2022-08-29 14:18:18 +01:00
parent 9e3cd3f00e
commit 7bfc0848ef
10 changed files with 161 additions and 136 deletions

View File

@ -16,9 +16,11 @@ boltcards_static_files = [
boltcards_ext: APIRouter = APIRouter(prefix="/boltcards", tags=["boltcards"]) boltcards_ext: APIRouter = APIRouter(prefix="/boltcards", tags=["boltcards"])
def boltcards_renderer(): def boltcards_renderer():
return template_renderer(["lnbits/extensions/boltcards/templates"]) return template_renderer(["lnbits/extensions/boltcards/templates"])
from .lnurl import * # noqa from .lnurl import * # noqa
from .tasks import * # noqa from .tasks import * # noqa
from .views import * # noqa from .views import * # noqa

View File

@ -2,5 +2,5 @@
"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",
"icon": "payment", "icon": "payment",
"contributors": ["iwarpbtc"] "contributors": ["iwarpbtc", "arcbtc", "leesalminen"]
} }

View File

@ -116,7 +116,9 @@ async def delete_card(card_id: str) -> None:
# Delete refunds # Delete refunds
refunds = await get_refunds([hit]) refunds = await get_refunds([hit])
for refund in refunds: for refund in refunds:
await db.execute("DELETE FROM boltcards.refunds WHERE id = ?", (refund.hit_id,)) await db.execute(
"DELETE FROM boltcards.refunds WHERE id = ?", (refund.hit_id,)
)
async def update_card_counter(counter: int, id: str): async def update_card_counter(counter: int, id: str):
@ -125,6 +127,7 @@ async def update_card_counter(counter: int, id: str):
(counter, id), (counter, id),
) )
async def enable_disable_card(enable: bool, id: str) -> Optional[Card]: async def enable_disable_card(enable: bool, id: str) -> Optional[Card]:
row = await db.execute( row = await db.execute(
"UPDATE boltcards.cards SET enable = ? WHERE id = ?", "UPDATE boltcards.cards SET enable = ? WHERE id = ?",
@ -132,6 +135,7 @@ async def enable_disable_card(enable: bool, id: str) -> Optional[Card]:
) )
return await get_card(id) return await get_card(id)
async def update_card_otp(otp: str, id: str): async def update_card_otp(otp: str, id: str):
await db.execute( await db.execute(
"UPDATE boltcards.cards SET otp = ? WHERE id = ?", "UPDATE boltcards.cards SET otp = ? WHERE id = ?",
@ -157,19 +161,23 @@ async def get_hits(cards_ids: Union[str, List[str]]) -> List[Hit]:
return [Hit(**row) for row in rows] return [Hit(**row) for row in rows]
async def get_hits_today(card_id: Union[str, List[str]]) -> List[Hit]: async def get_hits_today(card_id: Union[str, List[str]]) -> List[Hit]:
rows = await db.fetchall( rows = await db.fetchall(
f"SELECT * FROM boltcards.hits WHERE card_id = ? AND time >= DATE('now') AND time < DATE('now', '+1 day')", (card_id,) 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] return [Hit(**row) for row in rows]
async def spend_hit(id: str): async def spend_hit(id: str):
await db.execute( await db.execute(
"UPDATE boltcards.hits SET spent = ? WHERE id = ?", "UPDATE boltcards.hits SET spent = ? WHERE id = ?",
(True, id), (True, id),
) )
async def create_hit(card_id, ip, useragent, old_ctr, new_ctr) -> Hit: async def create_hit(card_id, ip, useragent, old_ctr, new_ctr) -> Hit:
hit_id = urlsafe_short_hash() hit_id = urlsafe_short_hash()
await db.execute( await db.execute(
@ -201,6 +209,7 @@ async def create_hit(card_id, ip, useragent, old_ctr, new_ctr) -> Hit:
assert hit, "Newly recorded hit couldn't be retrieved" assert hit, "Newly recorded hit couldn't be retrieved"
return hit return hit
async def create_refund(hit_id, refund_amount) -> Refund: async def create_refund(hit_id, refund_amount) -> Refund:
refund_id = urlsafe_short_hash() refund_id = urlsafe_short_hash()
await db.execute( await db.execute(
@ -224,13 +233,17 @@ async def create_refund(hit_id, refund_amount) -> Refund:
assert refund, "Newly recorded hit couldn't be retrieved" assert refund, "Newly recorded hit couldn't be retrieved"
return refund return refund
async def get_refund(refund_id: str) -> Optional[Refund]: async def get_refund(refund_id: str) -> Optional[Refund]:
row = await db.fetchone(f"SELECT * FROM boltcards.refunds WHERE id = ?", (refund_id)) row = await db.fetchone(
f"SELECT * FROM boltcards.refunds WHERE id = ?", (refund_id)
)
if not row: if not row:
return None return None
refund = dict(**row) refund = dict(**row)
return Refund.parse_obj(refund) return Refund.parse_obj(refund)
async def get_refunds(hits_ids: Union[str, List[str]]) -> List[Refund]: async def get_refunds(hits_ids: Union[str, List[str]]) -> List[Refund]:
q = ",".join(["?"] * len(hits_ids)) q = ",".join(["?"] * len(hits_ids))
rows = await db.fetchall( rows = await db.fetchall(

View File

@ -95,15 +95,14 @@ async def api_scan(p, c, request: Request, card_uid: str = None):
lnurlpay = lnurl_encode(request.url_for("boltcards.lnurlp_response", hit_id=hit.id)) lnurlpay = lnurl_encode(request.url_for("boltcards.lnurlp_response", hit_id=hit.id))
return { return {
"tag": "withdrawRequest", "tag": "withdrawRequest",
"callback": request.url_for( "callback": request.url_for("boltcards.lnurl_callback", hitid=hit.id),
"boltcards.lnurl_callback", hitid=hit.id
),
"k1": hit.id, "k1": hit.id,
"minWithdrawable": 1 * 1000, "minWithdrawable": 1 * 1000,
"maxWithdrawable": card.tx_limit * 1000, "maxWithdrawable": card.tx_limit * 1000,
"defaultDescription": f"Boltcard (refund address {lnurlpay})", "defaultDescription": f"Boltcard (refund address {lnurlpay})",
} }
@boltcards_ext.get( @boltcards_ext.get(
"/api/v1/lnurl/cb/{hitid}", "/api/v1/lnurl/cb/{hitid}",
status_code=HTTPStatus.OK, status_code=HTTPStatus.OK,
@ -158,8 +157,10 @@ async def api_auth(a, request: Request):
return response return response
###############LNURLPAY REFUNDS################# ###############LNURLPAY REFUNDS#################
@boltcards_ext.get( @boltcards_ext.get(
"/api/v1/lnurlp/{hit_id}", "/api/v1/lnurlp/{hit_id}",
response_class=HTMLResponse, response_class=HTMLResponse,
@ -199,14 +200,12 @@ async def lnurlp_callback(
wallet_id=card.wallet, wallet_id=card.wallet,
amount=int(amount) / 1000, amount=int(amount) / 1000,
memo=f"Refund {hit_id}", memo=f"Refund {hit_id}",
unhashed_description=LnurlPayMetadata(json.dumps([["text/plain", "Refund"]])).encode("utf-8"), unhashed_description=LnurlPayMetadata(
json.dumps([["text/plain", "Refund"]])
).encode("utf-8"),
extra={"refund": hit_id}, extra={"refund": hit_id},
) )
payResponse = {"pr": payment_request, "successAction": success_action, "routes": []} payResponse = {"pr": payment_request, "successAction": success_action, "routes": []}
return json.dumps(payResponse) return json.dumps(payResponse)

View File

@ -36,14 +36,13 @@ class Card(BaseModel):
return cls(**dict(row)) return cls(**dict(row))
def lnurl(self, req: Request) -> Lnurl: def lnurl(self, req: Request) -> Lnurl:
url = req.url_for( url = req.url_for("boltcard.lnurl_response", device_id=self.id, _external=True)
"boltcard.lnurl_response", device_id=self.id, _external=True
)
return lnurl_encode(url) return lnurl_encode(url)
async def lnurlpay_metadata(self) -> LnurlPayMetadata: async def lnurlpay_metadata(self) -> LnurlPayMetadata:
return LnurlPayMetadata(json.dumps([["text/plain", self.title]])) return LnurlPayMetadata(json.dumps([["text/plain", self.title]]))
class CreateCardData(BaseModel): class CreateCardData(BaseModel):
card_name: str = Query(...) card_name: str = Query(...)
uid: str = Query(...) uid: str = Query(...)
@ -58,6 +57,7 @@ class CreateCardData(BaseModel):
prev_k1: str = Query(ZERO_KEY) prev_k1: str = Query(ZERO_KEY)
prev_k2: str = Query(ZERO_KEY) prev_k2: str = Query(ZERO_KEY)
class Hit(BaseModel): class Hit(BaseModel):
id: str id: str
card_id: str card_id: str
@ -72,6 +72,7 @@ class Hit(BaseModel):
def from_row(cls, row: Row) -> "Hit": def from_row(cls, row: Row) -> "Hit":
return cls(**dict(row)) return cls(**dict(row))
class Refund(BaseModel): class Refund(BaseModel):
id: str id: str
hit_id: str hit_id: str

View File

@ -9,7 +9,6 @@ const mapCards = obj => {
return obj return obj
} }
new Vue({ new Vue({
el: '#vue', el: '#vue',
mixins: [windowMixin], mixins: [windowMixin],
@ -23,11 +22,12 @@ new Vue({
cardDialog: { cardDialog: {
show: false, show: false,
data: { data: {
counter:1, counter: 1,
k0: '', k0: '',
k1: '', k1: '',
k2: '', k2: '',
card_name:''}, card_name: ''
},
temp: {} temp: {}
}, },
cardsTable: { cardsTable: {
@ -190,7 +190,7 @@ new Vue({
}) })
}) })
}, },
openQrCodeDialog(cardId) { openQrCodeDialog (cardId) {
var card = _.findWhere(this.cards, {id: cardId}) var card = _.findWhere(this.cards, {id: cardId})
this.qrCodeDialog.data = { this.qrCodeDialog.data = {
link: window.location.origin + '/boltcards/api/v1/auth?a=' + card.otp, link: window.location.origin + '/boltcards/api/v1/auth?a=' + card.otp,
@ -217,7 +217,7 @@ new Vue({
typeof this.cardDialog.data.card_name === 'string' && typeof this.cardDialog.data.card_name === 'string' &&
this.cardDialog.data.card_name.search('debug') > -1 this.cardDialog.data.card_name.search('debug') > -1
self.cardDialog.data.k0 = debugcard self.cardDialog.data.k0 = debugcard
? '11111111111111111111111111111111' ? '11111111111111111111111111111111'
: genRanHex(32) : genRanHex(32)
@ -352,7 +352,7 @@ new Vue({
}, },
exportRefundsCSV: function () { exportRefundsCSV: function () {
LNbits.utils.exportCSV(this.refundsTable.columns, this.refunds) LNbits.utils.exportCSV(this.refundsTable.columns, this.refunds)
}, }
}, },
created: function () { created: function () {
if (this.g.user.wallets.length) { if (this.g.user.wallets.length) {

View File

@ -27,8 +27,9 @@ async def on_invoice_paid(payment: Payment) -> None:
if payment.extra.get("wh_status"): if payment.extra.get("wh_status"):
# this webhook has already been sent # this webhook has already been sent
return return
hit = await get_hit(payment.extra.get("tag")[7:len(payment.extra.get("tag"))]) hit = await get_hit(payment.extra.get("tag")[7 : len(payment.extra.get("tag"))])
if hit: if hit:
refund = await create_refund(hit_id=hit.id, refund_amount=payment.extra.get("amount")) refund = await create_refund(
hit_id=hit.id, refund_amount=payment.extra.get("amount")
)
await mark_webhook_sent(payment, 1) await mark_webhook_sent(payment, 1)

View File

@ -15,10 +15,6 @@
>More details</a >More details</a
> >
<br /> <br />
<small>
Created by,
<a href="https://twitter.com/btcslovnik">iWarp</a></small
>
</p> </p>
</q-card-section> </q-card-section>
</q-card> </q-card>

View File

@ -8,11 +8,18 @@
<div class="row items-center no-wrap q-mb-md"> <div class="row items-center no-wrap q-mb-md">
<div class="col"> <div class="col">
<div class="row justify-start" style="width:150px"> <div class="row justify-start" style="width:150px">
<div class="col" > <div class="col">
<h5 class="text-subtitle1 q-my-none">Cards</h5> <h5 class="text-subtitle1 q-my-none">Cards</h5>
</div> </div>
<div class="col"> <div class="col">
<q-btn round size="sm" icon="add" unelevated color="primary" @click="addCardOpen"> <q-btn
round
size="sm"
icon="add"
unelevated
color="primary"
@click="addCardOpen"
>
<q-tooltip>Add card</q-tooltip> <q-tooltip>Add card</q-tooltip>
</q-btn> </q-btn>
</div> </div>
@ -53,32 +60,38 @@
icon="qr_code" icon="qr_code"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="openQrCodeDialog(props.row.id)" @click="openQrCodeDialog(props.row.id)"
><q-tooltip>Card key credentials</q-tooltip></q-btn> ><q-tooltip>Card key credentials</q-tooltip></q-btn
>
</q-td> </q-td>
<q-td auto-width> <q-td auto-width>
<q-btn <q-btn
outline outline
color="grey" color="grey"
@click="copyText(lnurlLink + props.row.uid)" @click="copyText(lnurlLink + props.row.uid)"
lnurlLink >lnurl://...<q-tooltip>Click to copy, then add to NFC card</q-tooltip> lnurlLink
</q-btn> >lnurl://...<q-tooltip
>Click to copy, then add to NFC card</q-tooltip
>
</q-btn>
</q-td> </q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props"> <q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }} {{ col.value }}
</q-td> </q-td>
<q-td auto-width> <q-td auto-width>
<q-btn <q-btn
v-if="props.row.enable" v-if="props.row.enable"
dense dense
@click="enableCard(props.row.wallet, props.row.id, false)" @click="enableCard(props.row.wallet, props.row.id, false)"
color="pink" color="pink"
>DISABLE</q-btn> >DISABLE</q-btn
>
<q-btn <q-btn
v-else v-else
dense dense
@click="enableCard(props.row.wallet, props.row.id, true)" @click="enableCard(props.row.wallet, props.row.id, true)"
color="green" color="green"
>ENABLE</q-btn> >ENABLE</q-btn
>
</q-td> </q-td>
<q-td auto-width> <q-td auto-width>
<q-btn <q-btn
@ -88,7 +101,8 @@
@click="updateCardDialog(props.row.id)" @click="updateCardDialog(props.row.id)"
icon="edit" icon="edit"
color="light-blue" color="light-blue"
><q-tooltip>Edit card</q-tooltip></q-btn> ><q-tooltip>Edit card</q-tooltip></q-btn
>
</q-td> </q-td>
<q-td auto-width> <q-td auto-width>
<q-btn <q-btn
@ -98,7 +112,10 @@
@click="deleteCard(props.row.id)" @click="deleteCard(props.row.id)"
icon="cancel" icon="cancel"
color="pink" color="pink"
><q-tooltip>Deleting card will also delete all records</q-tooltip></q-btn> ><q-tooltip
>Deleting card will also delete all records</q-tooltip
></q-btn
>
</q-td> </q-td>
</q-tr> </q-tr>
</template> </template>
@ -220,7 +237,7 @@
type="number" type="number"
label="Max transaction (sats)" label="Max transaction (sats)"
class="q-pr-sm" class="q-pr-sm"
></q-input> ></q-input>
</div> </div>
<div class="col"> <div class="col">
<q-input <q-input
@ -234,86 +251,82 @@
</div> </div>
</div> </div>
<q-input <q-input
filled filled
dense dense
emit-value emit-value
v-model.trim="cardDialog.data.card_name" v-model.trim="cardDialog.data.card_name"
type="text" type="text"
label="Card name " label="Card name "
></q-input> ></q-input>
<div class="row"> <div class="row">
<div class="col-10"> <div class="col-10">
<q-input
filled
<q-input dense
filled emit-value
dense v-model.trim="cardDialog.data.uid"
emit-value type="text"
v-model.trim="cardDialog.data.uid" label="Card UID "
type="text" ><q-tooltip
label="Card UID " >Get from the card you'll use, using an NFC app</q-tooltip
><q-tooltip>Get from the card you'll use, using an NFC app</q-tooltip></q-input> ></q-input
>
</div> </div>
<div class="col-2 q-pl-sm"> <div class="col-2 q-pl-sm">
<q-btn <q-btn
outline outline
disable disable
color="grey" color="grey"
icon="nfc" icon="nfc"
:disable="nfcTagReading" :disable="nfcTagReading"
><q-tooltip>Tap card to scan UID (coming soon)</q-tooltip></q-btn> ><q-tooltip>Tap card to scan UID (coming soon)</q-tooltip></q-btn
>
</div>
</div> </div>
</div>
<q-toggle <q-toggle
v-model="toggleAdvanced" v-model="toggleAdvanced"
label="Show advanced options" label="Show advanced options"
></q-toggle> ></q-toggle>
<div v-show="toggleAdvanced"> <div v-show="toggleAdvanced">
<q-input <q-input
filled filled
dense dense
v-model.trim="cardDialog.data.k0"
v-model.trim="cardDialog.data.k0" type="text"
type="text" label="Card Auth key (K0)"
label="Card Auth key (K0)" hint="Used to authentificate with the card (16 bytes in HEX). "
hint="Used to authentificate with the card (16 bytes in HEX). " @randomkey
@randomkey >
> </q-input>
</q-input> <q-input
<q-input filled
filled dense
dense v-model.trim="cardDialog.data.k1"
type="text"
v-model.trim="cardDialog.data.k1" label="Card Meta key (K1)"
type="text" hint="Used for encypting of the message (16 bytes in HEX)."
label="Card Meta key (K1)" ></q-input>
hint="Used for encypting of the message (16 bytes in HEX)." <q-input
></q-input> filled
<q-input dense
filled v-model.trim="cardDialog.data.k2"
dense type="text"
label="Card File key (K2)"
v-model.trim="cardDialog.data.k2" hint="Used for CMAC of the message (16 bytes in HEX)."
type="text" >
label="Card File key (K2)" </q-input>
hint="Used for CMAC of the message (16 bytes in HEX)." <q-input
> filled
</q-input> dense
<q-input v-model.number="cardDialog.data.counter"
filled type="number"
dense label="Initial counter"
v-model.number="cardDialog.data.counter" ><q-tooltip class="bg-grey-8" anchor="bottom left" self="top left"
type="number" >Zero if you don't know.</q-tooltip
label="Initial counter" ></q-input
><q-tooltip class="bg-grey-8" anchor="bottom left" self="top left" >
>Zero if you don't know.</q-tooltip <q-btn
></q-input
>
<q-btn
unelevated unelevated
color="primary" color="primary"
class="q-ml-auto" class="q-ml-auto"
@ -322,7 +335,7 @@
v-el:keybtn v-el:keybtn
>Generate keys</q-btn >Generate keys</q-btn
> >
</div> </div>
<div class="row q-mt-lg"> <div class="row q-mt-lg">
<q-btn <q-btn
v-if="cardDialog.data.id" v-if="cardDialog.data.id"

View File

@ -29,6 +29,7 @@ from .nxp424 import decryptSUN, getSunMAC
from loguru import logger from loguru import logger
@boltcards_ext.get("/api/v1/cards") @boltcards_ext.get("/api/v1/cards")
async def api_cards( async def api_cards(
g: WalletTypeInfo = Depends(get_key_type), all_wallets: bool = Query(False) g: WalletTypeInfo = Depends(get_key_type), all_wallets: bool = Query(False)
@ -89,6 +90,7 @@ async def api_card_create_or_update(
card = await create_card(wallet_id=wallet.wallet.id, data=data) card = await create_card(wallet_id=wallet.wallet.id, data=data)
return card.dict() return card.dict()
@boltcards_ext.get("/api/v1/cards/enable/{card_id}/{enable}", status_code=HTTPStatus.OK) @boltcards_ext.get("/api/v1/cards/enable/{card_id}/{enable}", status_code=HTTPStatus.OK)
async def enable_card( async def enable_card(
card_id, card_id,
@ -97,16 +99,13 @@ async def enable_card(
): ):
card = await get_card(card_id) card = await get_card(card_id)
if not card: if not card:
raise HTTPException( raise HTTPException(detail="No card found.", status_code=HTTPStatus.NOT_FOUND)
detail="No card found.", status_code=HTTPStatus.NOT_FOUND
)
if card.wallet != wallet.wallet.id: if card.wallet != wallet.wallet.id:
raise HTTPException( raise HTTPException(detail="Not your card.", status_code=HTTPStatus.FORBIDDEN)
detail="Not your card.", status_code=HTTPStatus.FORBIDDEN
)
card = await enable_disable_card(enable=enable, id=card_id) card = await enable_disable_card(enable=enable, id=card_id)
return card.dict() return card.dict()
@boltcards_ext.delete("/api/v1/cards/{card_id}") @boltcards_ext.delete("/api/v1/cards/{card_id}")
async def api_card_delete(card_id, wallet: WalletTypeInfo = Depends(require_admin_key)): async def api_card_delete(card_id, wallet: WalletTypeInfo = Depends(require_admin_key)):
card = await get_card(card_id) card = await get_card(card_id)
@ -139,6 +138,7 @@ async def api_hits(
return [hit.dict() for hit in await get_hits(cards_ids)] return [hit.dict() for hit in await get_hits(cards_ids)]
@boltcards_ext.get("/api/v1/refunds") @boltcards_ext.get("/api/v1/refunds")
async def api_hits( async def api_hits(
g: WalletTypeInfo = Depends(get_key_type), all_wallets: bool = Query(False) g: WalletTypeInfo = Depends(get_key_type), all_wallets: bool = Query(False)