package domain import ( "errors" "fmt" "strings" "time" "unicode/utf8" ) type UserRole string const ( UserRoleAdmin UserRole = "ADMIN" UserRoleBuyer UserRole = "BUYER" MaxUsernameBytes = 128 MaxDeviceNameBytes = 128 MaxVersionBytes = 128 MinPasswordBytes = 6 MaxPasswordBytes = 72 ) type User struct { ID string Username string PasswordHash string Role UserRole IsActive bool CreatedAt time.Time UpdatedAt time.Time } type Device struct { ID string Name string TokenHash string BoundUserID *string AppVersion *string AndroidVersion *string PDDVersion *string LastSeenAt *time.Time ReadinessAt *time.Time AccessibilityEnabled bool PDDInstalled bool IsEnabled bool CreatedAt time.Time UpdatedAt time.Time } type AdminSession struct { ID string TokenHash string UserID string ExpiresAt time.Time RevokedAt *time.Time CreatedAt time.Time } type AccessToken struct { ID string TokenHash string UserID string DeviceID string ExpiresAt time.Time RevokedAt *time.Time CreatedAt time.Time } type AuthPrincipal struct { UserID string Username string Role UserRole SessionID string DeviceID string ExpiresAt time.Time } type AuthValidationError struct { Fields map[string]string } func (e *AuthValidationError) Error() string { return "authentication input validation failed" } func NormalizeUsername(value string) string { return strings.ToLower(strings.TrimSpace(value)) } func ValidateUserInput( username string, password string, role UserRole, ) error { fields := make(map[string]string) normalized := NormalizeUsername(username) switch { case normalized == "": fields["username"] = "required" case !utf8.ValidString(normalized): fields["username"] = "must be valid UTF-8" case len([]byte(normalized)) > MaxUsernameBytes: fields["username"] = fmt.Sprintf( "must not exceed %d UTF-8 bytes", MaxUsernameBytes, ) } switch { case password == "": fields["password"] = "required" case !utf8.ValidString(password): fields["password"] = "must be valid UTF-8" case len([]byte(password)) < MinPasswordBytes: fields["password"] = fmt.Sprintf( "must be at least %d UTF-8 bytes", MinPasswordBytes, ) case len([]byte(password)) > MaxPasswordBytes: fields["password"] = fmt.Sprintf( "must not exceed %d UTF-8 bytes", MaxPasswordBytes, ) } if role != UserRoleAdmin && role != UserRoleBuyer { fields["role"] = "must be ADMIN or BUYER" } if len(fields) > 0 { return &AuthValidationError{Fields: fields} } return nil } func ValidateDeviceName(name string) error { name = strings.TrimSpace(name) switch { case name == "": return errors.New("device name is required") case !utf8.ValidString(name): return errors.New("device name must be valid UTF-8") case len([]byte(name)) > MaxDeviceNameBytes: return fmt.Errorf( "device name must not exceed %d UTF-8 bytes", MaxDeviceNameBytes, ) default: return nil } }