herald-totp

Multi-language Documentation
TOTP 2FA service for the Herald/Stargate stack: enroll (bind), verify, and optional backup codes. It does not send codes; users generate TOTP in an authenticator app (e.g. Google Authenticator). Stargate calls herald-totp for per-user TOTP instead of a single global secret.
The HTTP server currently uses Fiber v3.5.0 and the matching v2 module lines of the Fiber-facing kit packages. Building from source requires Go 1.26 or later; go.mod is the authoritative source for exact dependency versions.
Core Features
- Enroll:
POST /v1/enroll/start (returns QR content) and POST /v1/enroll/confirm (confirm with one TOTP code).
- Verify:
POST /v1/verify (TOTP or backup code), returns subject, amr, issued_at; optional challenge_id for replay protection.
- Revoke:
POST /v1/revoke to remove TOTP credential and backup codes for a subject.
- Status:
GET /v1/status?subject=... to check if a user has TOTP enabled.
- Backup codes: 10 one-time codes returned on confirm; can be used in verify when the device is lost.
- Security: Encrypted secret storage (AES-GCM), rate limiting, atomic one-time consumption of TOTP steps, backup codes, and challenge IDs, plus API key or HMAC auth.
- Graceful shutdown: On
SIGINT or SIGTERM, server stops accepting new requests and shuts down with a 10s timeout.
Architecture
Stargate orchestrates login and TOTP bind; herald-totp stores per-user TOTP secrets (encrypted) and backup codes in Redis and performs enroll/verify.
sequenceDiagram
participant User
participant Stargate
participant HeraldTotp as herald-totp
participant Redis
Note over User,Redis: Bind flow (after login)
User->>Stargate: Open TOTP enroll page
Stargate->>HeraldTotp: POST /v1/enroll/start (subject)
HeraldTotp->>Redis: Store temp enrollment
HeraldTotp-->>Stargate: enroll_id, otpauth_uri
Stargate-->>User: Show QR code
User->>Stargate: Enter TOTP code
Stargate->>HeraldTotp: POST /v1/enroll/confirm (enroll_id, code)
HeraldTotp->>Redis: Save credential, backup_codes
HeraldTotp-->>Stargate: backup_codes
Stargate-->>User: Bind done
Note over User,Redis: Login flow (TOTP step)
User->>Stargate: Submit TOTP or backup code
Stargate->>HeraldTotp: POST /v1/verify (subject, code)
HeraldTotp->>Redis: Read credential, verify
HeraldTotp-->>Stargate: ok, subject, amr, issued_at
Stargate-->>User: Session created
- Stargate: ForwardAuth / login and TOTP bind orchestration; calls herald-totp for enroll and verify.
- herald-totp: Per-user TOTP secrets (AES-GCM in Redis), enroll/confirm, verify (TOTP or backup code), revoke, status.
- Redis: Credentials, enrollment temp state, backup codes, rate limits.
Protocol
- POST /v1/enroll/start – Start enrollment; returns
enroll_id, otpauth_uri (and optionally secret_base32).
- POST /v1/enroll/confirm – Submit TOTP code to confirm; returns
backup_codes.
- POST /v1/verify – Verify TOTP or backup code; returns
ok, subject, amr, issued_at.
- POST /v1/revoke – Remove TOTP and backup codes for a subject.
- GET /v1/status?subject=... – Check if TOTP is enabled for subject.
- GET /healthz – Service and Redis health (via health-kit).
Configuration
| Variable |
Description |
Default |
Required |
PORT |
Listen port (with or without leading colon) |
:8084 |
No |
HERALD_TOTP_ENCRYPTION_KEY |
Exact 32-byte AES-256 key for secret encryption |
`` |
Yes (startup) |
API_KEY |
If set, callers must send X-API-Key |
`` |
No |
HMAC_SECRET / HERALD_TOTP_HMAC_KEYS |
HMAC auth |
`` |
No |
REDIS_ADDR |
Redis address |
localhost:6379 |
Yes |
EXPOSE_SECRET_IN_ENROLL |
If false, omit secret_base32 in enroll/start response |
true |
No |
LOG_LEVEL |
Log level: trace, debug, info, warn, error |
info |
No |
See docs/enUS/DEPLOYMENT.md for full options.
Stargate integration
- Set
HERALD_TOTP_ENABLED=true, HERALD_TOTP_BASE_URL=http://herald-totp:8084, and HERALD_TOTP_API_KEY or HERALD_TOTP_HMAC_SECRET to match herald-totp.
Quick Start
Build & run (binary)
export HERALD_TOTP_ENCRYPTION_KEY="$(openssl rand -base64 24)"
export REDIS_ADDR=localhost:6379
go build -o herald-totp .
./herald-totp
Redis must already be reachable at REDIS_ADDR. Keep the generated encryption
key stable after credentials have been created; changing it makes existing TOTP
secrets unreadable.
Run with Docker
export HERALD_TOTP_ENCRYPTION_KEY="$(openssl rand -base64 24)"
docker network create herald-totp
docker run -d --name herald-totp-redis --network herald-totp redis:8-alpine
docker build -t herald-totp .
docker run -d --name herald-totp --network herald-totp -p 8084:8084 \
-e HERALD_TOTP_ENCRYPTION_KEY \
-e REDIS_ADDR=herald-totp-redis:6379 \
herald-totp
Optional: add -e API_KEY=your_shared_secret and set HERALD_TOTP_API_KEY to the same value on Stargate.
Released images are also available from GHCR. Use an immutable version tag for
deployments, for example ghcr.io/soulteary/herald-totp:v1.0.0.
Documentation
Testing
go test ./...
Coverage:
go test -cover ./...
go test -coverprofile=coverage.out ./...
go tool cover -func=coverage.out
go tool cover -html=coverage.out
Lint: golangci-lint run.
Operation
- Graceful shutdown: On
SIGINT or SIGTERM, the server stops accepting new requests and shuts down with a 10s timeout. Logs "shutting down" and any shutdown error.
- Logging: Structured JSON logs via logger-kit. Set
LOG_LEVEL to debug for more detail.
License
See LICENSE for details.