package server import ( "errors" "github.com/thomiceli/opengist/internal/utils" "github.com/thomiceli/opengist/internal/web/context" "github.com/thomiceli/opengist/internal/web/handler" "html/template" "io" "net/http" "strings" "github.com/gorilla/sessions" "github.com/labstack/echo/v4" "github.com/rs/zerolog/log" "github.com/thomiceli/opengist/internal/auth" "github.com/thomiceli/opengist/internal/config" "github.com/thomiceli/opengist/internal/db" "github.com/thomiceli/opengist/internal/i18n" ) type Template struct { templates *template.Template } func (t *Template) Render(w io.Writer, name string, data interface{}, _ echo.Context) error { return t.templates.ExecuteTemplate(w, name, data) } type Server struct { echo *echo.Echo flashStore *sessions.CookieStore // session store for flash messages UserStore *sessions.FilesystemStore // session store for user sessions dev bool sessionsPath string ignoreCsrf bool } func NewServer(isDev bool, sessionsPath string, ignoreCsrf bool) *Server { e := echo.New() e.HideBanner = true e.HidePort = true s := &Server{echo: e, dev: isDev, sessionsPath: sessionsPath, ignoreCsrf: ignoreCsrf} s.useCustomContext() if err := i18n.Locales.LoadAll(); err != nil { log.Fatal().Err(err).Msg("Failed to load locales") } s.RegisterMiddlewares() s.setFuncMap() s.echo.HTTPErrorHandler = s.errorHandler e.Validator = utils.NewValidator() if !s.dev { parseManifestEntries() } s.setupRoutes() return s } func (s *Server) Start() { addr := config.C.HttpHost + ":" + config.C.HttpPort log.Info().Msg("Starting HTTP server on http://" + addr) if err := s.echo.Start(addr); err != nil && err != http.ErrServerClosed { log.Fatal().Err(err).Msg("Failed to start HTTP server") } } func (s *Server) Stop() { if err := s.echo.Close(); err != nil { log.Fatal().Err(err).Msg("Failed to stop HTTP server") } } func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.echo.ServeHTTP(w, r) } func writePermission(next Handler) Handler { return func(ctx *context.OGContext) error { gist := ctx.GetData("gist") user := ctx.User if !gist.(*db.Gist).CanWrite(user) { return ctx.RedirectTo("/" + gist.(*db.Gist).User.Username + "/" + gist.(*db.Gist).Identifier()) } return next(ctx) } } func adminPermission(next Handler) Handler { return func(ctx *context.OGContext) error { user := ctx.User if user == nil || !user.IsAdmin { return ctx.NotFound("User not found") } return next(ctx) } } func logged(next Handler) Handler { return func(ctx *context.OGContext) error { user := ctx.User if user != nil { return next(ctx) } return ctx.RedirectTo("/all") } } func inMFASession(next Handler) Handler { return func(ctx *context.OGContext) error { sess := ctx.GetSession() _, ok := sess.Values["mfaID"].(uint) if !ok { return ctx.ErrorRes(400, ctx.Tr("error.not-in-mfa-session"), nil) } return next(ctx) } } func makeCheckRequireLogin(isSingleGistAccess bool) Middleware { return func(next Handler) Handler { return func(ctx *context.OGContext) error { if user := ctx.User; user != nil { return next(ctx) } allow, err := auth.ShouldAllowUnauthenticatedGistAccess(handler.ContextAuthInfo{Context: ctx}, isSingleGistAccess) if err != nil { log.Fatal().Err(err).Msg("Failed to check if unauthenticated access is allowed") } if !allow { ctx.AddFlash(ctx.Tr("flash.auth.must-be-logged-in"), "error") return ctx.RedirectTo("/login") } return next(ctx) } } } func checkRequireLogin(next Handler) Handler { return makeCheckRequireLogin(false)(next) } func noRouteFound(ctx *context.OGContext) error { return ctx.NotFound("Page not found") } func (s *Server) errorHandler(err error, ctx echo.Context) { var httpErr *echo.HTTPError if errors.As(err, &httpErr) { acceptJson := strings.Contains(ctx.Request().Header.Get("Accept"), "application/json") data := ctx.Request().Context().Value("data").(echo.Map) data["error"] = err if acceptJson { if err := ctx.JSON(httpErr.Code, httpErr); err != nil { log.Fatal().Err(err).Send() } return } if err := ctx.Render(httpErr.Code, "error", data); err != nil { log.Fatal().Err(err).Send() } return } log.Fatal().Err(err).Send() }