mirror of
https://github.com/labstack/echo.git
synced 2025-07-17 01:43:02 +02:00
Merge SameSite mode for CSRF (PR #1524) from 'pr0head/master'
This commit is contained in:
@ -57,6 +57,10 @@ type (
|
|||||||
// Indicates if CSRF cookie is HTTP only.
|
// Indicates if CSRF cookie is HTTP only.
|
||||||
// Optional. Default value false.
|
// Optional. Default value false.
|
||||||
CookieHTTPOnly bool `yaml:"cookie_http_only"`
|
CookieHTTPOnly bool `yaml:"cookie_http_only"`
|
||||||
|
|
||||||
|
// Indicates SameSite mode of the CSRF cookie.
|
||||||
|
// Optional. Default value SameSiteDefaultMode.
|
||||||
|
CookieSameSite http.SameSite `yaml:"cookie_same_site"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// csrfTokenExtractor defines a function that takes `echo.Context` and returns
|
// csrfTokenExtractor defines a function that takes `echo.Context` and returns
|
||||||
@ -67,12 +71,13 @@ type (
|
|||||||
var (
|
var (
|
||||||
// DefaultCSRFConfig is the default CSRF middleware config.
|
// DefaultCSRFConfig is the default CSRF middleware config.
|
||||||
DefaultCSRFConfig = CSRFConfig{
|
DefaultCSRFConfig = CSRFConfig{
|
||||||
Skipper: DefaultSkipper,
|
Skipper: DefaultSkipper,
|
||||||
TokenLength: 32,
|
TokenLength: 32,
|
||||||
TokenLookup: "header:" + echo.HeaderXCSRFToken,
|
TokenLookup: "header:" + echo.HeaderXCSRFToken,
|
||||||
ContextKey: "csrf",
|
ContextKey: "csrf",
|
||||||
CookieName: "_csrf",
|
CookieName: "_csrf",
|
||||||
CookieMaxAge: 86400,
|
CookieMaxAge: 86400,
|
||||||
|
CookieSameSite: http.SameSiteDefaultMode,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -105,6 +110,9 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc {
|
|||||||
if config.CookieMaxAge == 0 {
|
if config.CookieMaxAge == 0 {
|
||||||
config.CookieMaxAge = DefaultCSRFConfig.CookieMaxAge
|
config.CookieMaxAge = DefaultCSRFConfig.CookieMaxAge
|
||||||
}
|
}
|
||||||
|
if config.CookieSameSite == http.SameSiteNoneMode {
|
||||||
|
config.CookieSecure = true
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
parts := strings.Split(config.TokenLookup, ":")
|
parts := strings.Split(config.TokenLookup, ":")
|
||||||
@ -157,6 +165,9 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc {
|
|||||||
if config.CookieDomain != "" {
|
if config.CookieDomain != "" {
|
||||||
cookie.Domain = config.CookieDomain
|
cookie.Domain = config.CookieDomain
|
||||||
}
|
}
|
||||||
|
if config.CookieSameSite != http.SameSiteDefaultMode {
|
||||||
|
cookie.SameSite = config.CookieSameSite
|
||||||
|
}
|
||||||
cookie.Expires = time.Now().Add(time.Duration(config.CookieMaxAge) * time.Second)
|
cookie.Expires = time.Now().Add(time.Duration(config.CookieMaxAge) * time.Second)
|
||||||
cookie.Secure = config.CookieSecure
|
cookie.Secure = config.CookieSecure
|
||||||
cookie.HttpOnly = config.CookieHTTPOnly
|
cookie.HttpOnly = config.CookieHTTPOnly
|
||||||
|
12
middleware/csrf_samesite.go
Normal file
12
middleware/csrf_samesite.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// +build !go1.12
|
||||||
|
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// SameSiteNoneMode required to be redefined for Go 1.12 support (see #1524)
|
||||||
|
SameSiteNoneMode http.SameSite = http.SameSiteNoneMode
|
||||||
|
)
|
12
middleware/csrf_samesite_1.12.go
Normal file
12
middleware/csrf_samesite_1.12.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// +build go1.12
|
||||||
|
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// SameSiteNoneMode required to be redefined for Go 1.12 support (see #1524)
|
||||||
|
SameSiteNoneMode http.SameSite = 4
|
||||||
|
)
|
@ -1,6 +1,7 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -81,3 +82,79 @@ func TestCSRFTokenFromQuery(t *testing.T) {
|
|||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
csrfTokenFromQuery("csrf")
|
csrfTokenFromQuery("csrf")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCSRFSetSameSiteMode(t *testing.T) {
|
||||||
|
e := echo.New()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
c := e.NewContext(req, rec)
|
||||||
|
|
||||||
|
csrf := CSRFWithConfig(CSRFConfig{
|
||||||
|
CookieSameSite: http.SameSiteStrictMode,
|
||||||
|
})
|
||||||
|
|
||||||
|
h := csrf(func(c echo.Context) error {
|
||||||
|
return c.String(http.StatusOK, "test")
|
||||||
|
})
|
||||||
|
|
||||||
|
r := h(c)
|
||||||
|
assert.NoError(t, r)
|
||||||
|
assert.Regexp(t, "SameSite=Strict", rec.Header()["Set-Cookie"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCSRFWithoutSameSiteMode(t *testing.T) {
|
||||||
|
e := echo.New()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
c := e.NewContext(req, rec)
|
||||||
|
|
||||||
|
csrf := CSRFWithConfig(CSRFConfig{})
|
||||||
|
|
||||||
|
h := csrf(func(c echo.Context) error {
|
||||||
|
return c.String(http.StatusOK, "test")
|
||||||
|
})
|
||||||
|
|
||||||
|
r := h(c)
|
||||||
|
assert.NoError(t, r)
|
||||||
|
assert.NotRegexp(t, "SameSite=", rec.Header()["Set-Cookie"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCSRFWithSameSiteDefaultMode(t *testing.T) {
|
||||||
|
e := echo.New()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
c := e.NewContext(req, rec)
|
||||||
|
|
||||||
|
csrf := CSRFWithConfig(CSRFConfig{
|
||||||
|
CookieSameSite: http.SameSiteDefaultMode,
|
||||||
|
})
|
||||||
|
|
||||||
|
h := csrf(func(c echo.Context) error {
|
||||||
|
return c.String(http.StatusOK, "test")
|
||||||
|
})
|
||||||
|
|
||||||
|
r := h(c)
|
||||||
|
assert.NoError(t, r)
|
||||||
|
fmt.Println(rec.Header()["Set-Cookie"])
|
||||||
|
assert.NotRegexp(t, "SameSite=", rec.Header()["Set-Cookie"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCSRFWithSameSiteModeNone(t *testing.T) {
|
||||||
|
e := echo.New()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
c := e.NewContext(req, rec)
|
||||||
|
|
||||||
|
csrf := CSRFWithConfig(CSRFConfig{
|
||||||
|
CookieSameSite: SameSiteNoneMode,
|
||||||
|
})
|
||||||
|
|
||||||
|
h := csrf(func(c echo.Context) error {
|
||||||
|
return c.String(http.StatusOK, "test")
|
||||||
|
})
|
||||||
|
|
||||||
|
r := h(c)
|
||||||
|
assert.NoError(t, r)
|
||||||
|
assert.Regexp(t, "SameSite=None", rec.Header()["Set-Cookie"])
|
||||||
|
assert.Regexp(t, "Secure", rec.Header()["Set-Cookie"])
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user