package main import ( "crypto/rand" "crypto/sha256" "crypto/subtle" "encoding/base64" "errors" "fmt" "strconv" "strings" "golang.org/x/crypto/argon2" ) const ( RoleViewer = "viewer" RoleOperator = "operator" RoleAdministrator = "administrator" ) type User struct { ID string `json:"id"` Username string `json:"username"` Role string `json:"role"` } func HashPassword(password string) (string, error) { if len(password) < 12 || len(password) > 1024 { return "", errors.New("password must be between 12 and 1024 bytes") } salt := make([]byte, 16) if _, err := rand.Read(salt); err != nil { return "", err } const memory = 64 * 1024 const iterations = 3 const parallelism = 2 hash := argon2.IDKey([]byte(password), salt, iterations, memory, parallelism, 32) return fmt.Sprintf("$argon2id$v=19$m=%d,t=%d,p=%d$%s$%s", memory, iterations, parallelism, base64.RawStdEncoding.EncodeToString(salt), base64.RawStdEncoding.EncodeToString(hash)), nil } func VerifyPassword(encoded, password string) bool { parts := strings.Split(encoded, "$") if len(parts) != 6 || parts[1] != "argon2id" || parts[2] != "v=19" { return false } var memory uint32 var iterations uint32 var parallelism uint8 if _, err := fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &memory, &iterations, ¶llelism); err != nil { return false } if memory > 128*1024 || iterations > 10 || parallelism > 8 { return false } salt, err := base64.RawStdEncoding.DecodeString(parts[4]) if err != nil || len(salt) < 16 { return false } expected, err := base64.RawStdEncoding.DecodeString(parts[5]) if err != nil || len(expected) != 32 { return false } actual := argon2.IDKey([]byte(password), salt, iterations, memory, parallelism, uint32(len(expected))) return subtle.ConstantTimeCompare(actual, expected) == 1 } func randomToken() (string, []byte, error) { raw := make([]byte, 32) if _, err := rand.Read(raw); err != nil { return "", nil, err } token := base64.RawURLEncoding.EncodeToString(raw) digest := sha256.Sum256([]byte(token)) return token, digest[:], nil } func tokenHash(token string) []byte { digest := sha256.Sum256([]byte(token)) return digest[:] } func validRole(role string) bool { return role == RoleViewer || role == RoleOperator || role == RoleAdministrator } func permits(role, required string) bool { rank := map[string]int{RoleViewer: 1, RoleOperator: 2, RoleAdministrator: 3} return rank[role] >= rank[required] && rank[required] > 0 } func validUsername(username string) bool { if len(username) < 3 || len(username) > 64 { return false } for _, char := range username { if !(char >= 'a' && char <= 'z') && !(char >= 'A' && char <= 'Z') && !(char >= '0' && char <= '9') && !strings.ContainsRune("._-", char) { return false } } return true } func parseInt(value string, fallback int) int { parsed, err := strconv.Atoi(value) if err != nil { return fallback } return parsed }