package util import ( "crypto/sha256" "crypto/subtle" "encoding/base64" "encoding/hex" "fmt" "strings" ) // SHA256Hex returns the hex-encoded SHA-256 hash of a string. func SHA256Hex(input string) string { h := sha256.Sum256([]byte(input)) return hex.EncodeToString(h[:]) } // IsTokenValid checks a plaintext input against a plaintext secret. // Per review-resolution #24: SHA-256 both sides, then constant-time compare. func IsTokenValid(input, secret string) bool { if secret == "" || input == "" { return false } inputHash := SHA256Hex(input) secretHash := SHA256Hex(secret) return subtle.ConstantTimeCompare([]byte(inputHash), []byte(secretHash)) == 1 } // IsGrantTokenValid checks a plaintext input against a stored SHA-256 hash. // Per review-resolution #24: hash the input, then constant-time compare with stored hash. func IsGrantTokenValid(input, storedHash string) bool { if input == "" || storedHash == "" { return false } inputHash := SHA256Hex(input) return subtle.ConstantTimeCompare([]byte(inputHash), []byte(storedHash)) == 1 } // RandomToken generates a 24-byte random base64url token (no padding). func RandomToken() (string, error) { var buf [24]byte if _, err := readRandom(buf[:]); err != nil { return "", err } return base64.RawURLEncoding.EncodeToString(buf[:]), nil } // EncodeBase64URL encodes to standard base64 URL encoding with padding. func EncodeBase64URL(input string) string { return base64.URLEncoding.EncodeToString([]byte(input)) } // EncodeBase64RawURL encodes to base64 URL encoding without padding. func EncodeBase64RawURL(input string) string { return base64.RawURLEncoding.EncodeToString([]byte(input)) } // EncodeBase64Std encodes to standard base64 encoding. func EncodeBase64Std(input string) string { return base64.StdEncoding.EncodeToString([]byte(input)) } // DecodeBase64Std decodes standard base64. func DecodeBase64Std(s string) (string, error) { b, err := base64.StdEncoding.DecodeString(s) if err != nil { return "", err } return string(b), nil } // DecodeBase64URL decodes URL-safe base64 with padding. func DecodeBase64URL(s string) (string, error) { b, err := base64.URLEncoding.DecodeString(s) if err != nil { return "", err } return string(b), nil } // DecodeBase64RawURL decodes URL-safe base64 without padding. // Per review-resolution #15: used for SSR. func DecodeBase64RawURL(s string) (string, error) { b, err := base64.RawURLEncoding.DecodeString(s) if err != nil { return "", err } return string(b), nil } // DecodeBase64Auto tries Std first, then RawURL — per review-resolution #15. func DecodeBase64Auto(s string) (string, error) { s = strings.TrimSpace(s) // Try standard base64 first if b, err := base64.StdEncoding.DecodeString(s); err == nil { return string(b), nil } // Try raw URL encoding (no padding) if b, err := base64.RawURLEncoding.DecodeString(s); err == nil { return string(b), nil } // Try URL encoding with padding if b, err := base64.URLEncoding.DecodeString(s); err == nil { return string(b), nil } return "", fmt.Errorf("invalid base64") } // Base64Utf8 encodes a UTF-8 string to standard base64. func Base64Utf8(input string) string { return base64.StdEncoding.EncodeToString([]byte(input)) }