diff --git a/internal/handlers/response.go b/internal/handlers/response.go index e79207d05..96301c354 100644 --- a/internal/handlers/response.go +++ b/internal/handlers/response.go @@ -190,3 +190,10 @@ func respondUnauthorized(ctx *middlewares.AutheliaCtx, message string) { ctx.SetStatusCode(fasthttp.StatusUnauthorized) ctx.SetJSONError(message) } + +// SetStatusCodeResponse writes a response status code and an appropriate body on either a +// *fasthttp.RequestCtx or *middlewares.AutheliaCtx. +func SetStatusCodeResponse(ctx responseWriter, statusCode int) { + ctx.SetStatusCode(statusCode) + ctx.SetBodyString(fmt.Sprintf("%d %s", statusCode, fasthttp.StatusMessage(statusCode))) +} diff --git a/internal/handlers/types.go b/internal/handlers/types.go index e4670220e..33e096c25 100644 --- a/internal/handlers/types.go +++ b/internal/handlers/types.go @@ -1,6 +1,8 @@ package handlers import ( + "io" + "github.com/authelia/authelia/v4/internal/authentication" ) @@ -124,3 +126,12 @@ type PassworPolicyBody struct { RequireNumber bool `json:"require_number"` RequireSpecial bool `json:"require_special"` } + +type responseWriter interface { + SetStatusCode(statusCode int) + SetBodyString(body string) + SetBody(body []byte) + SetContentType(contentType string) + SetContentTypeBytes(contentType []byte) + SetBodyStream(bodyStream io.Reader, bodySize int) +} diff --git a/internal/server/const.go b/internal/server/const.go index 610b2953e..a5b79668e 100644 --- a/internal/server/const.go +++ b/internal/server/const.go @@ -8,7 +8,37 @@ const ( logoFile = "logo.png" ) -var rootFiles = []string{"favicon.ico", "manifest.json", "robots.txt"} +var ( + rootFiles = []string{"favicon.ico", "manifest.json", "robots.txt"} + swaggerFiles = []string{ + "favicon-16x16.png", + "favicon-32x32.png", + "index.css", + "oauth2-redirect.html", + "swagger-initializer.js", + "swagger-ui-bundle.js", + "swagger-ui-bundle.js.map", + "swagger-ui-es-bundle-core.js", + "swagger-ui-es-bundle-core.js.map", + "swagger-ui-es-bundle.js", + "swagger-ui-es-bundle.js.map", + "swagger-ui-standalone-preset.js", + "swagger-ui-standalone-preset.js.map", + "swagger-ui.css", + "swagger-ui.css.map", + "swagger-ui.js", + "swagger-ui.js.map", + } + + // Directories excluded from the not found handler proceeding to the next() handler. + httpServerDirs = []struct { + name, prefix string + }{ + {name: "/api", prefix: "/api/"}, + {name: "/.well-known", prefix: "/.well-known/"}, + {name: "/static", prefix: "/static/"}, + } +) const ( dev = "dev" diff --git a/internal/server/handler_notfound.go b/internal/server/handler_notfound.go new file mode 100644 index 000000000..c8002daa4 --- /dev/null +++ b/internal/server/handler_notfound.go @@ -0,0 +1,25 @@ +package server + +import ( + "strings" + + "github.com/valyala/fasthttp" + + "github.com/authelia/authelia/v4/internal/handlers" +) + +func handleNotFound(next fasthttp.RequestHandler) fasthttp.RequestHandler { + return func(ctx *fasthttp.RequestCtx) { + path := strings.ToLower(string(ctx.Path())) + + for i := 0; i < len(httpServerDirs); i++ { + if path == httpServerDirs[i].name || strings.HasPrefix(path, httpServerDirs[i].prefix) { + handlers.SetStatusCodeResponse(ctx, fasthttp.StatusNotFound) + + return + } + } + + next(ctx) + } +} diff --git a/internal/server/server.go b/internal/server/server.go index f5195322c..660eb5cba 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -49,15 +49,18 @@ func registerRoutes(configuration schema.Configuration, providers middlewares.Pr r.GET("/", autheliaMiddleware(serveIndexHandler)) r.OPTIONS("/", autheliaMiddleware(handleOPTIONS)) - r.GET("/api/", autheliaMiddleware(serveSwaggerHandler)) - r.GET("/api/"+apiFile, autheliaMiddleware(serveSwaggerAPIHandler)) - for _, f := range rootFiles { r.GET("/"+f, middlewares.AssetOverrideMiddleware(configuration.Server.AssetPath, embeddedFS)) } + r.GET("/api/", autheliaMiddleware(serveSwaggerHandler)) + r.GET("/api/"+apiFile, autheliaMiddleware(serveSwaggerAPIHandler)) + + for _, file := range swaggerFiles { + r.GET("/api/"+file, embeddedFS) + } + r.GET("/static/{filepath:*}", middlewares.AssetOverrideMiddleware(configuration.Server.AssetPath, embeddedFS)) - r.ANY("/api/{filepath:*}", embeddedFS) r.GET("/api/health", autheliaMiddleware(handlers.HealthGet)) r.GET("/api/state", autheliaMiddleware(handlers.StateGet)) @@ -155,7 +158,12 @@ func registerRoutes(configuration schema.Configuration, providers middlewares.Pr r.GET("/debug/vars", expvarhandler.ExpvarHandler) } - r.NotFound = autheliaMiddleware(serveIndexHandler) + r.NotFound = handleNotFound(autheliaMiddleware(serveIndexHandler)) + + r.HandleMethodNotAllowed = true + r.MethodNotAllowed = func(ctx *fasthttp.RequestCtx) { + handlers.SetStatusCodeResponse(ctx, fasthttp.StatusMethodNotAllowed) + } handler := middlewares.LogRequestMiddleware(r.Handler) if configuration.Server.Path != "" { diff --git a/internal/suites/scenario_backend_protection_test.go b/internal/suites/scenario_backend_protection_test.go index 445d310d9..f831e2609 100644 --- a/internal/suites/scenario_backend_protection_test.go +++ b/internal/suites/scenario_backend_protection_test.go @@ -64,6 +64,10 @@ func (s *BackendProtectionScenario) TestInvalidEndpointsReturn404() { s.AssertRequestStatusCode("POST", fmt.Sprintf("%s/api/not_existing/second", AutheliaBaseURL), 404) } +func (s *BackendProtectionScenario) TestInvalidEndpointsReturn405() { + s.AssertRequestStatusCode("PUT", fmt.Sprintf("%s/api/configuration", AutheliaBaseURL), 405) +} + func TestRunBackendProtection(t *testing.T) { if testing.Short() { t.Skip("skipping suite test in short mode")