diff --git a/CLAUDE.md b/CLAUDE.md index 3e9a874..cbe8a30 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -93,7 +93,7 @@ Entry point: `main.go` → opens SQLite DB → runs CLI or starts HTTP server on - `phoenix/` — HTTP client for Phoenix Server API (invoices, payments, balance, channels). Uses basic auth from phoenix config (password cached at startup with `sync.Once`) - `crypto/` — AES-CMAC authentication and AES decryption for Bolt Card NFC protocol - `util/` — Error handling helpers (`CheckAndLog`), random hex generation, QR code encoding -- `build/` — Version string (currently "0.20.1"), date/time injected at build +- `build/` — Version string (currently "0.20.2"), date/time injected at build - `web-content/` — Static assets under `public/`, SPA build output under `admin/spa/` ### Route Groups (`web/app.go`) diff --git a/docker/card/admin-ui/src/components/two-factor-card.tsx b/docker/card/admin-ui/src/components/two-factor-card.tsx index 91e1e22..779bc57 100644 --- a/docker/card/admin-ui/src/components/two-factor-card.tsx +++ b/docker/card/admin-ui/src/components/two-factor-card.tsx @@ -134,7 +134,14 @@ export function TwoFactorCard() { inputMode="numeric" value={code} onChange={(e) => setCode(e.target.value.trim())} + onKeyDown={(e) => { + if (e.key === "Enter" && code.length > 0 && !enable.isPending) { + e.preventDefault(); + enable.mutate(); + } + }} placeholder="123456" + autoFocus /> diff --git a/docker/card/build/build.go b/docker/card/build/build.go index e3d0a72..0e3893f 100644 --- a/docker/card/build/build.go +++ b/docker/card/build/build.go @@ -1,5 +1,5 @@ package build -var Version string = "0.20.1" +var Version string = "0.20.2" var Date string var Time string diff --git a/docker/card/web/totp.go b/docker/card/web/totp.go index 0c21b2f..2ebb485 100644 --- a/docker/card/web/totp.go +++ b/docker/card/web/totp.go @@ -8,11 +8,15 @@ import ( ) // newTotpKey generates a fresh TOTP secret for the admin. accountName is the -// label shown in the authenticator app (we pass host_domain). Returns the -// base32 secret and the otpauth:// provisioning URI. +// label shown in the authenticator app (we pass host_domain). The issuer is +// "Boltcard Hub" (one token) to avoid wrong-logo brand matching in Authy. +// Returns the base32 secret and the otpauth:// provisioning URI. func newTotpKey(accountName string) (secret string, url string, err error) { key, err := totp.Generate(totp.GenerateOpts{ - Issuer: "Bolt Card Hub", + // "Boltcard" as a single token (not "Bolt Card") so authenticator apps + // that pick a logo by matching the issuer (e.g. Authy) don't false-match + // the unrelated "Bolt" brand and suggest the wrong logo. + Issuer: "Boltcard Hub", AccountName: accountName, }) if err != nil { diff --git a/docker/card/web/totp_test.go b/docker/card/web/totp_test.go index 76420b0..ff8c55a 100644 --- a/docker/card/web/totp_test.go +++ b/docker/card/web/totp_test.go @@ -19,6 +19,11 @@ func TestNewTotpKey_ProducesValidatableSecret(t *testing.T) { if !strings.HasPrefix(url, "otpauth://totp/") { t.Fatalf("expected otpauth URI, got %q", url) } + // Issuer must be the single-token "Boltcard" so Authy doesn't match the + // unrelated "Bolt" brand and suggest the wrong logo. + if !strings.Contains(url, "Boltcard") { + t.Fatalf("expected 'Boltcard' issuer in URI, got %q", url) + } code, err := totp.GenerateCode(secret, time.Now()) if err != nil {