48 lines
1.1 KiB
Go
48 lines
1.1 KiB
Go
package data
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
|
|
twofactor "git.wntrmute.dev/kyle/goutils/twofactor"
|
|
)
|
|
|
|
const totpPeriod = 30
|
|
|
|
// builtinTOTPValidator delegates TOTP validation to goutils/twofactor.
|
|
type builtinTOTPValidator struct{}
|
|
|
|
func (builtinTOTPValidator) Validate(secret, code string, at time.Time, window int) bool {
|
|
if secret == "" || code == "" {
|
|
return false
|
|
}
|
|
// Normalize secret similar to common authenticator apps: remove spaces and uppercase.
|
|
norm := strings.ToUpper(strings.ReplaceAll(secret, " ", ""))
|
|
norm = twofactor.Pad(norm)
|
|
otp, err := twofactor.NewGoogleTOTP(norm)
|
|
if err != nil || otp == nil {
|
|
return false
|
|
}
|
|
|
|
// Compute the base counter for the provided time (period 30s, start 0).
|
|
base := uint64(at.Unix()&unsignedMask64) / totpPeriod // #nosec G115 - masked off overflow
|
|
// Check +/- window steps.
|
|
for i := -window; i <= window; i++ {
|
|
var ctr uint64
|
|
if i < 0 {
|
|
// Guard underflow.
|
|
offs := uint64(-i)
|
|
if offs > base {
|
|
continue
|
|
}
|
|
ctr = base - offs
|
|
} else {
|
|
ctr = base + uint64(i)
|
|
}
|
|
if otp.OATH.OTP(ctr) == code {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|