package domain import ( "errors" "strings" "unicode" "unicode/utf8" "golang.org/x/text/cases" "golang.org/x/text/unicode/norm" ) const ( MinimumPasswordRunes = 12 MaximumPasswordBytes = 1024 MaximumIdentityRunes = 128 ) var ( ErrInvalidUsername = errors.New("invalid username") ErrInvalidDisplayName = errors.New("invalid display name") ErrInvalidPassword = errors.New("invalid password") ) type OwnerIdentity struct { Username string UsernameKey string DisplayName string } func NewOwnerIdentity(username, displayName string) (OwnerIdentity, error) { username, usernameKey, err := NormalizeUsername(username) if err != nil { return OwnerIdentity{}, err } displayName = norm.NFC.String(strings.TrimSpace(displayName)) if !validIdentityText(displayName) { return OwnerIdentity{}, ErrInvalidDisplayName } return OwnerIdentity{ Username: username, UsernameKey: usernameKey, DisplayName: displayName, }, nil } func NormalizeUsername(username string) (string, string, error) { username = norm.NFC.String(strings.TrimSpace(username)) if !validIdentityText(username) { return "", "", ErrInvalidUsername } return username, cases.Fold().String(username), nil } func ValidatePassword(password string) error { if !utf8.ValidString(password) || len(password) > MaximumPasswordBytes || utf8.RuneCountInString(password) < MinimumPasswordRunes { return ErrInvalidPassword } return nil } func validIdentityText(value string) bool { count := utf8.RuneCountInString(value) if count == 0 || count > MaximumIdentityRunes { return false } for _, character := range value { if unicode.IsControl(character) { return false } } return true }