Implement owner setup and browser authentication sessions
Some checks failed
CI / Backend (push) Failing after 1s
CI / Frontend (push) Successful in 13s
CI / Contracts and repository policy (push) Failing after 3s
CI / Container (push) Has been skipped

This commit is contained in:
Elijah 2026-07-16 19:45:40 -07:00
parent 077cf7601a
commit 715423ab8e
21 changed files with 2185 additions and 14 deletions

View file

@ -0,0 +1,73 @@
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
}

View file

@ -0,0 +1,26 @@
package domain
import (
"errors"
"testing"
)
func TestNewOwnerIdentityNormalizesAndFoldsUsername(t *testing.T) {
t.Parallel()
identity, err := NewOwnerIdentity(" ÉLIJAH ", " Elijah Kuntz ")
if err != nil {
t.Fatalf("NewOwnerIdentity returned an error: %v", err)
}
if identity.Username != "ÉLIJAH" || identity.UsernameKey != "élijah" || identity.DisplayName != "Elijah Kuntz" {
t.Fatalf("unexpected identity: %#v", identity)
}
}
func TestValidatePasswordRejectsShortPassword(t *testing.T) {
t.Parallel()
if err := ValidatePassword("too-short"); !errors.Is(err, ErrInvalidPassword) {
t.Fatalf("ValidatePassword error = %v, want %v", err, ErrInvalidPassword)
}
}