formatting

This commit is contained in:
Lee Salminen
2022-08-29 08:51:32 -06:00
parent 6d9d037c06
commit fe83cad7a3
6 changed files with 242 additions and 73 deletions

View File

@@ -2,21 +2,19 @@ import base64
import hashlib import hashlib
import hmac import hmac
import json import json
import secrets
from http import HTTPStatus from http import HTTPStatus
from io import BytesIO from io import BytesIO
from typing import Optional from typing import Optional
from loguru import logger
from embit import bech32, compact from embit import bech32, compact
from fastapi import Request from fastapi import Request
from fastapi.param_functions import Query from fastapi.param_functions import Query
from starlette.exceptions import HTTPException
import secrets
from http import HTTPStatus
from fastapi.params import Depends, Query from fastapi.params import Depends, Query
from lnurl import Lnurl, LnurlWithdrawResponse
from lnurl import encode as lnurl_encode # type: ignore
from lnurl.types import LnurlPayMetadata # type: ignore
from loguru import logger
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from starlette.requests import Request from starlette.requests import Request
from starlette.responses import HTMLResponse from starlette.responses import HTMLResponse
@@ -24,17 +22,12 @@ from starlette.responses import HTMLResponse
from lnbits.core.services import create_invoice from lnbits.core.services import create_invoice
from lnbits.core.views.api import pay_invoice from lnbits.core.views.api import pay_invoice
from lnurl import Lnurl, LnurlWithdrawResponse
from lnurl import encode as lnurl_encode # type: ignore
from lnurl.types import LnurlPayMetadata # type: ignore
from . import boltcards_ext from . import boltcards_ext
from .crud import ( from .crud import (
create_hit, create_hit,
get_card, get_card,
get_card_by_uid,
get_card_by_otp, get_card_by_otp,
get_card, get_card_by_uid,
get_hit, get_hit,
get_hits_today, get_hits_today,
spend_hit, spend_hit,

View File

@@ -1,9 +1,8 @@
from fastapi.params import Query
from pydantic import BaseModel
from sqlite3 import Row from sqlite3 import Row
from typing import Optional from typing import Optional
from fastapi import Request from fastapi import Request
from fastapi.params import Query
from lnurl import Lnurl from lnurl import Lnurl
from lnurl import encode as lnurl_encode # type: ignore from lnurl import encode as lnurl_encode # type: ignore
from lnurl.models import LnurlPaySuccessAction, UrlAction # type: ignore from lnurl.models import LnurlPaySuccessAction, UrlAction # type: ignore

View File

@@ -250,7 +250,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,

View File

@@ -7,7 +7,7 @@ from lnbits.core import db as core_db
from lnbits.core.models import Payment from lnbits.core.models import Payment
from lnbits.tasks import register_invoice_listener from lnbits.tasks import register_invoice_listener
from .crud import get_hit, create_refund from .crud import create_refund, get_hit
async def wait_for_paid_invoices(): async def wait_for_paid_invoices():

View File

@@ -7,23 +7,38 @@
<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>
</div> </div>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<q-btn flat color="grey" @click="exportCardsCSV">Export to CSV</q-btn> <q-btn flat color="grey" @click="exportCardsCSV"
>Export to CSV</q-btn
>
</div> </div>
</div> </div>
<q-table dense flat :data="cards" row-key="id" :columns="cardsTable.columns" <q-table
:pagination.sync="cardsTable.pagination"> dense
flat
:data="cards"
row-key="id"
:columns="cardsTable.columns"
:pagination.sync="cardsTable.pagination"
>
{% raw %} {% raw %}
<template v-slot:header="props"> <template v-slot:header="props">
<q-tr :props="props"> <q-tr :props="props">
@@ -38,8 +53,13 @@
<template v-slot:body="props"> <template v-slot:body="props">
<q-tr :props="props"> <q-tr :props="props">
<q-td auto-width> <q-td auto-width>
<q-btn unelevated dense icon="qr_code" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'" <q-btn
@click="openQrCodeDialog(props.row.id)"> unelevated
dense
icon="qr_code"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="openQrCodeDialog(props.row.id)"
>
<q-tooltip>Card key credentials</q-tooltip> <q-tooltip>Card key credentials</q-tooltip>
</q-btn> </q-btn>
</q-td> </q-td>
@@ -47,19 +67,45 @@
{{ col.value }} {{ col.value }}
</q-td> </q-td>
<q-td auto-width> <q-td auto-width>
<q-btn v-if="props.row.enable" dense @click="enableCard(props.row.wallet, props.row.id, false)" <q-btn
color="pink">DISABLE</q-btn> v-if="props.row.enable"
<q-btn v-else dense @click="enableCard(props.row.wallet, props.row.id, true)" color="green">ENABLE dense
@click="enableCard(props.row.wallet, props.row.id, false)"
color="pink"
>DISABLE</q-btn
>
<q-btn
v-else
dense
@click="enableCard(props.row.wallet, props.row.id, true)"
color="green"
>ENABLE
</q-btn> </q-btn>
</q-td> </q-td>
<q-td auto-width> <q-td auto-width>
<q-btn flat dense size="xs" @click="updateCardDialog(props.row.id)" icon="edit" color="light-blue"> <q-btn
flat
dense
size="xs"
@click="updateCardDialog(props.row.id)"
icon="edit"
color="light-blue"
>
<q-tooltip>Edit card</q-tooltip> <q-tooltip>Edit card</q-tooltip>
</q-btn> </q-btn>
</q-td> </q-td>
<q-td auto-width> <q-td auto-width>
<q-btn flat dense size="xs" @click="deleteCard(props.row.id)" icon="cancel" color="pink"> <q-btn
<q-tooltip>Deleting card will also delete all records</q-tooltip> flat
dense
size="xs"
@click="deleteCard(props.row.id)"
icon="cancel"
color="pink"
>
<q-tooltip
>Deleting card will also delete all records</q-tooltip
>
</q-btn> </q-btn>
</q-td> </q-td>
</q-tr> </q-tr>
@@ -75,11 +121,19 @@
<h5 class="text-subtitle1 q-my-none">Hits</h5> <h5 class="text-subtitle1 q-my-none">Hits</h5>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<q-btn flat color="grey" @click="exportCardsCSV">Export to CSV</q-btn> <q-btn flat color="grey" @click="exportCardsCSV"
>Export to CSV</q-btn
>
</div> </div>
</div> </div>
<q-table dense flat :data="hits" row-key="id" :columns="hitsTable.columns" <q-table
:pagination.sync="hitsTable.pagination"> dense
flat
:data="hits"
row-key="id"
:columns="hitsTable.columns"
:pagination.sync="hitsTable.pagination"
>
{% raw %} {% raw %}
<template v-slot:header="props"> <template v-slot:header="props">
<q-tr :props="props"> <q-tr :props="props">
@@ -106,11 +160,19 @@
<h5 class="text-subtitle1 q-my-none">Refunds</h5> <h5 class="text-subtitle1 q-my-none">Refunds</h5>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<q-btn flat color="grey" @click="exportRefundsCSV">Export to CSV</q-btn> <q-btn flat color="grey" @click="exportRefundsCSV"
>Export to CSV</q-btn
>
</div> </div>
</div> </div>
<q-table dense flat :data="refunds" row-key="id" :columns="refundsTable.columns" <q-table
:pagination.sync="refundsTable.pagination"> dense
flat
:data="refunds"
row-key="id"
:columns="refundsTable.columns"
:pagination.sync="refundsTable.pagination"
>
{% raw %} {% raw %}
<template v-slot:header="props"> <template v-slot:header="props">
<q-tr :props="props"> <q-tr :props="props">
@@ -147,54 +209,147 @@
<q-dialog v-model="cardDialog.show" position="top" @hide="closeFormDialog"> <q-dialog v-model="cardDialog.show" position="top" @hide="closeFormDialog">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card"> <q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form @submit="sendFormData" class="q-gutter-md"> <q-form @submit="sendFormData" class="q-gutter-md">
<q-select filled dense emit-value v-model="cardDialog.data.wallet" :options="g.user.walletOptions" <q-select
label="Wallet *"> filled
dense
emit-value
v-model="cardDialog.data.wallet"
:options="g.user.walletOptions"
label="Wallet *"
>
</q-select> </q-select>
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<q-input filled dense emit-value v-model.trim="cardDialog.data.tx_limit" type="number" <q-input
label="Max transaction (sats)" class="q-pr-sm"></q-input> filled
dense
emit-value
v-model.trim="cardDialog.data.tx_limit"
type="number"
label="Max transaction (sats)"
class="q-pr-sm"
></q-input>
</div> </div>
<div class="col"> <div class="col">
<q-input filled dense emit-value v-model.trim="cardDialog.data.daily_limit" type="number" <q-input
label="Daily limit (sats)"></q-input> filled
dense
emit-value
v-model.trim="cardDialog.data.daily_limit"
type="number"
label="Daily limit (sats)"
></q-input>
</div> </div>
</div> </div>
<q-input filled dense emit-value v-model.trim="cardDialog.data.card_name" type="text" label="Card name "> <q-input
filled
dense
emit-value
v-model.trim="cardDialog.data.card_name"
type="text"
label="Card name "
>
</q-input> </q-input>
<div class="row"> <div class="row">
<div class="col-10"> <div class="col-10">
<q-input filled dense emit-value v-model.trim="cardDialog.data.uid" type="text" label="Card UID "> <q-input
<q-tooltip>Get from the card you'll use, using an NFC app</q-tooltip> filled
dense
emit-value
v-model.trim="cardDialog.data.uid"
type="text"
label="Card UID "
>
<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 outline disable color="grey" icon="nfc" :disable="nfcTagReading" @click="readNfcTag()"> <q-btn
outline
disable
color="grey"
icon="nfc"
:disable="nfcTagReading"
@click="readNfcTag()"
>
<q-tooltip>Tap card to scan UID</q-tooltip> <q-tooltip>Tap card to scan UID</q-tooltip>
</q-btn> </q-btn>
</div> </div>
</div> </div>
<q-toggle v-model="toggleAdvanced" label="Show advanced options"></q-toggle> <q-toggle
v-model="toggleAdvanced"
label="Show advanced options"
></q-toggle>
<div v-show="toggleAdvanced"> <div v-show="toggleAdvanced">
<q-input filled dense v-model.trim="cardDialog.data.k0" type="text" label="Card Auth key (K0)" <q-input
hint="Used to authentificate with the card (16 bytes in HEX). " @randomkey> filled
dense
v-model.trim="cardDialog.data.k0"
type="text"
label="Card Auth key (K0)"
hint="Used to authentificate with the card (16 bytes in HEX). "
@randomkey
>
</q-input> </q-input>
<q-input filled dense v-model.trim="cardDialog.data.k1" type="text" label="Card Meta key (K1)" <q-input
hint="Used for encypting of the message (16 bytes in HEX)."></q-input> filled
<q-input filled dense v-model.trim="cardDialog.data.k2" type="text" label="Card File key (K2)" dense
hint="Used for CMAC of the message (16 bytes in HEX)."> v-model.trim="cardDialog.data.k1"
type="text"
label="Card Meta key (K1)"
hint="Used for encypting of the message (16 bytes in HEX)."
></q-input>
<q-input
filled
dense
v-model.trim="cardDialog.data.k2"
type="text"
label="Card File key (K2)"
hint="Used for CMAC of the message (16 bytes in HEX)."
>
</q-input> </q-input>
<q-input filled dense v-model.number="cardDialog.data.counter" type="number" 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> filled
dense
v-model.number="cardDialog.data.counter"
type="number"
label="Initial counter"
>
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left"
>Zero if you don't know.</q-tooltip
>
</q-input> </q-input>
<q-btn unelevated color="primary" class="q-ml-auto" v-on:click="generateKeys" v-on:click.right="debugKeys">Generate keys</q-btn> <q-btn
unelevated
color="primary"
class="q-ml-auto"
v-on:click="generateKeys"
v-on:click.right="debugKeys"
>Generate keys</q-btn
>
</div> </div>
<div class="row q-mt-lg"> <div class="row q-mt-lg">
<q-btn v-if="cardDialog.data.id" unelevated color="primary" type="submit">Update Card</q-btn> <q-btn
<q-btn v-else unelevated color="primary" :disable="cardDialog.data.uid == null" type="submit">Create Card v-if="cardDialog.data.id"
unelevated
color="primary"
type="submit"
>Update Card</q-btn
>
<q-btn
v-else
unelevated
color="primary"
:disable="cardDialog.data.uid == null"
type="submit"
>Create Card
</q-btn> </q-btn>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn> <q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Cancel</q-btn
>
</div> </div>
</q-form> </q-form>
</q-card> </q-card>
@@ -204,10 +359,19 @@
<q-card v-if="qrCodeDialog.data" class="q-pa-lg lnbits__dialog-card"> <q-card v-if="qrCodeDialog.data" class="q-pa-lg lnbits__dialog-card">
{% raw %} {% raw %}
<q-responsive :ratio="1" class="q-mx-xl q-mb-md"> <q-responsive :ratio="1" class="q-mx-xl q-mb-md">
<qrcode :value="qrCodeDialog.data.link" :options="{width: 800}" class="rounded-borders"></qrcode> <qrcode
:value="qrCodeDialog.data.link"
:options="{width: 800}"
class="rounded-borders"
></qrcode>
</q-responsive> </q-responsive>
<p style="word-break: break-all" class="text-center"> <p style="word-break: break-all" class="text-center">
(Keys for <a href="https://play.google.com/store/apps/details?id=com.lightningnfcapp" target="_blank">bolt-nfc-android-app</a>) (Keys for
<a
href="https://play.google.com/store/apps/details?id=com.lightningnfcapp"
target="_blank"
>bolt-nfc-android-app</a
>)
</p> </p>
<p style="word-break: break-all"> <p style="word-break: break-all">
<strong>Name:</strong> {{ qrCodeDialog.data.name }}<br /> <strong>Name:</strong> {{ qrCodeDialog.data.name }}<br />
@@ -215,11 +379,24 @@
<strong>Lock key:</strong> {{ qrCodeDialog.data.k0 }}<br /> <strong>Lock key:</strong> {{ qrCodeDialog.data.k0 }}<br />
<strong>Meta key:</strong> {{ qrCodeDialog.data.k1 }}<br /> <strong>Meta key:</strong> {{ qrCodeDialog.data.k1 }}<br />
<strong>File key:</strong> {{ qrCodeDialog.data.k2 }}<br /> <strong>File key:</strong> {{ qrCodeDialog.data.k2 }}<br />
</p><br /> </p>
<br />
<q-btn unelevated outline color="grey" @click="copyText(lnurlLink + qrCodeDialog.data.uid)" label="Base url (LNURL://)"> <q-btn
unelevated
outline
color="grey"
@click="copyText(lnurlLink + qrCodeDialog.data.uid)"
label="Base url (LNURL://)"
>
</q-btn> </q-btn>
<q-btn unelevated outline color="grey" @click="copyText(qrCodeDialog.data.link)" label="Keys/Auth link"> <q-btn
unelevated
outline
color="grey"
@click="copyText(qrCodeDialog.data.link)"
label="Keys/Auth link"
>
</q-btn> </q-btn>
<q-tooltip>Click to copy, then add to NFC card</q-tooltip> <q-tooltip>Click to copy, then add to NFC card</q-tooltip>

View File

@@ -2,6 +2,7 @@ import secrets
from http import HTTPStatus from http import HTTPStatus
from fastapi.params import Depends, Query from fastapi.params import Depends, Query
from loguru import logger
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from starlette.requests import Request from starlette.requests import Request
@@ -13,22 +14,20 @@ from .crud import (
create_card, create_card,
create_hit, create_hit,
delete_card, delete_card,
enable_disable_card,
get_card, get_card,
get_card_by_otp, get_card_by_otp,
get_card_by_uid, get_card_by_uid,
get_cards, get_cards,
get_hits, get_hits,
get_refunds,
update_card, update_card,
update_card_counter, update_card_counter,
update_card_otp, update_card_otp,
enable_disable_card,
get_refunds,
) )
from .models import CreateCardData from .models import CreateCardData
from .nxp424 import decryptSUN, getSunMAC from .nxp424 import decryptSUN, getSunMAC
from loguru import logger
@boltcards_ext.get("/api/v1/cards") @boltcards_ext.get("/api/v1/cards")
async def api_cards( async def api_cards(
@@ -77,7 +76,8 @@ async def api_card_create_or_update(
checkUid = await get_card_by_uid(data.uid) checkUid = await get_card_by_uid(data.uid)
if checkUid: if checkUid:
raise HTTPException( raise HTTPException(
detail="UID already registered. Delete registered card and try again.", status_code=HTTPStatus.BAD_REQUEST detail="UID already registered. Delete registered card and try again.",
status_code=HTTPStatus.BAD_REQUEST,
) )
if card_id: if card_id:
card = await get_card(card_id) card = await get_card(card_id)