diff --git a/internal/config/server.go b/internal/config/server.go index bbdf4ae..1efc2f0 100644 --- a/internal/config/server.go +++ b/internal/config/server.go @@ -1,15 +1,18 @@ package config import ( + "path" + "github.com/spf13/cobra" "github.com/spf13/viper" ) type Admin struct { - Static string - ProxyAuth string - Username string - Password string + Static string + PathPrefix string + ProxyAuth string + Username string + Password string } type Server struct { @@ -55,6 +58,11 @@ func (Server) Init(cmd *cobra.Command) error { return err } + cmd.PersistentFlags().String("admin.path_prefix", "/", "path prefix for admin client and API") + if err := viper.BindPFlag("admin.path_prefix", cmd.PersistentFlags().Lookup("admin.path_prefix")); err != nil { + return err + } + cmd.PersistentFlags().String("admin.proxy_auth", "", "require auth: proxy authentication URL, only allow if it returns 200") if err := viper.BindPFlag("admin.proxy_auth", cmd.PersistentFlags().Lookup("admin.proxy_auth")); err != nil { return err @@ -81,6 +89,7 @@ func (s *Server) Set() { s.PProf = viper.GetBool("pprof") s.Admin.Static = viper.GetString("admin.static") + s.Admin.PathPrefix = path.Join("/", path.Clean(viper.GetString("admin.path_prefix"))) s.Admin.ProxyAuth = viper.GetString("admin.proxy_auth") s.Admin.Username = viper.GetString("admin.username") s.Admin.Password = viper.GetString("admin.password") diff --git a/internal/server/manager.go b/internal/server/manager.go index 97e461e..1263165 100644 --- a/internal/server/manager.go +++ b/internal/server/manager.go @@ -2,7 +2,9 @@ package server import ( "context" + "errors" "io" + "io/fs" "net" "net/http" "os" @@ -170,44 +172,69 @@ func New(ApiManager types.ApiManager, roomConfig *config.Room, config *config.Se return next } - // cache static file paths - staticFiles := map[string]struct{}{} - if config.Admin.Static != "" { - filepath.Walk(config.Admin.Static, - func(p string, info os.FileInfo, err error) error { - if err != nil { - return err - } - staticFiles[path.Clean(p)] = struct{}{} - return nil - }) - } - - // serve static files - router.Use(func(next http.Handler) http.Handler { - // if static files are disabled - if config.Admin.Static == "" { - return next + // DEPRECATED: admin should not be served from the same path as rooms + if config.Admin.PathPrefix == "/" { + // cache static file paths + staticFiles := map[string]struct{}{} + if config.Admin.Static != "" { + filepath.Walk(config.Admin.Static, + func(p string, info os.FileInfo, err error) error { + if err != nil { + return err + } + staticFiles[path.Clean(p)] = struct{}{} + return nil + }) } - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - filePath := path.Join(config.Admin.Static, r.URL.Path) - - // check if file exists to serve it - if _, ok := staticFiles[filePath]; ok { - // serve protected assets - protected(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.ServeFile(w, r, filePath) - })).ServeHTTP(w, r) - return + // serve static files + router.Use(func(next http.Handler) http.Handler { + // if static files are disabled + if config.Admin.Static == "" { + return next } - next.ServeHTTP(w, r) - }) - }) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + filePath := path.Join(config.Admin.Static, r.URL.Path) - // serve protected API - router.With(protected).Route("/api", ApiManager.Mount) + // check if file exists to serve it + if _, ok := staticFiles[filePath]; ok { + // serve protected assets + protected(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.ServeFile(w, r, filePath) + })).ServeHTTP(w, r) + return + } + + next.ServeHTTP(w, r) + }) + }) + + // serve protected API + router.With(protected).Route("/api", ApiManager.Mount) + } else { + router.With(protected).Route(config.Admin.PathPrefix+"/", func(r chi.Router) { + // serve protected API + r.Route("/api", ApiManager.Mount) + + // serve static files + r.Get("/*", func(w http.ResponseWriter, r *http.Request) { + filePath := chi.URLParam(r, "*") + if filePath == "" { + filePath = "index.html" + } + filePath = filepath.Clean(filePath) + filePath = filepath.Join(config.Admin.Static, filePath) + if _, err := os.Stat(filePath); err == nil { + http.ServeFile(w, r, filePath) + } else if errors.Is(err, fs.ErrNotExist) { + http.NotFound(w, r) + } else { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + }) + }) + } // handle all remaining paths with proxy router.Handle("/*", proxyHandler)