All checks were successful
check / check (push) Successful in 2m43s
Replace github.com/go-chi/chi v1.5.5 with github.com/go-chi/chi/v5 v5.2.1 to resolve GO-2026-4316 (open redirect in RedirectSlashes middleware). Update all import paths across the codebase and update documentation references in README.md and CONVENTIONS.md.
170 lines
3.1 KiB
Go
170 lines
3.1 KiB
Go
package server
|
|
|
|
import (
|
|
"io/fs"
|
|
"net/http"
|
|
"time"
|
|
|
|
"git.eeqj.de/sneak/neoirc/web"
|
|
|
|
sentryhttp "github.com/getsentry/sentry-go/http"
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/go-chi/chi/v5/middleware"
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
const routeTimeout = 60 * time.Second
|
|
|
|
// SetupRoutes configures the HTTP routes and middleware.
|
|
func (srv *Server) SetupRoutes() {
|
|
srv.router = chi.NewRouter()
|
|
|
|
srv.router.Use(middleware.Recoverer)
|
|
srv.router.Use(middleware.RequestID)
|
|
srv.router.Use(srv.mw.Logging())
|
|
|
|
if viper.GetString("METRICS_USERNAME") != "" {
|
|
srv.router.Use(srv.mw.Metrics())
|
|
}
|
|
|
|
srv.router.Use(srv.mw.CORS())
|
|
srv.router.Use(srv.mw.CSP())
|
|
srv.router.Use(middleware.Timeout(routeTimeout))
|
|
|
|
if srv.sentryEnabled {
|
|
sentryHandler := sentryhttp.New(
|
|
sentryhttp.Options{ //nolint:exhaustruct // optional fields
|
|
Repanic: true,
|
|
},
|
|
)
|
|
|
|
srv.router.Use(sentryHandler.Handle)
|
|
}
|
|
|
|
// Health check.
|
|
srv.router.Get(
|
|
"/.well-known/healthcheck.json",
|
|
srv.handlers.HandleHealthCheck(),
|
|
)
|
|
|
|
// Protected metrics endpoint.
|
|
if viper.GetString("METRICS_USERNAME") != "" {
|
|
srv.router.Group(func(router chi.Router) {
|
|
router.Use(srv.mw.MetricsAuth())
|
|
router.Get("/metrics",
|
|
http.HandlerFunc(
|
|
promhttp.Handler().ServeHTTP,
|
|
))
|
|
})
|
|
}
|
|
|
|
// API v1.
|
|
srv.router.Route("/api/v1", srv.setupAPIv1)
|
|
|
|
// Serve embedded SPA.
|
|
srv.setupSPA()
|
|
}
|
|
|
|
func (srv *Server) setupAPIv1(router chi.Router) {
|
|
router.Get(
|
|
"/server",
|
|
srv.handlers.HandleServerInfo(),
|
|
)
|
|
router.Post(
|
|
"/session",
|
|
srv.handlers.HandleCreateSession(),
|
|
)
|
|
router.Post(
|
|
"/register",
|
|
srv.handlers.HandleRegister(),
|
|
)
|
|
router.Post(
|
|
"/login",
|
|
srv.handlers.HandleLogin(),
|
|
)
|
|
router.Get(
|
|
"/state",
|
|
srv.handlers.HandleState(),
|
|
)
|
|
router.Post(
|
|
"/logout",
|
|
srv.handlers.HandleLogout(),
|
|
)
|
|
router.Get(
|
|
"/users/me",
|
|
srv.handlers.HandleUsersMe(),
|
|
)
|
|
router.Get(
|
|
"/messages",
|
|
srv.handlers.HandleGetMessages(),
|
|
)
|
|
router.Post(
|
|
"/messages",
|
|
srv.handlers.HandleSendCommand(),
|
|
)
|
|
router.Get(
|
|
"/history",
|
|
srv.handlers.HandleGetHistory(),
|
|
)
|
|
router.Get(
|
|
"/channels",
|
|
srv.handlers.HandleListAllChannels(),
|
|
)
|
|
router.Get(
|
|
"/channels/{channel}/members",
|
|
srv.handlers.HandleChannelMembers(),
|
|
)
|
|
}
|
|
|
|
func (srv *Server) setupSPA() {
|
|
distFS, err := fs.Sub(web.Dist, "dist")
|
|
if err != nil {
|
|
srv.log.Error(
|
|
"failed to get web dist filesystem",
|
|
"error", err,
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
fileServer := http.FileServer(http.FS(distFS))
|
|
|
|
srv.router.Get("/*", func(
|
|
writer http.ResponseWriter,
|
|
request *http.Request,
|
|
) {
|
|
readFS, ok := distFS.(fs.ReadFileFS)
|
|
if !ok {
|
|
fileServer.ServeHTTP(writer, request)
|
|
|
|
return
|
|
}
|
|
|
|
fileData, readErr := readFS.ReadFile(
|
|
request.URL.Path[1:],
|
|
)
|
|
if readErr != nil || len(fileData) == 0 {
|
|
indexHTML, indexErr := readFS.ReadFile(
|
|
"index.html",
|
|
)
|
|
if indexErr != nil {
|
|
http.NotFound(writer, request)
|
|
|
|
return
|
|
}
|
|
|
|
writer.Header().Set(
|
|
"Content-Type",
|
|
"text/html; charset=utf-8",
|
|
)
|
|
writer.WriteHeader(http.StatusOK)
|
|
_, _ = writer.Write(indexHTML)
|
|
|
|
return
|
|
}
|
|
|
|
fileServer.ServeHTTP(writer, request)
|
|
})
|
|
}
|