Black/prettier
This commit is contained in:
parent
9e3cd3f00e
commit
7bfc0848ef
|
@ -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
|
||||||
|
|
|
@ -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"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,17 +233,21 @@ 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(
|
||||||
f"SELECT * FROM boltcards.refunds WHERE hit_id IN ({q})", (*hits_ids,)
|
f"SELECT * FROM boltcards.refunds WHERE hit_id IN ({q})", (*hits_ids,)
|
||||||
)
|
)
|
||||||
|
|
||||||
return [Refund(**row) for row in rows]
|
return [Refund(**row) for row in rows]
|
||||||
|
|
|
@ -59,7 +59,7 @@ async def api_scan(p, c, request: Request, card_uid: str = None):
|
||||||
if not card:
|
if not card:
|
||||||
return {"status": "ERROR", "reason": "No card."}
|
return {"status": "ERROR", "reason": "No card."}
|
||||||
if not card.enable:
|
if not card.enable:
|
||||||
return {"status": "ERROR", "reason": "Card is disabled."}
|
return {"status": "ERROR", "reason": "Card is disabled."}
|
||||||
try:
|
try:
|
||||||
card_uid, counter = decryptSUN(bytes.fromhex(p), bytes.fromhex(card.k1))
|
card_uid, counter = decryptSUN(bytes.fromhex(p), bytes.fromhex(card.k1))
|
||||||
if card.uid.upper() != card_uid.hex().upper():
|
if card.uid.upper() != card_uid.hex().upper():
|
||||||
|
@ -70,7 +70,7 @@ async def api_scan(p, c, request: Request, card_uid: str = None):
|
||||||
return {"status": "ERROR", "reason": "Error decrypting card."}
|
return {"status": "ERROR", "reason": "Error decrypting card."}
|
||||||
|
|
||||||
ctr_int = int.from_bytes(counter, "little")
|
ctr_int = int.from_bytes(counter, "little")
|
||||||
|
|
||||||
if ctr_int <= card.counter:
|
if ctr_int <= card.counter:
|
||||||
return {"status": "ERROR", "reason": "This link is already used."}
|
return {"status": "ERROR", "reason": "This link is already used."}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -114,8 +113,8 @@ async def lnurl_callback(
|
||||||
pr: str = Query(None),
|
pr: str = Query(None),
|
||||||
k1: str = Query(None),
|
k1: str = Query(None),
|
||||||
):
|
):
|
||||||
hit = await get_hit(k1)
|
hit = await get_hit(k1)
|
||||||
card = await get_card(hit.card_id)
|
card = await get_card(hit.card_id)
|
||||||
if not hit:
|
if not hit:
|
||||||
return {"status": "ERROR", "reason": f"LNURL-pay record not found."}
|
return {"status": "ERROR", "reason": f"LNURL-pay record not found."}
|
||||||
try:
|
try:
|
||||||
|
@ -158,16 +157,18 @@ 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,
|
||||||
name="boltcards.lnurlp_response",
|
name="boltcards.lnurlp_response",
|
||||||
)
|
)
|
||||||
async def lnurlp_response(req: Request, hit_id: str = Query(None)):
|
async def lnurlp_response(req: Request, hit_id: str = Query(None)):
|
||||||
hit = await get_hit(hit_id)
|
hit = await get_hit(hit_id)
|
||||||
card = await get_card(hit.card_id)
|
card = await get_card(hit.card_id)
|
||||||
if not hit:
|
if not hit:
|
||||||
return {"status": "ERROR", "reason": f"LNURL-pay record not found."}
|
return {"status": "ERROR", "reason": f"LNURL-pay record not found."}
|
||||||
if not card.enable:
|
if not card.enable:
|
||||||
|
@ -190,8 +191,8 @@ async def lnurlp_response(req: Request, hit_id: str = Query(None)):
|
||||||
async def lnurlp_callback(
|
async def lnurlp_callback(
|
||||||
req: Request, hit_id: str = Query(None), amount: str = Query(None)
|
req: Request, hit_id: str = Query(None), amount: str = Query(None)
|
||||||
):
|
):
|
||||||
hit = await get_hit(hit_id)
|
hit = await get_hit(hit_id)
|
||||||
card = await get_card(hit.card_id)
|
card = await get_card(hit.card_id)
|
||||||
if not hit:
|
if not hit:
|
||||||
return {"status": "ERROR", "reason": f"LNURL-pay record not found."}
|
return {"status": "ERROR", "reason": f"LNURL-pay record not found."}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
@ -79,4 +80,4 @@ class Refund(BaseModel):
|
||||||
time: int
|
time: int
|
||||||
|
|
||||||
def from_row(cls, row: Row) -> "Refund":
|
def from_row(cls, row: Row) -> "Refund":
|
||||||
return cls(**dict(row))
|
return cls(**dict(row))
|
||||||
|
|
|
@ -9,12 +9,11 @@ const mapCards = obj => {
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
el: '#vue',
|
el: '#vue',
|
||||||
mixins: [windowMixin],
|
mixins: [windowMixin],
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
toggleAdvanced: false,
|
toggleAdvanced: false,
|
||||||
nfcTagReading: false,
|
nfcTagReading: false,
|
||||||
cards: [],
|
cards: [],
|
||||||
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -7,12 +7,19 @@
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<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"
|
||||||
|
|
|
@ -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)
|
||||||
|
@ -157,4 +157,4 @@ async def api_hits(
|
||||||
for hit in hits:
|
for hit in hits:
|
||||||
hits_ids.append(hit.id)
|
hits_ids.append(hit.id)
|
||||||
|
|
||||||
return [refund.dict() for refund in await get_refunds(hits_ids)]
|
return [refund.dict() for refund in await get_refunds(hits_ids)]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user