fine tune existing and add more middleware

This commit is contained in:
Iris Lightshard 2022-05-20 21:36:54 -06:00
parent 1dd23fe176
commit 2f41f53ebf
Signed by: Iris Lightshard
GPG key ID: 3B7FBC22144E6398
7 changed files with 98 additions and 19 deletions

View file

@ -36,12 +36,12 @@ Features may be added here at any time as things are in early stages right now:
### etc ### etc
* [ ] middleware for easing auth flow: * [x] middleware for easing auth flow:
- [x] `Protected`: require login - [x] `Protected`: require login
- [x] `Authorize`: login and redirect - [x] `Authorize`: login and redirect
- [ ] `Bunt`: logout and redirect - [x] `Bunt`: logout and redirect
- [ ] `Fortify`: setup CSRF protection (use on the form) - [x] `Fortify`: setup CSRF protection (use on the form)
- [ ] `Defend`: enact CSRF protection (use on the endpoint) - [x] `Defend`: enact CSRF protection (use on the endpoint)
* [ ] generic DAL wrapper? might be unneccessary * [ ] generic DAL wrapper? might be unneccessary
## license ## license

View file

@ -23,6 +23,8 @@ type UserStore interface {
AddUser(user string, password string) error AddUser(user string, password string) error
DeleteUser(user string) error DeleteUser(user string) error
ChangePassword(user string, oldPassword string, newPassword 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 SetData(user string, key string, value interface{}) error
GetData(user string, key string) (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 { if loginErr == nil {
cookie.StoreToken("user", user, w, t) cookie.StoreToken("user", user, w, t)
cookie.StoreToken("session", session, 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 nil
} }
return loginErr return loginErr
@ -42,6 +47,8 @@ func Logout(user string, userStore UserStore, w http.ResponseWriter) error {
if logoutErr == nil { if logoutErr == nil {
cookie.StoreToken("user", "", w, 0) cookie.StoreToken("user", "", w, 0)
cookie.StoreToken("session", "", w, 0) cookie.StoreToken("session", "", w, 0)
cookie.StoreToken("csrfToken", "", w, 0)
userStore.SetData(user, "csrfToken", "")
return nil return nil
} }
return logoutErr return logoutErr

View file

@ -117,6 +117,20 @@ func (self *IndentalUserDB) AddUser(user string, password string) error {
return nil 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 { func (self *IndentalUserDB) SetData(user string, key string, value interface{}) error {
if _, exists := self.Users[user]; !exists { if _, exists := self.Users[user]; !exists {
return errors.New("User not in DB") return errors.New("User not in DB")

View file

@ -8,7 +8,7 @@ import (
"nilfm.cc/git/quartzgun/cookie" "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) { handlerFunc := func(w http.ResponseWriter, req *http.Request) {
user, err := cookie.GetToken("user", req) user, err := cookie.GetToken("user", req)
if err == nil { if err == nil {
@ -26,13 +26,34 @@ func Protected(next http.Handler, method string, userStore auth.UserStore) http.
} }
fmt.Printf("unauthorized...\n") fmt.Printf("unauthorized...\n")
req.Method = http.MethodGet req.Method = http.MethodGet
http.Redirect(w, req, "/login", http.StatusSeeOther) http.Redirect(w, req, login, http.StatusSeeOther)
} }
return http.HandlerFunc(handlerFunc) 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) { handlerFunc := func(w http.ResponseWriter, req *http.Request) {
err := auth.Login( err := auth.Login(
req.FormValue("user"), 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")) fmt.Printf("logged in as %s\n", req.FormValue("user"))
http.Redirect(w, req, next, http.StatusSeeOther) http.Redirect(w, req, next, http.StatusSeeOther)
} else { } else {
*req = *req.WithContext(
context.WithValue(
req.Context(),
"message",
"Incorrect credentials"))
fmt.Printf("login failed!\n") fmt.Printf("login failed!\n")
req.Method = http.MethodGet req.Method = http.MethodGet
http.Redirect(w, req, "/login", http.StatusSeeOther) http.Redirect(w, req, denied, http.StatusSeeOther)
} }
} }
return http.HandlerFunc(handlerFunc) 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)
}

View file

@ -47,11 +47,11 @@ func TestMain(m *testing.M) {
rtr.Get("/login", renderer.Template( rtr.Get("/login", renderer.Template(
"testData/templates/login.html")) "testData/templates/login.html"))
rtr.Post("/login", middleware.Authorize("/", udb)) rtr.Post("/login", middleware.Authorize("/", udb, "/login?tryagain=1"))
rtr.Get("/", middleware.Protected( rtr.Get("/", middleware.Protected(
renderer.Template( renderer.Template(
"testData/templates/test.html"), http.MethodGet, udb)) "testData/templates/test.html"), http.MethodGet, udb, "/login"))
rtr.Get("/json", ApiSomething(renderer.JSON("apiData"))) rtr.Get("/json", ApiSomething(renderer.JSON("apiData")))

View file

@ -3,6 +3,7 @@ package renderer
import ( import (
"encoding/json" "encoding/json"
"encoding/xml" "encoding/xml"
"fmt"
"html/template" "html/template"
"net/http" "net/http"
) )
@ -11,7 +12,10 @@ func Template(t ...string) http.Handler {
tmpl := template.Must(template.ParseFiles(t...)) tmpl := template.Must(template.ParseFiles(t...))
handlerFunc := func(w http.ResponseWriter, req *http.Request) { 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) return http.HandlerFunc(handlerFunc)

View file

@ -1,4 +1,4 @@
{{ $errorMsg := (.Context).Value "message" }} {{ $tryagain := .FormValue "tryagain" }}
<!DOCTYPE html> <!DOCTYPE html>
<html lang='en'> <html lang='en'>
@ -9,8 +9,8 @@
<title>Nirvash &mdash; Login</title> <title>Nirvash &mdash; Login</title>
</head> </head>
<body> <body>
{{ if $errorMsg }} {{ if $tryagain }}
<div class="error">{{ $errorMsg }}</div> <div class="error">Incorrect credentials; please try again.</div>
{{ end }} {{ end }}
<form action='/login' method='post'> <form action='/login' method='post'>
<input type="text" name="user" placeholder="user"> <input type="text" name="user" placeholder="user">