init from gitlab
This commit is contained in:
75
internal/server/middleware/auth.go
Normal file
75
internal/server/middleware/auth.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/labstack/echo/v4"
|
||||
echoMiddleware "github.com/labstack/echo/v4/middleware"
|
||||
"gitlab.com/texm/shokku/internal/env"
|
||||
"gitlab.com/texm/shokku/internal/server/auth"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
tokenContextKey = "user-token"
|
||||
usedCookieAuthContextKey = "cookie-auth"
|
||||
)
|
||||
|
||||
func skipDuringSetup(e *env.Env, c echo.Context) bool {
|
||||
reqPath := c.Request().URL.Path
|
||||
return !e.SetupCompleted && strings.HasPrefix(reqPath, "/api/setup")
|
||||
}
|
||||
|
||||
func ProvideUserContext(e *env.Env) echo.MiddlewareFunc {
|
||||
logger := middlewareLogger("userContext")
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if skipDuringSetup(e, c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
if err := e.Auth.SetUserContext(c, tokenContextKey); err != nil {
|
||||
logger.Error().Err(err).Msg("failed to set context")
|
||||
return echo.ErrInternalServerError
|
||||
}
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tokenAuthSkipper(e *env.Env) echoMiddleware.Skipper {
|
||||
return func(c echo.Context) bool {
|
||||
return skipDuringSetup(e, c)
|
||||
}
|
||||
}
|
||||
|
||||
func TokenAuth(e *env.Env) echo.MiddlewareFunc {
|
||||
config := echoMiddleware.JWTConfig{
|
||||
Claims: &auth.User{},
|
||||
SigningKey: e.Auth.GetSigningKey(),
|
||||
TokenLookupFuncs: []echoMiddleware.ValuesExtractor{SplitTokenLookup},
|
||||
TokenLookup: "header:Authorization",
|
||||
ContextKey: tokenContextKey,
|
||||
Skipper: tokenAuthSkipper(e),
|
||||
}
|
||||
return echoMiddleware.JWTWithConfig(config)
|
||||
}
|
||||
|
||||
func SplitTokenLookup(c echo.Context) ([]string, error) {
|
||||
dataCookie, err := c.Request().Cookie(auth.DataCookieName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("no data cookie: %w", err)
|
||||
}
|
||||
signatureCookie, err := c.Request().Cookie(auth.SignatureCookieName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("no signature cookie: %w", err)
|
||||
}
|
||||
|
||||
c.Set(usedCookieAuthContextKey, true)
|
||||
authToken := dataCookie.Value + "." + signatureCookie.Value
|
||||
return []string{authToken}, nil
|
||||
}
|
||||
|
||||
func CheckCookieAuthUsed(c echo.Context) bool {
|
||||
v, ok := c.Get(usedCookieAuthContextKey).(bool)
|
||||
return ok && v
|
||||
}
|
||||
10
internal/server/middleware/middleware.go
Normal file
10
internal/server/middleware/middleware.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func middlewareLogger(ware string) zerolog.Logger {
|
||||
return log.With().Str("middleware", ware).Logger()
|
||||
}
|
||||
31
internal/server/middleware/recover.go
Normal file
31
internal/server/middleware/recover.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
echoMiddleware "github.com/labstack/echo/v4/middleware"
|
||||
"github.com/rs/zerolog"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func logErrorFunc(logger zerolog.Logger, debug bool) echoMiddleware.LogErrorFunc {
|
||||
return func(c echo.Context, err error, stack []byte) error {
|
||||
event := logger.Error().Err(err)
|
||||
if debug {
|
||||
stacklines := strings.Split(string(stack), "\n")
|
||||
funcName := strings.TrimRight(strings.SplitAfter(stacklines[5], "(0x")[0], "(0x")
|
||||
callSite := strings.Trim(strings.SplitAfter(stacklines[6], " ")[0], "\t ")
|
||||
event.Str("func", funcName)
|
||||
event.Str("callsite", callSite)
|
||||
}
|
||||
event.Msg("Recovered from panic")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func Recover(debug bool) echo.MiddlewareFunc {
|
||||
logger := middlewareLogger("recover")
|
||||
cfg := echoMiddleware.RecoverConfig{
|
||||
LogErrorFunc: logErrorFunc(logger, debug),
|
||||
}
|
||||
return echoMiddleware.RecoverWithConfig(cfg)
|
||||
}
|
||||
87
internal/server/middleware/request_logger.go
Normal file
87
internal/server/middleware/request_logger.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitlab.com/texm/shokku/internal/server/dto"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func parseDokkuError(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
err := next(c)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if dokkuHttpErr := dto.MaybeConvertDokkuError(err); dokkuHttpErr != nil {
|
||||
log.Debug().Err(err).Msgf("converted dokku error to %s", dokkuHttpErr.Error())
|
||||
return dokkuHttpErr
|
||||
}
|
||||
|
||||
log.Error().Err(err).Str("path", c.Path()).Msg("got error")
|
||||
return echo.ErrInternalServerError
|
||||
}
|
||||
}
|
||||
|
||||
func RequestLogger(debug bool) echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
start := time.Now()
|
||||
|
||||
chainErr := next(c)
|
||||
if chainErr != nil {
|
||||
c.Error(chainErr)
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
res := c.Response()
|
||||
|
||||
if isStaticFile, ok := c.Get("static").(bool); ok && isStaticFile {
|
||||
contentType := res.Header().Get("Content-Type")
|
||||
isHTML := strings.HasPrefix(contentType, echo.MIMETextHTML)
|
||||
if !isHTML {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
n := res.Status
|
||||
l := log.Info()
|
||||
msg := "Success"
|
||||
switch {
|
||||
case n >= 500:
|
||||
l = log.Error().Err(chainErr)
|
||||
// logger.With(zap.Error(chainErr)).Error("Server error", fields...)
|
||||
msg = "Server error"
|
||||
case n >= 400:
|
||||
msg = "Client error"
|
||||
case n >= 300:
|
||||
msg = "Redirect"
|
||||
}
|
||||
|
||||
if debug {
|
||||
l.Str("request", fmt.Sprintf("%s %s", req.Method, req.RequestURI))
|
||||
l.Int("status", res.Status)
|
||||
} else {
|
||||
l.Str("remote_ip", c.RealIP())
|
||||
l.Str("latency", time.Since(start).String())
|
||||
l.Str("host", req.Host)
|
||||
l.Str("request", fmt.Sprintf("%s %s", req.Method, req.RequestURI))
|
||||
l.Int("status", res.Status)
|
||||
l.Int64("size", res.Size)
|
||||
}
|
||||
|
||||
id := req.Header.Get(echo.HeaderXRequestID)
|
||||
if id != "" {
|
||||
l.Str("request_id", id)
|
||||
}
|
||||
|
||||
l.Msg(msg)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
30
internal/server/middleware/security.go
Normal file
30
internal/server/middleware/security.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
echoMiddleware "github.com/labstack/echo/v4/middleware"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Secure() echo.MiddlewareFunc {
|
||||
// logger := e.Logger.Desugar()
|
||||
// debug := e.DebugMode
|
||||
|
||||
// cfg := echomiddleware.SecureConfig{}
|
||||
cfg := echoMiddleware.DefaultSecureConfig
|
||||
return echoMiddleware.SecureWithConfig(cfg)
|
||||
}
|
||||
|
||||
func CSRF() echo.MiddlewareFunc {
|
||||
// we skip requests where cookie authentication was not used,
|
||||
// as these are api requests - not from the browser
|
||||
cfg := echoMiddleware.CSRFConfig{
|
||||
CookieName: "_csrf",
|
||||
CookiePath: "/",
|
||||
CookieSameSite: http.SameSiteStrictMode,
|
||||
Skipper: func(c echo.Context) bool {
|
||||
return !CheckCookieAuthUsed(c)
|
||||
},
|
||||
}
|
||||
return echoMiddleware.CSRFWithConfig(cfg)
|
||||
}
|
||||
59
internal/server/middleware/setup.go
Normal file
59
internal/server/middleware/setup.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"gitlab.com/texm/shokku/internal/env"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
notSetupErr = "server not setup"
|
||||
invalidKeyErr = "setup key invalid"
|
||||
setupKeyHeader = "X-Setup-Key"
|
||||
)
|
||||
|
||||
func ServerSetupBlocker(e *env.Env, setupKey string) echo.MiddlewareFunc {
|
||||
logger := middlewareLogger("setup")
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
reqPath := c.Request().URL.Path
|
||||
|
||||
if !strings.HasPrefix(reqPath, "/api") {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
if reqPath == "/api/github/events" {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
if reqPath == "/api/setup/status" {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
isSetupRoute := strings.HasPrefix(reqPath, "/api/setup")
|
||||
if e.SetupCompleted {
|
||||
if isSetupRoute {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "already set up")
|
||||
}
|
||||
return next(c)
|
||||
}
|
||||
|
||||
providedKey := c.Request().Header.Get(setupKeyHeader)
|
||||
if providedKey != setupKey {
|
||||
logger.Debug().
|
||||
Str("path", reqPath).
|
||||
Str("provided", providedKey).
|
||||
Msg("invalid setup key")
|
||||
return echo.NewHTTPError(http.StatusForbidden, invalidKeyErr)
|
||||
}
|
||||
|
||||
if isSetupRoute {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
logger.Debug().Str("path", reqPath).Msg("not setup path")
|
||||
return echo.NewHTTPError(http.StatusForbidden, notSetupErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
29
internal/server/middleware/static.go
Normal file
29
internal/server/middleware/static.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
echomiddleware "github.com/labstack/echo/v4/middleware"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func staticFileSkipperFunc(c echo.Context) bool {
|
||||
if strings.HasPrefix(c.Request().URL.Path, "/api") {
|
||||
c.Set("static", false)
|
||||
return true
|
||||
}
|
||||
c.Set("static", true)
|
||||
return false
|
||||
}
|
||||
|
||||
func StaticFiles(staticFS fs.FS) echo.MiddlewareFunc {
|
||||
cfg := echomiddleware.StaticConfig{
|
||||
Root: "dist",
|
||||
Index: "app.html",
|
||||
HTML5: true,
|
||||
Filesystem: http.FS(staticFS),
|
||||
Skipper: staticFileSkipperFunc,
|
||||
}
|
||||
return echomiddleware.StaticWithConfig(cfg)
|
||||
}
|
||||
Reference in New Issue
Block a user