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
* [ ] 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

View file

@ -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

View file

@ -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")

View file

@ -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)
}

View file

@ -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")))

View file

@ -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)

View file

@ -1,4 +1,4 @@
{{ $errorMsg := (.Context).Value "message" }}
{{ $tryagain := .FormValue "tryagain" }}
<!DOCTYPE html>
<html lang='en'>
@ -9,8 +9,8 @@
<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">