Tutorial 04 — TOTP authenticator handoff
Objectif — mint a TOTP secret, hand it off via QR code, then require the rolling 6-digit code at every binary launch. Concepts — RFC 6238 · ±30 s clock-skew window · authenticator apps (Google Auth / Authy / 1Password / Yubico) Attendu — the live code is accepted; a bogus
000000is rejected with exit 1.
In the TUI
8→ TOTP screen.n→ mint a fresh secret. The new row is selected.Q→ pop the QR overlay. Hand your phone to the licensee and let them scan it into Google Authenticator / Authy / 1Password / Yubico.Escto close the overlay.- Bind it to a licence:
2→ Licences →n→ wizard, step 7 (TOTP): pick the secret you just minted →Enter. - After signing,
E→/tmp/alice.license→Enter. 3→ Issuers →E→/tmp/issuer.pub→Enter.
The secret never leaves the manager DB — only the QR was displayed, once.

In your program
package main
import (
"bufio"
"log"
"os"
"strings"
license "github.com/oioio-space/maldev/license"
)
func main() {
licPEM, _ := os.ReadFile("/tmp/alice.license")
pubPEM, _ := os.ReadFile("/tmp/issuer.pub")
pub, kid, _ := license.ParsePublicKey(pubPEM)
trusted := license.Trusted{Keys: license.SingleKey(kid, pub)}
// Prompt the user for the current 6-digit code.
os.Stdout.WriteString("TOTP code: ")
line, _ := bufio.NewReader(os.Stdin).ReadString('\n')
code := strings.TrimSpace(line)
v, err := license.Verify(licPEM, trusted, license.WithTOTPCode(code))
if err != nil {
log.Fatalf("license check failed: %v", err)
}
log.Printf("running for %s", v.Subject)
}
Wrong code → err != nil. The window tolerates ±30 s of clock
drift; outside that, the user retypes the current code.
Runnable client:
examples/.../04-totp-authenticator/client.
Test it together
go test ./examples/license-manager/tutorials/04-totp-authenticator
Renders the tape, issues a real TOTP-bound licence, runs the
client with both the live code (accepted) and 000000
(rejected).