158 lines
3.7 KiB
Go
158 lines
3.7 KiB
Go
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
|
|
}
|