aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDerek Stevens <nilix@nilfm.cc>2022-05-20 21:36:54 -0600
committerDerek Stevens <nilix@nilfm.cc>2022-05-20 21:36:54 -0600
commit2f41f53ebfba7cf71cd127c8354999c21e3188cb (patch)
tree97a23509bee63cdcb1a33553cd33f25acb99fccb
parent1dd23fe176ca430b11b550729d8cc06035afb609 (diff)
fine tune existing and add more middlewarev0.1.0
-rw-r--r--README.md8
-rw-r--r--auth/auth.go7
-rw-r--r--indentalUserDB/indentalUserDB.go14
-rw-r--r--middleware/middleware.go70
-rw-r--r--quartzgun_test.go4
-rw-r--r--renderer/renderer.go6
-rw-r--r--testData/templates/login.html6
7 files changed, 97 insertions, 18 deletions
diff --git a/README.md b/README.md
index 85a5344..2a82d41 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/auth/auth.go b/auth/auth.go
index ccb5573..c7a21e1 100644
--- a/auth/auth.go
+++ b/auth/auth.go
@@ -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
diff --git a/indentalUserDB/indentalUserDB.go b/indentalUserDB/indentalUserDB.go
index f79d314..971d113 100644
--- a/indentalUserDB/indentalUserDB.go
+++ b/indentalUserDB/indentalUserDB.go
@@ -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")
diff --git a/middleware/middleware.go b/middleware/middleware.go
index cbd1998..d83f4b1 100644
--- a/middleware/middleware.go
+++ b/middleware/middleware.go
@@ -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,15 +66,48 @@ 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 {
+ 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)
diff --git a/quartzgun_test.go b/quartzgun_test.go
index 3c9d099..cc99b83 100644
--- a/quartzgun_test.go
+++ b/quartzgun_test.go
@@ -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")))
diff --git a/renderer/renderer.go b/renderer/renderer.go
index 00288a8..3d912eb 100644
--- a/renderer/renderer.go
+++ b/renderer/renderer.go
@@ -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)
diff --git a/testData/templates/login.html b/testData/templates/login.html
index f3f740e..d9e28d2 100644
--- a/testData/templates/login.html
+++ b/testData/templates/login.html
@@ -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">