package apikey import ( "context" "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/hex" "errors" "strings" "time" ) var ( ErrInvalid = errors.New("invalid API key") ErrStore = errors.New("API key store unavailable") ) type Record struct { ID string TenantID *string Name string KeyPrefix string KeyHash []byte Scopes []string Enabled bool RequestsPerMinute int MonthlyRequestQuota int64 MonthlyTokenQuota int64 ExpiresAt *time.Time LastUsedAt *time.Time CreatedAt time.Time } type Principal struct { APIKeyID string `json:"api_key_id"` TenantID *string `json:"tenant_id,omitempty"` Scopes []string `json:"scopes"` RequestsPerMinute int `json:"requests_per_minute"` MonthlyRequestQuota int64 `json:"monthly_request_quota"` MonthlyTokenQuota int64 `json:"monthly_token_quota"` } func Generate() (secret, prefix string, hash []byte, err error) { random := make([]byte, 32) if _, err = rand.Read(random); err != nil { return "", "", nil, err } secret = "gw_" + base64.RawURLEncoding.EncodeToString(random) prefix = secret[:16] digest := sha256.Sum256([]byte(secret)) return secret, prefix, digest[:], nil } func Digest(secret string) ([]byte, string) { digest := sha256.Sum256([]byte(strings.TrimSpace(secret))) return digest[:], hex.EncodeToString(digest[:]) } func HasScope(scopes []string, required string) bool { for _, scope := range scopes { if scope == "*" || scope == required { return true } } return false } type KeyAuthenticator interface { Authenticate(context.Context, string) error } type PrincipalAuthenticator interface { AuthenticatePrincipal(context.Context, string) (Principal, error) }