mirror of
https://github.com/thomiceli/opengist.git
synced 2024-12-23 04:52: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"
|
OpenIDConnect = "openid-connect"
|
||||||
)
|
)
|
||||||
|
|
||||||
func register(ctx *context.OGContext) error {
|
func Register(ctx *context.OGContext) error {
|
||||||
disableSignup := ctx.GetData("DisableSignup")
|
disableSignup := ctx.GetData("DisableSignup")
|
||||||
disableForm := ctx.GetData("DisableLoginForm")
|
disableForm := ctx.GetData("DisableLoginForm")
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ func register(ctx *context.OGContext) error {
|
||||||
return ctx.HTML_("auth_form.html")
|
return ctx.HTML_("auth_form.html")
|
||||||
}
|
}
|
||||||
|
|
||||||
func processRegister(ctx *context.OGContext) error {
|
func ProcessRegister(ctx *context.OGContext) error {
|
||||||
disableSignup := ctx.GetData("DisableSignup")
|
disableSignup := ctx.GetData("DisableSignup")
|
||||||
|
|
||||||
code := ctx.QueryParam("code")
|
code := ctx.QueryParam("code")
|
||||||
|
@ -127,7 +127,7 @@ func processRegister(ctx *context.OGContext) error {
|
||||||
return ctx.RedirectTo("/")
|
return ctx.RedirectTo("/")
|
||||||
}
|
}
|
||||||
|
|
||||||
func login(ctx *context.OGContext) error {
|
func Login(ctx *context.OGContext) error {
|
||||||
ctx.SetData("title", ctx.TrH("auth.login"))
|
ctx.SetData("title", ctx.TrH("auth.login"))
|
||||||
ctx.SetData("htmlTitle", ctx.TrH("auth.login"))
|
ctx.SetData("htmlTitle", ctx.TrH("auth.login"))
|
||||||
ctx.SetData("disableForm", ctx.GetData("DisableLoginForm"))
|
ctx.SetData("disableForm", ctx.GetData("DisableLoginForm"))
|
||||||
|
@ -135,7 +135,7 @@ func login(ctx *context.OGContext) error {
|
||||||
return ctx.HTML_("auth_form.html")
|
return ctx.HTML_("auth_form.html")
|
||||||
}
|
}
|
||||||
|
|
||||||
func processLogin(ctx *context.OGContext) error {
|
func ProcessLogin(ctx *context.OGContext) error {
|
||||||
if ctx.GetData("DisableLoginForm") == true {
|
if ctx.GetData("DisableLoginForm") == true {
|
||||||
return ctx.ErrorRes(403, ctx.Tr("error.login-disabled-form"), nil)
|
return ctx.ErrorRes(403, ctx.Tr("error.login-disabled-form"), nil)
|
||||||
}
|
}
|
||||||
|
@ -189,7 +189,7 @@ func processLogin(ctx *context.OGContext) error {
|
||||||
return ctx.RedirectTo("/")
|
return ctx.RedirectTo("/")
|
||||||
}
|
}
|
||||||
|
|
||||||
func mfa(ctx *context.OGContext) error {
|
func Mfa(ctx *context.OGContext) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
user := db.User{ID: ctx.GetSession().Values["mfaID"].(uint)}
|
user := db.User{ID: ctx.GetSession().Values["mfaID"].(uint)}
|
||||||
|
@ -205,7 +205,7 @@ func mfa(ctx *context.OGContext) error {
|
||||||
return ctx.HTML_("mfa.html")
|
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())
|
user, err := gothic.CompleteUserAuth(ctx.Response(), ctx.Request())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctx.ErrorRes(400, ctx.Tr("error.complete-oauth-login", err.Error()), err)
|
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("/")
|
return ctx.RedirectTo("/")
|
||||||
}
|
}
|
||||||
|
|
||||||
func oauth(ctx *context.OGContext) error {
|
func Oauth(ctx *context.OGContext) error {
|
||||||
provider := ctx.Param("provider")
|
provider := ctx.Param("provider")
|
||||||
|
|
||||||
httpProtocol := "http"
|
httpProtocol := "http"
|
||||||
|
@ -401,7 +401,7 @@ func oauth(ctx *context.OGContext) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func oauthUnlink(ctx *context.OGContext) error {
|
func OauthUnlink(ctx *context.OGContext) error {
|
||||||
provider := ctx.Param("provider")
|
provider := ctx.Param("provider")
|
||||||
|
|
||||||
currUser := ctx.User
|
currUser := ctx.User
|
||||||
|
@ -425,7 +425,7 @@ func oauthUnlink(ctx *context.OGContext) error {
|
||||||
return ctx.RedirectTo("/settings")
|
return ctx.RedirectTo("/settings")
|
||||||
}
|
}
|
||||||
|
|
||||||
func beginWebAuthnBinding(ctx *context.OGContext) error {
|
func BeginWebAuthnBinding(ctx *context.OGContext) error {
|
||||||
credsCreation, jsonWaSession, err := webauthn.BeginBinding(ctx.User)
|
credsCreation, jsonWaSession, err := webauthn.BeginBinding(ctx.User)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctx.ErrorRes(500, "Cannot begin WebAuthn registration", err)
|
return ctx.ErrorRes(500, "Cannot begin WebAuthn registration", err)
|
||||||
|
@ -439,7 +439,7 @@ func beginWebAuthnBinding(ctx *context.OGContext) error {
|
||||||
return ctx.JSON(200, credsCreation)
|
return ctx.JSON(200, credsCreation)
|
||||||
}
|
}
|
||||||
|
|
||||||
func finishWebAuthnBinding(ctx *context.OGContext) error {
|
func FinishWebAuthnBinding(ctx *context.OGContext) error {
|
||||||
sess := ctx.GetSession()
|
sess := ctx.GetSession()
|
||||||
jsonWaSession, ok := sess.Values["webauthn_registration_session"].([]byte)
|
jsonWaSession, ok := sess.Values["webauthn_registration_session"].([]byte)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -483,7 +483,7 @@ func finishWebAuthnBinding(ctx *context.OGContext) error {
|
||||||
return ctx.JSON_([]string{"OK"})
|
return ctx.JSON_([]string{"OK"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func beginWebAuthnLogin(ctx *context.OGContext) error {
|
func BeginWebAuthnLogin(ctx *context.OGContext) error {
|
||||||
credsCreation, jsonWaSession, err := webauthn.BeginDiscoverableLogin()
|
credsCreation, jsonWaSession, err := webauthn.BeginDiscoverableLogin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctx.JsonErrorRes(401, "Cannot begin WebAuthn login", err)
|
return ctx.JsonErrorRes(401, "Cannot begin WebAuthn login", err)
|
||||||
|
@ -497,7 +497,7 @@ func beginWebAuthnLogin(ctx *context.OGContext) error {
|
||||||
return ctx.JSON_(credsCreation)
|
return ctx.JSON_(credsCreation)
|
||||||
}
|
}
|
||||||
|
|
||||||
func finishWebAuthnLogin(ctx *context.OGContext) error {
|
func FinishWebAuthnLogin(ctx *context.OGContext) error {
|
||||||
sess := ctx.GetSession()
|
sess := ctx.GetSession()
|
||||||
sessionData, ok := sess.Values["webauthn_login_session"].([]byte)
|
sessionData, ok := sess.Values["webauthn_login_session"].([]byte)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -518,7 +518,7 @@ func finishWebAuthnLogin(ctx *context.OGContext) error {
|
||||||
return ctx.JSON_([]string{"OK"})
|
return ctx.JSON_([]string{"OK"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func beginWebAuthnAssertion(ctx *context.OGContext) error {
|
func BeginWebAuthnAssertion(ctx *context.OGContext) error {
|
||||||
sess := ctx.GetSession()
|
sess := ctx.GetSession()
|
||||||
|
|
||||||
ogUser, err := db.GetUserById(sess.Values["mfaID"].(uint))
|
ogUser, err := db.GetUserById(sess.Values["mfaID"].(uint))
|
||||||
|
@ -538,7 +538,7 @@ func beginWebAuthnAssertion(ctx *context.OGContext) error {
|
||||||
return ctx.JSON_(credsCreation)
|
return ctx.JSON_(credsCreation)
|
||||||
}
|
}
|
||||||
|
|
||||||
func finishWebAuthnAssertion(ctx *context.OGContext) error {
|
func FinishWebAuthnAssertion(ctx *context.OGContext) error {
|
||||||
sess := ctx.GetSession()
|
sess := ctx.GetSession()
|
||||||
sessionData, ok := sess.Values["webauthn_assertion_session"].([]byte)
|
sessionData, ok := sess.Values["webauthn_assertion_session"].([]byte)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -566,7 +566,7 @@ func finishWebAuthnAssertion(ctx *context.OGContext) error {
|
||||||
return ctx.JSON_([]string{"OK"})
|
return ctx.JSON_([]string{"OK"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func beginTotp(ctx *context.OGContext) error {
|
func BeginTotp(ctx *context.OGContext) error {
|
||||||
user := ctx.User
|
user := ctx.User
|
||||||
|
|
||||||
if _, hasTotp, err := user.HasMFA(); err != nil {
|
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
|
user := ctx.User
|
||||||
|
|
||||||
if _, hasTotp, err := user.HasMFA(); err != nil {
|
if _, hasTotp, err := user.HasMFA(); err != nil {
|
||||||
|
@ -656,7 +656,7 @@ func finishTotp(ctx *context.OGContext) error {
|
||||||
return ctx.HTML_("totp.html")
|
return ctx.HTML_("totp.html")
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertTotp(ctx *context.OGContext) error {
|
func AssertTotp(ctx *context.OGContext) error {
|
||||||
var err error
|
var err error
|
||||||
dto := &db.TOTPDTO{}
|
dto := &db.TOTPDTO{}
|
||||||
if err := ctx.Bind(dto); err != nil {
|
if err := ctx.Bind(dto); err != nil {
|
||||||
|
@ -704,7 +704,7 @@ func assertTotp(ctx *context.OGContext) error {
|
||||||
return ctx.RedirectTo(redirectUrl)
|
return ctx.RedirectTo(redirectUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
func disableTotp(ctx *context.OGContext) error {
|
func DisableTotp(ctx *context.OGContext) error {
|
||||||
user := ctx.User
|
user := ctx.User
|
||||||
userTotp, err := db.GetTOTPByUserID(user.ID)
|
userTotp, err := db.GetTOTPByUserID(user.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -719,7 +719,7 @@ func disableTotp(ctx *context.OGContext) error {
|
||||||
return ctx.RedirectTo("/settings")
|
return ctx.RedirectTo("/settings")
|
||||||
}
|
}
|
||||||
|
|
||||||
func regenerateTotpRecoveryCodes(ctx *context.OGContext) error {
|
func RegenerateTotpRecoveryCodes(ctx *context.OGContext) error {
|
||||||
user := ctx.User
|
user := ctx.User
|
||||||
userTotp, err := db.GetTOTPByUserID(user.ID)
|
userTotp, err := db.GetTOTPByUserID(user.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -735,7 +735,7 @@ func regenerateTotpRecoveryCodes(ctx *context.OGContext) error {
|
||||||
return ctx.HTML_("totp.html")
|
return ctx.HTML_("totp.html")
|
||||||
}
|
}
|
||||||
|
|
||||||
func logout(ctx *context.OGContext) error {
|
func Logout(ctx *context.OGContext) error {
|
||||||
ctx.DeleteSession()
|
ctx.DeleteSession()
|
||||||
ctx.DeleteCsrfCookie()
|
ctx.DeleteCsrfCookie()
|
||||||
return ctx.RedirectTo("/all")
|
return ctx.RedirectTo("/all")
|
||||||
|
|
|
@ -44,7 +44,7 @@ var routes = []struct {
|
||||||
{"(.*?)/objects/pack/pack-[0-9a-f]{40}\\.idx$", "GET", idxFile},
|
{"(.*?)/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 {
|
for _, route := range routes {
|
||||||
matched, _ := regexp.MatchString(route.gitUrl, ctx.Request().URL.Path)
|
matched, _ := regexp.MatchString(route.gitUrl, ctx.Request().URL.Path)
|
||||||
if ctx.Request().Method == route.method && matched {
|
if ctx.Request().Method == route.method && matched {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func healthcheck(ctx *context.OGContext) error {
|
func Healthcheck(ctx *context.OGContext) error {
|
||||||
// Check database connection
|
// Check database connection
|
||||||
dbOk := "ok"
|
dbOk := "ok"
|
||||||
httpStatus := 200
|
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
|
// until we have a proper metrics endpoint
|
||||||
func metrics(ctx *context.OGContext) error {
|
func Metrics(ctx *context.OGContext) error {
|
||||||
return ctx.String(200, "")
|
return ctx.String(200, "")
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ import (
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
func userSettings(ctx *context.OGContext) error {
|
func UserSettings(ctx *context.OGContext) error {
|
||||||
user := ctx.User
|
user := ctx.User
|
||||||
|
|
||||||
keys, err := db.GetSSHKeysByUserID(user.ID)
|
keys, err := db.GetSSHKeysByUserID(user.ID)
|
||||||
|
@ -46,7 +46,7 @@ func userSettings(ctx *context.OGContext) error {
|
||||||
return ctx.HTML_("settings.html")
|
return ctx.HTML_("settings.html")
|
||||||
}
|
}
|
||||||
|
|
||||||
func emailProcess(ctx *context.OGContext) error {
|
func EmailProcess(ctx *context.OGContext) error {
|
||||||
user := ctx.User
|
user := ctx.User
|
||||||
email := ctx.FormValue("email")
|
email := ctx.FormValue("email")
|
||||||
var hash string
|
var hash string
|
||||||
|
@ -69,7 +69,7 @@ func emailProcess(ctx *context.OGContext) error {
|
||||||
return ctx.RedirectTo("/settings")
|
return ctx.RedirectTo("/settings")
|
||||||
}
|
}
|
||||||
|
|
||||||
func accountDeleteProcess(ctx *context.OGContext) error {
|
func AccountDeleteProcess(ctx *context.OGContext) error {
|
||||||
user := ctx.User
|
user := ctx.User
|
||||||
|
|
||||||
if err := user.Delete(); err != nil {
|
if err := user.Delete(); err != nil {
|
||||||
|
@ -79,7 +79,7 @@ func accountDeleteProcess(ctx *context.OGContext) error {
|
||||||
return ctx.RedirectTo("/all")
|
return ctx.RedirectTo("/all")
|
||||||
}
|
}
|
||||||
|
|
||||||
func sshKeysProcess(ctx *context.OGContext) error {
|
func SshKeysProcess(ctx *context.OGContext) error {
|
||||||
user := ctx.User
|
user := ctx.User
|
||||||
|
|
||||||
dto := new(db.SSHKeyDTO)
|
dto := new(db.SSHKeyDTO)
|
||||||
|
@ -118,7 +118,7 @@ func sshKeysProcess(ctx *context.OGContext) error {
|
||||||
return ctx.RedirectTo("/settings")
|
return ctx.RedirectTo("/settings")
|
||||||
}
|
}
|
||||||
|
|
||||||
func sshKeysDelete(ctx *context.OGContext) error {
|
func SshKeysDelete(ctx *context.OGContext) error {
|
||||||
user := ctx.User
|
user := ctx.User
|
||||||
keyId, err := strconv.Atoi(ctx.Param("id"))
|
keyId, err := strconv.Atoi(ctx.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -139,7 +139,7 @@ func sshKeysDelete(ctx *context.OGContext) error {
|
||||||
return ctx.RedirectTo("/settings")
|
return ctx.RedirectTo("/settings")
|
||||||
}
|
}
|
||||||
|
|
||||||
func passkeyDelete(ctx *context.OGContext) error {
|
func PasskeyDelete(ctx *context.OGContext) error {
|
||||||
user := ctx.User
|
user := ctx.User
|
||||||
keyId, err := strconv.Atoi(ctx.Param("id"))
|
keyId, err := strconv.Atoi(ctx.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -159,7 +159,7 @@ func passkeyDelete(ctx *context.OGContext) error {
|
||||||
return ctx.RedirectTo("/settings")
|
return ctx.RedirectTo("/settings")
|
||||||
}
|
}
|
||||||
|
|
||||||
func passwordProcess(ctx *context.OGContext) error {
|
func PasswordProcess(ctx *context.OGContext) error {
|
||||||
user := ctx.User
|
user := ctx.User
|
||||||
|
|
||||||
dto := new(db.UserDTO)
|
dto := new(db.UserDTO)
|
||||||
|
@ -187,7 +187,7 @@ func passwordProcess(ctx *context.OGContext) error {
|
||||||
return ctx.RedirectTo("/settings")
|
return ctx.RedirectTo("/settings")
|
||||||
}
|
}
|
||||||
|
|
||||||
func usernameProcess(ctx *context.OGContext) error {
|
func UsernameProcess(ctx *context.OGContext) error {
|
||||||
user := ctx.User
|
user := ctx.User
|
||||||
|
|
||||||
dto := new(db.UserDTO)
|
dto := new(db.UserDTO)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/thomiceli/opengist/internal/web/context"
|
"github.com/thomiceli/opengist/internal/web/context"
|
||||||
"golang.org/x/text/cases"
|
"golang.org/x/text/cases"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -24,16 +25,16 @@ func (s *Server) useCustomContext() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) RegisterMiddlewares(e *echo.Echo) {
|
func (s *Server) RegisterMiddlewares() {
|
||||||
e.Use(Middleware(dataInit).ToEcho())
|
s.echo.Use(Middleware(dataInit).ToEcho())
|
||||||
e.Use(Middleware(locale).ToEcho())
|
s.echo.Use(Middleware(locale).ToEcho())
|
||||||
|
|
||||||
e.Pre(middleware.MethodOverrideWithConfig(middleware.MethodOverrideConfig{
|
s.echo.Pre(middleware.MethodOverrideWithConfig(middleware.MethodOverrideConfig{
|
||||||
Getter: middleware.MethodFromForm("_method"),
|
Getter: middleware.MethodFromForm("_method"),
|
||||||
}))
|
}))
|
||||||
e.Pre(middleware.RemoveTrailingSlash())
|
s.echo.Pre(middleware.RemoveTrailingSlash())
|
||||||
e.Pre(middleware.CORS())
|
s.echo.Pre(middleware.CORS())
|
||||||
e.Pre(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
|
s.echo.Pre(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
|
||||||
LogURI: true, LogStatus: true, LogMethod: true,
|
LogURI: true, LogStatus: true, LogMethod: true,
|
||||||
LogValuesFunc: func(ctx echo.Context, v middleware.RequestLoggerValues) error {
|
LogValuesFunc: func(ctx echo.Context, v middleware.RequestLoggerValues) error {
|
||||||
log.Info().Str("uri", v.URI).Int("status", v.Status).Str("method", v.Method).
|
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
|
return nil
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
e.Use(middleware.Recover())
|
s.echo.Use(middleware.Recover())
|
||||||
e.Use(middleware.Secure())
|
s.echo.Use(middleware.Secure())
|
||||||
|
|
||||||
e.Use(Middleware(sessionInit).ToEcho())
|
s.echo.Use(Middleware(sessionInit).ToEcho())
|
||||||
}
|
}
|
||||||
|
|
||||||
func dataInit(next Handler) Handler {
|
func dataInit(next Handler) Handler {
|
||||||
|
@ -155,7 +156,13 @@ func sessionInit(next Handler) Handler {
|
||||||
|
|
||||||
func csrfInit(next Handler) Handler {
|
func csrfInit(next Handler) Handler {
|
||||||
return func(ctx *context.OGContext) error {
|
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)
|
return next(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,213 @@
|
||||||
package server
|
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")
|
log.Fatal().Err(err).Msg("Failed to load locales")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.RegisterMiddlewares(e)
|
s.RegisterMiddlewares()
|
||||||
s.setFuncMap()
|
s.setFuncMap()
|
||||||
s.setHTTPErrorHandler()
|
s.setHTTPErrorHandler()
|
||||||
|
|
||||||
|
@ -68,140 +68,7 @@ func NewServer(isDev bool, sessionsPath string, ignoreCsrf bool) *Server {
|
||||||
parseManifestEntries()
|
parseManifestEntries()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Web based routes
|
s.setupRoutes()
|
||||||
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)
|
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,18 +3,8 @@ package server
|
||||||
import (
|
import (
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/thomiceli/opengist/internal/web/context"
|
"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 Handler func(ctx *context.OGContext) error
|
||||||
type Middleware func(next Handler) Handler
|
type Middleware func(next Handler) Handler
|
||||||
|
|
||||||
|
@ -31,3 +21,22 @@ func (m Middleware) ToEcho() echo.MiddlewareFunc {
|
||||||
}).ToEcho()
|
}).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