fine tune existing and add more middleware
This commit is contained in:
parent
1dd23fe176
commit
2f41f53ebf
7 changed files with 98 additions and 19 deletions
|
@ -36,12 +36,12 @@ Features may be added here at any time as things are in early stages right now:
|
|||
|
||||
### 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
|
||||
|
|
|
@ -23,6 +23,8 @@ type UserStore interface {
|
|||
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 @@ func Login(user string, password string, userStore UserStore, w http.ResponseWri
|
|||
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 @@ func Logout(user string, userStore UserStore, w http.ResponseWriter) error {
|
|||
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
|
||||
|
|
|
@ -117,6 +117,20 @@ func (self *IndentalUserDB) AddUser(user string, password string) error {
|
|||
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")
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"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 @@ func Protected(next http.Handler, method string, userStore auth.UserStore) http.
|
|||
}
|
||||
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 Authorize(next string, userStore auth.UserStore) http.Handler {
|
||||
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, denied string) http.Handler {
|
||||
handlerFunc := func(w http.ResponseWriter, req *http.Request) {
|
||||
err := auth.Login(
|
||||
req.FormValue("user"),
|
||||
|
@ -45,16 +66,49 @@ func Authorize(next string, userStore auth.UserStore) http.Handler {
|
|||
fmt.Printf("logged in as %s\n", req.FormValue("user"))
|
||||
http.Redirect(w, req, next, http.StatusSeeOther)
|
||||
} else {
|
||||
*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)
|
||||
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(),
|
||||
"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)
|
||||
}
|
||||
|
|
|
@ -47,11 +47,11 @@ func TestMain(m *testing.M) {
|
|||
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")))
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package renderer
|
|||
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)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{{ $errorMsg := (.Context).Value "message" }}
|
||||
{{ $tryagain := .FormValue "tryagain" }}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang='en'>
|
||||
|
@ -9,8 +9,8 @@
|
|||
<title>Nirvash — 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">
|
||||
|
|
Loading…
Reference in a new issue