dkforest

A forum and chat platform (onion)
git clone https://git.dasho.dev/n0tr1v/dkforest.git
Log | Files | Refs | LICENSE

middlewares.go (18369B)


      1 package middlewares
      2 
      3 import (
      4 	"dkforest/bindata"
      5 	"dkforest/pkg/cache"
      6 	"dkforest/pkg/captcha"
      7 	"dkforest/pkg/config"
      8 	"dkforest/pkg/database"
      9 	"dkforest/pkg/utils"
     10 	"dkforest/pkg/web/clientFrontends"
     11 	"dkforest/pkg/web/handlers"
     12 	hutils "dkforest/pkg/web/handlers/utils"
     13 	"github.com/labstack/echo"
     14 	"github.com/labstack/echo/middleware"
     15 	"github.com/nicksnyder/go-i18n/v2/i18n"
     16 	"github.com/ulule/limiter"
     17 	"github.com/ulule/limiter/drivers/store/memory"
     18 	"net"
     19 	"net/http"
     20 	"strings"
     21 	"time"
     22 )
     23 
     24 // GzipMiddleware ...
     25 func GzipMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
     26 	return func(c echo.Context) error {
     27 		authUser := c.Get("authUser").(*database.User)
     28 		cfg := middleware.GzipConfig{
     29 			Level: 5,
     30 			Skipper: func(c echo.Context) bool {
     31 				if c.Path() == "/bhcli/downloads/:filename" ||
     32 					c.Path() == "/vip/downloads/:filename" ||
     33 					c.Path() == "/vip/challenges/re-1/:filename" ||
     34 					c.Path() == "/chess/:key" ||
     35 					c.Path() == "/chess/:key/analyze" ||
     36 					c.Path() == "/poker/:roomID/stream" ||
     37 					c.Path() == "/poker/:roomID/logs" ||
     38 					c.Path() == "/poker/:roomID/bet" ||
     39 					c.Path() == "/api/v1/chat/messages/:roomName/stream" ||
     40 					c.Path() == "/api/v1/chat/messages/:roomName/stream/menu" ||
     41 					(c.Path() == "/api/v1/chat/top-bar/:roomName" && authUser != nil && authUser.UseStreamTopBar) ||
     42 					c.Path() == "/uploads/:filename" ||
     43 					c.Path() == "/" {
     44 					return true
     45 				}
     46 				return false
     47 			},
     48 		}
     49 		return middleware.GzipWithConfig(cfg)(next)(c)
     50 	}
     51 }
     52 
     53 // BodyLimit ...
     54 var BodyLimit = middleware.BodyLimitWithConfig(middleware.BodyLimitConfig{
     55 	Limit: "1M",
     56 	Skipper: func(c echo.Context) bool {
     57 		if c.Path() == "/api/v1/chat/top-bar/:roomName" {
     58 			return true
     59 		}
     60 		return false
     61 	},
     62 })
     63 
     64 // CaptchaMiddleware ...
     65 func CaptchaMiddleware() echo.MiddlewareFunc {
     66 	return func(next echo.HandlerFunc) echo.HandlerFunc {
     67 		return func(c echo.Context) error {
     68 			var data captchaMiddlewareData
     69 			data.CaptchaDescription = "Captcha required"
     70 			data.CaptchaID, data.CaptchaImg = captcha.New()
     71 			const captchaRequiredTmpl = "captcha-required"
     72 			if c.Request().Method == http.MethodPost {
     73 				captchaID := c.Request().PostFormValue("captcha_id")
     74 				captchaInput := c.Request().PostFormValue("captcha")
     75 				if err := hutils.CaptchaVerifyString(c, captchaID, captchaInput); err != nil {
     76 					data.ErrCaptcha = err.Error()
     77 					return c.Render(http.StatusOK, captchaRequiredTmpl, data)
     78 				}
     79 				return next(c)
     80 			}
     81 			return c.Render(http.StatusOK, captchaRequiredTmpl, data)
     82 		}
     83 	}
     84 }
     85 
     86 // GenericRateLimitMiddleware rate limit on userID if authenticated, or circuitID otherwise
     87 // This rate limiter should be used for endpoints that are accessible by both unauthenticated and authenticated users.
     88 func GenericRateLimitMiddleware(period time.Duration, limit int64) echo.MiddlewareFunc {
     89 	rate := limiter.Rate{Period: period, Limit: limit}
     90 	store := memory.NewStore()
     91 	limiterInstance := limiter.New(store, rate)
     92 
     93 	return func(next echo.HandlerFunc) echo.HandlerFunc {
     94 		return func(c echo.Context) error {
     95 			key := "ip_" + c.RealIP()
     96 			if authUser, ok := c.Get("authUser").(*database.User); ok && authUser != nil {
     97 				key = "userid_" + authUser.ID.String()
     98 			} else if conn, ok := c.Request().Context().Value("conn").(net.Conn); ok {
     99 				circuitID := config.ConnMap.Get(conn)
    100 				key = "circuitid_" + utils.FormatInt64(circuitID)
    101 			}
    102 			context, err := limiterInstance.Get(c.Request().Context(), key)
    103 			if err != nil {
    104 				return next(c)
    105 			}
    106 			c.Response().Header().Add("X-RateLimit-Limit", utils.FormatInt64(context.Limit))
    107 			c.Response().Header().Add("X-RateLimit-Remaining", utils.FormatInt64(context.Remaining))
    108 			c.Response().Header().Add("X-RateLimit-Reset", utils.FormatInt64(context.Reset))
    109 			if context.Reached {
    110 				return c.Render(http.StatusTooManyRequests, "flash", handlers.FlashResponse{Message: "Rate limit exceeded", Redirect: c.Request().URL.String(), Type: "alert-warning"})
    111 			}
    112 			return next(c)
    113 		}
    114 	}
    115 }
    116 
    117 func CircuitRateLimitMiddleware(period time.Duration, limit int64, kill bool) echo.MiddlewareFunc {
    118 	rate := limiter.Rate{Period: period, Limit: limit}
    119 	store := memory.NewStore()
    120 	limiterInstance := limiter.New(store, rate)
    121 
    122 	return func(next echo.HandlerFunc) echo.HandlerFunc {
    123 		return func(c echo.Context) error {
    124 
    125 			if conn, ok := c.Request().Context().Value("conn").(net.Conn); ok {
    126 				circuitID := config.ConnMap.Get(conn)
    127 
    128 				context, err := limiterInstance.Get(c.Request().Context(), utils.FormatInt64(circuitID))
    129 				if err != nil {
    130 					return next(c)
    131 				}
    132 				c.Response().Header().Add("X-RateLimit-Limit", utils.FormatInt64(context.Limit))
    133 				c.Response().Header().Add("X-RateLimit-Remaining", utils.FormatInt64(context.Remaining))
    134 				c.Response().Header().Add("X-RateLimit-Reset", utils.FormatInt64(context.Reset))
    135 				if context.Reached {
    136 					if kill {
    137 						config.ConnMap.CloseCircuit(circuitID)
    138 						return c.NoContent(http.StatusOK)
    139 					}
    140 					return c.Render(http.StatusTooManyRequests, "flash", handlers.FlashResponse{Message: "Rate limit exceeded", Redirect: c.Request().URL.String(), Type: "alert-warning"})
    141 				}
    142 			}
    143 			return next(c)
    144 		}
    145 	}
    146 }
    147 
    148 // AuthRateLimitMiddleware ...
    149 func AuthRateLimitMiddleware(period time.Duration, limit int64) echo.MiddlewareFunc {
    150 	rate := limiter.Rate{Period: period, Limit: limit}
    151 	store := memory.NewStore()
    152 	limiterInstance := limiter.New(store, rate)
    153 
    154 	return func(next echo.HandlerFunc) echo.HandlerFunc {
    155 		return func(c echo.Context) error {
    156 			authUser := c.Get("authUser").(*database.User)
    157 			context, err := limiterInstance.Get(c.Request().Context(), utils.FormatInt64(int64(authUser.ID)))
    158 			if err != nil {
    159 				// fmt.Errorf("could not get context for IP %s - %v", c.RealIP(), err)
    160 				return next(c)
    161 			}
    162 			c.Response().Header().Add("X-RateLimit-Limit", utils.FormatInt64(context.Limit))
    163 			c.Response().Header().Add("X-RateLimit-Remaining", utils.FormatInt64(context.Remaining))
    164 			c.Response().Header().Add("X-RateLimit-Reset", utils.FormatInt64(context.Reset))
    165 			if context.Reached {
    166 				return c.Render(http.StatusTooManyRequests, "flash", handlers.FlashResponse{Message: "Rate limit exceeded", Redirect: c.Request().URL.String(), Type: "alert-warning"})
    167 				//return c.JSON(429, map[string]string{"message": fmt.Sprintf("Rate limit exceeded for %s", authUser.Username)})
    168 			}
    169 			return next(c)
    170 		}
    171 	}
    172 }
    173 
    174 // CSRFMiddleware ...
    175 func CSRFMiddleware() echo.MiddlewareFunc {
    176 	csrfConfig := CSRFConfig{
    177 		TokenLookup:    "form:csrf",
    178 		CookieDomain:   config.Global.CookieDomain.Get(),
    179 		CookiePath:     "/",
    180 		CookieHTTPOnly: true,
    181 		CookieSecure:   config.Global.CookieSecure.Get(),
    182 		CookieMaxAge:   utils.OneMonthSecs,
    183 		SameSite:       http.SameSiteLaxMode,
    184 		Skipper: func(c echo.Context) bool {
    185 			apiKey := c.Request().Header.Get("DKF_API_KEY")
    186 			return (apiKey != "" && strings.HasPrefix(c.Path(), "/api/v1/")) ||
    187 				c.Path() == "/api/v1/battleship" ||
    188 				c.Path() == "/api/v1/werewolf" ||
    189 				c.Path() == "/chess/:key" ||
    190 				c.Path() == "/poker/:roomID/sit/:pos" ||
    191 				c.Path() == "/poker/:roomID/unsit" ||
    192 				c.Path() == "/poker/:roomID/bet" ||
    193 				c.Path() == "/poker/:roomID/logs" ||
    194 				c.Path() == "/poker/:roomID/deal"
    195 		},
    196 	}
    197 	return CSRFWithConfig(csrfConfig)
    198 }
    199 
    200 // I18nMiddleware ...
    201 func I18nMiddleware(bundle *i18n.Bundle, defaultLang string) echo.MiddlewareFunc {
    202 	return func(next echo.HandlerFunc) echo.HandlerFunc {
    203 		return func(c echo.Context) error {
    204 			if strings.HasPrefix(c.Path(), "/sse/") {
    205 				return next(c)
    206 			}
    207 			accept := c.Request().Header.Get("Accept-Language")
    208 
    209 			// This is how the language is chosen:
    210 			// - User preference (if set)
    211 			// - App lang flag (if set)
    212 			// - Browser accept-language header
    213 			// - Default en
    214 
    215 			lang := ""
    216 			user, _ := c.Get("authUser").(*database.User)
    217 			if user != nil && user.Lang != "" {
    218 				lang = user.Lang
    219 			} else if defaultLang != "" {
    220 				lang = defaultLang
    221 			}
    222 			c.Set("lang", lang)
    223 			c.Set("accept-language", accept)
    224 			c.Set("bundle", bundle)
    225 			return next(c)
    226 		}
    227 	}
    228 }
    229 
    230 func SetDatabaseMiddleware(db *database.DkfDB) echo.MiddlewareFunc {
    231 	return func(next echo.HandlerFunc) echo.HandlerFunc {
    232 		return func(ctx echo.Context) error {
    233 			ctx.Set("database", db)
    234 			return next(ctx)
    235 		}
    236 	}
    237 }
    238 
    239 func SetClientFEMiddleware(clientFE clientFrontends.ClientFrontend) echo.MiddlewareFunc {
    240 	return func(next echo.HandlerFunc) echo.HandlerFunc {
    241 		return func(ctx echo.Context) error {
    242 			ctx.Set("clientFE", clientFE)
    243 			return next(ctx)
    244 		}
    245 	}
    246 }
    247 
    248 // SetUserMiddleware Get user and put it into echo context.
    249 // - Get auth-token from cookie
    250 // - If exists, get user from database
    251 // - If found, set user in echo context
    252 // - Otherwise, empty user will be put in context
    253 func SetUserMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    254 	return func(ctx echo.Context) error {
    255 		db := ctx.Get("database").(*database.DkfDB)
    256 		var nilUser *database.User
    257 		var user database.User
    258 
    259 		if apiKey := ctx.Request().Header.Get("DKF_API_KEY"); apiKey != "" {
    260 			// Login using DKF_API_KEY
    261 			if err := db.GetUserByApiKey(&user, apiKey); err == nil {
    262 				ctx.Set("authUser", &user)
    263 				return next(ctx)
    264 			}
    265 		} else if authCookie, err := ctx.Cookie(hutils.AuthCookieName); err == nil {
    266 			// Login using auth cookie
    267 			if err := db.GetUserBySessionKey(&user, authCookie.Value); err == nil {
    268 				ctx.Set("authUser", &user)
    269 				return next(ctx)
    270 			} else {
    271 			}
    272 		}
    273 
    274 		ctx.Set("authUser", nilUser)
    275 		return next(ctx)
    276 	}
    277 }
    278 
    279 type RateLimit[K comparable, V any] struct {
    280 	cache *cache.Cache[K, V]
    281 	value V
    282 }
    283 
    284 func NewRateLimit[K comparable](defaultExpiration time.Duration) *RateLimit[K, struct{}] {
    285 	return NewRateLimitV[K, struct{}](defaultExpiration)
    286 }
    287 
    288 func NewRateLimitV[K comparable, V any](defaultExpiration time.Duration) *RateLimit[K, V] {
    289 	return &RateLimit[K, V]{
    290 		cache: cache.NewWithKey[K, V](defaultExpiration, time.Minute),
    291 	}
    292 }
    293 
    294 func (l *RateLimit[K, V]) RateLimit(k K, clb func()) {
    295 	if !l.cache.Has(k) {
    296 		clb()
    297 		l.cache.SetD(k, l.value)
    298 	}
    299 }
    300 
    301 func (l *RateLimit[K, V]) RateLimitV(k K, clb func() (V, error)) (V, bool, error) {
    302 	var err error
    303 	if !l.cache.Has(k) {
    304 		l.value, err = clb()
    305 		l.cache.SetD(k, l.value)
    306 		return l.value, true, err
    307 	}
    308 	return l.value, false, err
    309 }
    310 
    311 var lastSeenRL = NewRateLimit[database.UserID](time.Second)
    312 
    313 // IsAuthMiddleware will ensure user is authenticated.
    314 // - Find user from context
    315 // - If user is empty, redirect to home
    316 func IsAuthMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    317 	return func(c echo.Context) error {
    318 		user := c.Get("authUser").(*database.User)
    319 		db := c.Get("database").(*database.DkfDB)
    320 		if user == nil {
    321 			if strings.HasPrefix(c.Path(), "/api/") {
    322 				return c.String(http.StatusUnauthorized, "unauthorized")
    323 			}
    324 			referralToken := c.QueryParam("r")
    325 			if strings.HasPrefix(c.Path(), "/poker") && referralToken != "" {
    326 				if len(referralToken) == 9 {
    327 					hutils.CreatePokerReferralCookie(c, referralToken)
    328 				}
    329 			}
    330 			return c.Redirect(http.StatusFound, "/?redirect="+c.Request().URL.String())
    331 		}
    332 
    333 		c.Response().Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
    334 
    335 		lastSeenRL.RateLimit(user.ID, func() {
    336 			now := time.Now()
    337 			db.DB().Exec("UPDATE users SET last_seen_at = ?, updated_at = ? WHERE id = ?", now, now, int64(user.ID))
    338 		})
    339 
    340 		// Prevent clickjacking by setting the header on every logged in page
    341 		if !strings.Contains(c.Path(), "/chess/:key/form") &&
    342 			!strings.Contains(c.Path(), "/chess/:key/stats") &&
    343 			!strings.Contains(c.Path(), "/api/v1/chat/messages") &&
    344 			!strings.Contains(c.Path(), "/api/v1/chat/messages/:roomName/stream") &&
    345 			!strings.Contains(c.Path(), "/api/v1/chat/messages/:roomName/stream/menu") &&
    346 			!strings.Contains(c.Path(), "/api/v1/chat/top-bar") &&
    347 			!strings.Contains(c.Path(), "/api/v1/chat/controls") &&
    348 			!strings.Contains(c.Path(), "/poker/:roomID/stream") &&
    349 			!strings.Contains(c.Path(), "/poker/:roomID/sit/:pos") &&
    350 			!strings.Contains(c.Path(), "/poker/:roomID/unsit") &&
    351 			!strings.Contains(c.Path(), "/poker/:roomID/bet") &&
    352 			!strings.Contains(c.Path(), "/poker/:roomID/logs") &&
    353 			!strings.Contains(c.Path(), "/poker/:roomID/deal") {
    354 			c.Response().Header().Set("X-Frame-Options", "DENY")
    355 		}
    356 
    357 		return next(c)
    358 	}
    359 }
    360 
    361 func ForceCaptchaMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    362 	return func(c echo.Context) error {
    363 		user := c.Get("authUser").(*database.User)
    364 		if user.CaptchaRequired && c.Path() != "/captcha-required" {
    365 			return c.Redirect(http.StatusFound, "/captcha-required")
    366 		}
    367 		return next(c)
    368 	}
    369 }
    370 
    371 // HellbannedCookieMiddleware if a user is HB and doesn't have the cookie, creates it.
    372 // We use this cookie to auto HB new account created by this person.
    373 func HellbannedCookieMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    374 	return func(c echo.Context) error {
    375 		user := c.Get("authUser").(*database.User)
    376 		if user != nil && user.IsHellbanned {
    377 			if _, err := c.Cookie(hutils.HBCookieName); err != nil {
    378 				cookie := hutils.CreateCookie(hutils.HBCookieName, utils.GenerateToken3(), utils.OneMonthSecs)
    379 				c.SetCookie(cookie)
    380 			}
    381 		}
    382 		return next(c)
    383 	}
    384 }
    385 
    386 // ClubMiddleware ...
    387 func ClubMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    388 	return func(c echo.Context) error {
    389 		user := c.Get("authUser").(*database.User)
    390 		if user != nil && (!user.IsAdmin && !user.IsClubMember) {
    391 			var data unauthorizedData
    392 			data.Message = `To access this section, you need an official invitation from the team.`
    393 			return c.Render(http.StatusOK, "unauthorized", data)
    394 		}
    395 		return next(c)
    396 	}
    397 }
    398 
    399 // VipMiddleware ...
    400 func VipMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    401 	return func(c echo.Context) error {
    402 		user := c.Get("authUser").(*database.User)
    403 		if user != nil && user.GPGPublicKey == "" {
    404 			var data unauthorizedData
    405 			data.Message = `To access this section, you need to have a valid PGP public key linked to your profile.<br />
    406 <a href="/settings/pgp">Add your PGP public key to your profile here</a>`
    407 			return c.Render(http.StatusOK, "unauthorized", data)
    408 		}
    409 		return next(c)
    410 	}
    411 }
    412 
    413 // IsModeratorMiddleware only moderators can access these routes.
    414 func IsModeratorMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    415 	return func(c echo.Context) error {
    416 		user := c.Get("authUser").(*database.User)
    417 		if user == nil || !user.IsModerator() {
    418 			if strings.HasPrefix(c.Path(), "/api") {
    419 				if user == nil {
    420 					return c.NoContent(http.StatusUnauthorized)
    421 				} else if !user.IsModerator() {
    422 					return c.NoContent(http.StatusForbidden)
    423 				}
    424 				return c.NoContent(http.StatusInternalServerError)
    425 			}
    426 			return c.Redirect(http.StatusFound, "/")
    427 		}
    428 		return next(c)
    429 	}
    430 }
    431 
    432 // IsAdminMiddleware only administrators can access these routes.
    433 func IsAdminMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    434 	return func(c echo.Context) error {
    435 		user := c.Get("authUser").(*database.User)
    436 		if user == nil || !user.IsAdmin {
    437 			if strings.HasPrefix(c.Path(), "/api") {
    438 				if user == nil {
    439 					return c.NoContent(http.StatusUnauthorized)
    440 				} else if !user.IsAdmin {
    441 					return c.NoContent(http.StatusForbidden)
    442 				}
    443 				return c.NoContent(http.StatusInternalServerError)
    444 			}
    445 			return c.Redirect(http.StatusFound, "/")
    446 		}
    447 		return next(c)
    448 	}
    449 }
    450 
    451 func AprilFoolMiddleware() echo.MiddlewareFunc {
    452 	return func(next echo.HandlerFunc) echo.HandlerFunc {
    453 		return func(c echo.Context) error {
    454 			if strings.HasPrefix(c.Path(), "/api/v1/") {
    455 				return next(c)
    456 			}
    457 
    458 			year, month, day := time.Now().UTC().Date()
    459 			if year == 2022 && month == time.April && day == 1 {
    460 				vv := hutils.GetAprilFoolCookie(c)
    461 				if vv < 3 {
    462 					hutils.CreateAprilFoolCookie(c, vv+1)
    463 					return c.Render(http.StatusOK, "seized", nil)
    464 				}
    465 			}
    466 			return next(c)
    467 		}
    468 	}
    469 }
    470 
    471 func DdosMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    472 	stopFn := func(c echo.Context) error {
    473 		hutils.KillCircuit(c)
    474 		config.RejectedReqCounter.Incr()
    475 		time.Sleep(utils.RandSec(5, 20))
    476 		return c.NoContent(http.StatusOK)
    477 	}
    478 	return func(c echo.Context) error {
    479 		config.RpsCounter.Incr()
    480 		if authCookie, err := c.Cookie(hutils.AuthCookieName); err == nil {
    481 			if len(authCookie.Value) > 64 {
    482 				return stopFn(c)
    483 			}
    484 		}
    485 		if csrfCookie, err := c.Cookie("_csrf"); err == nil {
    486 			if len(csrfCookie.Value) > 32 {
    487 				return stopFn(c)
    488 			}
    489 		}
    490 		if len(c.QueryParam("captcha")) > 6 {
    491 			return stopFn(c)
    492 		}
    493 		return next(c)
    494 	}
    495 }
    496 
    497 // MaintenanceMiddleware ...
    498 func MaintenanceMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    499 	return func(c echo.Context) error {
    500 		if config.MaintenanceAtom.IsFalse() {
    501 			return next(c)
    502 		}
    503 		if strings.HasPrefix(c.Path(), "/admin/") ||
    504 			strings.HasPrefix(c.Path(), "/master-admin/") ||
    505 			strings.HasPrefix(c.Path(), "/api/v1/master-admin") {
    506 			return next(c)
    507 		}
    508 		asset := bindata.MustAsset("views/pages/maintenance.gohtml")
    509 		return c.HTML(http.StatusOK, string(asset))
    510 	}
    511 }
    512 
    513 // MaybeAuthMiddleware let un-authenticated users access the page if MaybeAuthEnabled is enabled.
    514 // Otherwise, the user needs to be authenticated to access the page.
    515 func MaybeAuthMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    516 	return func(c echo.Context) error {
    517 		if config.MaybeAuthEnabled.IsFalse() {
    518 			if user := c.Get("authUser").(*database.User); user == nil {
    519 				return c.Redirect(http.StatusFound, "/")
    520 			}
    521 		}
    522 		return next(c)
    523 	}
    524 }
    525 
    526 // NoAuthMiddleware redirect to / is the user is authenticated
    527 func NoAuthMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    528 	return func(c echo.Context) error {
    529 		if user := c.Get("authUser").(*database.User); user != nil {
    530 			return c.Redirect(http.StatusFound, "/")
    531 		}
    532 		return next(c)
    533 	}
    534 }
    535 
    536 // FirstUseMiddleware if first use, redirect to /
    537 func FirstUseMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    538 	return func(c echo.Context) error {
    539 		if config.IsFirstUse.IsTrue() && c.Path() != "/" {
    540 			return c.Redirect(http.StatusFound, "/")
    541 		}
    542 		return next(c)
    543 	}
    544 }
    545 
    546 // SecureMiddleware ...
    547 var SecureMiddleware = middleware.SecureWithConfig(middleware.SecureConfig{
    548 	XSSProtection:      "1; mode=block",
    549 	ContentTypeNosniff: "nosniff",
    550 	XFrameOptions:      "SAMEORIGIN",
    551 	//HSTSMaxAge:         3600,
    552 	//ContentSecurityPolicy: "default-src 'self'",
    553 })
    554 
    555 func SetUselessHeadersMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    556 	return func(c echo.Context) error {
    557 		c.Response().Header().Set("X-Powered-By", "the almighty n0tr1v")
    558 		return next(c)
    559 	}
    560 }