Photoblog: CSRF protection
This commit is contained in:
parent
f0bf40f412
commit
2daba7c575
@ -13,6 +13,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/csrf"
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -29,15 +30,17 @@ type AuthCookie struct {
|
|||||||
Store *sessions.CookieStore
|
Store *sessions.CookieStore
|
||||||
DataDir *os.File
|
DataDir *os.File
|
||||||
PasswordSecret string
|
PasswordSecret string
|
||||||
|
CsrfSecret string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constructor AuthCookie
|
// 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{
|
app := &AuthCookie{
|
||||||
Templates: tpl,
|
Templates: tpl,
|
||||||
Store: sessions.NewCookieStore([]byte(sessionSecret)),
|
Store: sessions.NewCookieStore([]byte(sessionSecret)),
|
||||||
DataDir: data,
|
DataDir: data,
|
||||||
PasswordSecret: passwordSecret,
|
PasswordSecret: passwordSecret,
|
||||||
|
CsrfSecret: csrfSecret,
|
||||||
}
|
}
|
||||||
app.Store.Options = &sessions.Options{
|
app.Store.Options = &sessions.Options{
|
||||||
Secure: true,
|
Secure: true,
|
||||||
@ -47,6 +50,18 @@ func NewAuthCookie(tpl *template.Template, sessionSecret, passwordSecret string,
|
|||||||
return app
|
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
|
// Verify Username
|
||||||
func (app *AuthCookie) VerifyUsername(username string) error {
|
func (app *AuthCookie) VerifyUsername(username string) error {
|
||||||
if username == "" {
|
if username == "" {
|
||||||
@ -130,18 +145,20 @@ func (app *AuthCookie) RedirectHome(res http.ResponseWriter, req *http.Request)
|
|||||||
|
|
||||||
// login: form
|
// login: form
|
||||||
func (app *AuthCookie) LoginPage(res http.ResponseWriter, req *http.Request) {
|
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 {
|
if req.Method == http.MethodPost {
|
||||||
username := req.FormValue(USER)
|
model.Username = req.FormValue(USER)
|
||||||
formErr[USER] = app.VerifyUsername(username)
|
model.UsernameError = app.VerifyUsername(model.Username)
|
||||||
formErr[PWD] = app.VerifyPassword(username, req.FormValue(PWD), res, req)
|
model.PasswordError = app.VerifyPassword(model.Username, req.FormValue(PWD), res, req)
|
||||||
if formErr[USER] == nil && formErr[PWD] == nil {
|
if model.UsernameError == nil && model.PasswordError == nil {
|
||||||
app.SaveUsername(username, res, req)
|
app.SaveUsername(model.Username, res, req)
|
||||||
app.RedirectHome(res, req)
|
app.RedirectHome(res, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
app.Templates.ExecuteTemplate(res, "login.html", formErr)
|
app.Templates.ExecuteTemplate(res, "login.html", model)
|
||||||
}
|
}
|
||||||
|
|
||||||
// logout: delete session and redirect to home
|
// logout: delete session and redirect to home
|
||||||
|
@ -17,6 +17,7 @@ import (
|
|||||||
type PhotoBlogConfig struct {
|
type PhotoBlogConfig struct {
|
||||||
SessionSecret string `json:"session-secret"`
|
SessionSecret string `json:"session-secret"`
|
||||||
PasswordSecret string `json:"password-secret"`
|
PasswordSecret string `json:"password-secret"`
|
||||||
|
CsrfSecret string `json:"csrf-secret"`
|
||||||
DataDir string `json:"data-dir"`
|
DataDir string `json:"data-dir"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +59,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
app := photo.PhotoBlog{
|
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) {
|
http.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) {
|
||||||
@ -68,8 +69,9 @@ func main() {
|
|||||||
app.HomePage(res, req)
|
app.HomePage(res, req)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
CSRF := app.Protect()
|
||||||
http.HandleFunc("/upload", app.UploadPage)
|
http.HandleFunc("/upload", app.UploadPage)
|
||||||
http.HandleFunc("/login", app.LoginPage)
|
http.HandleFunc("/login", app.LoginPage)
|
||||||
http.HandleFunc("/logout", app.LogoutPage)
|
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"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/csrf"
|
||||||
|
|
||||||
"meutel.net/meutel/go-examples/photoblog/admin"
|
"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
|
// File and date of last modification
|
||||||
type TimedFile struct {
|
type TimedFile struct {
|
||||||
Mod time.Time
|
Mod time.Time
|
||||||
@ -164,7 +180,7 @@ func (app *PhotoBlog) UploadPage(res http.ResponseWriter, req *http.Request) {
|
|||||||
app.RedirectHome(res, req)
|
app.RedirectHome(res, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
data := NewPhotoModel(app.Username(res, req))
|
data := NewPhotoUploadModel(app.Username(res, req), csrf.Token(req))
|
||||||
if req.Method == http.MethodPost {
|
if req.Method == http.MethodPost {
|
||||||
message, err := app.AddPhoto(res, req)
|
message, err := app.AddPhoto(res, req)
|
||||||
data.Err = err
|
data.Err = err
|
||||||
|
@ -2,13 +2,14 @@
|
|||||||
<h1>Authentification</h1>
|
<h1>Authentification</h1>
|
||||||
<div class="form-container">
|
<div class="form-container">
|
||||||
<form action="/login" method="POST">
|
<form action="/login" method="POST">
|
||||||
|
<input type="hidden" name="gorilla.csrf.Token" value="{{ .CsrfToken }}">
|
||||||
<label>
|
<label>
|
||||||
Username: {{ if .username }}<b class="error">{{ .username }}</b>{{ end }}
|
Username: {{ if .UsernameError }}<b class="error">{{ .UsernameError }}</b>{{ end }}
|
||||||
<input type="text" name="username" placeholder="login...">
|
<input type="text" name="username" value="{{ .Username }}" placeholder="login...">
|
||||||
</label>
|
</label>
|
||||||
<br>
|
<br>
|
||||||
<label>
|
<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...">
|
<input type="password" name="password" placeholder="password...">
|
||||||
</label>
|
</label>
|
||||||
<br>
|
<br>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<h1>Send photo</h1>
|
<h1>Send photo</h1>
|
||||||
<div class="form-container">
|
<div class="form-container">
|
||||||
<form action="/upload" method="POST" enctype="multipart/form-data">
|
<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 .Message }}<p class="message">{{ .Message }}</p>{{ end }}
|
||||||
{{ if .Err }}<p class="error">{{ .Err }}</p>{{ end }}
|
{{ if .Err }}<p class="error">{{ .Err }}</p>{{ end }}
|
||||||
<label>
|
<label>
|
||||||
|
Loading…
Reference in New Issue
Block a user