Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
8 changes: 8 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions .idea/csrf.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

87 changes: 56 additions & 31 deletions csrf.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ package csrf
import (
"crypto/rand"
"fmt"
"go.wandrs.dev/binding"
"go.wandrs.dev/inject"
"go.wandrs.dev/session"
r "math/rand"
"net/http"
"reflect"
"time"

"github.com/go-macaron/session"
"gopkg.in/macaron.v1"
)

// CSRF represents a CSRF service and is used to get the current token and validate a suspect token.
Expand Down Expand Up @@ -199,9 +200,11 @@ func prepareOptions(options []Options) Options {

// Generate maps CSRF to each request. If this request is a Get request, it will generate a new token.
// Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie.
func Generate(options ...Options) macaron.Handler {
func Generate(options ...Options) func(http.Handler) http.Handler {
opt := prepareOptions(options)
return func(ctx *macaron.Context, sess session.Store) {

return binding.Inject(func(injector inject.Injector) error {
ctx := binding.ResponseWriter(injector)
x := &csrf{
Secret: opt.Secret,
Header: opt.Header,
Expand All @@ -212,12 +215,12 @@ func Generate(options ...Options) macaron.Handler {
CookieHttpOnly: opt.CookieHttpOnly,
ErrorFunc: opt.ErrorFunc,
}
ctx.MapTo(x, (*CSRF)(nil))

if opt.Origin && len(ctx.Req.Header.Get("Origin")) > 0 {
return
if opt.Origin && len(ctx.Header().Get("Origin")) > 0 {
return nil
}

sess := session.GetSession(injector)
x.ID = "0"
uid := sess.Get(opt.SessionKey)
if uid != nil {
Expand All @@ -231,53 +234,75 @@ func Generate(options ...Options) macaron.Handler {
_ = sess.Set(opt.oldSeesionKey, x.ID)
} else {
// If cookie present, map existing token, else generate a new one.
if val := ctx.GetCookie(opt.Cookie); len(val) > 0 {
// FIXME: test coverage.
if val := session.GetCookie(ctx.R().Request(), opt.Cookie); len(val) > 0 {
x.Token = val
} else {
needsNew = true
}
}

if needsNew {
// FIXME: actionId.
x.Token = GenerateToken(x.Secret, x.ID, "POST")
if opt.SetCookie {
ctx.SetCookie(opt.Cookie, x.Token, 0, opt.CookiePath, opt.CookieDomain, opt.Secure, opt.CookieHttpOnly, time.Now().AddDate(0, 0, 1))
newCookie := session.NewCookie(opt.Cookie, x.Token, opt.CookiePath, opt.CookieDomain, opt.Secure, opt.CookieHttpOnly, time.Now().AddDate(0, 0, 1))
ctx.Header().Add("Set-Cookie", newCookie.String())
}
}

if opt.SetHeader {
ctx.Resp.Header().Add(opt.Header, x.Token)
ctx.Header().Add(opt.Header, x.Token)
}
}
injector.Map(x)

return nil
})
}

// Csrfer maps CSRF to each request. If this request is a Get request, it will generate a new token.
// Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie.
func Csrfer(options ...Options) macaron.Handler {
func Csrfer(options ...Options) func(next http.Handler) http.Handler {
return Generate(options...)
}

func GetCSRF(injector inject.Injector) *csrf {
return injector.GetVal(reflect.TypeOf(&csrf{})).Interface().(*csrf)
}

// Validate should be used as a per route middleware. It attempts to get a token from a "X-CSRFToken"
// HTTP header and then a "_csrf" form value. If one of these is found, the token will be validated
// using ValidToken. If this validation fails, custom Error is sent in the reply.
// If neither a header or form value is found, http.StatusBadRequest is sent.
func Validate(ctx *macaron.Context, x CSRF) {
if token := ctx.Req.Header.Get(x.GetHeaderName()); len(token) > 0 {
if !x.ValidToken(token) {
ctx.SetCookie(x.GetCookieName(), "", -1, x.GetCookiePath())
x.Error(ctx.Resp)
}
return
}
if token := ctx.Req.FormValue(x.GetFormName()); len(token) > 0 {
if !x.ValidToken(token) {
ctx.SetCookie(x.GetCookieName(), "", -1, x.GetCookiePath())
x.Error(ctx.Resp)
}
return
}
func Validate(x CSRF) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if token := r.Header.Get(x.GetHeaderName()); len(token) > 0 {
if !x.ValidToken(token) {
cookie := &http.Cookie{
Name: x.GetCookieName(),
Value: "",
Path: x.GetCookiePath(),
}
http.SetCookie(w, cookie)
x.Error(w)
return
}
} else if token := r.FormValue(x.GetFormName()); len(token) > 0 {
if !x.ValidToken(token) {
cookie := &http.Cookie{
Name: x.GetCookieName(),
Value: "",
Path: x.GetCookiePath(),
}
http.SetCookie(w, cookie)
x.Error(w)
return
}
} else {
http.Error(w, "Bad Request: no CSRF token present", http.StatusBadRequest)
return
}

http.Error(ctx.Resp, "Bad Request: no CSRF token present", http.StatusBadRequest)
next.ServeHTTP(w, r)
})
}
}
8 changes: 5 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
module github.com/go-macaron/csrf
module go.wandrs.dev/csrf

go 1.12

require (
github.com/go-macaron/session v0.0.0-20190805070824-1a3cdc6f5659
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337
github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e
github.com/smartystreets/goconvey v1.6.4
go.wandrs.dev/binding v0.0.3-0.20210906074014-ef9ff4cd15de
go.wandrs.dev/inject v0.0.2-0.20210830111053-4cf53490454a
go.wandrs.dev/session v0.0.0-20230714090251-542544d34622
gopkg.in/macaron.v1 v1.3.4
)
Loading