From 40e14065e1a780512bd9a34d2229b308f7e1aaf6 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Tue, 8 Sep 2026 16:13:18 +0100 Subject: [PATCH 1/2] feat: require owner authorization for ATM mode --- models.py | 7 +++ static/js/tpos.js | 69 +++++++++++++++++++++-------- templates/tpos/dialogs.html | 41 ++++++++++------- templates/tpos/tpos.html | 13 ++++++ tests/e2e/tpos.spec.ts | 88 ++++++++++++++++++++++++++++++++----- tests/test_api.py | 54 +++++++++++++++++++++++ views_atm.py | 51 +++++++++++++++++++-- 7 files changed, 276 insertions(+), 47 deletions(-) diff --git a/models.py b/models.py index f6f34890..b80adb4e 100644 --- a/models.py +++ b/models.py @@ -16,6 +16,13 @@ class CreateWithdrawPay(BaseModel): pay_link: str +class AuthorizeAtm(BaseModel): + password: str = Field(..., min_length=1, max_length=256) + + class Config: + extra = "forbid" + + class CreateTposInvoiceTabSettlement(BaseModel): tab_id: str = Field(..., min_length=1) amount: float = Field(..., gt=0) diff --git a/static/js/tpos.js b/static/js/tpos.js index 142587cd..eef2c940 100644 --- a/static/js/tpos.js +++ b/static/js/tpos.js @@ -34,8 +34,11 @@ window.app = Vue.createApp({ tipAmount: 0.0, tipRounding: null, hasNFC: false, - atmBox: false, - hidePin: true, + atmDialog: { + show: false, + loading: false, + password: '' + }, atmMode: false, atmToken: '', nfcTagReading: false, @@ -801,10 +804,32 @@ window.app = Vue.createApp({ }, exitAtmMode() { this.atmMode = false + this.atmToken = '' + if (this.connectionWithdraw) { + this.connectionWithdraw.close() + this.connectionWithdraw = null + } this.getRates() this.cancelAddAmount() }, - startAtmMode() { + async startAtmMode() { + try { + const {data} = await LNbits.api.request( + 'POST', + `/tpos/api/v1/atm/${this.tposId}/create` + ) + this.enterAtmMode(data) + } catch (error) { + if ([401, 403].includes(error.response?.status)) { + this.atmDialog.show = true + } else { + LNbits.utils.notifyApiError(error) + } + } + }, + enterAtmMode(data) { + if (data.claimed != false) return + this.atmToken = data.id if (!this.showPoS) { this.showPoS = true } @@ -813,16 +838,26 @@ window.app = Vue.createApp({ if (this.atmPremium > 0) { this.exchangeRate = this.exchangeRate / (1 + this.atmPremium) } - LNbits.api - .request('POST', `/tpos/api/v1/atm/${this.tposId}/create`) - .then(res => { - this.atmToken = res.data.id - if (res.data.claimed == false) { - this.atmBox = false - this.atmMode = true - } - }) - .catch(LNbits.utils.notifyApiError) + this.atmMode = true + this.atmDialog.show = false + this.atmDialog.password = '' + }, + async atmLogin() { + this.atmDialog.loading = true + try { + const {data} = await LNbits.api.request( + 'POST', + `/tpos/api/v1/atm/${this.tposId}/authorize`, + null, + {password: this.atmDialog.password} + ) + this.enterAtmMode(data) + } catch (error) { + LNbits.utils.notifyApiError(error) + this.atmDialog.password = '' + } finally { + this.atmDialog.loading = false + } }, lnaddressSubmit() { LNbits.api @@ -894,10 +929,8 @@ window.app = Vue.createApp({ this.connectionWithdraw.onmessage = e => { if (e.data == 'paid') { this.invoiceDialog.show = false - this.atmToken = '' + this.exitAtmMode() this.showComplete() - this.atmMode = false - this.connectionWithdraw.close() } } this.getRates() @@ -1997,8 +2030,8 @@ window.app = Vue.createApp({ // do nothing if the event was already processed if (event.defaultPrevented) return - // active only in the the PoS mode, not in the Cart mode or ATM pin - if (!this.showPoS || this.atmBox) return + // active only in the PoS mode, not in the Cart mode or ATM login + if (!this.showPoS || this.atmDialog.show) return // prevent weird behaviour when setting round tip if (this.tipDialog.show) return diff --git a/templates/tpos/dialogs.html b/templates/tpos/dialogs.html index 821b5ac6..4434caaa 100644 --- a/templates/tpos/dialogs.html +++ b/templates/tpos/dialogs.html @@ -280,28 +280,39 @@
@delete="deleteHeldCart" > - - + + -
Withdraw PIN
+
Unlock ATM
- + -
- +
+ +
diff --git a/templates/tpos/tpos.html b/templates/tpos/tpos.html index fe4254e2..3bd07b9a 100644 --- a/templates/tpos/tpos.html +++ b/templates/tpos/tpos.html @@ -102,6 +102,9 @@
{% include "tpos/dialogs.html" %} + + +