-
-
Save egonelbre/db8cb2d92ce4fa27b40e85ec02f573e8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// π 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