diff --git a/README.md b/README.md index 78a9def..a8d6d87 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,12 @@ If you want to gift a Boltcard, make sure to [include the following data](https: - Otherwise read it with the Bolt-Card app (Read NFC) and paste it to the field. - Advanced Options - Card Keys (k0, k1, k2) will be automatically generated if not explicitly set. - - Set to 16 bytes of 0s (00000000000000000000000000000000) to leave the keys in default (empty) state (this is unsecure). + - Set to 16 bytes of 0s (00000000000000000000000000000000) to leave the keys in default (empty) state (this is unsecure, merely for debuging). - GENERATE KEY button fill the keys randomly. + - Expiry date + - You can set an expiry date on a card. After this date LNbits will not longer allow payment with this card. + - You can enable, extend or disable expiry date anytime just by updating the card (without necessity of rewriting it physicaly). + - Without an expiry date, the card is valid indefinitely. - Click CREATE CARD button - Click the QR code button next to a card to view its details. Backup the keys now! They'll be comfortable in your password manager. - Now you can scan the QR code with the Boltcard app (Create Bolt Card -> SCAN QR CODE). diff --git a/crud.py b/crud.py index 0a678e7..a6abc30 100644 --- a/crud.py +++ b/crud.py @@ -27,9 +27,10 @@ async def create_card(data: CreateCardData, wallet_id: str) -> Card: k0, k1, k2, - otp + otp, + expiry_date ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( card_id, @@ -45,6 +46,7 @@ async def create_card(data: CreateCardData, wallet_id: str) -> Card: data.k1, data.k2, secrets.token_hex(16), + data.expiry_date, ), ) card = await get_card(card_id) diff --git a/lnurl.py b/lnurl.py index 4b39374..2081b16 100644 --- a/lnurl.py +++ b/lnurl.py @@ -1,5 +1,6 @@ import json import secrets +from datetime import datetime from http import HTTPStatus from urllib.parse import urlparse @@ -42,6 +43,14 @@ async def api_scan(p, c, request: Request, external_id: str): return {"status": "ERROR", "reason": "No card."} if not card.enable: return {"status": "ERROR", "reason": "Card is disabled."} + if card.expiry_date != "": + today = datetime.today() + try: + expiry_date = datetime.strptime(card.expiry_date, "%Y/%m/%d") + except: + return {"status": "ERROR", "reason": "Invalid expiry date."} + if today > expiry_date: + return {"status": "ERROR", "reason": "Card expired."} try: card_uid, counter = decryptSUN(bytes.fromhex(p), bytes.fromhex(card.k1)) if card.uid.upper() != card_uid.hex().upper(): diff --git a/migrations.py b/migrations.py index 43d5bb0..c98a5a1 100644 --- a/migrations.py +++ b/migrations.py @@ -55,3 +55,13 @@ async def m001_initial(db): ); """ ) + + +async def m002_add_expiry(db): + """ + Special column for webhook endpoints that can be assigned + to each different invoice. + """ + + await db.execute("ALTER TABLE boltcards.cards ADD COLUMN expiry_date TEXT") + await db.execute("UPDATE boltcards.cards SET expiry_date = ''") diff --git a/models.py b/models.py index 5ea4be1..dd9cb47 100644 --- a/models.py +++ b/models.py @@ -28,6 +28,7 @@ class Card(BaseModel): prev_k2: str otp: str time: int + expiry_date: str @classmethod def from_row(cls, row: Row) -> "Card": @@ -54,6 +55,7 @@ class CreateCardData(BaseModel): prev_k0: str = Query(ZERO_KEY) prev_k1: str = Query(ZERO_KEY) prev_k2: str = Query(ZERO_KEY) + expiry_date: str = Query("") class Hit(BaseModel): diff --git a/static/js/index.js b/static/js/index.js index 880a555..cded21d 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -15,6 +15,7 @@ new Vue({ data: function () { return { toggleAdvanced: false, + toggleExpiry: false, nfcTagReading: false, lnurlLink: `${window.location.host}/boltcards/api/v1/scan/`, cards: [], @@ -28,7 +29,8 @@ new Vue({ k1: '', k2: '', uid: '', - card_name: '' + card_name: '', + expiry_date: '' }, temp: {} }, @@ -331,6 +333,10 @@ new Vue({ createCard: function (wallet, data) { var self = this + if (!this.toggleExpiry) { + this.cardDialog.data.expiry_date = '' + } + LNbits.api .request('POST', '/boltcards/api/v1/cards', wallet.adminkey, data) .then(function (response) { @@ -346,6 +352,8 @@ new Vue({ var card = _.findWhere(this.cards, {id: formId}) this.cardDialog.data = _.clone(card) + this.toggleExpiry = !!this.cardDialog.data.expiry_date + this.cardDialog.temp.k0 = this.cardDialog.data.k0 this.cardDialog.temp.k1 = this.cardDialog.data.k1 this.cardDialog.temp.k2 = this.cardDialog.data.k2 @@ -355,6 +363,10 @@ new Vue({ updateCard: function (wallet, data) { var self = this + if (!this.toggleExpiry) { + this.cardDialog.data.expiry_date = '' + } + if ( this.cardDialog.temp.k0 != data.k0 || this.cardDialog.temp.k1 != data.k1 || diff --git a/templates/boltcards/index.html b/templates/boltcards/index.html index 7e06e5e..279e678 100644 --- a/templates/boltcards/index.html +++ b/templates/boltcards/index.html @@ -344,6 +344,15 @@