package handlers import ( "io" "net/http" "github.com/go-chi/chi/v5" "sneak.berlin/go/upaas/internal/models" "sneak.berlin/go/upaas/internal/service/audit" ) // maxWebhookBodySize is the maximum allowed size of a webhook request body (1MB). const maxWebhookBodySize = 1 << 20 // HandleWebhook handles incoming Gitea webhooks. func (h *Handlers) HandleWebhook() http.HandlerFunc { //nolint:funlen // audit logging adds necessary length return func(writer http.ResponseWriter, request *http.Request) { secret := chi.URLParam(request, "secret") if secret == "" { http.NotFound(writer, request) return } // Find app by webhook secret application, findErr := models.FindAppByWebhookSecret( request.Context(), h.db, secret, ) if findErr != nil { h.log.Error("failed to find app by webhook secret", "error", findErr) http.Error(writer, "Internal Server Error", http.StatusInternalServerError) return } if application == nil { http.NotFound(writer, request) return } // Read request body with size limit to prevent memory exhaustion body, readErr := io.ReadAll(io.LimitReader(request.Body, maxWebhookBodySize)) if readErr != nil { h.log.Error("failed to read webhook body", "error", readErr) http.Error(writer, "Bad Request", http.StatusBadRequest) return } // Get event type from header eventType := request.Header.Get("X-Gitea-Event") if eventType == "" { eventType = "push" } // Log webhook receipt h.audit.LogFromRequest(request.Context(), request, audit.LogEntry{ Username: "webhook", Action: models.AuditActionWebhookReceive, ResourceType: models.AuditResourceWebhook, ResourceID: application.ID, Detail: "webhook from app: " + application.Name + ", event: " + eventType, }) // Process webhook webhookErr := h.webhook.HandleWebhook( request.Context(), application, eventType, body, ) if webhookErr != nil { h.log.Error("failed to process webhook", "error", webhookErr) http.Error(writer, "Internal Server Error", http.StatusInternalServerError) return } writer.WriteHeader(http.StatusOK) } }