feat(machine): Bolt Card (NFC) tap-to-pay on cash-out #83

Merged
padreug merged 9 commits from feat/boltcard-nfc-cashout into dev 2026-08-05 21:38:34 +00:00
2 changed files with 24 additions and 2 deletions
Showing only changes of commit 0a3156855c - Show all commits

feat(deploy): authorize bitspire for pcscd (polkit) + NFC diagnostics

pcscd gates clients via polkit; the sandboxed bitspire user was "Rejected
unauthorized PC/SC client", so add a polkit rule granting it
access_pcsc/access_card. Also log NFC reader status + taps from the main
process to journald (value redacted — it carries the card's SUN p/c) so
reader detection and taps are observable during testing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Patrick Mulligan 2026-08-05 05:01:08 +02:00

View file

@ -833,8 +833,17 @@ app.whenReady().then(() => {
// best-effort: if the reader/pcscd is absent it just reports 'unavailable'
// and the cash-out QR path is unaffected.
void startNfcReader(
(lnurlw) => mainWindow?.webContents.send('nfc:card-tapped', lnurlw),
(status) => mainWindow?.webContents.send('nfc:status', status)
(lnurlw) => {
// Don't log the value — it carries the card's single-use SUN p/c.
console.log(`[NFC] card tapped — lnurlw (${lnurlw.length} chars) → renderer`)
mainWindow?.webContents.send('nfc:card-tapped', lnurlw)
},
(status: NfcStatus) => {
console.log(
`[NFC] status=${status.state}${status.reader ? ` reader="${status.reader}"` : ''}${status.message ? ` — ${status.message}` : ''}`
)
mainWindow?.webContents.send('nfc:status', status)
}
)
app.on('activate', () => {

View file

@ -92,6 +92,19 @@
# attached — pcscd just idles.
services.pcscd.enable = true;
# pcscd gates client access via polkit; without a rule the sandboxed
# `bitspire` service user is "Rejected unauthorized PC/SC client". Authorize
# it to talk to the daemon and the card.
security.polkit.extraConfig = ''
polkit.addRule(function(action, subject) {
if ((action.id == "org.debian.pcsc-lite.access_pcsc" ||
action.id == "org.debian.pcsc-lite.access_card") &&
subject.user == "bitspire") {
return polkit.Result.YES;
}
});
'';
# Disable suspend/hibernate for kiosk
systemd.targets = {
sleep.enable = false;