package token import ( "crypto/rand" "crypto/sha256" "encoding/base64" "fmt" "strings" ) const TokenLength = 32 // 32 bytes = 256 bits per token // Tokens holds the 3 authentication tokens type Tokens struct { Token1 string `json:"token1"` Token2 string `json:"token2"` Token3 string `json:"token3"` } // Generate creates 3 cryptographically random tokens func Generate() (*Tokens, error) { t := &Tokens{} var err error t.Token1, err = randomToken() if err != nil { return nil, fmt.Errorf("generating token 1: %w", err) } t.Token2, err = randomToken() if err != nil { return nil, fmt.Errorf("generating token 2: %w", err) } t.Token3, err = randomToken() if err != nil { return nil, fmt.Errorf("generating token 3: %w", err) } return t, nil } // Hash computes the SHA256 hash of the 3 tokens combined func (t *Tokens) Hash() string { combined := strings.Join([]string{t.Token1, t.Token2, t.Token3}, ":") sum := sha256.Sum256([]byte(combined)) return fmt.Sprintf("%x", sum) } // HashFromString computes the hash from a pre-combined token string func HashFromTokens(token1, token2, token3 string) string { t := &Tokens{Token1: token1, Token2: token2, Token3: token3} return t.Hash() } func randomToken() (string, error) { b := make([]byte, TokenLength) if _, err := rand.Read(b); err != nil { return "", err } return base64.URLEncoding.EncodeToString(b), nil }