rdev/internal/auth/service.go
jordan bc47e426b0 feat: Add CI pipeline proxy, DNS alias management, and worker executor system
- Add ListPipelines/GetPipeline to CIProvider port with Woodpecker adapter
- Add DNS alias endpoints: GET/POST/DELETE /projects/{id}/domains
- Implement worker executor daemon, build executor, and git operations
- Add build service, worker service, and build audit tracking
- Add worker registry with PostgreSQL adapter and migration
- Add multi-provider code agent interface (Claude Code + OpenCode)
- Add create-and-build combo endpoint
- Update landing-page cookbook to reflect all gaps closed
- Fix tech debt: unified validation, auth scopes, error wrapping, slog patterns

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 21:05:28 -07:00

116 lines
3.1 KiB
Go

package auth
import (
"context"
"fmt"
"time"
"github.com/orchard9/rdev/internal/domain"
"github.com/orchard9/rdev/internal/service"
)
// APIKey is an alias for domain.APIKey.
// All API key behavior (IsExpired, IsRevoked, etc.) lives in domain/apikey.go.
type APIKey = domain.APIKey
// Error sentinels — delegate to domain errors.
// Consumers should migrate to domain.ErrXxx over time.
var (
ErrKeyNotFound = domain.ErrKeyNotFound
ErrKeyRevoked = domain.ErrKeyRevoked
ErrKeyExpired = domain.ErrKeyExpired
ErrIPNotAllowed = domain.ErrIPNotAllowed
)
// CreateKeyRequest is the input for creating a new key.
type CreateKeyRequest struct {
Name string
Scopes []Scope
ProjectIDs []string // nil = all projects
AllowedIPs []string // CIDR notation; nil = no restriction
ExpiresIn time.Duration // 0 = never
CreatedBy string
}
// CreateKeyResponse is the output of creating a new key.
type CreateKeyResponse struct {
Key *APIKey
Secret string // Full key, shown only once
}
// Service handles API key operations.
// It wraps service.APIKeyService to provide the same interface as before
// while delegating to the hexagonal service layer.
type Service struct {
svc *service.APIKeyService
adminKey string
}
// NewService creates a new auth service.
// Accepts a service.APIKeyService (hexagonal) instead of raw *sql.DB.
func NewService(svc *service.APIKeyService, adminKey string) *Service {
return &Service{
svc: svc,
adminKey: adminKey,
}
}
// IsAdminKey checks if the provided key is the super admin key.
func (s *Service) IsAdminKey(key string) bool {
return s.adminKey != "" && key == s.adminKey
}
// Create generates a new API key.
func (s *Service) Create(ctx context.Context, req CreateKeyRequest) (*CreateKeyResponse, error) {
// Validate scopes
if !ValidateScopes(req.Scopes) {
return nil, fmt.Errorf("invalid scopes")
}
// Convert []string ProjectIDs to []domain.ProjectID
var projectIDs []domain.ProjectID
if req.ProjectIDs != nil {
projectIDs = make([]domain.ProjectID, len(req.ProjectIDs))
for i, p := range req.ProjectIDs {
projectIDs[i] = domain.ProjectID(p)
}
}
result, err := s.svc.Create(ctx, service.CreateKeyRequest{
Name: req.Name,
Scopes: req.Scopes,
ProjectIDs: projectIDs,
AllowedIPs: req.AllowedIPs,
ExpiresIn: req.ExpiresIn,
CreatedBy: req.CreatedBy,
})
if err != nil {
return nil, err
}
return &CreateKeyResponse{
Key: result.Key,
Secret: result.Secret,
}, nil
}
// Validate checks if a key is valid and returns the key details.
func (s *Service) Validate(ctx context.Context, key string) (*APIKey, error) {
return s.svc.Validate(ctx, key)
}
// List returns all API keys (without secrets).
func (s *Service) List(ctx context.Context) ([]*APIKey, error) {
return s.svc.List(ctx)
}
// Get returns a single API key by ID.
func (s *Service) Get(ctx context.Context, id string) (*APIKey, error) {
return s.svc.Get(ctx, domain.APIKeyID(id))
}
// Revoke marks an API key as revoked.
func (s *Service) Revoke(ctx context.Context, id string) error {
return s.svc.Revoke(ctx, domain.APIKeyID(id))
}