rdev/internal/domain/apikey.go
jordan 538ea57ed4 feat: Add claude-config API, security hardening, and testing infrastructure
Claude Config API (v0.6):
- Add CRUD endpoints for commands, skills, and agents
- Commands/skills/agents stored in /workspace/.claude/ (per-project, in git)
- Credentials shared via PVC at /root/.claude/ (shared across pods)
- Use base64 encoding for file writes (prevents shell injection)
- Add content size limits (1MB max)

Security Hardening:
- Add sanitize package for command/prompt validation
- Add rate limiting middleware (token bucket algorithm)
- Add concurrent command limiting
- Add input sanitization to all command handlers
- Gitignore secrets.yaml and credentials.yaml
- Add *.example templates for secrets

Testing Infrastructure:
- Add testutil package with mocks and fixtures
- Add unit tests for auth package (63% coverage)
- Add unit tests for executor (47% coverage)
- Add handler integration tests (40% coverage)
- Add 100% coverage for sanitize, cmdlimit packages
- Add 96% coverage for ratelimit package

Infrastructure:
- Shared Claude credentials PVC (ReadWriteMany)
- Reduced workspace PVC size from 20Gi to 5Gi
- Add init container cleanup before git clone
- Document Longhorn RWX requirements

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

84 lines
2.0 KiB
Go

package domain
import "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
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
}