Files
scrum-solitare/src/state/utils.go
2026-03-06 12:30:05 +02:00

91 lines
1.9 KiB
Go

package state
import (
"crypto/rand"
"crypto/sha256"
"crypto/subtle"
"encoding/hex"
"fmt"
"sort"
"strings"
"time"
"unicode"
)
func randomHex(bytes int) string {
buf := make([]byte, bytes)
_, _ = rand.Read(buf)
return hex.EncodeToString(buf)
}
func newUUIDv4() string {
buf := make([]byte, 16)
_, _ = rand.Read(buf)
buf[6] = (buf[6] & 0x0f) | 0x40
buf[8] = (buf[8] & 0x3f) | 0x80
return fmt.Sprintf("%x-%x-%x-%x-%x", buf[0:4], buf[4:6], buf[6:8], buf[8:10], buf[10:16])
}
func normalizeName(input string, max int) string {
trimmed := strings.TrimSpace(input)
if trimmed == "" {
return ""
}
builder := strings.Builder{}
for _, r := range trimmed {
if r == '\n' || r == '\r' || r == '\t' {
continue
}
if unicode.IsPrint(r) {
builder.WriteRune(r)
}
if builder.Len() >= max {
break
}
}
return strings.TrimSpace(builder.String())
}
func normalizeCard(input string) string {
return normalizeName(input, 8)
}
func hashPassword(password, salt string) string {
h := sha256.Sum256([]byte(salt + ":" + password))
return hex.EncodeToString(h[:])
}
func passwordMatches(password, salt, expectedHash string) bool {
computed := hashPassword(password, salt)
return subtle.ConstantTimeCompare([]byte(computed), []byte(expectedHash)) == 1
}
func secureTokenMatches(expected, provided string) bool {
if expected == "" || provided == "" || len(expected) != len(provided) {
return false
}
return subtle.ConstantTimeCompare([]byte(expected), []byte(provided)) == 1
}
func nowUTC() time.Time {
return time.Now().UTC()
}
func sortParticipants(participants map[string]*Participant) []*Participant {
list := make([]*Participant, 0, len(participants))
for _, participant := range participants {
list = append(list, participant)
}
sort.SliceStable(list, func(i, j int) bool {
if list[i].JoinedAt.Equal(list[j].JoinedAt) {
return list[i].Username < list[j].Username
}
return list[i].JoinedAt.Before(list[j].JoinedAt)
})
return list
}