package apikey import ( "crypto/rand" "encoding/base64" "fmt" "strings" ) const ( // PrefixLive is the prefix for production API keys PrefixLive = "live_sk_" // PrefixTest is the prefix for test/sandbox API keys PrefixTest = "test_sk_" // KeyLength is the length of the random part (40 chars in base64url) KeyLength = 30 // 30 bytes = 40 base64url chars ) // Generator generates API keys type Generator interface { // Generate creates a new live API key Generate() (string, error) // GenerateTest creates a new test API key GenerateTest() (string, error) } type generator struct{} // NewGenerator creates a new API key generator func NewGenerator() Generator { return &generator{} } // Generate creates a new live API key func (g *generator) Generate() (string, error) { return g.generateWithPrefix(PrefixLive) } // GenerateTest creates a new test API key func (g *generator) GenerateTest() (string, error) { return g.generateWithPrefix(PrefixTest) } func (g *generator) generateWithPrefix(prefix string) (string, error) { // Generate cryptographically secure random bytes b := make([]byte, KeyLength) if _, err := rand.Read(b); err != nil { return "", fmt.Errorf("failed to generate random bytes: %w", err) } // Encode to base64url (URL-safe, no padding) key := base64.RawURLEncoding.EncodeToString(b) // Remove any special chars and make lowercase for consistency key = strings.Map(func(r rune) rune { if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') { return r } return -1 // Remove character }, key) // Ensure we have at least 40 characters if len(key) < 40 { // Pad with additional random bytes if needed additional := make([]byte, 10) rand.Read(additional) extraKey := base64.RawURLEncoding.EncodeToString(additional) key += extraKey } // Trim to exactly 40 characters key = key[:40] return prefix + key, nil } // ExtractPrefix extracts the prefix from an API key func ExtractPrefix(apiKey string) string { if len(apiKey) < 13 { return "" } return apiKey[:13] // "live_sk_a1b2" or "test_sk_a1b2" } // ExtractLastFour extracts the last 4 characters from an API key func ExtractLastFour(apiKey string) string { if len(apiKey) < 4 { return "" } return apiKey[len(apiKey)-4:] } // IsValid checks if an API key has a valid format func IsValid(apiKey string) bool { return strings.HasPrefix(apiKey, PrefixLive) || strings.HasPrefix(apiKey, PrefixTest) }