monorepo/cloud/maplepress-backend/internal/domain/user/entity.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
}