togo install togo-framework/auth-mfaAdds the security layer Fort-grade auth needs, on top of the togo auth plugin:
TOTP 2FA + recovery codes, email OTP, magic-link login, account lockout, password policy, and an MFA challenge state-machine that gates a full session behind a second factor.
primary login ──► if mfa.Required(user): IssueChallenge() ──► challenge token (5 min, NOT a session)
│
client completes a factor (TOTP / recovery / OTP) ─────────┘
▼
on success: auth.IssueSession() ──► real session
Challenge tokens are HMAC-signed with purpose=mfa and are rejected as sessions.
| Method | Path | Purpose |
|---|---|---|
| POST | /totp/enroll |
generate a TOTP secret + otpauth:// URI (QR) |
| POST | /totp/verify |
verify a code (activates; pass challenge to complete login) |
| POST | /totp/disable |
remove 2FA |
| POST | /recovery/generate |
8 single-use recovery codes (shown once) |
| POST | /recovery/verify |
consume a recovery code |
| POST | /otp/send |
email a 6-digit OTP (rate-limited 3/10min) |
| POST | /otp/verify |
verify the OTP |
| POST | /magic-link/send |
email a signed login link |
| GET | /magic-link/verify?token= |
log in via the link |
| GET | /challenge/status?challenge= |
inspect a pending challenge |
m, _ := mfa.FromKernel(k)
if m.Required(userID) {
tok, _ := m.IssueChallenge(userID) // hand the client a challenge token instead of a session
}
secret, uri, _ := m.EnrollTOTP(userID, "MyApp") // show the QR
m.VerifyTOTP(userID, "123456") // activates on first success
codes := m.GenerateRecoveryCodes(userID, 8)
m.RecordFailure(userID); locked, until := m.IsLocked(userID) // lockout
m.ValidatePassword(pw) // policy| Env | Default | |
|---|---|---|
MFA_CHALLENGE_SECRET |
dev secret | HMAC key for challenge / magic-link tokens — set in prod |
MFA_OTP_TTL |
5m |
email-OTP lifetime |
MFA_LOCKOUT_THRESHOLD |
5 |
failures before lock |
MFA_LOCKOUT_MINUTES |
15 |
lock duration |
MFA_PW_MIN_LEN |
12 |
password minimum length |
OTP/magic-link delivery: wire the togo mail/notifications plugin (the code is logged in dev). State is in a bounded in-memory store with a pluggable Store (WithStore) for DB persistence.