package server import ( "github.com/labstack/echo/v4" "github.com/thomiceli/opengist/internal/config" "github.com/thomiceli/opengist/internal/index" "github.com/thomiceli/opengist/internal/web/context" "github.com/thomiceli/opengist/internal/web/handlers" "github.com/thomiceli/opengist/public" "net/http" "os" "path" "path/filepath" "strings" "time" ) func (s *Server) registerRoutes() { r := NewRouter(s.echo.Group("")) { r.GET("/", handlers.Create, logged) r.POST("/", handlers.ProcessCreate, logged) r.POST("/preview", handlers.Preview, logged) r.GET("/healthcheck", handlers.Healthcheck) r.GET("/metrics", handlers.Metrics) r.GET("/register", handlers.Register) r.POST("/register", handlers.ProcessRegister) r.GET("/login", handlers.Login) r.POST("/login", handlers.ProcessLogin) r.GET("/logout", handlers.Logout) r.GET("/oauth/:provider", handlers.Oauth) r.GET("/oauth/:provider/callback", handlers.OauthCallback) r.GET("/oauth/:provider/unlink", handlers.OauthUnlink, logged) r.POST("/webauthn/bind", handlers.BeginWebAuthnBinding, logged) r.POST("/webauthn/bind/finish", handlers.FinishWebAuthnBinding, logged) r.POST("/webauthn/login", handlers.BeginWebAuthnLogin) r.POST("/webauthn/login/finish", handlers.FinishWebAuthnLogin) r.POST("/webauthn/assertion", handlers.BeginWebAuthnAssertion, inMFASession) r.POST("/webauthn/assertion/finish", handlers.FinishWebAuthnAssertion, inMFASession) r.GET("/mfa", handlers.Mfa, inMFASession) r.POST("/mfa/totp/assertion", handlers.AssertTotp, inMFASession) sA := r.SubGroup("/settings") { sA.Use(logged) sA.GET("", handlers.UserSettings) sA.POST("/email", handlers.EmailProcess) sA.DELETE("/account", handlers.AccountDeleteProcess) sA.POST("/ssh-keys", handlers.SshKeysProcess) sA.DELETE("/ssh-keys/:id", handlers.SshKeysDelete) sA.DELETE("/passkeys/:id", handlers.PasskeyDelete) sA.PUT("/password", handlers.PasswordProcess) sA.PUT("/username", handlers.UsernameProcess) sA.GET("/totp/generate", handlers.BeginTotp) sA.POST("/totp/generate", handlers.FinishTotp) sA.DELETE("/totp", handlers.DisableTotp) sA.POST("/totp/regenerate", handlers.RegenerateTotpRecoveryCodes) } sB := r.SubGroup("/admin-panel") { sB.Use(adminPermission) sB.GET("", handlers.AdminIndex) sB.GET("/users", handlers.AdminUsers) sB.POST("/users/:user/delete", handlers.AdminUserDelete) sB.GET("/gists", handlers.AdminGists) sB.POST("/gists/:gist/delete", handlers.AdminGistDelete) sB.GET("/invitations", handlers.AdminInvitations) sB.POST("/invitations", handlers.AdminInvitationsCreate) sB.POST("/invitations/:id/delete", handlers.AdminInvitationsDelete) sB.POST("/sync-fs", handlers.AdminSyncReposFromFS) sB.POST("/sync-db", handlers.AdminSyncReposFromDB) sB.POST("/gc-repos", handlers.AdminGcRepos) sB.POST("/sync-previews", handlers.AdminSyncGistPreviews) sB.POST("/reset-hooks", handlers.AdminResetHooks) sB.POST("/index-gists", handlers.AdminIndexGists) sB.GET("/configuration", handlers.AdminConfig) sB.PUT("/set-config", handlers.AdminSetConfig) } if config.C.HttpGit { r.Any("/init/*", handlers.GitHttp, gistNewPushSoftInit) } r.GET("/all", handlers.AllGists, checkRequireLogin) if index.Enabled() { r.GET("/search", handlers.Search, checkRequireLogin) } else { r.GET("/search", handlers.AllGists, checkRequireLogin) } r.GET("/:user", handlers.AllGists, checkRequireLogin) r.GET("/:user/liked", handlers.AllGists, checkRequireLogin) r.GET("/:user/forked", handlers.AllGists, checkRequireLogin) sC := r.SubGroup("/:user/:gistname") { sC.Use(makeCheckRequireLogin(true), gistInit) sC.GET("", handlers.GistIndex) sC.GET("/rev/:revision", handlers.GistIndex) sC.GET("/revisions", handlers.Revisions) sC.GET("/archive/:revision", handlers.DownloadZip) sC.POST("/visibility", handlers.EditVisibility, logged, writePermission) sC.POST("/delete", handlers.DeleteGist, logged, writePermission) sC.GET("/raw/:revision/:file", handlers.RawFile) sC.GET("/download/:revision/:file", handlers.DownloadFile) sC.GET("/edit", handlers.Edit, logged, writePermission) sC.POST("/edit", handlers.ProcessCreate, logged, writePermission) sC.POST("/like", handlers.Like, logged) sC.GET("/likes", handlers.Likes, checkRequireLogin) sC.POST("/fork", handlers.Fork, logged) sC.GET("/forks", handlers.Forks, checkRequireLogin) sC.PUT("/checkbox", handlers.Checkbox, logged, writePermission) } } customFs := os.DirFS(filepath.Join(config.GetHomeDir(), "custom")) r.GET("/assets/*", func(ctx *context.Context) error { if _, err := public.Files.Open(path.Join("assets", ctx.Param("*"))); !s.dev && err == nil { ctx.Response().Header().Set("Cache-Control", "public, max-age=31536000") ctx.Response().Header().Set("Expires", time.Now().AddDate(1, 0, 0).Format(http.TimeFormat)) return echo.WrapHandler(http.FileServer(http.FS(public.Files)))(ctx) } // if the custom file is an .html template, render it if strings.HasSuffix(ctx.Param("*"), ".html") { if err := ctx.HTML_(ctx.Param("*")); err != nil { return ctx.NotFound("Page not found") } return nil } return echo.WrapHandler(http.StripPrefix("/assets/", http.FileServer(http.FS(customFs))))(ctx) }) // Git HTTP routes if config.C.HttpGit { r.Any("/:user/:gistname/*", handlers.GitHttp, gistSoftInit) } r.Any("/*", noRouteFound) } // Router wraps echo.Group to provide custom Handler support type Router struct { *echo.Group } func NewRouter(g *echo.Group) *Router { return &Router{Group: g} } func (r *Router) SubGroup(prefix string, m ...Middleware) *Router { echoMiddleware := make([]echo.MiddlewareFunc, len(m)) for i, mw := range m { mw := mw // capture for closure echoMiddleware[i] = func(next echo.HandlerFunc) echo.HandlerFunc { return chain(func(c *context.Context) error { return next(c) }, mw).toEchoHandler() } } return NewRouter(r.Group.Group(prefix, echoMiddleware...)) } func (r *Router) GET(path string, h Handler, m ...Middleware) { r.Group.GET(path, chain(h, m...).toEchoHandler()) } func (r *Router) POST(path string, h Handler, m ...Middleware) { r.Group.POST(path, chain(h, m...).toEchoHandler()) } func (r *Router) PUT(path string, h Handler, m ...Middleware) { r.Group.PUT(path, chain(h, m...).toEchoHandler()) } func (r *Router) DELETE(path string, h Handler, m ...Middleware) { r.Group.DELETE(path, chain(h, m...).toEchoHandler()) } func (r *Router) PATCH(path string, h Handler, m ...Middleware) { r.Group.PATCH(path, chain(h, m...).toEchoHandler()) } func (r *Router) Any(path string, h Handler, m ...Middleware) { r.Group.Any(path, chain(h, m...).toEchoHandler()) } func (r *Router) Use(middleware ...Middleware) { for _, m := range middleware { m := m // capture for closure r.Group.Use(func(next echo.HandlerFunc) echo.HandlerFunc { return chain(func(c *context.Context) error { return next(c) }, m).toEchoHandler() }) } }