diff --git a/cmd/owl/reset_password.go b/cmd/owl/reset_password.go
index 6c9506c..78179cf 100644
--- a/cmd/owl/reset_password.go
+++ b/cmd/owl/reset_password.go
@@ -3,7 +3,6 @@ package main
import (
"fmt"
"h4kor/owl-blogs"
- "math/rand"
"github.com/spf13/cobra"
)
@@ -35,14 +34,7 @@ var resetPasswordCmd = &cobra.Command{
}
// generate a random password and print it
- const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
-
- b := make([]byte, 16)
- for i := range b {
- b[i] = chars[rand.Intn(len(chars))]
- }
- password := string(b)
-
+ password := owl.GenerateRandomString(16)
user.ResetPassword(password)
fmt.Println("User: ", user.Name())
diff --git a/cmd/owl/web/auth_test.go b/cmd/owl/web/auth_test.go
index a7485fe..fa8ed54 100644
--- a/cmd/owl/web/auth_test.go
+++ b/cmd/owl/web/auth_test.go
@@ -16,6 +16,8 @@ func TestAuthPostWrongPassword(t *testing.T) {
repo, user := getSingleUserTestRepo()
user.ResetPassword("testpassword")
+ csrfToken := "test_csrf_token"
+
// Create Request and Response
form := url.Values{}
form.Add("password", "wrongpassword")
@@ -23,9 +25,12 @@ func TestAuthPostWrongPassword(t *testing.T) {
form.Add("redirect_uri", "http://example.com/response")
form.Add("response_type", "code")
form.Add("state", "test_state")
+ form.Add("csrf_token", csrfToken)
+
req, err := http.NewRequest("POST", user.AuthUrl()+"verify/", strings.NewReader(form.Encode()))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Content-Length", strconv.Itoa(len(form.Encode())))
+ req.AddCookie(&http.Cookie{Name: "csrf_token", Value: csrfToken})
assertions.AssertNoError(t, err, "Error creating request")
rr := httptest.NewRecorder()
router := main.SingleUserRouter(&repo)
@@ -39,6 +44,8 @@ func TestAuthPostCorrectPassword(t *testing.T) {
repo, user := getSingleUserTestRepo()
user.ResetPassword("testpassword")
+ csrfToken := "test_csrf_token"
+
// Create Request and Response
form := url.Values{}
form.Add("password", "testpassword")
@@ -46,9 +53,11 @@ func TestAuthPostCorrectPassword(t *testing.T) {
form.Add("redirect_uri", "http://example.com/response")
form.Add("response_type", "code")
form.Add("state", "test_state")
+ form.Add("csrf_token", csrfToken)
req, err := http.NewRequest("POST", user.AuthUrl()+"verify/", strings.NewReader(form.Encode()))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Content-Length", strconv.Itoa(len(form.Encode())))
+ req.AddCookie(&http.Cookie{Name: "csrf_token", Value: csrfToken})
assertions.AssertNoError(t, err, "Error creating request")
rr := httptest.NewRecorder()
router := main.SingleUserRouter(&repo)
diff --git a/cmd/owl/web/handler.go b/cmd/owl/web/handler.go
index 0d100aa..8a7ead3 100644
--- a/cmd/owl/web/handler.go
+++ b/cmd/owl/web/handler.go
@@ -100,6 +100,15 @@ func userAuthHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Reque
return
}
+ // Double Submit Cookie Pattern
+ // https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie
+ csrfToken := owl.GenerateRandomString(32)
+ cookie := http.Cookie{
+ Name: "csrf_token",
+ Value: csrfToken,
+ }
+ http.SetCookie(w, &cookie)
+
reqData := owl.AuthRequestData{
Me: me,
ClientId: clientId,
@@ -107,6 +116,7 @@ func userAuthHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Reque
State: state,
ResponseType: responseType,
User: user,
+ CsrfToken: csrfToken,
}
html, err := owl.RenderUserAuthPage(reqData)
@@ -204,6 +214,23 @@ func userAuthVerifyHandler(repo *owl.Repository) func(http.ResponseWriter, *http
response_type := r.FormValue("response_type")
state := r.FormValue("state")
+ // CSRF check
+ formCsrfToken := r.FormValue("csrf_token")
+ cookieCsrfToken, err := r.Cookie("csrf_token")
+
+ if err != nil {
+ println("Error getting csrf token from cookie: ", err.Error())
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte("Error getting csrf token from cookie"))
+ return
+ }
+ if formCsrfToken != cookieCsrfToken.Value {
+ println("Invalid csrf token")
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte("Invalid csrf token"))
+ return
+ }
+
password_valid := user.VerifyPassword(password)
if !password_valid {
http.Redirect(w, r,
diff --git a/embed/auth.html b/embed/auth.html
index 9ed5df9..03884ce 100644
--- a/embed/auth.html
+++ b/embed/auth.html
@@ -7,5 +7,6 @@
+
\ No newline at end of file
diff --git a/renderer.go b/renderer.go
index bd455d0..03e5540 100644
--- a/renderer.go
+++ b/renderer.go
@@ -27,6 +27,7 @@ type AuthRequestData struct {
State string
ResponseType string
User User
+ CsrfToken string
}
func renderEmbedTemplate(templateFile string, data interface{}) (string, error) {
diff --git a/user.go b/user.go
index 2c58b84..4141814 100644
--- a/user.go
+++ b/user.go
@@ -2,7 +2,6 @@ package owl
import (
"fmt"
- "math/rand"
"net/url"
"os"
"path"
@@ -287,12 +286,7 @@ func (user User) addAuthCode(code AuthCode) error {
func (user User) GenerateAuthCode(client_id string, redirect_uri string) (string, error) {
// generate code
- const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
- b := make([]byte, 32)
- for i := range b {
- b[i] = chars[rand.Intn(len(chars))]
- }
- code := string(b)
+ code := GenerateRandomString(32)
return code, user.addAuthCode(AuthCode{
Code: code,
ClientId: client_id,
diff --git a/utils.go b/utils.go
new file mode 100644
index 0000000..27083a2
--- /dev/null
+++ b/utils.go
@@ -0,0 +1,16 @@
+package owl
+
+import (
+ "crypto/rand"
+ "math/big"
+)
+
+func GenerateRandomString(length int) string {
+ chars := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
+ b := make([]rune, length)
+ for i := range b {
+ k, _ := rand.Int(rand.Reader, big.NewInt(int64(len(chars))))
+ b[i] = chars[k.Int64()]
+ }
+ return string(b)
+}