fix(server): respond with 404/405 appropriately (#3087)
This adjusts the not found handler to not respond with a 404 on not found endpoints that are part of the /api or /.well-known folders, and respond with a 405 when the method isn't implemented. Co-authored-by: Amir Zarrinkafsh <nightah@me.com>pull/3108/head^2
parent
fa143ea029
commit
2502d89682
|
@ -190,3 +190,10 @@ func respondUnauthorized(ctx *middlewares.AutheliaCtx, message string) {
|
||||||
ctx.SetStatusCode(fasthttp.StatusUnauthorized)
|
ctx.SetStatusCode(fasthttp.StatusUnauthorized)
|
||||||
ctx.SetJSONError(message)
|
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)))
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/authentication"
|
"github.com/authelia/authelia/v4/internal/authentication"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -124,3 +126,12 @@ type PassworPolicyBody struct {
|
||||||
RequireNumber bool `json:"require_number"`
|
RequireNumber bool `json:"require_number"`
|
||||||
RequireSpecial bool `json:"require_special"`
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -8,7 +8,37 @@ const (
|
||||||
logoFile = "logo.png"
|
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 (
|
const (
|
||||||
dev = "dev"
|
dev = "dev"
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -49,15 +49,18 @@ func registerRoutes(configuration schema.Configuration, providers middlewares.Pr
|
||||||
r.GET("/", autheliaMiddleware(serveIndexHandler))
|
r.GET("/", autheliaMiddleware(serveIndexHandler))
|
||||||
r.OPTIONS("/", autheliaMiddleware(handleOPTIONS))
|
r.OPTIONS("/", autheliaMiddleware(handleOPTIONS))
|
||||||
|
|
||||||
r.GET("/api/", autheliaMiddleware(serveSwaggerHandler))
|
|
||||||
r.GET("/api/"+apiFile, autheliaMiddleware(serveSwaggerAPIHandler))
|
|
||||||
|
|
||||||
for _, f := range rootFiles {
|
for _, f := range rootFiles {
|
||||||
r.GET("/"+f, middlewares.AssetOverrideMiddleware(configuration.Server.AssetPath, embeddedFS))
|
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.GET("/static/{filepath:*}", middlewares.AssetOverrideMiddleware(configuration.Server.AssetPath, embeddedFS))
|
||||||
r.ANY("/api/{filepath:*}", embeddedFS)
|
|
||||||
|
|
||||||
r.GET("/api/health", autheliaMiddleware(handlers.HealthGet))
|
r.GET("/api/health", autheliaMiddleware(handlers.HealthGet))
|
||||||
r.GET("/api/state", autheliaMiddleware(handlers.StateGet))
|
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.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)
|
handler := middlewares.LogRequestMiddleware(r.Handler)
|
||||||
if configuration.Server.Path != "" {
|
if configuration.Server.Path != "" {
|
||||||
|
|
|
@ -64,6 +64,10 @@ func (s *BackendProtectionScenario) TestInvalidEndpointsReturn404() {
|
||||||
s.AssertRequestStatusCode("POST", fmt.Sprintf("%s/api/not_existing/second", AutheliaBaseURL), 404)
|
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) {
|
func TestRunBackendProtection(t *testing.T) {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("skipping suite test in short mode")
|
t.Skip("skipping suite test in short mode")
|
||||||
|
|
Loading…
Reference in New Issue