package data import ( "crypto/subtle" "errors" "fmt" "time" "github.com/oklog/ulid/v2" "golang.org/x/crypto/scrypt" ) 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 { ID string Created int64 User string Password []byte Salt []byte TOTPSecret string Roles []string } // HasRole checks if the user has a specific role func (u *User) HasRole(role string) bool { for _, r := range u.Roles { if r == role { return true } } return false } // HasPermission checks if the user has a specific permission using the authorization service func (u *User) HasPermission(authService *AuthorizationService, resource, action string) (bool, error) { return authService.UserHasPermission(u.ID, resource, action) } // GetPermissions returns all permissions for the user using the authorization service func (u *User) GetPermissions(authService *AuthorizationService) ([]Permission, error) { return authService.GetUserPermissions(u.ID) } type Login struct { User string `json:"user"` 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, derivedKeyLength) } // CheckPassword verifies only the username and password, without TOTP verification func (u *User) CheckPassword(login *Login) bool { if u.User != login.User { return false } derived, err := derive(login.Password, u.Salt) if err != nil { return false } 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 != emptyString && login.TOTPCode != emptyString { // Use the ValidateTOTPCode method to validate the TOTP code valid, validErr := u.ValidateTOTPCode(login.TOTPCode) if validErr != nil || !valid { return false } } else if u.TOTPSecret != emptyString && login.TOTPCode == emptyString { // TOTP is enabled but no code was provided return false } return true } func (u *User) Register(login *Login) error { var err error if u.User != emptyString && u.User != login.User { return errors.New("invalid user") } if u.ID == emptyString { u.ID = ulid.Make().String() } u.User = login.User u.Salt, err = Salt() if err != nil { return fmt.Errorf("failed to register user: %w", err) } u.Password, err = derive(login.Password, u.Salt) if err != nil { return fmt.Errorf("key derivation failed: %w", err) } u.Created = time.Now().Unix() return nil } // GenerateTOTPSecret generates a new TOTP secret for the user func (u *User) GenerateTOTPSecret() (string, error) { // Generate a random secret secret, err := GenerateRandomBase32(totpSecretLength) if err != nil { return emptyString, fmt.Errorf("failed to generate TOTP secret: %w", err) } u.TOTPSecret = secret return u.TOTPSecret, nil } // ValidateTOTPCode validates a TOTP code against the user's TOTP secret func (u *User) ValidateTOTPCode(code string) (bool, error) { if u.TOTPSecret == emptyString { return false, errors.New("TOTP not enabled for user") } // Use the twofactor package to validate the code valid := ValidateTOTP(u.TOTPSecret, code) return valid, nil } // HasTOTP returns true if TOTP is enabled for the user func (u *User) HasTOTP() bool { return u.TOTPSecret != emptyString }