all repos — quartzgun @ 2f41f53ebfba7cf71cd127c8354999c21e3188cb

lightweight web framework in go

fine tune existing and add more middleware
Iris Lightshard nilix@nilfm.cc
PGP Signature
-----BEGIN PGP SIGNATURE-----

iQIzBAABCAAdFiEEkFh6dA+k/6CXFXU4O3+8IhROY5gFAmKIXlYACgkQO3+8IhRO
Y5iBxhAAj35iarBhQoocEsV9Nn4zjiCa7ZRRoLYahkLzGJf1bzsXPZI011cuAJCe
ZvVRYJeJC3lQCB6grmcwPUSNKHvoKghMev7y9BcH7BpMspT31e28KCZBFBn/GFiv
RKY1HGwRQ/tORuhpjQMdtSt3m/+9Hd6TCX446tKaEjx7bup54PXFdjxEVZWBCo0Z
5NnRlq5e4yN2ApMJp4V07rdpla8QU0nnqTbcujbGtIKkfsczaOPgYacOOCoObWDg
WIrd4gt0Rj65qnZv0EO2ljfbBzDPwoHJfYhOTli/R/OvVEEAvbOn/c8WVGOPrhbU
7tLmgkw2OEFo4QHw6/BgzHIPMLPedbeGflrX7R4bNnVojNO22UHbxo+2MyRoz+SR
bqGR5+CM00hpD+KKSXDio903G/pcE7+H4hra7YjRS6WfWCS1w0g73s5J3X0W0Mlq
hCHTEG/cVtFty+4m+evYRqjmk+K2k8lReiDy5huHy3rWFblCSLSmMvnjMp/1iSp0
ZDO0jCuVC++6GXmwBeMbW5eI98uOv25CVhEwH9Tc7ZmTFTnVMOius4XSylUCB0eu
2NoBg0gxQ931dzYhm81dX6UamNUWRvxiYmSIlYGzdCLihdJQG81GhTOx6CVehSTh
5D+G9DZtU+ODfD5uAZhOYp4Zd5icn5DZv/G0AQHgkIT+nPxLAZ0=
=sMhX
-----END PGP SIGNATURE-----
commit

2f41f53ebfba7cf71cd127c8354999c21e3188cb

parent

1dd23fe176ca430b11b550729d8cc06035afb609

M README.mdREADME.md

@@ -36,12 +36,12 @@ * [x] POC [indental](https://wiki.xxiivv.com/site/indental.html) `UserStore` implementation

### etc -* [ ] middleware for easing auth flow: +* [x] middleware for easing auth flow: - [x] `Protected`: require login - [x] `Authorize`: login and redirect - - [ ] `Bunt`: logout and redirect - - [ ] `Fortify`: setup CSRF protection (use on the form) - - [ ] `Defend`: enact CSRF protection (use on the endpoint) + - [x] `Bunt`: logout and redirect + - [x] `Fortify`: setup CSRF protection (use on the form) + - [x] `Defend`: enact CSRF protection (use on the endpoint) * [ ] generic DAL wrapper? might be unneccessary ## license
M auth/auth.goauth/auth.go

@@ -23,6 +23,8 @@ EndSession(user string) error

AddUser(user string, password string) error DeleteUser(user string) error ChangePassword(user string, oldPassword string, newPassword string) error + GetLastLoginTime(user string) (time.Time, error) + GetLastTimeSeen(user string) (time.Time, error) SetData(user string, key string, value interface{}) error GetData(user string, key string) (interface{}, error) }

@@ -32,6 +34,9 @@ session, loginErr := userStore.InitiateSession(user, password)

if loginErr == nil { cookie.StoreToken("user", user, w, t) cookie.StoreToken("session", session, w, t) + csrfToken := cookie.GenToken(64) + cookie.StoreToken("csrfToken", csrfToken, w, t) + userStore.SetData(user, "csrfToken", csrfToken) return nil } return loginErr

@@ -42,6 +47,8 @@ logoutErr := userStore.EndSession(user)

if logoutErr == nil { cookie.StoreToken("user", "", w, 0) cookie.StoreToken("session", "", w, 0) + cookie.StoreToken("csrfToken", "", w, 0) + userStore.SetData(user, "csrfToken", "") return nil } return logoutErr
M indentalUserDB/indentalUserDB.goindentalUserDB/indentalUserDB.go

@@ -117,6 +117,20 @@ writeDB(self.Basis, self.Users)

return nil } +func (self *IndentalUserDB) GetLastLoginTime(user string) (time.Time, error) { + if usr, exists := self.Users[user]; exists { + return usr.LoginTime, nil + } + return time.UnixMicro(0), errors.New("User not in DB") +} + +func (self *IndentalUserDB) GetLastTimeSeen(user string) (time.Time, error) { + if usr, exists := self.Users[user]; exists { + return usr.LastSeen, nil + } + return time.UnixMicro(0), errors.New("User not in DB") +} + func (self *IndentalUserDB) SetData(user string, key string, value interface{}) error { if _, exists := self.Users[user]; !exists { return errors.New("User not in DB")
M middleware/middleware.gomiddleware/middleware.go

@@ -8,7 +8,7 @@ "nilfm.cc/git/quartzgun/auth"

"nilfm.cc/git/quartzgun/cookie" ) -func Protected(next http.Handler, method string, userStore auth.UserStore) http.Handler { +func Protected(next http.Handler, method string, userStore auth.UserStore, login string) http.Handler { handlerFunc := func(w http.ResponseWriter, req *http.Request) { user, err := cookie.GetToken("user", req) if err == nil {

@@ -26,13 +26,34 @@ }

} fmt.Printf("unauthorized...\n") req.Method = http.MethodGet - http.Redirect(w, req, "/login", http.StatusSeeOther) + http.Redirect(w, req, login, http.StatusSeeOther) + } + + return http.HandlerFunc(handlerFunc) +} + +func Bunt(next string, userStore auth.UserStore, denied string) http.Handler { + handlerFunc := func(w http.ResponseWriter, req *http.Request) { + user, err := cookie.GetToken("user", req) + if err == nil { + err := auth.Logout( + user, + userStore, + w) + if err == nil { + req.Method = http.MethodGet + http.Redirect(w, req, next, http.StatusSeeOther) + return + } + } + req.Method = http.MethodGet + http.Redirect(w, req, denied, http.StatusUnauthorized) } return http.HandlerFunc(handlerFunc) } -func Authorize(next string, userStore auth.UserStore) http.Handler { +func Authorize(next string, userStore auth.UserStore, denied string) http.Handler { handlerFunc := func(w http.ResponseWriter, req *http.Request) { err := auth.Login( req.FormValue("user"),

@@ -45,15 +66,48 @@ req.Method = http.MethodGet

fmt.Printf("logged in as %s\n", req.FormValue("user")) http.Redirect(w, req, next, http.StatusSeeOther) } else { + fmt.Printf("login failed!\n") + req.Method = http.MethodGet + http.Redirect(w, req, denied, http.StatusSeeOther) + } + } + + return http.HandlerFunc(handlerFunc) +} + +func Fortify(next http.Handler) http.Handler { + handlerFunc := func(w http.ResponseWriter, req *http.Request) { + token, err := cookie.GetToken("csrfToken", req) + if err == nil { *req = *req.WithContext( context.WithValue( req.Context(), - "message", - "Incorrect credentials")) - fmt.Printf("login failed!\n") - req.Method = http.MethodGet - http.Redirect(w, req, "/login", http.StatusSeeOther) + "csrfToken", + token)) + } + next.ServeHTTP(w, req) + } + + return http.HandlerFunc(handlerFunc) +} + +func Defend(next http.Handler, userStore auth.UserStore, denied string) http.Handler { + handlerFunc := func(w http.ResponseWriter, req *http.Request) { + user, err := cookie.GetToken("user", req) + if err == nil { + masterToken, err := userStore.GetData(user, "csrfToken") + if err == nil { + cookieToken, err := cookie.GetToken("csrfToken", req) + if err == nil { + formToken := req.FormValue("csrfToken") + if formToken == cookieToken && formToken == masterToken.(string) { + next.ServeHTTP(w, req) + return + } + } + } } + http.Redirect(w, req, denied, http.StatusUnauthorized) } return http.HandlerFunc(handlerFunc)
M quartzgun_test.goquartzgun_test.go

@@ -47,11 +47,11 @@

rtr.Get("/login", renderer.Template( "testData/templates/login.html")) - rtr.Post("/login", middleware.Authorize("/", udb)) + rtr.Post("/login", middleware.Authorize("/", udb, "/login?tryagain=1")) rtr.Get("/", middleware.Protected( renderer.Template( - "testData/templates/test.html"), http.MethodGet, udb)) + "testData/templates/test.html"), http.MethodGet, udb, "/login")) rtr.Get("/json", ApiSomething(renderer.JSON("apiData")))
M renderer/renderer.gorenderer/renderer.go

@@ -3,6 +3,7 @@

import ( "encoding/json" "encoding/xml" + "fmt" "html/template" "net/http" )

@@ -11,7 +12,10 @@ func Template(t ...string) http.Handler {

tmpl := template.Must(template.ParseFiles(t...)) handlerFunc := func(w http.ResponseWriter, req *http.Request) { - tmpl.Execute(w, req) + err := tmpl.Execute(w, req) + if err != nil { + fmt.Printf(err.Error()) + } } return http.HandlerFunc(handlerFunc)
M testData/templates/login.htmltestData/templates/login.html

@@ -1,4 +1,4 @@

-{{ $errorMsg := (.Context).Value "message" }} +{{ $tryagain := .FormValue "tryagain" }} <!DOCTYPE html> <html lang='en'>

@@ -9,8 +9,8 @@ <meta name='viewport' content='width=device-width,initial-scale=1'>

<title>Nirvash &mdash; Login</title> </head> <body> - {{ if $errorMsg }} - <div class="error">{{ $errorMsg }}</div> + {{ if $tryagain }} + <div class="error">Incorrect credentials; please try again.</div> {{ end }} <form action='/login' method='post'> <input type="text" name="user" placeholder="user">