96 lines
2.4 KiB
Go
96 lines
2.4 KiB
Go
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)
|
|
}
|