@@ -2,20 +2,26 @@ package handlers
22
33import (
44 "context"
5+ "encoding/json"
6+ "log/slog"
7+ "net/http"
8+ "strings"
59
610 "github.com/danielgtaylor/huma/v2"
711 humamw "github.com/getarcaneapp/arcane/backend/api/middleware"
812 "github.com/getarcaneapp/arcane/backend/internal/common"
13+ "github.com/getarcaneapp/arcane/backend/internal/config"
914 "github.com/getarcaneapp/arcane/backend/internal/services"
1015 "github.com/getarcaneapp/arcane/backend/pkg/authz"
16+ pkgutils "github.com/getarcaneapp/arcane/backend/pkg/utils"
1117 "github.com/getarcaneapp/arcane/types/base"
1218 "github.com/getarcaneapp/arcane/types/event"
19+ "github.com/labstack/echo/v4"
1320)
1421
1522// EventHandler handles event management endpoints.
1623type EventHandler struct {
1724 eventService * services.EventService
18- apiKeySvc * services.ApiKeyService
1925}
2026
2127// ============================================================================
@@ -58,15 +64,6 @@ type GetEventsByEnvironmentOutput struct {
5864 Body EventPaginatedResponse
5965}
6066
61- type CreateEventInput struct {
62- XAPIKey string `header:"X-API-Key" doc:"API key for environment-scoped event forwarding"`
63- Body event.CreateEvent
64- }
65-
66- type CreateEventOutput struct {
67- Body base.ApiResponse [event.Event ]
68- }
69-
7067type DeleteEventInput struct {
7168 EventID string `path:"eventId" doc:"Event ID"`
7269}
@@ -79,11 +76,72 @@ type DeleteEventOutput struct {
7976// Registration
8077// ============================================================================
8178
79+ // RegisterAgentEventIngestion registers the manager ingestion endpoint used by
80+ // direct agents when no edge tunnel is active. This route is not part of the
81+ // Huma/OpenAPI surface and authenticates only with the configured agent token.
82+ func RegisterAgentEventIngestion (g * echo.Group , eventService * services.EventService , cfg * config.Config ) {
83+ g .POST ("/events" , func (c echo.Context ) error {
84+ if eventService == nil {
85+ return c .JSON (http .StatusInternalServerError , base.ApiResponse [base.MessageResponse ]{
86+ Success : false ,
87+ Data : base.MessageResponse {Message : "service not available" },
88+ })
89+ }
90+ if cfg == nil || strings .TrimSpace (cfg .AgentToken ) == "" {
91+ slog .Warn ("agent event ingestion is disabled because agent token is not configured" )
92+ return c .JSON (http .StatusServiceUnavailable , base.ApiResponse [base.MessageResponse ]{
93+ Success : false ,
94+ Data : base.MessageResponse {Message : "agent event ingestion is not configured" },
95+ })
96+ }
97+ if ! validAgentEventIngestionTokenInternal (c .Request (), cfg ) {
98+ return c .JSON (http .StatusUnauthorized , base.ApiResponse [base.MessageResponse ]{
99+ Success : false ,
100+ Data : base.MessageResponse {Message : "invalid agent token" },
101+ })
102+ }
103+
104+ var input services.CreateEventRequest
105+ decoder := json .NewDecoder (http .MaxBytesReader (c .Response (), c .Request ().Body , 1 << 20 ))
106+ if err := decoder .Decode (& input ); err != nil {
107+ return c .JSON (http .StatusBadRequest , base.ApiResponse [base.MessageResponse ]{
108+ Success : false ,
109+ Data : base.MessageResponse {Message : "invalid event payload" },
110+ })
111+ }
112+ if strings .TrimSpace (string (input .Type )) == "" || strings .TrimSpace (input .Title ) == "" {
113+ return c .JSON (http .StatusBadRequest , base.ApiResponse [base.MessageResponse ]{
114+ Success : false ,
115+ Data : base.MessageResponse {Message : "event type and title are required" },
116+ })
117+ }
118+
119+ if _ , err := eventService .CreateEvent (c .Request ().Context (), input ); err != nil {
120+ return c .JSON (http .StatusInternalServerError , base.ApiResponse [base.MessageResponse ]{
121+ Success : false ,
122+ Data : base.MessageResponse {Message : (& common.EventCreationError {Err : err }).Error ()},
123+ })
124+ }
125+
126+ return c .JSON (http .StatusAccepted , base.ApiResponse [base.MessageResponse ]{
127+ Success : true ,
128+ Data : base.MessageResponse {Message : "event ingested" },
129+ })
130+ })
131+ }
132+
133+ func validAgentEventIngestionTokenInternal (r * http.Request , cfg * config.Config ) bool {
134+ if cfg == nil {
135+ return false
136+ }
137+ token := r .Header .Get (pkgutils .HeaderAgentToken )
138+ return token != "" && token == cfg .AgentToken
139+ }
140+
82141// RegisterEvents registers all event management endpoints.
83- func RegisterEvents (api huma.API , eventService * services.EventService , apiKeySvc * services. ApiKeyService ) {
142+ func RegisterEvents (api huma.API , eventService * services.EventService ) {
84143 h := & EventHandler {
85144 eventService : eventService ,
86- apiKeySvc : apiKeySvc ,
87145 }
88146
89147 huma .Register (api , huma.Operation {
@@ -100,23 +158,6 @@ func RegisterEvents(api huma.API, eventService *services.EventService, apiKeySvc
100158 Middlewares : humamw .RequirePermission (api , authz .PermEventsRead ),
101159 }, h .ListEvents )
102160
103- huma .Register (api , huma.Operation {
104- OperationID : "createEvent" ,
105- Method : "POST" ,
106- Path : "/events" ,
107- Summary : "Create an event" ,
108- Description : "Create a new system event" ,
109- Tags : []string {"Events" },
110- Security : []map [string ][]string {
111- {"BearerAuth" : {}},
112- {"ApiKeyAuth" : {}},
113- },
114- // TODO: introduce a dedicated PermEventsCreate (and PermEventsDelete)
115- // permission. Today the events taxonomy only exposes PermEventsRead,
116- // so admin-level write actions reuse the read permission.
117- Middlewares : humamw .RequirePermission (api , authz .PermEventsRead ),
118- }, h .CreateEvent )
119-
120161 huma .Register (api , huma.Operation {
121162 OperationID : "deleteEvent" ,
122163 Method : "DELETE" ,
@@ -128,10 +169,7 @@ func RegisterEvents(api huma.API, eventService *services.EventService, apiKeySvc
128169 {"BearerAuth" : {}},
129170 {"ApiKeyAuth" : {}},
130171 },
131- // TODO: introduce a dedicated PermEventsDelete permission. Today the
132- // events taxonomy only exposes PermEventsRead, so admin-level write
133- // actions reuse the read permission.
134- Middlewares : humamw .RequirePermission (api , authz .PermEventsRead ),
172+ Middlewares : humamw .RequirePermission (api , authz .PermEventsDelete ),
135173 }, h .DeleteEvent )
136174
137175 huma .Register (api , huma.Operation {
@@ -227,35 +265,6 @@ func (h *EventHandler) GetEventsByEnvironment(ctx context.Context, input *GetEve
227265 }, nil
228266}
229267
230- // CreateEvent creates a new event.
231- func (h * EventHandler ) CreateEvent (ctx context.Context , input * CreateEventInput ) (* CreateEventOutput , error ) {
232- if h .eventService == nil {
233- return nil , huma .Error500InternalServerError ("service not available" )
234- }
235-
236- if h .apiKeySvc != nil && input .XAPIKey != "" {
237- resolvedEnvironmentID , err := h .apiKeySvc .GetEnvironmentByApiKey (ctx , input .XAPIKey )
238- if err != nil {
239- return nil , huma .Error401Unauthorized ("invalid environment API key" )
240- }
241- if resolvedEnvironmentID != nil && * resolvedEnvironmentID != "" {
242- input .Body .EnvironmentID = new (* resolvedEnvironmentID )
243- }
244- }
245-
246- evt , err := h .eventService .CreateEventFromDto (ctx , input .Body )
247- if err != nil {
248- return nil , huma .Error500InternalServerError ((& common.EventCreationError {Err : err }).Error ())
249- }
250-
251- return & CreateEventOutput {
252- Body : base.ApiResponse [event.Event ]{
253- Success : true ,
254- Data : * evt ,
255- },
256- }, nil
257- }
258-
259268// DeleteEvent deletes an event.
260269func (h * EventHandler ) DeleteEvent (ctx context.Context , input * DeleteEventInput ) (* DeleteEventOutput , error ) {
261270 if h .eventService == nil {
0 commit comments