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 ( ScopeProjectsRead Scope = "projects:read" ScopeProjectsExecute Scope = "projects:execute" ScopeKeysRead Scope = "keys:read" ScopeKeysWrite Scope = "keys:write" ScopeAuditRead Scope = "audit:read" ScopeQueueRead Scope = "queue:read" ScopeQueueWrite Scope = "queue:write" ScopeWebhookRead Scope = "webhook:read" ScopeWebhookWrite Scope = "webhook:write" ScopeWorkersRead Scope = "workers:read" ScopeWorkersWrite Scope = "workers:write" ScopeBuildRead Scope = "build:read" ScopeBuildWrite Scope = "build:write" ScopeAdmin Scope = "admin" ) // AllScopes is the list of all valid scopes. var AllScopes = []Scope{ ScopeProjectsRead, ScopeProjectsExecute, ScopeKeysRead, ScopeKeysWrite, ScopeAuditRead, ScopeQueueRead, ScopeQueueWrite, ScopeWebhookRead, ScopeWebhookWrite, ScopeWorkersRead, ScopeWorkersWrite, ScopeBuildRead, ScopeBuildWrite, ScopeAdmin, } // ScopeDescriptions provides human-readable descriptions. var ScopeDescriptions = map[Scope]string{ ScopeProjectsRead: "List and view project details", ScopeProjectsExecute: "Execute commands (claude, shell, git) on projects", ScopeKeysRead: "List API keys (metadata only, not secrets)", ScopeKeysWrite: "Create and revoke API keys", ScopeAuditRead: "View audit logs for command executions", ScopeQueueRead: "View queued commands and queue status", ScopeQueueWrite: "Enqueue and cancel queued commands", ScopeWebhookRead: "View webhooks and delivery history", ScopeWebhookWrite: "Create, update, and delete webhooks", ScopeWorkersRead: "View workers and worker status", ScopeWorkersWrite: "Manage workers (drain, register)", ScopeBuildRead: "View build status and history", ScopeBuildWrite: "Start and manage builds", ScopeAdmin: "Full administrative access (includes all scopes)", } // IsValid checks if a scope is valid. func (s Scope) IsValid() bool { for _, scope := range AllScopes { if scope == s { return true } } return false } // String returns the scope as a string. func (s Scope) String() string { return string(s) } // ScopesFromStrings converts string slice to Scope slice. func ScopesFromStrings(ss []string) []Scope { scopes := make([]Scope, len(ss)) for i, s := range ss { scopes[i] = Scope(s) } return scopes } // ScopesToStrings converts Scope slice to string slice. func ScopesToStrings(scopes []Scope) []string { ss := make([]string, len(scopes)) for i, s := range scopes { ss[i] = string(s) } return ss } // ValidateScopes checks if all scopes are valid. func ValidateScopes(scopes []Scope) bool { for _, s := range scopes { if !s.IsValid() { return false } } return true } // HasScope checks if a scope list contains a required scope. // Admin scope grants access to everything. func HasScope(scopes []Scope, required Scope) bool { for _, s := range scopes { if s == ScopeAdmin || s == required { return true } } return false } // HasAnyScope checks if a scope list contains any of the required scopes. func HasAnyScope(scopes []Scope, required ...Scope) bool { for _, r := range required { if HasScope(scopes, r) { return true } } return false } // 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 }