rdev/internal/domain/apikey.go
jordan 72d16929ca feat: Implement hexagonal architecture with services, webhooks, queue, and telemetry
Major refactoring to hexagonal (ports & adapters) architecture:

- Add service layer (apikey_service, project_service) for business logic
- Add webhook system with dispatcher and delivery tracking
- Add command queue with priority-based processing
- Add rate limiting with sliding window algorithm
- Add audit logging for command execution
- Add OpenTelemetry integration (traces, metrics, spans)
- Add circuit breaker for fault tolerance
- Add cached repository wrapper for performance
- Add comprehensive validation package
- Add Kubernetes client integration for pod management
- Add database migrations (allowed_ips, audit_log, rate_limiting, queue, webhooks)
- Add network policy and PodDisruptionBudget for k8s
- Remove legacy executor and projects/registry packages
- Untrack secrets.yaml (now managed via envault)
- Add coverage.out to .gitignore
- Add e2e test infrastructure with docker-compose
- Add comprehensive documentation (API, architecture, operations, plans)
- Add golangci-lint config and pre-commit hook

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 19:57:46 -07:00

118 lines
2.8 KiB
Go

package domain
import (
"net"
"time"
)
// APIKeyID is a strongly-typed identifier for API keys.
type APIKeyID string
// Scope represents a permission scope for API keys.
type Scope string
const (
ScopeAdmin Scope = "admin"
ScopeProjectsRead Scope = "projects:read"
ScopeProjectsExecute Scope = "projects:execute"
ScopeKeysManage Scope = "keys:manage"
)
// APIKey represents an API key for authentication.
type APIKey struct {
ID APIKeyID
Name string
KeyPrefix string // First 8 chars of key for identification
Scopes []Scope
ProjectIDs []ProjectID // nil = access to all projects
AllowedIPs []string // CIDR notation, e.g., ["192.168.1.0/24", "10.0.0.0/8"]; nil = no restriction
CreatedAt time.Time
ExpiresAt *time.Time
LastUsedAt *time.Time
RevokedAt *time.Time
CreatedBy string
}
// IsExpired returns true if the key has expired.
func (k *APIKey) IsExpired() bool {
if k.ExpiresAt == nil {
return false
}
return time.Now().After(*k.ExpiresAt)
}
// IsRevoked returns true if the key has been revoked.
func (k *APIKey) IsRevoked() bool {
return k.RevokedAt != nil
}
// IsActive returns true if the key is valid for use.
func (k *APIKey) IsActive() bool {
return !k.IsRevoked() && !k.IsExpired()
}
// HasScope returns true if the key has the specified scope.
func (k *APIKey) HasScope(scope Scope) bool {
// Admin scope grants all permissions
for _, s := range k.Scopes {
if s == ScopeAdmin || s == scope {
return true
}
}
return false
}
// HasAnyScope returns true if the key has any of the specified scopes.
func (k *APIKey) HasAnyScope(scopes ...Scope) bool {
for _, scope := range scopes {
if k.HasScope(scope) {
return true
}
}
return false
}
// HasProjectAccess returns true if the key can access the given project.
func (k *APIKey) HasProjectAccess(projectID ProjectID) bool {
// Admin or nil project list means access to all projects
if k.HasScope(ScopeAdmin) || k.ProjectIDs == nil {
return true
}
for _, pid := range k.ProjectIDs {
if pid == projectID {
return true
}
}
return false
}
// IsIPAllowed checks if the given IP address is allowed by the key's IP restrictions.
// Returns true if no IP restrictions are set or if the IP matches any allowed CIDR.
func (k *APIKey) IsIPAllowed(clientIP string) bool {
// No restrictions means all IPs are allowed
if len(k.AllowedIPs) == 0 {
return true
}
ip := net.ParseIP(clientIP)
if ip == nil {
return false
}
for _, cidr := range k.AllowedIPs {
_, network, err := net.ParseCIDR(cidr)
if err != nil {
// If not a CIDR, try parsing as single IP
allowedIP := net.ParseIP(cidr)
if allowedIP != nil && allowedIP.Equal(ip) {
return true
}
continue
}
if network.Contains(ip) {
return true
}
}
return false
}