Skip to content

Instantly share code, notes, and snippets.

@egonelbre
Forked from empijei/closures.go
Last active April 30, 2020 09:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save egonelbre/db8cb2d92ce4fa27b40e85ec02f573e8 to your computer and use it in GitHub Desktop.
Save egonelbre/db8cb2d92ce4fa27b40e85ec02f573e8 to your computer and use it in GitHub Desktop.
// ๐Ÿš€ Fiber is an Express inspired web framework written in Go with ๐Ÿ’–
// ๐Ÿ“Œ API Documentation: https://fiber.wiki
// ๐Ÿ“ Github Repository: https://github.com/gofiber/fiber
// ๐Ÿ™ Credits to github.com/labstack/echo/blob/master/middleware/csrf.go
package csrf
import (
"crypto/subtle"
"errors"
"net/http"
"strings"
"time"
"github.com/akyoto/uuid"
"github.com/gofiber/fiber"
)
// Config ...
type Config struct {
// Filter defines a function to skip middleware.
// Optional. Default: nil
Filter func(*fiber.Ctx) bool
// TokenLength is the length of the generated token.
TokenLength uint8
// Optional. Default value 32.
// TokenLookup is a string in the form of "<source>:<key>" that is used
// to extract token from the request.
// Optional. Default value "header:X-CSRF-Token".
// Possible values:
// - "header:<name>"
// - "form:<name>"
// - "query:<name>"
TokenLookup string
// Context key to store generated CSRF token into context.
// Optional. Default value "csrf".
ContextKey string
// Name of the CSRF cookie. This cookie will store CSRF token.
// Optional. Default value "csrf".
CookieName string
// Domain of the CSRF cookie.
// Optional. Default value none.
CookieDomain string
// Path of the CSRF cookie.
// Optional. Default value none.
CookiePath string
// Max age (in seconds) of the CSRF cookie.
// Optional. Default value 86400 (24hr).
CookieMaxAge int
// Indicates if CSRF cookie is secure.
// Optional. Default value false.
CookieSecure bool
// Indicates if CSRF cookie is HTTP only.
// Optional. Default value false.
CookieHTTPOnly bool
}
// New ...
func New(config ...Config) func(*fiber.Ctx) {
// Init config
var cfg Config
if len(config) > 0 {
cfg = config[0]
}
if cfg.TokenLength == 0 {
cfg.TokenLength = 32
}
if cfg.TokenLookup == "" {
cfg.TokenLookup = "header:X-CSRF-Token"
}
if cfg.ContextKey == "" {
cfg.ContextKey = "csrf"
}
if cfg.CookieName == "" {
cfg.CookieName = "_csrf"
}
if cfg.CookieMaxAge == 0 {
cfg.CookieMaxAge = 86400
}
parts := strings.Split(cfg.TokenLookup, ":")
csrfSource := (*fiber.Ctx).Get
switch parts[0] {
case "form":
csrfSource = (*fiber.Ctx).FormValue
case "query":
csrfSource = (*fiber.Ctx).Query
case "param":
csrfSource = (*fiber.Ctx).Params
}
csrfKey := parts[1]
return func(c *fiber.Ctx) {
// Filter request to skip middleware
if cfg.Filter != nil && cfg.Filter(c) {
c.Next()
return
}
key := c.Cookies(cfg.CookieName)
token := ""
if key == "" {
token = uuid.New().String()
} else {
token = key
}
switch c.Method() {
case http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodTrace:
default:
// Validate token only for requests which are not defined as 'safe' by RFC7231
clientToken := csrfSource(c, csrfKey)
if clientToken == "" {
c.SendStatus(fiber.StatusBadRequest)
return
}
if subtle.ConstantTimeCompare([]byte(token), []byte(clientToken)) != 1 {
c.SendStatus(fiber.StatusForbidden)
return
}
}
// Set CSRF cookie
cookie := new(fiber.Cookie)
cookie.Name = cfg.CookieName
cookie.Value = token
if cfg.CookiePath != "" {
cookie.Path = cfg.CookiePath
}
if cfg.CookieDomain != "" {
cookie.Domain = cfg.CookieDomain
}
cookie.Expires = time.Now().Add(time.Duration(cfg.CookieMaxAge) * time.Second)
cookie.Secure = cfg.CookieSecure
cookie.HTTPOnly = cfg.CookieHTTPOnly
c.Cookie(cookie)
// Store token in context
c.Locals(cfg.ContextKey, token)
// Protect clients from caching the response
c.Vary(fiber.HeaderCookie)
c.Next()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment