package totp import ( "encoding/base32" "errors" "fmt" "time" "edge-infra.dev/pkg/edge/api/graph/model" "github.com/pquerna/otp" "github.com/pquerna/otp/totp" ) const ( // DefaultTokenDuration is the default time in seconds // a totp token will be valid for DefaultTokenDuration = 600 // DefaultOtpLength is the default length of // the totp token DefaultOtpLength = 6 // InvalidTOTPToken message for invalid totp token InvalidTOTPToken = "invalid totp token/secret" ) var ( ErrInvalidTOTPToken = errors.New(InvalidTOTPToken) ) // GenerateTotp generates a totp token with the provided secret func GenerateTotp(totpSecret string) (*model.Totp, error) { secret := base32.StdEncoding.EncodeToString([]byte(totpSecret)) currentTime := time.Now().UTC() expiryTime := currentTime.Add(DefaultTokenDuration * time.Second) passcode, err := totp.GenerateCodeCustom(secret, currentTime, totp.ValidateOpts{ Period: DefaultTokenDuration, Digits: otp.Digits(DefaultOtpLength), }) if err != nil { return nil, err } response := &model.Totp{ Code: passcode, CreatedAt: currentTime.String(), ExpiresAt: expiryTime.String(), Duration: DefaultTokenDuration, } return response, nil } // ValidateTotpToken validates that a totp token is valid and has not be tampered with or expired func ValidateTotpToken(token string, totpSecret string) error { secret := base32.StdEncoding.EncodeToString([]byte(totpSecret)) currentTime := time.Now().UTC() valid, err := totp.ValidateCustom(token, secret, currentTime, totp.ValidateOpts{ Period: DefaultTokenDuration, Digits: otp.Digits(DefaultOtpLength), }) if err != nil { return fmt.Errorf("%s: %v", InvalidTOTPToken, err) } if !valid { return ErrInvalidTOTPToken } return nil }