mirror of
https://github.com/thomiceli/opengist.git
synced 2024-12-22 12:32:40 +00:00
wip
This commit is contained in:
parent
60027e4ab8
commit
d2f6fe1ab8
8 changed files with 283 additions and 188 deletions
|
@ -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")
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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, "")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(`<input type="hidden" name="_csrf" value="`+csrf+`">`))
|
||||
ctx.SetData("csrfHtml", template.HTML(`<input type="hidden" name="_csrf" value="`+csrf+`">`))
|
||||
|
||||
return next(ctx)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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(`<input type="hidden" name="_csrf" value="`+csrf+`">`))
|
||||
ctx.SetData("csrfHtml", template.HTML(`<input type="hidden" name="_csrf" value="`+csrf+`">`))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue