diff --git a/internal/web/handler/auth.go b/internal/web/handler/auth.go
index 735b687..499d8a2 100644
--- a/internal/web/handler/auth.go
+++ b/internal/web/handler/auth.go
@@ -37,7 +37,7 @@ const (
OpenIDConnect = "openid-connect"
)
-func register(ctx *context.OGContext) error {
+func Register(ctx *context.OGContext) error {
disableSignup := ctx.GetData("DisableSignup")
disableForm := ctx.GetData("DisableLoginForm")
@@ -58,7 +58,7 @@ func register(ctx *context.OGContext) error {
return ctx.HTML_("auth_form.html")
}
-func processRegister(ctx *context.OGContext) error {
+func ProcessRegister(ctx *context.OGContext) error {
disableSignup := ctx.GetData("DisableSignup")
code := ctx.QueryParam("code")
@@ -127,7 +127,7 @@ func processRegister(ctx *context.OGContext) error {
return ctx.RedirectTo("/")
}
-func login(ctx *context.OGContext) error {
+func Login(ctx *context.OGContext) error {
ctx.SetData("title", ctx.TrH("auth.login"))
ctx.SetData("htmlTitle", ctx.TrH("auth.login"))
ctx.SetData("disableForm", ctx.GetData("DisableLoginForm"))
@@ -135,7 +135,7 @@ func login(ctx *context.OGContext) error {
return ctx.HTML_("auth_form.html")
}
-func processLogin(ctx *context.OGContext) error {
+func ProcessLogin(ctx *context.OGContext) error {
if ctx.GetData("DisableLoginForm") == true {
return ctx.ErrorRes(403, ctx.Tr("error.login-disabled-form"), nil)
}
@@ -189,7 +189,7 @@ func processLogin(ctx *context.OGContext) error {
return ctx.RedirectTo("/")
}
-func mfa(ctx *context.OGContext) error {
+func Mfa(ctx *context.OGContext) error {
var err error
user := db.User{ID: ctx.GetSession().Values["mfaID"].(uint)}
@@ -205,7 +205,7 @@ func mfa(ctx *context.OGContext) error {
return ctx.HTML_("mfa.html")
}
-func oauthCallback(ctx *context.OGContext) error {
+func OauthCallback(ctx *context.OGContext) error {
user, err := gothic.CompleteUserAuth(ctx.Response(), ctx.Request())
if err != nil {
return ctx.ErrorRes(400, ctx.Tr("error.complete-oauth-login", err.Error()), err)
@@ -311,7 +311,7 @@ func oauthCallback(ctx *context.OGContext) error {
return ctx.RedirectTo("/")
}
-func oauth(ctx *context.OGContext) error {
+func Oauth(ctx *context.OGContext) error {
provider := ctx.Param("provider")
httpProtocol := "http"
@@ -401,7 +401,7 @@ func oauth(ctx *context.OGContext) error {
return nil
}
-func oauthUnlink(ctx *context.OGContext) error {
+func OauthUnlink(ctx *context.OGContext) error {
provider := ctx.Param("provider")
currUser := ctx.User
@@ -425,7 +425,7 @@ func oauthUnlink(ctx *context.OGContext) error {
return ctx.RedirectTo("/settings")
}
-func beginWebAuthnBinding(ctx *context.OGContext) error {
+func BeginWebAuthnBinding(ctx *context.OGContext) error {
credsCreation, jsonWaSession, err := webauthn.BeginBinding(ctx.User)
if err != nil {
return ctx.ErrorRes(500, "Cannot begin WebAuthn registration", err)
@@ -439,7 +439,7 @@ func beginWebAuthnBinding(ctx *context.OGContext) error {
return ctx.JSON(200, credsCreation)
}
-func finishWebAuthnBinding(ctx *context.OGContext) error {
+func FinishWebAuthnBinding(ctx *context.OGContext) error {
sess := ctx.GetSession()
jsonWaSession, ok := sess.Values["webauthn_registration_session"].([]byte)
if !ok {
@@ -483,7 +483,7 @@ func finishWebAuthnBinding(ctx *context.OGContext) error {
return ctx.JSON_([]string{"OK"})
}
-func beginWebAuthnLogin(ctx *context.OGContext) error {
+func BeginWebAuthnLogin(ctx *context.OGContext) error {
credsCreation, jsonWaSession, err := webauthn.BeginDiscoverableLogin()
if err != nil {
return ctx.JsonErrorRes(401, "Cannot begin WebAuthn login", err)
@@ -497,7 +497,7 @@ func beginWebAuthnLogin(ctx *context.OGContext) error {
return ctx.JSON_(credsCreation)
}
-func finishWebAuthnLogin(ctx *context.OGContext) error {
+func FinishWebAuthnLogin(ctx *context.OGContext) error {
sess := ctx.GetSession()
sessionData, ok := sess.Values["webauthn_login_session"].([]byte)
if !ok {
@@ -518,7 +518,7 @@ func finishWebAuthnLogin(ctx *context.OGContext) error {
return ctx.JSON_([]string{"OK"})
}
-func beginWebAuthnAssertion(ctx *context.OGContext) error {
+func BeginWebAuthnAssertion(ctx *context.OGContext) error {
sess := ctx.GetSession()
ogUser, err := db.GetUserById(sess.Values["mfaID"].(uint))
@@ -538,7 +538,7 @@ func beginWebAuthnAssertion(ctx *context.OGContext) error {
return ctx.JSON_(credsCreation)
}
-func finishWebAuthnAssertion(ctx *context.OGContext) error {
+func FinishWebAuthnAssertion(ctx *context.OGContext) error {
sess := ctx.GetSession()
sessionData, ok := sess.Values["webauthn_assertion_session"].([]byte)
if !ok {
@@ -566,7 +566,7 @@ func finishWebAuthnAssertion(ctx *context.OGContext) error {
return ctx.JSON_([]string{"OK"})
}
-func beginTotp(ctx *context.OGContext) error {
+func BeginTotp(ctx *context.OGContext) error {
user := ctx.User
if _, hasTotp, err := user.HasMFA(); err != nil {
@@ -599,7 +599,7 @@ func beginTotp(ctx *context.OGContext) error {
}
-func finishTotp(ctx *context.OGContext) error {
+func FinishTotp(ctx *context.OGContext) error {
user := ctx.User
if _, hasTotp, err := user.HasMFA(); err != nil {
@@ -656,7 +656,7 @@ func finishTotp(ctx *context.OGContext) error {
return ctx.HTML_("totp.html")
}
-func assertTotp(ctx *context.OGContext) error {
+func AssertTotp(ctx *context.OGContext) error {
var err error
dto := &db.TOTPDTO{}
if err := ctx.Bind(dto); err != nil {
@@ -704,7 +704,7 @@ func assertTotp(ctx *context.OGContext) error {
return ctx.RedirectTo(redirectUrl)
}
-func disableTotp(ctx *context.OGContext) error {
+func DisableTotp(ctx *context.OGContext) error {
user := ctx.User
userTotp, err := db.GetTOTPByUserID(user.ID)
if err != nil {
@@ -719,7 +719,7 @@ func disableTotp(ctx *context.OGContext) error {
return ctx.RedirectTo("/settings")
}
-func regenerateTotpRecoveryCodes(ctx *context.OGContext) error {
+func RegenerateTotpRecoveryCodes(ctx *context.OGContext) error {
user := ctx.User
userTotp, err := db.GetTOTPByUserID(user.ID)
if err != nil {
@@ -735,7 +735,7 @@ func regenerateTotpRecoveryCodes(ctx *context.OGContext) error {
return ctx.HTML_("totp.html")
}
-func logout(ctx *context.OGContext) error {
+func Logout(ctx *context.OGContext) error {
ctx.DeleteSession()
ctx.DeleteCsrfCookie()
return ctx.RedirectTo("/all")
diff --git a/internal/web/handler/git_http.go b/internal/web/handler/git_http.go
index 278620d..ee29f8a 100644
--- a/internal/web/handler/git_http.go
+++ b/internal/web/handler/git_http.go
@@ -44,7 +44,7 @@ var routes = []struct {
{"(.*?)/objects/pack/pack-[0-9a-f]{40}\\.idx$", "GET", idxFile},
}
-func gitHttp(ctx *context.OGContext) error {
+func GitHttp(ctx *context.OGContext) error {
for _, route := range routes {
matched, _ := regexp.MatchString(route.gitUrl, ctx.Request().URL.Path)
if ctx.Request().Method == route.method && matched {
diff --git a/internal/web/handler/healthcheck.go b/internal/web/handler/healthcheck.go
index 0ae6953..31c69c0 100644
--- a/internal/web/handler/healthcheck.go
+++ b/internal/web/handler/healthcheck.go
@@ -6,7 +6,7 @@ import (
"time"
)
-func healthcheck(ctx *context.OGContext) error {
+func Healthcheck(ctx *context.OGContext) error {
// Check database connection
dbOk := "ok"
httpStatus := 200
@@ -24,8 +24,8 @@ func healthcheck(ctx *context.OGContext) error {
})
}
-// metrics is a dummy handler to satisfy the /metrics endpoint (for Prometheus, Openmetrics, etc.)
+// Metrics is a dummy handler to satisfy the /metrics endpoint (for Prometheus, Openmetrics, etc.)
// until we have a proper metrics endpoint
-func metrics(ctx *context.OGContext) error {
+func Metrics(ctx *context.OGContext) error {
return ctx.String(200, "")
}
diff --git a/internal/web/handler/settings.go b/internal/web/handler/settings.go
index ea18541..03ad6a1 100644
--- a/internal/web/handler/settings.go
+++ b/internal/web/handler/settings.go
@@ -18,7 +18,7 @@ import (
"golang.org/x/crypto/ssh"
)
-func userSettings(ctx *context.OGContext) error {
+func UserSettings(ctx *context.OGContext) error {
user := ctx.User
keys, err := db.GetSSHKeysByUserID(user.ID)
@@ -46,7 +46,7 @@ func userSettings(ctx *context.OGContext) error {
return ctx.HTML_("settings.html")
}
-func emailProcess(ctx *context.OGContext) error {
+func EmailProcess(ctx *context.OGContext) error {
user := ctx.User
email := ctx.FormValue("email")
var hash string
@@ -69,7 +69,7 @@ func emailProcess(ctx *context.OGContext) error {
return ctx.RedirectTo("/settings")
}
-func accountDeleteProcess(ctx *context.OGContext) error {
+func AccountDeleteProcess(ctx *context.OGContext) error {
user := ctx.User
if err := user.Delete(); err != nil {
@@ -79,7 +79,7 @@ func accountDeleteProcess(ctx *context.OGContext) error {
return ctx.RedirectTo("/all")
}
-func sshKeysProcess(ctx *context.OGContext) error {
+func SshKeysProcess(ctx *context.OGContext) error {
user := ctx.User
dto := new(db.SSHKeyDTO)
@@ -118,7 +118,7 @@ func sshKeysProcess(ctx *context.OGContext) error {
return ctx.RedirectTo("/settings")
}
-func sshKeysDelete(ctx *context.OGContext) error {
+func SshKeysDelete(ctx *context.OGContext) error {
user := ctx.User
keyId, err := strconv.Atoi(ctx.Param("id"))
if err != nil {
@@ -139,7 +139,7 @@ func sshKeysDelete(ctx *context.OGContext) error {
return ctx.RedirectTo("/settings")
}
-func passkeyDelete(ctx *context.OGContext) error {
+func PasskeyDelete(ctx *context.OGContext) error {
user := ctx.User
keyId, err := strconv.Atoi(ctx.Param("id"))
if err != nil {
@@ -159,7 +159,7 @@ func passkeyDelete(ctx *context.OGContext) error {
return ctx.RedirectTo("/settings")
}
-func passwordProcess(ctx *context.OGContext) error {
+func PasswordProcess(ctx *context.OGContext) error {
user := ctx.User
dto := new(db.UserDTO)
@@ -187,7 +187,7 @@ func passwordProcess(ctx *context.OGContext) error {
return ctx.RedirectTo("/settings")
}
-func usernameProcess(ctx *context.OGContext) error {
+func UsernameProcess(ctx *context.OGContext) error {
user := ctx.User
dto := new(db.UserDTO)
diff --git a/internal/web/server/middlewares.go b/internal/web/server/middlewares.go
index 4544447..802e962 100644
--- a/internal/web/server/middlewares.go
+++ b/internal/web/server/middlewares.go
@@ -10,6 +10,7 @@ import (
"github.com/thomiceli/opengist/internal/web/context"
"golang.org/x/text/cases"
"golang.org/x/text/language"
+ "html/template"
"net/http"
"strings"
"time"
@@ -24,16 +25,16 @@ func (s *Server) useCustomContext() {
})
}
-func (s *Server) RegisterMiddlewares(e *echo.Echo) {
- e.Use(Middleware(dataInit).ToEcho())
- e.Use(Middleware(locale).ToEcho())
+func (s *Server) RegisterMiddlewares() {
+ s.echo.Use(Middleware(dataInit).ToEcho())
+ s.echo.Use(Middleware(locale).ToEcho())
- e.Pre(middleware.MethodOverrideWithConfig(middleware.MethodOverrideConfig{
+ s.echo.Pre(middleware.MethodOverrideWithConfig(middleware.MethodOverrideConfig{
Getter: middleware.MethodFromForm("_method"),
}))
- e.Pre(middleware.RemoveTrailingSlash())
- e.Pre(middleware.CORS())
- e.Pre(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
+ s.echo.Pre(middleware.RemoveTrailingSlash())
+ s.echo.Pre(middleware.CORS())
+ s.echo.Pre(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
LogURI: true, LogStatus: true, LogMethod: true,
LogValuesFunc: func(ctx echo.Context, v middleware.RequestLoggerValues) error {
log.Info().Str("uri", v.URI).Int("status", v.Status).Str("method", v.Method).
@@ -42,10 +43,10 @@ func (s *Server) RegisterMiddlewares(e *echo.Echo) {
return nil
},
}))
- e.Use(middleware.Recover())
- e.Use(middleware.Secure())
+ s.echo.Use(middleware.Recover())
+ s.echo.Use(middleware.Secure())
- e.Use(Middleware(sessionInit).ToEcho())
+ s.echo.Use(Middleware(sessionInit).ToEcho())
}
func dataInit(next Handler) Handler {
@@ -155,7 +156,13 @@ func sessionInit(next Handler) Handler {
func csrfInit(next Handler) Handler {
return func(ctx *context.OGContext) error {
- setCsrfHtmlForm(ctx)
+ var csrf string
+ if csrfToken, ok := ctx.Get("csrf").(string); ok {
+ csrf = csrfToken
+ }
+ ctx.SetData("csrfHtml", template.HTML(``))
+ ctx.SetData("csrfHtml", template.HTML(``))
+
return next(ctx)
}
}
diff --git a/internal/web/server/router.go b/internal/web/server/router.go
index abb4e43..f81d7fb 100644
--- a/internal/web/server/router.go
+++ b/internal/web/server/router.go
@@ -1 +1,213 @@
package server
+
+import (
+ "github.com/labstack/echo/v4"
+ "github.com/labstack/echo/v4/middleware"
+ "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/handler"
+ "github.com/thomiceli/opengist/public"
+ "net/http"
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+ "time"
+)
+
+func (s *Server) setupRoutes() {
+ r := NewRouter(s.echo.Group(""))
+
+ // Web based routes
+ {
+ if !s.ignoreCsrf {
+ r.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
+ TokenLookup: "form:_csrf,header:X-CSRF-Token",
+ CookiePath: "/",
+ CookieHTTPOnly: true,
+ CookieSameSite: http.SameSiteStrictMode,
+ }))
+ r.Use(csrfInit)
+ }
+
+ r.GET("/", handler.Create, logged)
+ r.POST("/", handler.ProcessCreate, logged)
+ r.POST("/preview", handler.Preview, logged)
+
+ r.GET("/healthcheck", handler.Healthcheck)
+ r.GET("/metrics", handler.Metrics)
+
+ r.GET("/register", handler.Register)
+ r.POST("/register", handler.ProcessRegister)
+ r.GET("/login", handler.Login)
+ r.POST("/login", handler.ProcessLogin)
+ r.GET("/logout", handler.Logout)
+ r.GET("/oauth/:provider", handler.Oauth)
+ r.GET("/oauth/:provider/callback", handler.OauthCallback)
+ r.GET("/oauth/:provider/unlink", handler.OauthUnlink, logged)
+ r.POST("/webauthn/bind", handler.BeginWebAuthnBinding, logged)
+ r.POST("/webauthn/bind/finish", handler.FinishWebAuthnBinding, logged)
+ r.POST("/webauthn/login", handler.BeginWebAuthnLogin)
+ r.POST("/webauthn/login/finish", handler.FinishWebAuthnLogin)
+ r.POST("/webauthn/assertion", handler.BeginWebAuthnAssertion, inMFASession)
+ r.POST("/webauthn/assertion/finish", handler.FinishWebAuthnAssertion, inMFASession)
+ r.GET("/mfa", handler.Mfa, inMFASession)
+ r.POST("/mfa/totp/assertion", handler.AssertTotp, inMFASession)
+
+ r.GET("/settings", handler.UserSettings, logged)
+ r.POST("/settings/email", handler.EmailProcess, logged)
+ r.DELETE("/settings/account", handler.AccountDeleteProcess, logged)
+ r.POST("/settings/ssh-keys", handler.SshKeysProcess, logged)
+ r.DELETE("/settings/ssh-keys/:id", handler.SshKeysDelete, logged)
+ r.DELETE("/settings/passkeys/:id", handler.PasskeyDelete, logged)
+ r.PUT("/settings/password", handler.PasswordProcess, logged)
+ r.PUT("/settings/username", handler.UsernameProcess, logged)
+ r.GET("/settings/totp/generate", handler.BeginTotp, logged)
+ r.POST("/settings/totp/generate", handler.FinishTotp, logged)
+ r.DELETE("/settings/totp", handler.DisableTotp, logged)
+ r.POST("/settings/totp/regenerate", handler.RegenerateTotpRecoveryCodes, logged)
+
+ g2 := g1.Group("/admin-panel")
+ {
+ g2.Use(adminPermission)
+ g2.GET("", adminIndex)
+ g2.GET("/users", Handler(adminUsers).ToEcho())
+ g2.POST("/users/:user/delete", adminUserDelete)
+ g2.GET("/gists", adminGists)
+ g2.POST("/gists/:gist/delete", adminGistDelete)
+ g2.GET("/invitations", adminInvitations)
+ g2.POST("/invitations", adminInvitationsCreate)
+ g2.POST("/invitations/:id/delete", adminInvitationsDelete)
+ g2.POST("/sync-fs", adminSyncReposFromFS)
+ g2.POST("/sync-db", adminSyncReposFromDB)
+ g2.POST("/gc-repos", adminGcRepos)
+ g2.POST("/sync-previews", adminSyncGistPreviews)
+ g2.POST("/reset-hooks", adminResetHooks)
+ g2.POST("/index-gists", adminIndexGists)
+ g2.GET("/configuration", adminConfig)
+ g2.PUT("/set-config", adminSetConfig)
+ }
+
+ if config.C.HttpGit {
+ e.Any("/init/*", gitHttp, gistNewPushSoftInit)
+ }
+
+ g1.GET("/all", allGists, checkRequireLogin)
+
+ if index.Enabled() {
+ g1.GET("/search", search, checkRequireLogin)
+ } else {
+ g1.GET("/search", allGists, checkRequireLogin)
+ }
+
+ g1.GET("/:user", allGists, checkRequireLogin)
+ g1.GET("/:user/liked", allGists, checkRequireLogin)
+ g1.GET("/:user/forked", allGists, checkRequireLogin)
+
+ g3 := g1.Group("/:user/:gistname")
+ {
+ g3.Use(makeCheckRequireLogin(true), gistInit)
+ g3.GET("", gistIndex)
+ g3.GET("/rev/:revision", gistIndex)
+ g3.GET("/revisions", revisions)
+ g3.GET("/archive/:revision", downloadZip)
+ g3.POST("/visibility", editVisibility, logged, writePermission)
+ g3.POST("/delete", deleteGist, logged, writePermission)
+ g3.GET("/raw/:revision/:file", rawFile)
+ g3.GET("/download/:revision/:file", downloadFile)
+ g3.GET("/edit", edit, logged, writePermission)
+ g3.POST("/edit", processCreate, logged, writePermission)
+ g3.POST("/like", like, logged)
+ g3.GET("/likes", likes, checkRequireLogin)
+ g3.POST("/fork", fork, logged)
+ g3.GET("/forks", forks, checkRequireLogin)
+ g3.PUT("/checkbox", checkbox, logged, writePermission)
+ }
+ }
+
+ customFs := os.DirFS(filepath.Join(config.GetHomeDir(), "custom"))
+ e.GET("/assets/*", func(ctx echo.Context) error {
+ if _, err := public.Files.Open(path.Join("assets", ctx.Param("*"))); !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 := html(ctx, ctx.Param("*")); err != nil {
+ return 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 {
+ e.Any("/:user/:gistname/*", gitHttp, gistSoftInit)
+ }
+
+ e.Any("/*", noRouteFound)
+}
+
+// Router wraps echo.Group to provide custom Handler support
+type Router struct {
+ *echo.Group
+}
+
+// NewRouter creates a new Router instance
+func NewRouter(g *echo.Group) *Router {
+ return &Router{Group: g}
+}
+
+// SubGroup returns a new Router group with the given prefix and middleware
+func (r *Router) SubGroup(prefix string, m ...Middleware) *Router {
+ // Convert middleware only when creating group
+ 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.OGContext) error {
+ return next(c)
+ }, mw).toEchoHandler()
+ }
+ }
+ return NewRouter(r.Group.Group(prefix, echoMiddleware...))
+}
+
+// Route registration methods
+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())
+}
+
+// Use registers middleware for the entire router group
+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.OGContext) error {
+ return next(c)
+ }, m).toEchoHandler()
+ })
+ }
+}
diff --git a/internal/web/server/server.go b/internal/web/server/server.go
index 6066952..7cc6342 100644
--- a/internal/web/server/server.go
+++ b/internal/web/server/server.go
@@ -58,7 +58,7 @@ func NewServer(isDev bool, sessionsPath string, ignoreCsrf bool) *Server {
log.Fatal().Err(err).Msg("Failed to load locales")
}
- s.RegisterMiddlewares(e)
+ s.RegisterMiddlewares()
s.setFuncMap()
s.setHTTPErrorHandler()
@@ -68,140 +68,7 @@ func NewServer(isDev bool, sessionsPath string, ignoreCsrf bool) *Server {
parseManifestEntries()
}
- // Web based routes
- g1 := e.Group("")
- {
- if !ignoreCsrf {
- g1.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
- TokenLookup: "form:_csrf,header:X-CSRF-Token",
- CookiePath: "/",
- CookieHTTPOnly: true,
- CookieSameSite: http.SameSiteStrictMode,
- }))
- g1.Use(Middleware(csrfInit).ToEcho())
- }
-
- g1.GET("/", Handler(handler.Create).ToEcho(), logged)
- g1.POST("/", Handler(handler.ProcessCreate).ToEcho(), logged)
- g1.POST("/preview", preview, logged)
-
- g1.GET("/healthcheck", healthcheck)
- g1.GET("/metrics", metrics)
-
- g1.GET("/register", register)
- g1.POST("/register", processRegister)
- g1.GET("/login", login)
- g1.POST("/login", processLogin)
- g1.GET("/logout", logout)
- g1.GET("/oauth/:provider", oauth)
- g1.GET("/oauth/:provider/callback", oauthCallback)
- g1.GET("/oauth/:provider/unlink", oauthUnlink, logged)
- g1.POST("/webauthn/bind", beginWebAuthnBinding, logged)
- g1.POST("/webauthn/bind/finish", finishWebAuthnBinding, logged)
- g1.POST("/webauthn/login", beginWebAuthnLogin)
- g1.POST("/webauthn/login/finish", finishWebAuthnLogin)
- g1.POST("/webauthn/assertion", beginWebAuthnAssertion, inMFASession)
- g1.POST("/webauthn/assertion/finish", finishWebAuthnAssertion, inMFASession)
- g1.GET("/mfa", mfa, inMFASession)
- g1.POST("/mfa/totp/assertion", assertTotp, inMFASession)
-
- g1.GET("/settings", userSettings, logged)
- g1.POST("/settings/email", emailProcess, logged)
- g1.DELETE("/settings/account", accountDeleteProcess, logged)
- g1.POST("/settings/ssh-keys", sshKeysProcess, logged)
- g1.DELETE("/settings/ssh-keys/:id", sshKeysDelete, logged)
- g1.DELETE("/settings/passkeys/:id", passkeyDelete, logged)
- g1.PUT("/settings/password", passwordProcess, logged)
- g1.PUT("/settings/username", usernameProcess, logged)
- g1.GET("/settings/totp/generate", beginTotp, logged)
- g1.POST("/settings/totp/generate", finishTotp, logged)
- g1.DELETE("/settings/totp", disableTotp, logged)
- g1.POST("/settings/totp/regenerate", regenerateTotpRecoveryCodes, logged)
-
- g2 := g1.Group("/admin-panel")
- {
- g2.Use(adminPermission)
- g2.GET("", adminIndex)
- g2.GET("/users", Handler(adminUsers).ToEcho())
- g2.POST("/users/:user/delete", adminUserDelete)
- g2.GET("/gists", adminGists)
- g2.POST("/gists/:gist/delete", adminGistDelete)
- g2.GET("/invitations", adminInvitations)
- g2.POST("/invitations", adminInvitationsCreate)
- g2.POST("/invitations/:id/delete", adminInvitationsDelete)
- g2.POST("/sync-fs", adminSyncReposFromFS)
- g2.POST("/sync-db", adminSyncReposFromDB)
- g2.POST("/gc-repos", adminGcRepos)
- g2.POST("/sync-previews", adminSyncGistPreviews)
- g2.POST("/reset-hooks", adminResetHooks)
- g2.POST("/index-gists", adminIndexGists)
- g2.GET("/configuration", adminConfig)
- g2.PUT("/set-config", adminSetConfig)
- }
-
- if config.C.HttpGit {
- e.Any("/init/*", gitHttp, gistNewPushSoftInit)
- }
-
- g1.GET("/all", allGists, checkRequireLogin)
-
- if index.Enabled() {
- g1.GET("/search", search, checkRequireLogin)
- } else {
- g1.GET("/search", allGists, checkRequireLogin)
- }
-
- g1.GET("/:user", allGists, checkRequireLogin)
- g1.GET("/:user/liked", allGists, checkRequireLogin)
- g1.GET("/:user/forked", allGists, checkRequireLogin)
-
- g3 := g1.Group("/:user/:gistname")
- {
- g3.Use(makeCheckRequireLogin(true), gistInit)
- g3.GET("", gistIndex)
- g3.GET("/rev/:revision", gistIndex)
- g3.GET("/revisions", revisions)
- g3.GET("/archive/:revision", downloadZip)
- g3.POST("/visibility", editVisibility, logged, writePermission)
- g3.POST("/delete", deleteGist, logged, writePermission)
- g3.GET("/raw/:revision/:file", rawFile)
- g3.GET("/download/:revision/:file", downloadFile)
- g3.GET("/edit", edit, logged, writePermission)
- g3.POST("/edit", processCreate, logged, writePermission)
- g3.POST("/like", like, logged)
- g3.GET("/likes", likes, checkRequireLogin)
- g3.POST("/fork", fork, logged)
- g3.GET("/forks", forks, checkRequireLogin)
- g3.PUT("/checkbox", checkbox, logged, writePermission)
- }
- }
-
- customFs := os.DirFS(filepath.Join(config.GetHomeDir(), "custom"))
- e.GET("/assets/*", func(ctx echo.Context) error {
- if _, err := public.Files.Open(path.Join("assets", ctx.Param("*"))); !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 := html(ctx, ctx.Param("*")); err != nil {
- return 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 {
- e.Any("/:user/:gistname/*", gitHttp, gistSoftInit)
- }
-
- e.Any("/*", noRouteFound)
+ s.setupRoutes()
return s
}
diff --git a/internal/web/server/util.go b/internal/web/server/util.go
index e9872e5..6e2ce1f 100644
--- a/internal/web/server/util.go
+++ b/internal/web/server/util.go
@@ -3,18 +3,8 @@ package server
import (
"github.com/labstack/echo/v4"
"github.com/thomiceli/opengist/internal/web/context"
- "html/template"
)
-func setCsrfHtmlForm(ctx *context.OGContext) {
- var csrf string
- if csrfToken, ok := ctx.Get("csrf").(string); ok {
- csrf = csrfToken
- }
- ctx.SetData("csrfHtml", template.HTML(``))
- ctx.SetData("csrfHtml", template.HTML(``))
-}
-
type Handler func(ctx *context.OGContext) error
type Middleware func(next Handler) Handler
@@ -31,3 +21,22 @@ func (m Middleware) ToEcho() echo.MiddlewareFunc {
}).ToEcho()
}
}
+
+func (h Handler) toEchoHandler() echo.HandlerFunc {
+ return func(c echo.Context) error {
+ if ogc, ok := c.(*context.OGContext); ok {
+ return h(ogc)
+ }
+ // Could also add error handling for incorrect context type
+ return h(c.(*context.OGContext))
+ }
+}
+
+// Chain applies middleware to a handler without conversion to echo types
+func Chain(h Handler, middleware ...Middleware) Handler {
+ // Apply middleware in reverse order
+ for i := len(middleware) - 1; i >= 0; i-- {
+ h = middleware[i](h)
+ }
+ return h
+}