Files
cmroubao/backend-api/internal/domain/auth.go
T

147 lines
2.9 KiB
Go

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 = 12
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
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
}
}