Photoblog: CSRF protection
This commit is contained in:
parent
f0bf40f412
commit
2daba7c575
@ -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
|
||||
|
@ -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)))
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user