add favicon hiding when not authenticated

Signed-off-by: Ronni Skansing <rskansing@gmail.com>
This commit is contained in:
Ronni Skansing
2026-05-28 11:05:17 +02:00
parent 203fd86109
commit 1529c10b72
3 changed files with 65 additions and 16 deletions
+25 -10
View File
@@ -220,12 +220,13 @@ const (
// administrationServer is the administrationServer app
type administrationServer struct {
Server *http.Server
router *gin.Engine
logger *zap.SugaredLogger
production bool
embedBackendFS *embed.FS
certMagicConfig *certmagic.Config
Server *http.Server
router *gin.Engine
logger *zap.SugaredLogger
production bool
embedBackendFS *embed.FS
certMagicConfig *certmagic.Config
softSessionHandler gin.HandlerFunc
}
// NewAdministrationServer creates a new administration app
@@ -240,10 +241,11 @@ func NewAdministrationServer(
router = setupRoutes(router, controllers, middlewares)
return &administrationServer{
router: router,
logger: logger,
production: production,
certMagicConfig: certMagicConfig,
router: router,
logger: logger,
production: production,
certMagicConfig: certMagicConfig,
softSessionHandler: middlewares.SoftSessionHandler,
}
}
@@ -713,6 +715,15 @@ func (a *administrationServer) loadEmbeddedFileSystem(
embedFS := frontend.GetEmbededFS()
// make embedded .html work
frontend.LoadHTMLFromEmbedFS(a.router, *embedFS, "build/*.html")
// serve favicons only to authenticated users — unauthenticated requests get 404
// so the file is not indexable by scanners probing common paths
for _, faviconPath := range []string{"/favicon.ico", "/favicon.png"} {
fp := faviconPath
a.router.GET(fp, a.softSessionHandler, func(c *gin.Context) {
name := fp[1:] // strip leading /
c.FileFromFS("build/"+name, http.FS(*embedFS))
})
}
rootDir, err := embedFS.ReadDir("build")
if err != nil {
return errs.Wrap(err)
@@ -728,6 +739,10 @@ func (a *administrationServer) loadEmbeddedFileSystem(
})
continue
}
// skip favicons — registered separately with session gating
if path == "favicon.png" || path == "favicon.ico" {
continue
}
// any file in the root folder gets server as a file
a.router.GET("/"+path, func(c *gin.Context) {
c.FileFromFS("build/"+path, http.FS(*embedFS))
+13 -6
View File
@@ -11,9 +11,10 @@ import (
// Middlwares is a collection of middlewares
type Middlewares struct {
IPLimiter gin.HandlerFunc
LoginRateLimiter gin.HandlerFunc
SessionHandler gin.HandlerFunc
IPLimiter gin.HandlerFunc
LoginRateLimiter gin.HandlerFunc
SessionHandler gin.HandlerFunc
SoftSessionHandler gin.HandlerFunc
}
// NewMiddlewares creates a collection of middlewares
@@ -36,11 +37,17 @@ func NewMiddlewares(
utils.JSONResponseHandler,
logger,
)
softSessionHandler := middleware.NewSoftSessionHandler(
services.Session,
services.User,
logger,
)
return &Middlewares{
IPLimiter: ipLimiter,
LoginRateLimiter: loginThrottle,
SessionHandler: sessionHandler,
IPLimiter: ipLimiter,
LoginRateLimiter: loginThrottle,
SessionHandler: sessionHandler,
SoftSessionHandler: softSessionHandler,
}
}
+27
View File
@@ -3,6 +3,7 @@ package middleware
import (
"crypto/sha256"
"crypto/subtle"
"net/http"
"time"
"github.com/gin-gonic/gin"
@@ -15,6 +16,32 @@ import (
"go.uber.org/zap"
)
// NewSoftSessionHandler is like NewSessionHandler but returns 404 on auth failure
// instead of 401, so unauthenticated requests are indistinguishable from missing resources.
func NewSoftSessionHandler(
sessionService *service.Session,
userService *service.User,
logger *zap.SugaredLogger,
) gin.HandlerFunc {
return func(c *gin.Context) {
isValidAPISession := handleAPISession(c, userService, logger)
if isValidAPISession {
return
}
s, err := sessionService.GetAndExtendSession(c)
if err != nil {
c.AbortWithStatus(http.StatusNotFound)
return
}
if s.User == nil {
c.AbortWithStatus(http.StatusNotFound)
return
}
controller.SetSessionInGinContext(c, s)
c.Next()
}
}
// NewSessionHandler creates a middleware that authenticates the user
// by checking it has a session, and if it does, it extends the session and puts
// the user and the session in the gin context.