diff --git a/cmd/cc-backend/server.go b/cmd/cc-backend/server.go index bd704eb4..7e2b3ed3 100644 --- a/cmd/cc-backend/server.go +++ b/cmd/cc-backend/server.go @@ -106,6 +106,27 @@ func (s *Server) init() error { authHandle := auth.GetAuthInstance() + // Middleware must be defined before routes in chi + s.router.Use(func(next http.Handler) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + start := time.Now() + ww := middleware.NewWrapResponseWriter(rw, r.ProtoMajor) + next.ServeHTTP(ww, r) + cclog.Debugf("%s %s (%d, %.02fkb, %dms)", + r.Method, r.URL.RequestURI(), + ww.Status(), float32(ww.BytesWritten())/1024, + time.Since(start).Milliseconds()) + }) + }) + s.router.Use(middleware.Compress(5)) + s.router.Use(middleware.Recoverer) + s.router.Use(cors.Handler(cors.Options{ + AllowCredentials: true, + AllowedHeaders: []string{"X-Requested-With", "Content-Type", "Authorization", "Origin"}, + AllowedMethods: []string{"GET", "POST", "HEAD", "OPTIONS"}, + AllowedOrigins: []string{"*"}, + })) + s.restAPIHandle = api.New() info := map[string]any{} @@ -198,13 +219,26 @@ func (s *Server) init() error { }) // API routes (JWT token auth) - s.router.Route("/api", func(securedapi chi.Router) { - if !config.Keys.DisableAuthentication { - securedapi.Use(func(next http.Handler) http.Handler { - return authHandle.AuthAPI(next, onFailureResponse) - }) - } - s.restAPIHandle.MountAPIRoutes(securedapi) + s.router.Route("/api", func(apiRouter chi.Router) { + // Main API routes with API auth + apiRouter.Group(func(securedapi chi.Router) { + if !config.Keys.DisableAuthentication { + securedapi.Use(func(next http.Handler) http.Handler { + return authHandle.AuthAPI(next, onFailureResponse) + }) + } + s.restAPIHandle.MountAPIRoutes(securedapi) + }) + + // Metric store API routes with separate auth + apiRouter.Group(func(metricstoreapi chi.Router) { + if !config.Keys.DisableAuthentication { + metricstoreapi.Use(func(next http.Handler) http.Handler { + return authHandle.AuthMetricStoreAPI(next, onFailureResponse) + }) + } + s.restAPIHandle.MountMetricStoreAPIRoutes(metricstoreapi) + }) }) // User API routes @@ -217,8 +251,9 @@ func (s *Server) init() error { s.restAPIHandle.MountUserAPIRoutes(userapi) }) - // Config API routes - s.router.Route("/config", func(configapi chi.Router) { + // Config API routes (uses Group with full paths to avoid shadowing + // the /config page route that is registered in the secured group) + s.router.Group(func(configapi chi.Router) { if !config.Keys.DisableAuthentication { configapi.Use(func(next http.Handler) http.Handler { return authHandle.AuthConfigAPI(next, onFailureResponse) @@ -244,16 +279,6 @@ func (s *Server) init() error { } } - // Metric store API routes (mounted under /api but with different auth) - s.router.Route("/api", func(metricstoreapi chi.Router) { - if !config.Keys.DisableAuthentication { - metricstoreapi.Use(func(next http.Handler) http.Handler { - return authHandle.AuthMetricStoreAPI(next, onFailureResponse) - }) - } - s.restAPIHandle.MountMetricStoreAPIRoutes(metricstoreapi) - }) - // Custom 404 handler for unmatched routes s.router.NotFound(func(rw http.ResponseWriter, r *http.Request) { if strings.HasPrefix(r.URL.Path, "/api/") || strings.HasPrefix(r.URL.Path, "/userapi/") || @@ -287,15 +312,6 @@ func (s *Server) init() error { s.router.Handle("/*", http.FileServer(http.Dir(config.Keys.StaticFiles))) } - s.router.Use(middleware.Compress(5)) - s.router.Use(middleware.Recoverer) - s.router.Use(cors.Handler(cors.Options{ - AllowCredentials: true, - AllowedHeaders: []string{"X-Requested-With", "Content-Type", "Authorization", "Origin"}, - AllowedMethods: []string{"GET", "POST", "HEAD", "OPTIONS"}, - AllowedOrigins: []string{"*"}, - })) - return nil } @@ -306,19 +322,6 @@ const ( ) func (s *Server) Start(ctx context.Context) error { - // Add request logging middleware - s.router.Use(func(next http.Handler) http.Handler { - return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - start := time.Now() - ww := middleware.NewWrapResponseWriter(rw, r.ProtoMajor) - next.ServeHTTP(ww, r) - cclog.Debugf("%s %s (%d, %.02fkb, %dms)", - r.Method, r.URL.RequestURI(), - ww.Status(), float32(ww.BytesWritten())/1024, - time.Since(start).Milliseconds()) - }) - }) - // Use configurable timeouts with defaults readTimeout := time.Duration(defaultReadTimeout) * time.Second writeTimeout := time.Duration(defaultWriteTimeout) * time.Second diff --git a/internal/api/rest.go b/internal/api/rest.go index 90f64f18..575b1809 100644 --- a/internal/api/rest.go +++ b/internal/api/rest.go @@ -140,16 +140,18 @@ func (api *RestAPI) MountMetricStoreAPIRoutes(r chi.Router) { // MountConfigAPIRoutes registers configuration and user management endpoints. // These routes use session-based authentication and require admin privileges. +// Routes use full paths (including /config prefix) to avoid conflicting with +// the /config page route when registered via Group instead of Route. func (api *RestAPI) MountConfigAPIRoutes(r chi.Router) { // Settings Frontend Uses SessionAuth if api.Authentication != nil { - r.Get("/roles/", api.getRoles) - r.Post("/users/", api.createUser) - r.Put("/users/", api.createUser) - r.Get("/users/", api.getUsers) - r.Delete("/users/", api.deleteUser) - r.Post("/user/{id}", api.updateUser) - r.Post("/notice/", api.editNotice) + r.Get("/config/roles/", api.getRoles) + r.Post("/config/users/", api.createUser) + r.Put("/config/users/", api.createUser) + r.Get("/config/users/", api.getUsers) + r.Delete("/config/users/", api.deleteUser) + r.Post("/config/user/{id}", api.updateUser) + r.Post("/config/notice/", api.editNotice) } }