Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand Down
7 changes: 7 additions & 0 deletions docker/card/admin-ui/src/components/two-factor-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
/>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion docker/card/build/build.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package build

var Version string = "0.20.1"
var Version string = "0.20.2"
var Date string
var Time string
10 changes: 7 additions & 3 deletions docker/card/web/totp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 5 additions & 0 deletions docker/card/web/totp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading