Junie: TOTP flow update and db migrations.

This commit is contained in:
2025-06-06 12:42:23 -07:00
parent 396214739e
commit 95d96732d2
26 changed files with 1397 additions and 194 deletions

View File

@@ -14,6 +14,16 @@ const (
scryptN = 32768
scryptR = 8
scryptP = 2
// Constants for derived key length and comparison
derivedKeyLength = 32
validCompareResult = 1
// Empty string constant
emptyString = ""
// TOTP secret length in bytes (160 bits)
totpSecretLength = 20
)
type User struct {
@@ -48,16 +58,17 @@ func (u *User) GetPermissions(authService *AuthorizationService) ([]Permission,
type Login struct {
User string `json:"user"`
Password string `json:"password,omitzero"`
Token string `json:"token,omitzero"`
TOTPCode string `json:"totp_code,omitzero"`
Password string `json:"password,omitempty"`
Token string `json:"token,omitempty"`
TOTPCode string `json:"totp_code,omitempty"`
}
func derive(password string, salt []byte) ([]byte, error) {
return scrypt.Key([]byte(password), salt, scryptN, scryptR, scryptP, 32)
return scrypt.Key([]byte(password), salt, scryptN, scryptR, scryptP, derivedKeyLength)
}
func (u *User) Check(login *Login) bool {
// CheckPassword verifies only the username and password, without TOTP verification
func (u *User) CheckPassword(login *Login) bool {
if u.User != login.User {
return false
}
@@ -67,18 +78,23 @@ func (u *User) Check(login *Login) bool {
return false
}
if subtle.ConstantTimeCompare(derived, u.Password) != 1 {
return subtle.ConstantTimeCompare(derived, u.Password) == validCompareResult
}
func (u *User) Check(login *Login) bool {
// First check username and password
if !u.CheckPassword(login) {
return false
}
// If TOTP is enabled for the user, validate the TOTP code
if u.TOTPSecret != "" && login.TOTPCode != "" {
// If TOTP is enabled for the user, validate the TOTP code
if u.TOTPSecret != emptyString && login.TOTPCode != emptyString {
// Use the ValidateTOTPCode method to validate the TOTP code
valid, err := u.ValidateTOTPCode(login.TOTPCode)
if err != nil || !valid {
valid, validErr := u.ValidateTOTPCode(login.TOTPCode)
if validErr != nil || !valid {
return false
}
} else if u.TOTPSecret != "" && login.TOTPCode == "" {
} else if u.TOTPSecret != emptyString && login.TOTPCode == emptyString {
// TOTP is enabled but no code was provided
return false
}
@@ -89,11 +105,11 @@ func (u *User) Check(login *Login) bool {
func (u *User) Register(login *Login) error {
var err error
if u.User != "" && u.User != login.User {
if u.User != emptyString && u.User != login.User {
return errors.New("invalid user")
}
if u.ID == "" {
if u.ID == emptyString {
u.ID = ulid.Make().String()
}
@@ -115,9 +131,9 @@ func (u *User) Register(login *Login) error {
// GenerateTOTPSecret generates a new TOTP secret for the user
func (u *User) GenerateTOTPSecret() (string, error) {
// Generate a random secret
secret, err := GenerateRandomBase32(20) // 20 bytes = 160 bits
secret, err := GenerateRandomBase32(totpSecretLength)
if err != nil {
return "", fmt.Errorf("failed to generate TOTP secret: %w", err)
return emptyString, fmt.Errorf("failed to generate TOTP secret: %w", err)
}
u.TOTPSecret = secret
@@ -126,7 +142,7 @@ func (u *User) GenerateTOTPSecret() (string, error) {
// ValidateTOTPCode validates a TOTP code against the user's TOTP secret
func (u *User) ValidateTOTPCode(code string) (bool, error) {
if u.TOTPSecret == "" {
if u.TOTPSecret == emptyString {
return false, errors.New("TOTP not enabled for user")
}
@@ -137,5 +153,5 @@ func (u *User) ValidateTOTPCode(code string) (bool, error) {
// HasTOTP returns true if TOTP is enabled for the user
func (u *User) HasTOTP() bool {
return u.TOTPSecret != ""
return u.TOTPSecret != emptyString
}