169 lines
5.3 KiB
Go
169 lines
5.3 KiB
Go
package user
|
|
|
|
import (
|
|
"errors"
|
|
"regexp"
|
|
"time"
|
|
)
|
|
|
|
// User represents a user entity in the domain
|
|
// Every user strictly belongs to a tenant
|
|
type User struct {
|
|
ID string
|
|
Email string
|
|
FirstName string
|
|
LastName string
|
|
Name string
|
|
LexicalName string
|
|
Timezone string
|
|
|
|
// Role management
|
|
Role int
|
|
|
|
// State management
|
|
Status int
|
|
|
|
// Embedded structs for better organization
|
|
ProfileData *UserProfileData
|
|
|
|
// Encapsulating security related data
|
|
SecurityData *UserSecurityData
|
|
|
|
// Metadata about the user
|
|
Metadata *UserMetadata
|
|
|
|
// Limited metadata fields used for querying
|
|
TenantID string // Every user belongs to a tenant
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
}
|
|
|
|
// UserProfileData contains user profile information
|
|
type UserProfileData struct {
|
|
Phone string
|
|
Country string
|
|
Region string
|
|
City string
|
|
PostalCode string
|
|
AddressLine1 string
|
|
AddressLine2 string
|
|
HasShippingAddress bool
|
|
ShippingName string
|
|
ShippingPhone string
|
|
ShippingCountry string
|
|
ShippingRegion string
|
|
ShippingCity string
|
|
ShippingPostalCode string
|
|
ShippingAddressLine1 string
|
|
ShippingAddressLine2 string
|
|
Timezone string
|
|
AgreeTermsOfService bool
|
|
AgreePromotions bool
|
|
AgreeToTrackingAcrossThirdPartyAppsAndServices bool
|
|
}
|
|
|
|
// UserMetadata contains audit and tracking information
|
|
type UserMetadata struct {
|
|
// CWE-359: Encrypted IP addresses for GDPR compliance
|
|
CreatedFromIPAddress string // Encrypted with go-ipcrypt
|
|
CreatedFromIPTimestamp time.Time // For 90-day expiration tracking
|
|
CreatedByUserID string
|
|
CreatedAt time.Time
|
|
CreatedByName string
|
|
ModifiedFromIPAddress string // Encrypted with go-ipcrypt
|
|
ModifiedFromIPTimestamp time.Time // For 90-day expiration tracking
|
|
ModifiedByUserID string
|
|
ModifiedAt time.Time
|
|
ModifiedByName string
|
|
LastLoginAt time.Time
|
|
}
|
|
|
|
// FullName returns the user's full name computed from FirstName and LastName
|
|
func (u *User) FullName() string {
|
|
if u.FirstName == "" && u.LastName == "" {
|
|
return u.Name // Fallback to Name field if first/last are empty
|
|
}
|
|
return u.FirstName + " " + u.LastName
|
|
}
|
|
|
|
// UserSecurityData contains security-related information
|
|
type UserSecurityData struct {
|
|
PasswordHashAlgorithm string
|
|
PasswordHash string
|
|
|
|
WasEmailVerified bool
|
|
Code string
|
|
CodeType string // 'email_verification' or 'password_reset'
|
|
CodeExpiry time.Time
|
|
|
|
// OTPEnabled controls whether we force 2FA or not during login
|
|
OTPEnabled bool
|
|
|
|
// OTPVerified indicates user has successfully validated their OTP token after enabling 2FA
|
|
OTPVerified bool
|
|
|
|
// OTPValidated automatically gets set as `false` on successful login and then sets `true` once successfully validated by 2FA
|
|
OTPValidated bool
|
|
|
|
// OTPSecret the unique one-time password secret to be shared between our backend and 2FA authenticator apps
|
|
OTPSecret string
|
|
|
|
// OTPAuthURL is the URL used to share
|
|
OTPAuthURL string
|
|
|
|
// OTPBackupCodeHash is the one-time use backup code which resets the 2FA settings
|
|
OTPBackupCodeHash string
|
|
|
|
// OTPBackupCodeHashAlgorithm tracks the hashing algorithm used
|
|
OTPBackupCodeHashAlgorithm string
|
|
}
|
|
|
|
// Domain errors
|
|
var (
|
|
ErrUserNotFound = errors.New("user not found")
|
|
ErrInvalidEmail = errors.New("invalid email format")
|
|
ErrEmailRequired = errors.New("email is required")
|
|
ErrFirstNameRequired = errors.New("first name is required")
|
|
ErrLastNameRequired = errors.New("last name is required")
|
|
ErrNameRequired = errors.New("name is required")
|
|
ErrTenantIDRequired = errors.New("tenant ID is required")
|
|
ErrPasswordRequired = errors.New("password is required")
|
|
ErrPasswordTooShort = errors.New("password must be at least 8 characters")
|
|
ErrPasswordTooWeak = errors.New("password must contain uppercase, lowercase, number, and special character")
|
|
ErrRoleRequired = errors.New("role is required")
|
|
ErrUserAlreadyExists = errors.New("user already exists")
|
|
ErrInvalidCredentials = errors.New("invalid credentials")
|
|
ErrTermsOfServiceRequired = errors.New("must agree to terms of service")
|
|
)
|
|
|
|
// Email validation regex (basic)
|
|
var emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`)
|
|
|
|
// Validate validates the user entity
|
|
func (u *User) Validate() error {
|
|
if u.TenantID == "" {
|
|
return ErrTenantIDRequired
|
|
}
|
|
|
|
if u.Email == "" {
|
|
return ErrEmailRequired
|
|
}
|
|
|
|
if !emailRegex.MatchString(u.Email) {
|
|
return ErrInvalidEmail
|
|
}
|
|
|
|
if u.Name == "" {
|
|
return ErrNameRequired
|
|
}
|
|
|
|
// Validate ProfileData if present
|
|
if u.ProfileData != nil {
|
|
// Terms of Service is REQUIRED
|
|
if !u.ProfileData.AgreeTermsOfService {
|
|
return ErrTermsOfServiceRequired
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|