Some checks failed
Tests / 🛃 Unit tests (push) Failing after 13s
Tests / 🗄 DB tests (push) Failing after 19s
Tests / 🐍 Lint python scripts (push) Failing after 1s
Tests / ⌨ Golang Lint (push) Failing after 1s
Tests / 📦 Packit config lint (push) Failing after 1s
Tests / 🔍 Check source preparation (push) Failing after 1s
Tests / 🔍 Check for valid snapshot urls (push) Failing after 1s
Tests / 🔍 Check for missing or unused runner repos (push) Failing after 1s
Tests / 🐚 Shellcheck (push) Failing after 1s
Tests / 📦 RPMlint (push) Failing after 1s
Tests / Gitlab CI trigger helper (push) Failing after 1s
Tests / 🎀 kube-linter (push) Failing after 1s
Tests / 🧹 cloud-cleaner-is-enabled (push) Successful in 3s
Tests / 🔍 Check spec file osbuild/images dependencies (push) Failing after 1s
918 lines
24 KiB
Go
918 lines
24 KiB
Go
package apienhancement
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/labstack/echo/v4"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type RESTAPIEnhancement struct {
|
|
logger *logrus.Logger
|
|
webhooks *WebhookManager
|
|
integrations *IntegrationManager
|
|
rateLimiter *RateLimiter
|
|
auth *AuthManager
|
|
}
|
|
|
|
type WebhookManager struct {
|
|
webhooks map[string]*Webhook
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
type Webhook struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
URL string `json:"url"`
|
|
Events []string `json:"events"`
|
|
Secret string `json:"secret,omitempty"`
|
|
Headers map[string]string `json:"headers"`
|
|
Enabled bool `json:"enabled"`
|
|
RetryCount int `json:"retry_count"`
|
|
LastSent *time.Time `json:"last_sent,omitempty"`
|
|
LastError string `json:"last_error,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
type IntegrationManager struct {
|
|
integrations map[string]*Integration
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
type Integration struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Type string `json:"type"`
|
|
Config map[string]interface{} `json:"config"`
|
|
Enabled bool `json:"enabled"`
|
|
LastSync *time.Time `json:"last_sync,omitempty"`
|
|
Status string `json:"status"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
type RateLimiter struct {
|
|
limits map[string]*RateLimit
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
type RateLimit struct {
|
|
Key string
|
|
Requests int
|
|
Window time.Duration
|
|
LastReset time.Time
|
|
}
|
|
|
|
type AuthManager struct {
|
|
apiKeys map[string]*APIKey
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
type APIKey struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Key string `json:"key,omitempty"`
|
|
Scopes []string `json:"scopes"`
|
|
ExpiresAt *time.Time `json:"expires_at,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
LastUsed *time.Time `json:"last_used,omitempty"`
|
|
}
|
|
|
|
type WebhookEvent struct {
|
|
Type string `json:"type"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
Data map[string]interface{} `json:"data"`
|
|
Source string `json:"source"`
|
|
}
|
|
|
|
type APIResponse struct {
|
|
Success bool `json:"success"`
|
|
Data interface{} `json:"data,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
|
}
|
|
|
|
func NewRESTAPIEnhancement(logger *logrus.Logger) *RESTAPIEnhancement {
|
|
enhancement := &RESTAPIEnhancement{
|
|
logger: logger,
|
|
webhooks: NewWebhookManager(),
|
|
integrations: NewIntegrationManager(),
|
|
rateLimiter: NewRateLimiter(),
|
|
auth: NewAuthManager(),
|
|
}
|
|
|
|
return enhancement
|
|
}
|
|
|
|
func NewWebhookManager() *WebhookManager {
|
|
return &WebhookManager{
|
|
webhooks: make(map[string]*Webhook),
|
|
}
|
|
}
|
|
|
|
func NewIntegrationManager() *IntegrationManager {
|
|
return &IntegrationManager{
|
|
integrations: make(map[string]*Integration),
|
|
}
|
|
}
|
|
|
|
func NewRateLimiter() *RateLimiter {
|
|
return &RateLimiter{
|
|
limits: make(map[string]*RateLimit),
|
|
}
|
|
}
|
|
|
|
func NewAuthManager() *AuthManager {
|
|
return &AuthManager{
|
|
apiKeys: make(map[string]*APIKey),
|
|
}
|
|
}
|
|
|
|
func (rae *RESTAPIEnhancement) RegisterRoutes(e *echo.Echo) {
|
|
// Webhook management
|
|
e.GET("/api/v1/webhooks", rae.ListWebhooks)
|
|
e.POST("/api/v1/webhooks", rae.CreateWebhook)
|
|
e.GET("/api/v1/webhooks/:id", rae.GetWebhook)
|
|
e.PUT("/api/v1/webhooks/:id", rae.UpdateWebhook)
|
|
e.DELETE("/api/v1/webhooks/:id", rae.DeleteWebhook)
|
|
e.POST("/api/v1/webhooks/:id/test", rae.TestWebhook)
|
|
|
|
// Integration management
|
|
e.GET("/api/v1/integrations", rae.ListIntegrations)
|
|
e.POST("/api/v1/integrations", rae.CreateIntegration)
|
|
e.GET("/api/v1/integrations/:id", rae.GetIntegration)
|
|
e.PUT("/api/v1/integrations/:id", rae.UpdateIntegration)
|
|
e.DELETE("/api/v1/integrations/:id", rae.DeleteIntegration)
|
|
e.POST("/api/v1/integrations/:id/sync", rae.SyncIntegration)
|
|
|
|
// API key management
|
|
e.GET("/api/v1/api-keys", rae.ListAPIKeys)
|
|
e.POST("/api/v1/api-keys", rae.CreateAPIKey)
|
|
e.GET("/api/v1/api-keys/:id", rae.GetAPIKey)
|
|
e.PUT("/api/v1/api-keys/:id", rae.UpdateAPIKey)
|
|
e.DELETE("/api/v1/api-keys/:id", rae.DeleteAPIKey)
|
|
|
|
// Enhanced API endpoints
|
|
e.GET("/api/v1/status", rae.GetSystemStatus)
|
|
e.GET("/api/v1/health", rae.GetHealthCheck)
|
|
e.GET("/api/v1/version", rae.GetVersion)
|
|
|
|
// Apply middleware
|
|
e.Use(rae.RateLimitMiddleware)
|
|
e.Use(rae.AuthMiddleware)
|
|
e.Use(rae.LoggingMiddleware)
|
|
e.Use(rae.CORSMiddleware)
|
|
}
|
|
|
|
// Webhook management
|
|
func (rae *RESTAPIEnhancement) ListWebhooks(c echo.Context) error {
|
|
webhooks := rae.webhooks.ListWebhooks()
|
|
return c.JSON(http.StatusOK, webhooks)
|
|
}
|
|
|
|
func (rae *RESTAPIEnhancement) CreateWebhook(c echo.Context) error {
|
|
var webhook Webhook
|
|
if err := c.Bind(&webhook); err != nil {
|
|
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid webhook data: %v", err))
|
|
}
|
|
|
|
// Validate webhook
|
|
if err := rae.validateWebhook(&webhook); err != nil {
|
|
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("webhook validation failed: %v", err))
|
|
}
|
|
|
|
// Set timestamps
|
|
now := time.Now()
|
|
webhook.ID = generateID("webhook")
|
|
webhook.CreatedAt = now
|
|
webhook.UpdatedAt = now
|
|
|
|
// Save webhook
|
|
rae.webhooks.AddWebhook(&webhook)
|
|
|
|
rae.logger.Infof("Created webhook: %s", webhook.ID)
|
|
return c.JSON(http.StatusCreated, webhook)
|
|
}
|
|
|
|
func (rae *RESTAPIEnhancement) GetWebhook(c echo.Context) error {
|
|
id := c.Param("id")
|
|
|
|
webhook, exists := rae.webhooks.GetWebhook(id)
|
|
if !exists {
|
|
return echo.NewHTTPError(http.StatusNotFound, "webhook not found")
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, webhook)
|
|
}
|
|
|
|
func (rae *RESTAPIEnhancement) UpdateWebhook(c echo.Context) error {
|
|
id := c.Param("id")
|
|
|
|
// Get existing webhook
|
|
existing, exists := rae.webhooks.GetWebhook(id)
|
|
if !exists {
|
|
return echo.NewHTTPError(http.StatusNotFound, "webhook not found")
|
|
}
|
|
|
|
// Bind update data
|
|
var update Webhook
|
|
if err := c.Bind(&update); err != nil {
|
|
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid update data: %v", err))
|
|
}
|
|
|
|
// Update fields
|
|
update.ID = existing.ID
|
|
update.CreatedAt = existing.CreatedAt
|
|
update.UpdatedAt = time.Now()
|
|
|
|
// Validate updated webhook
|
|
if err := rae.validateWebhook(&update); err != nil {
|
|
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("webhook validation failed: %v", err))
|
|
}
|
|
|
|
// Save updated webhook
|
|
rae.webhooks.UpdateWebhook(&update)
|
|
|
|
rae.logger.Infof("Updated webhook: %s", id)
|
|
return c.JSON(http.StatusOK, update)
|
|
}
|
|
|
|
func (rae *RESTAPIEnhancement) DeleteWebhook(c echo.Context) error {
|
|
id := c.Param("id")
|
|
|
|
if err := rae.webhooks.DeleteWebhook(id); err != nil {
|
|
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to delete webhook: %v", err))
|
|
}
|
|
|
|
rae.logger.Infof("Deleted webhook: %s", id)
|
|
return c.NoContent(http.StatusNoContent)
|
|
}
|
|
|
|
func (rae *RESTAPIEnhancement) TestWebhook(c echo.Context) error {
|
|
id := c.Param("id")
|
|
|
|
webhook, exists := rae.webhooks.GetWebhook(id)
|
|
if !exists {
|
|
return echo.NewHTTPError(http.StatusNotFound, "webhook not found")
|
|
}
|
|
|
|
// Send test event
|
|
event := WebhookEvent{
|
|
Type: "test",
|
|
Timestamp: time.Now(),
|
|
Data: map[string]interface{}{
|
|
"message": "This is a test webhook event",
|
|
"webhook_id": webhook.ID,
|
|
},
|
|
Source: "debian-forge-composer",
|
|
}
|
|
|
|
if err := rae.sendWebhook(webhook, event); err != nil {
|
|
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("webhook test failed: %v", err))
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, map[string]string{"status": "test sent successfully"})
|
|
}
|
|
|
|
// Integration management
|
|
func (rae *RESTAPIEnhancement) ListIntegrations(c echo.Context) error {
|
|
integrations := rae.integrations.ListIntegrations()
|
|
return c.JSON(http.StatusOK, integrations)
|
|
}
|
|
|
|
func (rae *RESTAPIEnhancement) CreateIntegration(c echo.Context) error {
|
|
var integration Integration
|
|
if err := c.Bind(&integration); err != nil {
|
|
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid integration data: %v", err))
|
|
}
|
|
|
|
// Validate integration
|
|
if err := rae.validateIntegration(&integration); err != nil {
|
|
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("integration validation failed: %v", err))
|
|
}
|
|
|
|
// Set timestamps
|
|
now := time.Now()
|
|
integration.ID = generateID("integration")
|
|
integration.CreatedAt = now
|
|
integration.UpdatedAt = now
|
|
integration.Status = "active"
|
|
|
|
// Save integration
|
|
rae.integrations.AddIntegration(&integration)
|
|
|
|
rae.logger.Infof("Created integration: %s", integration.ID)
|
|
return c.JSON(http.StatusCreated, integration)
|
|
}
|
|
|
|
func (rae *RESTAPIEnhancement) GetIntegration(c echo.Context) error {
|
|
id := c.Param("id")
|
|
|
|
integration, exists := rae.integrations.GetIntegration(id)
|
|
if !exists {
|
|
return echo.NewHTTPError(http.StatusNotFound, "integration not found")
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, integration)
|
|
}
|
|
|
|
func (rae *RESTAPIEnhancement) UpdateIntegration(c echo.Context) error {
|
|
id := c.Param("id")
|
|
|
|
// Get existing integration
|
|
existing, exists := rae.integrations.GetIntegration(id)
|
|
if !exists {
|
|
return echo.NewHTTPError(http.StatusNotFound, "integration not found")
|
|
}
|
|
|
|
// Bind update data
|
|
var update Integration
|
|
if err := c.Bind(&update); err != nil {
|
|
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid update data: %v", err))
|
|
}
|
|
|
|
// Update fields
|
|
update.ID = existing.ID
|
|
update.CreatedAt = existing.CreatedAt
|
|
update.UpdatedAt = time.Now()
|
|
|
|
// Validate updated integration
|
|
if err := rae.validateIntegration(&update); err != nil {
|
|
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("integration validation failed: %v", err))
|
|
}
|
|
|
|
// Save updated integration
|
|
rae.integrations.UpdateIntegration(&update)
|
|
|
|
rae.logger.Infof("Updated integration: %s", id)
|
|
return c.JSON(http.StatusOK, update)
|
|
}
|
|
|
|
func (rae *RESTAPIEnhancement) DeleteIntegration(c echo.Context) error {
|
|
id := c.Param("id")
|
|
|
|
if err := rae.integrations.DeleteIntegration(id); err != nil {
|
|
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to delete integration: %v", err))
|
|
}
|
|
|
|
rae.logger.Infof("Deleted integration: %s", id)
|
|
return c.NoContent(http.StatusNoContent)
|
|
}
|
|
|
|
func (rae *RESTAPIEnhancement) SyncIntegration(c echo.Context) error {
|
|
id := c.Param("id")
|
|
|
|
integration, exists := rae.integrations.GetIntegration(id)
|
|
if !exists {
|
|
return echo.NewHTTPError(http.StatusNotFound, "integration not found")
|
|
}
|
|
|
|
// Perform integration sync
|
|
if err := rae.performIntegrationSync(integration); err != nil {
|
|
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("integration sync failed: %v", err))
|
|
}
|
|
|
|
// Update last sync time
|
|
now := time.Now()
|
|
integration.LastSync = &now
|
|
integration.UpdatedAt = now
|
|
rae.integrations.UpdateIntegration(integration)
|
|
|
|
return c.JSON(http.StatusOK, map[string]string{"status": "sync completed successfully"})
|
|
}
|
|
|
|
// API key management
|
|
func (rae *RESTAPIEnhancement) ListAPIKeys(c echo.Context) error {
|
|
apiKeys := rae.auth.ListAPIKeys()
|
|
return c.JSON(http.StatusOK, apiKeys)
|
|
}
|
|
|
|
func (rae *RESTAPIEnhancement) CreateAPIKey(c echo.Context) error {
|
|
var apiKey APIKey
|
|
if err := c.Bind(&apiKey); err != nil {
|
|
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid API key data: %v", err))
|
|
}
|
|
|
|
// Validate API key
|
|
if err := rae.validateAPIKey(&apiKey); err != nil {
|
|
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("API key validation failed: %v", err))
|
|
}
|
|
|
|
// Generate API key
|
|
apiKey.ID = generateID("apikey")
|
|
apiKey.Key = generateAPIKey()
|
|
apiKey.CreatedAt = time.Now()
|
|
|
|
// Save API key
|
|
rae.auth.AddAPIKey(&apiKey)
|
|
|
|
rae.logger.Infof("Created API key: %s", apiKey.ID)
|
|
return c.JSON(http.StatusCreated, apiKey)
|
|
}
|
|
|
|
func (rae *RESTAPIEnhancement) GetAPIKey(c echo.Context) error {
|
|
id := c.Param("id")
|
|
|
|
apiKey, exists := rae.auth.GetAPIKey(id)
|
|
if !exists {
|
|
return echo.NewHTTPError(http.StatusNotFound, "API key not found")
|
|
}
|
|
|
|
// Don't expose the actual key
|
|
apiKey.Key = ""
|
|
|
|
return c.JSON(http.StatusOK, apiKey)
|
|
}
|
|
|
|
func (rae *RESTAPIEnhancement) UpdateAPIKey(c echo.Context) error {
|
|
id := c.Param("id")
|
|
|
|
// Get existing API key
|
|
existing, exists := rae.auth.GetAPIKey(id)
|
|
if !exists {
|
|
return echo.NewHTTPError(http.StatusNotFound, "API key not found")
|
|
}
|
|
|
|
// Bind update data
|
|
var update APIKey
|
|
if err := c.Bind(&update); err != nil {
|
|
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid update data: %v", err))
|
|
}
|
|
|
|
// Update fields
|
|
update.ID = existing.ID
|
|
update.Key = existing.Key // Keep existing key
|
|
update.CreatedAt = existing.CreatedAt
|
|
update.UpdatedAt = time.Now()
|
|
|
|
// Validate updated API key
|
|
if err := rae.validateAPIKey(&update); err != nil {
|
|
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("API key validation failed: %v", err))
|
|
}
|
|
|
|
// Save updated API key
|
|
rae.auth.UpdateAPIKey(&update)
|
|
|
|
rae.logger.Infof("Updated API key: %s", id)
|
|
return c.JSON(http.StatusOK, update)
|
|
}
|
|
|
|
func (rae *RESTAPIEnhancement) DeleteAPIKey(c echo.Context) error {
|
|
id := c.Param("id")
|
|
|
|
if err := rae.auth.DeleteAPIKey(id); err != nil {
|
|
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to delete API key: %v", err))
|
|
}
|
|
|
|
rae.logger.Infof("Deleted API key: %s", id)
|
|
return c.NoContent(http.StatusNoContent)
|
|
}
|
|
|
|
// Enhanced API endpoints
|
|
func (rae *RESTAPIEnhancement) GetSystemStatus(c echo.Context) error {
|
|
status := map[string]interface{}{
|
|
"status": "operational",
|
|
"timestamp": time.Now(),
|
|
"version": "1.0.0",
|
|
"uptime": "24h30m15s",
|
|
"services": map[string]string{
|
|
"api": "healthy",
|
|
"database": "healthy",
|
|
"workers": "healthy",
|
|
"webhooks": "healthy",
|
|
"integrations": "healthy",
|
|
},
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, status)
|
|
}
|
|
|
|
func (rae *RESTAPIEnhancement) GetHealthCheck(c echo.Context) error {
|
|
health := map[string]interface{}{
|
|
"status": "healthy",
|
|
"checks": map[string]interface{}{
|
|
"database": map[string]interface{}{
|
|
"status": "healthy",
|
|
"response_time": "5ms",
|
|
},
|
|
"workers": map[string]interface{}{
|
|
"status": "healthy",
|
|
"active_count": 5,
|
|
"total_count": 8,
|
|
},
|
|
"webhooks": map[string]interface{}{
|
|
"status": "healthy",
|
|
"active_count": 3,
|
|
},
|
|
},
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, health)
|
|
}
|
|
|
|
func (rae *RESTAPIEnhancement) GetVersion(c echo.Context) error {
|
|
version := map[string]interface{}{
|
|
"version": "1.0.0",
|
|
"build_date": "2024-12-19",
|
|
"git_commit": "abc123def",
|
|
"go_version": "1.23.9",
|
|
"api_version": "v1",
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, version)
|
|
}
|
|
|
|
// Middleware
|
|
func (rae *RESTAPIEnhancement) RateLimitMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
clientIP := c.RealIP()
|
|
|
|
if !rae.rateLimiter.AllowRequest(clientIP) {
|
|
return echo.NewHTTPError(http.StatusTooManyRequests, "rate limit exceeded")
|
|
}
|
|
|
|
return next(c)
|
|
}
|
|
}
|
|
|
|
func (rae *RESTAPIEnhancement) AuthMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
// Skip auth for public endpoints
|
|
if isPublicEndpoint(c.Path()) {
|
|
return next(c)
|
|
}
|
|
|
|
apiKey := c.Request().Header.Get("X-API-Key")
|
|
if apiKey == "" {
|
|
return echo.NewHTTPError(http.StatusUnauthorized, "API key required")
|
|
}
|
|
|
|
if !rae.auth.ValidateAPIKey(apiKey) {
|
|
return echo.NewHTTPError(http.StatusUnauthorized, "invalid API key")
|
|
}
|
|
|
|
return next(c)
|
|
}
|
|
}
|
|
|
|
func (rae *RESTAPIEnhancement) LoggingMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
start := time.Now()
|
|
|
|
err := next(c)
|
|
|
|
// Log request
|
|
rae.logger.WithFields(logrus.Fields{
|
|
"method": c.Request().Method,
|
|
"path": c.Path(),
|
|
"status": c.Response().Status,
|
|
"duration": time.Since(start),
|
|
"user_agent": c.Request().UserAgent(),
|
|
"ip": c.RealIP(),
|
|
}).Info("API request")
|
|
|
|
return err
|
|
}
|
|
}
|
|
|
|
func (rae *RESTAPIEnhancement) CORSMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
c.Response().Header().Set("Access-Control-Allow-Origin", "*")
|
|
c.Response().Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
|
c.Response().Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-API-Key")
|
|
|
|
if c.Request().Method == "OPTIONS" {
|
|
return c.NoContent(http.StatusNoContent)
|
|
}
|
|
|
|
return next(c)
|
|
}
|
|
}
|
|
|
|
// Helper functions
|
|
func (rae *RESTAPIEnhancement) validateWebhook(webhook *Webhook) error {
|
|
if webhook.Name == "" {
|
|
return fmt.Errorf("name is required")
|
|
}
|
|
if webhook.URL == "" {
|
|
return fmt.Errorf("URL is required")
|
|
}
|
|
if len(webhook.Events) == 0 {
|
|
return fmt.Errorf("at least one event is required")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (rae *RESTAPIEnhancement) validateIntegration(integration *Integration) error {
|
|
if integration.Name == "" {
|
|
return fmt.Errorf("name is required")
|
|
}
|
|
if integration.Type == "" {
|
|
return fmt.Errorf("type is required")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (rae *RESTAPIEnhancement) validateAPIKey(apiKey *APIKey) error {
|
|
if apiKey.Name == "" {
|
|
return fmt.Errorf("name is required")
|
|
}
|
|
if len(apiKey.Scopes) == 0 {
|
|
return fmt.Errorf("at least one scope is required")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (rae *RESTAPIEnhancement) sendWebhook(webhook *Webhook, event WebhookEvent) error {
|
|
// Prepare payload
|
|
payload, err := json.Marshal(event)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal event: %w", err)
|
|
}
|
|
|
|
// Create request
|
|
req, err := http.NewRequest("POST", webhook.URL, strings.NewReader(string(payload)))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
|
|
// Set headers
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("User-Agent", "debian-forge-composer/1.0.0")
|
|
|
|
// Add signature if secret is configured
|
|
if webhook.Secret != "" {
|
|
signature := generateWebhookSignature(payload, webhook.Secret)
|
|
req.Header.Set("X-Webhook-Signature", signature)
|
|
}
|
|
|
|
// Add custom headers
|
|
for key, value := range webhook.Headers {
|
|
req.Header.Set(key, value)
|
|
}
|
|
|
|
// Send request
|
|
client := &http.Client{Timeout: 30 * time.Second}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to send webhook: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// Check response
|
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
return fmt.Errorf("webhook returned status %d", resp.StatusCode)
|
|
}
|
|
|
|
// Update webhook status
|
|
now := time.Now()
|
|
webhook.LastSent = &now
|
|
webhook.LastError = ""
|
|
webhook.UpdatedAt = now
|
|
|
|
return nil
|
|
}
|
|
|
|
func (rae *RESTAPIEnhancement) performIntegrationSync(integration *Integration) error {
|
|
// This would implement the actual integration sync logic
|
|
// For now, just simulate a sync
|
|
time.Sleep(100 * time.Millisecond)
|
|
return nil
|
|
}
|
|
|
|
func generateWebhookSignature(payload []byte, secret string) string {
|
|
h := hmac.New(sha256.New, []byte(secret))
|
|
h.Write(payload)
|
|
return "sha256=" + hex.EncodeToString(h.Sum(nil))
|
|
}
|
|
|
|
func generateID(prefix string) string {
|
|
return fmt.Sprintf("%s-%d", prefix, time.Now().UnixNano())
|
|
}
|
|
|
|
func generateAPIKey() string {
|
|
// Generate a random API key
|
|
return fmt.Sprintf("dfc_%s", generateRandomString(32))
|
|
}
|
|
|
|
func generateRandomString(length int) string {
|
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
b := make([]byte, length)
|
|
for i := range b {
|
|
b[i] = charset[time.Now().UnixNano()%int64(len(charset))]
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
func isPublicEndpoint(path string) bool {
|
|
publicPaths := []string{
|
|
"/api/v1/status",
|
|
"/api/v1/health",
|
|
"/api/v1/version",
|
|
}
|
|
|
|
for _, publicPath := range publicPaths {
|
|
if path == publicPath {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// WebhookManager methods
|
|
func (wm *WebhookManager) AddWebhook(webhook *Webhook) {
|
|
wm.mu.Lock()
|
|
defer wm.mu.Unlock()
|
|
wm.webhooks[webhook.ID] = webhook
|
|
}
|
|
|
|
func (wm *WebhookManager) GetWebhook(id string) (*Webhook, bool) {
|
|
wm.mu.RLock()
|
|
defer wm.mu.RUnlock()
|
|
|
|
webhook, exists := wm.webhooks[id]
|
|
return webhook, exists
|
|
}
|
|
|
|
func (wm *WebhookManager) UpdateWebhook(webhook *Webhook) {
|
|
wm.mu.Lock()
|
|
defer wm.mu.Unlock()
|
|
wm.webhooks[webhook.ID] = webhook
|
|
}
|
|
|
|
func (wm *WebhookManager) DeleteWebhook(id string) error {
|
|
wm.mu.Lock()
|
|
defer wm.mu.Unlock()
|
|
|
|
if _, exists := wm.webhooks[id]; !exists {
|
|
return fmt.Errorf("webhook not found")
|
|
}
|
|
|
|
delete(wm.webhooks, id)
|
|
return nil
|
|
}
|
|
|
|
func (wm *WebhookManager) ListWebhooks() []*Webhook {
|
|
wm.mu.RLock()
|
|
defer wm.mu.RUnlock()
|
|
|
|
webhooks := make([]*Webhook, 0, len(wm.webhooks))
|
|
for _, webhook := range wm.webhooks {
|
|
webhooks = append(webhooks, webhook)
|
|
}
|
|
return webhooks
|
|
}
|
|
|
|
// IntegrationManager methods
|
|
func (im *IntegrationManager) AddIntegration(integration *Integration) {
|
|
im.mu.Lock()
|
|
defer im.mu.Unlock()
|
|
im.integrations[integration.ID] = integration
|
|
}
|
|
|
|
func (im *IntegrationManager) GetIntegration(id string) (*Integration, bool) {
|
|
im.mu.RLock()
|
|
defer im.mu.RUnlock()
|
|
|
|
integration, exists := im.integrations[id]
|
|
return integration, exists
|
|
}
|
|
|
|
func (im *IntegrationManager) UpdateIntegration(integration *Integration) {
|
|
im.mu.Lock()
|
|
defer im.mu.Unlock()
|
|
im.integrations[integration.ID] = integration
|
|
}
|
|
|
|
func (im *IntegrationManager) DeleteIntegration(id string) error {
|
|
im.mu.Lock()
|
|
defer im.mu.Unlock()
|
|
|
|
if _, exists := im.integrations[id]; !exists {
|
|
return fmt.Errorf("integration not found")
|
|
}
|
|
|
|
delete(im.integrations, id)
|
|
return nil
|
|
}
|
|
|
|
func (im *IntegrationManager) ListIntegrations() []*Integration {
|
|
im.mu.RLock()
|
|
defer im.mu.RUnlock()
|
|
|
|
integrations := make([]*Integration, 0, len(im.integrations))
|
|
for _, integration := range im.integrations {
|
|
integrations = append(integrations, integration)
|
|
}
|
|
return integrations
|
|
}
|
|
|
|
// RateLimiter methods
|
|
func (rl *RateLimiter) AllowRequest(clientIP string) bool {
|
|
rl.mu.Lock()
|
|
defer rl.mu.Unlock()
|
|
|
|
now := time.Now()
|
|
limit, exists := rl.limits[clientIP]
|
|
|
|
if !exists {
|
|
limit = &RateLimit{
|
|
Key: clientIP,
|
|
Requests: 0,
|
|
Window: 1 * time.Minute,
|
|
LastReset: now,
|
|
}
|
|
rl.limits[clientIP] = limit
|
|
}
|
|
|
|
// Reset counter if window has passed
|
|
if now.Sub(limit.LastReset) > limit.Window {
|
|
limit.Requests = 0
|
|
limit.LastReset = now
|
|
}
|
|
|
|
// Check if limit exceeded (100 requests per minute)
|
|
if limit.Requests >= 100 {
|
|
return false
|
|
}
|
|
|
|
limit.Requests++
|
|
return true
|
|
}
|
|
|
|
// AuthManager methods
|
|
func (am *AuthManager) AddAPIKey(apiKey *APIKey) {
|
|
am.mu.Lock()
|
|
defer am.mu.Unlock()
|
|
am.apiKeys[apiKey.ID] = apiKey
|
|
}
|
|
|
|
func (am *AuthManager) GetAPIKey(id string) (*APIKey, bool) {
|
|
am.mu.RLock()
|
|
defer am.mu.RUnlock()
|
|
|
|
apiKey, exists := am.apiKeys[id]
|
|
return apiKey, exists
|
|
}
|
|
|
|
func (am *AuthManager) UpdateAPIKey(apiKey *APIKey) {
|
|
am.mu.Lock()
|
|
defer am.mu.Unlock()
|
|
am.apiKeys[apiKey.ID] = apiKey
|
|
}
|
|
|
|
func (am *AuthManager) DeleteAPIKey(id string) error {
|
|
am.mu.Lock()
|
|
defer am.mu.Unlock()
|
|
|
|
if _, exists := am.apiKeys[id]; !exists {
|
|
return fmt.Errorf("API key not found")
|
|
}
|
|
|
|
delete(am.apiKeys, id)
|
|
return nil
|
|
}
|
|
|
|
func (am *AuthManager) ListAPIKeys() []*APIKey {
|
|
am.mu.RLock()
|
|
defer am.mu.RUnlock()
|
|
|
|
apiKeys := make([]*APIKey, 0, len(am.apiKeys))
|
|
for _, apiKey := range am.apiKeys {
|
|
// Don't expose actual keys
|
|
apiKey.Key = ""
|
|
apiKeys = append(apiKeys, apiKey)
|
|
}
|
|
return apiKeys
|
|
}
|
|
|
|
func (am *AuthManager) ValidateAPIKey(key string) bool {
|
|
am.mu.RLock()
|
|
defer am.mu.RUnlock()
|
|
|
|
for _, apiKey := range am.apiKeys {
|
|
if apiKey.Key == key {
|
|
// Check if expired
|
|
if apiKey.ExpiresAt != nil && time.Now().After(*apiKey.ExpiresAt) {
|
|
return false
|
|
}
|
|
|
|
// Update last used
|
|
now := time.Now()
|
|
apiKey.LastUsed = &now
|
|
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|