From 9700a6e307b1c90a36771dbcfc3aaf1345fe6283 Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Tue, 14 Jan 2025 14:07:33 -0600 Subject: [PATCH 1/7] fix 500 with missing token --- calsub/http.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/calsub/http.go b/calsub/http.go index 8bfea7af3e..7d687abeea 100644 --- a/calsub/http.go +++ b/calsub/http.go @@ -77,6 +77,10 @@ func (s *Store) userNameMap(ctx context.Context, shifts []oncall.Shift) (map[str func (s *Store) ServeICalData(w http.ResponseWriter, req *http.Request) { ctx := req.Context() src := permission.Source(ctx) + if src == nil { + http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) + return + } cfg := config.FromContext(ctx) if src.Type != permission.SourceTypeCalendarSubscription || cfg.General.DisableCalendarSubscriptions { http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) From 7d3e75b2d02f7cff84841b5c226e1c32226dcb2c Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Tue, 14 Jan 2025 14:09:14 -0600 Subject: [PATCH 2/7] fix: update calendar endpoint to specify GET method --- app/inithttp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/inithttp.go b/app/inithttp.go index 80442a0c3c..b44ade8438 100644 --- a/app/inithttp.go +++ b/app/inithttp.go @@ -150,7 +150,7 @@ func (app *App) initHTTP(ctx context.Context) error { mux.HandleFunc("/api/v2/generic/incoming", generic.ServeCreateAlert) mux.HandleFunc("/api/v2/heartbeat/", generic.ServeHeartbeatCheck) mux.HandleFunc("/api/v2/user-avatar/", generic.ServeUserAvatar) - mux.HandleFunc("/api/v2/calendar", app.CalSubStore.ServeICalData) + mux.HandleFunc("GET /api/v2/calendar", app.CalSubStore.ServeICalData) mux.HandleFunc("/api/v2/twilio/message", app.twilioSMS.ServeMessage) mux.HandleFunc("/api/v2/twilio/message/status", app.twilioSMS.ServeStatusCallback) From 0600344bdbf2201b4f9bf3921d6c2f4fea0bb58d Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Tue, 14 Jan 2025 14:21:37 -0600 Subject: [PATCH 3/7] fix: specify HTTP methods for API endpoints and improve request handling --- app/inithttp.go | 65 ++++++++++++++++++++++++------------------- genericapi/handler.go | 15 ++-------- 2 files changed, 39 insertions(+), 41 deletions(-) diff --git a/app/inithttp.go b/app/inithttp.go index b44ade8438..37c3823174 100644 --- a/app/inithttp.go +++ b/app/inithttp.go @@ -121,43 +121,44 @@ func (app *App) initHTTP(ctx context.Context) error { UserStore: app.UserStore, }) - mux.Handle("/api/graphql", app.graphql2.Handler()) + mux.Handle("POST /api/graphql", app.graphql2.Handler()) - mux.HandleFunc("/api/v2/config", app.ConfigStore.ServeConfig) + mux.HandleFunc("GET /api/v2/config", app.ConfigStore.ServeConfig) + mux.HandleFunc("POST /api/v2/config", app.ConfigStore.ServeConfig) - mux.HandleFunc("/api/v2/identity/providers", app.AuthHandler.ServeProviders) - mux.HandleFunc("/api/v2/identity/logout", app.AuthHandler.ServeLogout) + mux.HandleFunc("GET /api/v2/identity/providers", app.AuthHandler.ServeProviders) + mux.HandleFunc("POST /api/v2/identity/logout", app.AuthHandler.ServeLogout) basicAuth := app.AuthHandler.IdentityProviderHandler("basic") - mux.HandleFunc("/api/v2/identity/providers/basic", basicAuth) + mux.HandleFunc("POST /api/v2/identity/providers/basic", basicAuth) githubAuth := app.AuthHandler.IdentityProviderHandler("github") - mux.HandleFunc("/api/v2/identity/providers/github", githubAuth) - mux.HandleFunc("/api/v2/identity/providers/github/callback", githubAuth) + mux.HandleFunc("POST /api/v2/identity/providers/github", githubAuth) + mux.HandleFunc("POST /api/v2/identity/providers/github/callback", githubAuth) oidcAuth := app.AuthHandler.IdentityProviderHandler("oidc") - mux.HandleFunc("/api/v2/identity/providers/oidc", oidcAuth) - mux.HandleFunc("/api/v2/identity/providers/oidc/callback", oidcAuth) + mux.HandleFunc("POST /api/v2/identity/providers/oidc", oidcAuth) + mux.HandleFunc("POST /api/v2/identity/providers/oidc/callback", oidcAuth) if expflag.ContextHas(ctx, expflag.UnivKeys) { mux.HandleFunc("POST /api/v2/uik", app.UIKHandler.ServeHTTP) } - mux.HandleFunc("/api/v2/mailgun/incoming", mailgun.IngressWebhooks(app.AlertStore, app.IntegrationKeyStore)) - mux.HandleFunc("/api/v2/grafana/incoming", grafana.GrafanaToEventsAPI(app.AlertStore, app.IntegrationKeyStore)) - mux.HandleFunc("/api/v2/site24x7/incoming", site24x7.Site24x7ToEventsAPI(app.AlertStore, app.IntegrationKeyStore)) - mux.HandleFunc("/api/v2/prometheusalertmanager/incoming", prometheus.PrometheusAlertmanagerEventsAPI(app.AlertStore, app.IntegrationKeyStore)) - - mux.HandleFunc("/api/v2/generic/incoming", generic.ServeCreateAlert) - mux.HandleFunc("/api/v2/heartbeat/", generic.ServeHeartbeatCheck) - mux.HandleFunc("/api/v2/user-avatar/", generic.ServeUserAvatar) + mux.HandleFunc("POST /api/v2/mailgun/incoming", mailgun.IngressWebhooks(app.AlertStore, app.IntegrationKeyStore)) + mux.HandleFunc("POST /api/v2/grafana/incoming", grafana.GrafanaToEventsAPI(app.AlertStore, app.IntegrationKeyStore)) + mux.HandleFunc("POST /api/v2/site24x7/incoming", site24x7.Site24x7ToEventsAPI(app.AlertStore, app.IntegrationKeyStore)) + mux.HandleFunc("POST /api/v2/prometheusalertmanager/incoming", prometheus.PrometheusAlertmanagerEventsAPI(app.AlertStore, app.IntegrationKeyStore)) + + mux.HandleFunc("POST /api/v2/generic/incoming", generic.ServeCreateAlert) + mux.HandleFunc("POST /api/v2/heartbeat/{heartbeatID}", generic.ServeHeartbeatCheck) + mux.HandleFunc("GET /api/v2/user-avatar/{userID}", generic.ServeUserAvatar) mux.HandleFunc("GET /api/v2/calendar", app.CalSubStore.ServeICalData) - mux.HandleFunc("/api/v2/twilio/message", app.twilioSMS.ServeMessage) - mux.HandleFunc("/api/v2/twilio/message/status", app.twilioSMS.ServeStatusCallback) - mux.HandleFunc("/api/v2/twilio/call", app.twilioVoice.ServeCall) - mux.HandleFunc("/api/v2/twilio/call/status", app.twilioVoice.ServeStatusCallback) + mux.HandleFunc("POST /api/v2/twilio/message", app.twilioSMS.ServeMessage) + mux.HandleFunc("POST /api/v2/twilio/message/status", app.twilioSMS.ServeStatusCallback) + mux.HandleFunc("POST /api/v2/twilio/call", app.twilioVoice.ServeCall) + mux.HandleFunc("POST /api/v2/twilio/call/status", app.twilioVoice.ServeStatusCallback) - mux.HandleFunc("/api/v2/slack/message-action", app.slackChan.ServeMessageAction) + mux.HandleFunc("POST /api/v2/slack/message-action", app.slackChan.ServeMessageAction) middleware = append(middleware, httpRewrite(app.cfg.HTTPPrefix, "/v1/graphql2", "/api/graphql"), @@ -204,20 +205,20 @@ func (app *App) initHTTP(ctx context.Context) error { }, ) - mux.HandleFunc("/health", app.healthCheck) - mux.HandleFunc("/health/engine", app.engineStatus) - mux.HandleFunc("/health/engine/cycle", app.engineCycle) + mux.HandleFunc("GET /health", app.healthCheck) + mux.HandleFunc("GET /health/engine", app.engineStatus) + mux.HandleFunc("GET /health/engine/cycle", app.engineCycle) webH, err := web.NewHandler(app.cfg.UIDir, app.cfg.HTTPPrefix) if err != nil { return err } // non-API/404s go to UI handler - mux.Handle("/", webH) + mux.Handle("GET /", webH) - mux.HandleFunc("/admin/riverui/", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("GET /admin/riverui/", func(w http.ResponseWriter, r *http.Request) { err := permission.LimitCheckAny(r.Context(), permission.Admin) - if permission.IsUnauthorized(err) && !strings.HasPrefix(r.URL.Path, "/admin/riverui/api") { + if permission.IsUnauthorized(err) { // render login since we're on a UI route webH.ServeHTTP(w, r) return @@ -228,6 +229,14 @@ func (app *App) initHTTP(ctx context.Context) error { app.RiverUI.ServeHTTP(w, r) }) + mux.HandleFunc("POST /admin/riverui/api/", func(w http.ResponseWriter, r *http.Request) { + err := permission.LimitCheckAny(r.Context(), permission.Admin) + if errutil.HTTPError(r.Context(), w, err) { + return + } + + app.RiverUI.ServeHTTP(w, r) + }) app.srv = &http.Server{ Handler: applyMiddleware(mux, middleware...), diff --git a/genericapi/handler.go b/genericapi/handler.go index c918c71604..557e943b26 100644 --- a/genericapi/handler.go +++ b/genericapi/handler.go @@ -30,11 +30,8 @@ func NewHandler(c Config) *Handler { // ServeUserAvatar will serve a redirect for a users avatar image. func (h *Handler) ServeUserAvatar(w http.ResponseWriter, req *http.Request) { - parts := strings.Split(req.URL.Path, "/") - userID := parts[len(parts)-1] - ctx := req.Context() - u, err := h.c.UserStore.FindOne(ctx, userID) + u, err := h.c.UserStore.FindOne(ctx, req.PathValue("userID")) if errors.Is(err, sql.ErrNoRows) { http.NotFound(w, req) return @@ -50,16 +47,8 @@ func (h *Handler) ServeUserAvatar(w http.ResponseWriter, req *http.Request) { // ServeHeartbeatCheck serves the heartbeat check-in endpoint. func (h *Handler) ServeHeartbeatCheck(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - if r.Method != "POST" { - http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } - - parts := strings.Split(r.URL.Path, "/") - monitorID := parts[len(parts)-1] - err := retry.DoTemporaryError(func(_ int) error { - return h.c.HeartbeatStore.RecordHeartbeat(ctx, monitorID) + return h.c.HeartbeatStore.RecordHeartbeat(ctx, r.PathValue("heartbeatID")) }, retry.Log(ctx), retry.Limit(12), From da939b8504d8a137dd20570dd7c947c8aaf71779 Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Tue, 14 Jan 2025 14:34:13 -0600 Subject: [PATCH 4/7] fix: handle 404 for invalid API routes to prevent incorrect responses --- app/inithttp.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/inithttp.go b/app/inithttp.go index 37c3823174..0c2e972176 100644 --- a/app/inithttp.go +++ b/app/inithttp.go @@ -213,7 +213,14 @@ func (app *App) initHTTP(ctx context.Context) error { if err != nil { return err } - // non-API/404s go to UI handler + + // This is necessary so that we can return 404 for invalid/unknown API routes, otherwise it will get caught by the UI handler and incorrectly return the index.html or a 405 (Method Not Allowed) error. + mux.Handle("GET /api/", http.NotFoundHandler()) + mux.Handle("POST /api/", http.NotFoundHandler()) + mux.Handle("GET /v1/", http.NotFoundHandler()) + mux.Handle("POST /v1/", http.NotFoundHandler()) + + // non-API/404s go to UI handler and return index.html mux.Handle("GET /", webH) mux.HandleFunc("GET /admin/riverui/", func(w http.ResponseWriter, r *http.Request) { From 52420bc99fc2f56351ab0ccde8a1935020e6b124 Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Tue, 14 Jan 2025 14:41:19 -0600 Subject: [PATCH 5/7] fix: add 404 handler for health check route to improve error handling --- app/inithttp.go | 1 + 1 file changed, 1 insertion(+) diff --git a/app/inithttp.go b/app/inithttp.go index 0c2e972176..237a0631cc 100644 --- a/app/inithttp.go +++ b/app/inithttp.go @@ -208,6 +208,7 @@ func (app *App) initHTTP(ctx context.Context) error { mux.HandleFunc("GET /health", app.healthCheck) mux.HandleFunc("GET /health/engine", app.engineStatus) mux.HandleFunc("GET /health/engine/cycle", app.engineCycle) + mux.Handle("GET /health/", http.NotFoundHandler()) webH, err := web.NewHandler(app.cfg.UIDir, app.cfg.HTTPPrefix) if err != nil { From d978436f819f64a6574e77d69404099b52d30663 Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Tue, 14 Jan 2025 15:25:50 -0600 Subject: [PATCH 6/7] fix: update HTTP methods for config and identity provider callbacks to improve API consistency --- app/inithttp.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/inithttp.go b/app/inithttp.go index 237a0631cc..b91db811bd 100644 --- a/app/inithttp.go +++ b/app/inithttp.go @@ -124,7 +124,7 @@ func (app *App) initHTTP(ctx context.Context) error { mux.Handle("POST /api/graphql", app.graphql2.Handler()) mux.HandleFunc("GET /api/v2/config", app.ConfigStore.ServeConfig) - mux.HandleFunc("POST /api/v2/config", app.ConfigStore.ServeConfig) + mux.HandleFunc("PUT /api/v2/config", app.ConfigStore.ServeConfig) mux.HandleFunc("GET /api/v2/identity/providers", app.AuthHandler.ServeProviders) mux.HandleFunc("POST /api/v2/identity/logout", app.AuthHandler.ServeLogout) @@ -134,11 +134,11 @@ func (app *App) initHTTP(ctx context.Context) error { githubAuth := app.AuthHandler.IdentityProviderHandler("github") mux.HandleFunc("POST /api/v2/identity/providers/github", githubAuth) - mux.HandleFunc("POST /api/v2/identity/providers/github/callback", githubAuth) + mux.HandleFunc("GET /api/v2/identity/providers/github/callback", githubAuth) oidcAuth := app.AuthHandler.IdentityProviderHandler("oidc") mux.HandleFunc("POST /api/v2/identity/providers/oidc", oidcAuth) - mux.HandleFunc("POST /api/v2/identity/providers/oidc/callback", oidcAuth) + mux.HandleFunc("GET /api/v2/identity/providers/oidc/callback", oidcAuth) if expflag.ContextHas(ctx, expflag.UnivKeys) { mux.HandleFunc("POST /api/v2/uik", app.UIKHandler.ServeHTTP) From eaae3668a94e407007a3d623708fb1fe2427257a Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Tue, 21 Jan 2025 10:48:24 -0600 Subject: [PATCH 7/7] fix: add handlers for GraphQL explore endpoint to improve API routing --- app/inithttp.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/inithttp.go b/app/inithttp.go index b91db811bd..14c20dba9c 100644 --- a/app/inithttp.go +++ b/app/inithttp.go @@ -224,6 +224,9 @@ func (app *App) initHTTP(ctx context.Context) error { // non-API/404s go to UI handler and return index.html mux.Handle("GET /", webH) + mux.Handle("GET /api/graphql/explore", webH) + mux.Handle("GET /api/graphql/explore/", webH) + mux.HandleFunc("GET /admin/riverui/", func(w http.ResponseWriter, r *http.Request) { err := permission.LimitCheckAny(r.Context(), permission.Admin) if permission.IsUnauthorized(err) {