package auth import ( "bytes" "image/png" "time" "github.com/pquerna/otp" "github.com/pquerna/otp/totp" ) // GenerateSecret creates a new TOTP secret for the given user. // Returns the base32-encoded secret, the otpauth:// URL, and a QR code as PNG bytes. func GenerateSecret(username, issuer string) (secret string, otpauthURL string, qrPNG []byte, err error) { key, err := totp.Generate(totp.GenerateOpts{ Issuer: issuer, AccountName: username, SecretSize: 20, Algorithm: otp.AlgorithmSHA1, Digits: otp.DigitsSix, Period: 30, }) if err != nil { return "", "", nil, err } secret = key.Secret() otpauthURL = key.URL() img, err := key.Image(200, 200) if err != nil { return secret, otpauthURL, nil, nil // QR code is optional } var buf bytes.Buffer if encErr := png.Encode(&buf, img); encErr == nil { qrPNG = buf.Bytes() } return secret, otpauthURL, qrPNG, nil } // ValidateTOTP checks whether the given code is valid for the base32 secret. // Allows +/- 1 time step (30s) tolerance. func ValidateTOTP(secret, code string) bool { valid, err := totp.ValidateCustom(code, secret, time.Now().UTC(), totp.ValidateOpts{ Period: 30, Skew: 1, Digits: otp.DigitsSix, Algorithm: otp.AlgorithmSHA1, }) return err == nil && valid }