From 2daba7c575e9dfd6645663c54030047df788e176 Mon Sep 17 00:00:00 2001 From: Meutel Date: Sun, 30 Jul 2017 11:05:20 +0200 Subject: [PATCH] Photoblog: CSRF protection --- photoblog/admin/admin.go | 33 +++++++++++++++++++++++++-------- photoblog/main.go | 6 ++++-- photoblog/photo/photo.go | 18 +++++++++++++++++- photoblog/templates/login.html | 7 ++++--- photoblog/templates/upload.html | 1 + 5 files changed, 51 insertions(+), 14 deletions(-) diff --git a/photoblog/admin/admin.go b/photoblog/admin/admin.go index 939874a..df0dc6f 100644 --- a/photoblog/admin/admin.go +++ b/photoblog/admin/admin.go @@ -13,6 +13,7 @@ import ( "path/filepath" "time" + "github.com/gorilla/csrf" "github.com/gorilla/sessions" ) @@ -29,15 +30,17 @@ type AuthCookie struct { Store *sessions.CookieStore DataDir *os.File PasswordSecret string + CsrfSecret string } // Constructor AuthCookie -func NewAuthCookie(tpl *template.Template, sessionSecret, passwordSecret string, data *os.File) *AuthCookie { +func NewAuthCookie(tpl *template.Template, sessionSecret, passwordSecret, csrfSecret string, data *os.File) *AuthCookie { app := &AuthCookie{ Templates: tpl, Store: sessions.NewCookieStore([]byte(sessionSecret)), DataDir: data, PasswordSecret: passwordSecret, + CsrfSecret: csrfSecret, } app.Store.Options = &sessions.Options{ Secure: true, @@ -47,6 +50,18 @@ func NewAuthCookie(tpl *template.Template, sessionSecret, passwordSecret string, return app } +type LoginModel struct { + Username string + UsernameError error + PasswordError error + CsrfToken string +} + +// Csrf handler wrapper +func (app *AuthCookie) Protect() func(http.Handler) http.Handler { + return csrf.Protect([]byte(app.CsrfSecret), csrf.Secure(true), csrf.HttpOnly(true)) +} + // Verify Username func (app *AuthCookie) VerifyUsername(username string) error { if username == "" { @@ -130,18 +145,20 @@ func (app *AuthCookie) RedirectHome(res http.ResponseWriter, req *http.Request) // login: form func (app *AuthCookie) LoginPage(res http.ResponseWriter, req *http.Request) { - formErr := make(map[string]error) + model := LoginModel{ + CsrfToken: csrf.Token(req), + } if req.Method == http.MethodPost { - username := req.FormValue(USER) - formErr[USER] = app.VerifyUsername(username) - formErr[PWD] = app.VerifyPassword(username, req.FormValue(PWD), res, req) - if formErr[USER] == nil && formErr[PWD] == nil { - app.SaveUsername(username, res, req) + model.Username = req.FormValue(USER) + model.UsernameError = app.VerifyUsername(model.Username) + model.PasswordError = app.VerifyPassword(model.Username, req.FormValue(PWD), res, req) + if model.UsernameError == nil && model.PasswordError == nil { + app.SaveUsername(model.Username, res, req) app.RedirectHome(res, req) return } } - app.Templates.ExecuteTemplate(res, "login.html", formErr) + app.Templates.ExecuteTemplate(res, "login.html", model) } // logout: delete session and redirect to home diff --git a/photoblog/main.go b/photoblog/main.go index ea64348..e37abea 100644 --- a/photoblog/main.go +++ b/photoblog/main.go @@ -17,6 +17,7 @@ import ( type PhotoBlogConfig struct { SessionSecret string `json:"session-secret"` PasswordSecret string `json:"password-secret"` + CsrfSecret string `json:"csrf-secret"` DataDir string `json:"data-dir"` } @@ -58,7 +59,7 @@ func main() { } app := photo.PhotoBlog{ - *admin.NewAuthCookie(tpl, conf.SessionSecret, conf.PasswordSecret, data), + *admin.NewAuthCookie(tpl, conf.SessionSecret, conf.PasswordSecret, conf.CsrfSecret, data), } http.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) { @@ -68,8 +69,9 @@ func main() { app.HomePage(res, req) } }) + CSRF := app.Protect() http.HandleFunc("/upload", app.UploadPage) http.HandleFunc("/login", app.LoginPage) http.HandleFunc("/logout", app.LogoutPage) - http.ListenAndServeTLS(":9443", "../cert.pem", "../key.pem", context.ClearHandler(http.DefaultServeMux)) + http.ListenAndServeTLS(":9443", "../cert.pem", "../key.pem", CSRF(context.ClearHandler(http.DefaultServeMux))) } diff --git a/photoblog/photo/photo.go b/photoblog/photo/photo.go index 922190a..443aede 100644 --- a/photoblog/photo/photo.go +++ b/photoblog/photo/photo.go @@ -11,6 +11,8 @@ import ( "strings" "time" + "github.com/gorilla/csrf" + "meutel.net/meutel/go-examples/photoblog/admin" ) @@ -57,6 +59,20 @@ func NewPhotoHomeModel(username string, photos []string) *PhotoHomeModel { } } +// Upload page model +type PhotoUploadModel struct { + PhotoModel + CsrfToken string +} + +// Constructor PhotoUploadModel +func NewPhotoUploadModel(username, tok string) *PhotoUploadModel { + return &PhotoUploadModel{ + PhotoModel: *NewPhotoModel(username), + CsrfToken: tok, + } +} + // File and date of last modification type TimedFile struct { Mod time.Time @@ -164,7 +180,7 @@ func (app *PhotoBlog) UploadPage(res http.ResponseWriter, req *http.Request) { app.RedirectHome(res, req) return } - data := NewPhotoModel(app.Username(res, req)) + data := NewPhotoUploadModel(app.Username(res, req), csrf.Token(req)) if req.Method == http.MethodPost { message, err := app.AddPhoto(res, req) data.Err = err diff --git a/photoblog/templates/login.html b/photoblog/templates/login.html index 5910201..45e0823 100644 --- a/photoblog/templates/login.html +++ b/photoblog/templates/login.html @@ -2,13 +2,14 @@

Authentification

+

diff --git a/photoblog/templates/upload.html b/photoblog/templates/upload.html index 2a482d2..9950e3a 100644 --- a/photoblog/templates/upload.html +++ b/photoblog/templates/upload.html @@ -2,6 +2,7 @@

Send photo

+ {{ if .Message }}

{{ .Message }}

{{ end }} {{ if .Err }}

{{ .Err }}

{{ end }}