Photoblog: CSRF protection

This commit is contained in:
Meutel 2017-07-30 11:05:20 +02:00
parent f0bf40f412
commit 2daba7c575
5 changed files with 51 additions and 14 deletions

View File

@ -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

View File

@ -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)))
}

View File

@ -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

View File

@ -2,13 +2,14 @@
<h1>Authentification</h1>
<div class="form-container">
<form action="/login" method="POST">
<input type="hidden" name="gorilla.csrf.Token" value="{{ .CsrfToken }}">
<label>
Username: {{ if .username }}<b class="error">{{ .username }}</b>{{ end }}
<input type="text" name="username" placeholder="login...">
Username: {{ if .UsernameError }}<b class="error">{{ .UsernameError }}</b>{{ end }}
<input type="text" name="username" value="{{ .Username }}" placeholder="login...">
</label>
<br>
<label>
Password: {{ if .password }}<b class="error">{{ .password }}</b>{{ end }}
Password: {{ if .PasswordError }}<b class="error">{{ .PasswordError }}</b>{{ end }}
<input type="password" name="password" placeholder="password...">
</label>
<br>

View File

@ -2,6 +2,7 @@
<h1>Send photo</h1>
<div class="form-container">
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="hidden" name="gorilla.csrf.Token" value="{{ .CsrfToken }}">
{{ if .Message }}<p class="message">{{ .Message }}</p>{{ end }}
{{ if .Err }}<p class="error">{{ .Err }}</p>{{ end }}
<label>