This commit is contained in:
Niko Abeler 2022-11-05 21:17:31 +01:00
parent 269262b381
commit f3f72d8111
7 changed files with 56 additions and 16 deletions

View File

@ -3,7 +3,6 @@ package main
import ( import (
"fmt" "fmt"
"h4kor/owl-blogs" "h4kor/owl-blogs"
"math/rand"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -35,14 +34,7 @@ var resetPasswordCmd = &cobra.Command{
} }
// generate a random password and print it // generate a random password and print it
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" password := owl.GenerateRandomString(16)
b := make([]byte, 16)
for i := range b {
b[i] = chars[rand.Intn(len(chars))]
}
password := string(b)
user.ResetPassword(password) user.ResetPassword(password)
fmt.Println("User: ", user.Name()) fmt.Println("User: ", user.Name())

View File

@ -16,6 +16,8 @@ func TestAuthPostWrongPassword(t *testing.T) {
repo, user := getSingleUserTestRepo() repo, user := getSingleUserTestRepo()
user.ResetPassword("testpassword") user.ResetPassword("testpassword")
csrfToken := "test_csrf_token"
// Create Request and Response // Create Request and Response
form := url.Values{} form := url.Values{}
form.Add("password", "wrongpassword") form.Add("password", "wrongpassword")
@ -23,9 +25,12 @@ func TestAuthPostWrongPassword(t *testing.T) {
form.Add("redirect_uri", "http://example.com/response") form.Add("redirect_uri", "http://example.com/response")
form.Add("response_type", "code") form.Add("response_type", "code")
form.Add("state", "test_state") form.Add("state", "test_state")
form.Add("csrf_token", csrfToken)
req, err := http.NewRequest("POST", user.AuthUrl()+"verify/", strings.NewReader(form.Encode())) 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-Type", "application/x-www-form-urlencoded")
req.Header.Add("Content-Length", strconv.Itoa(len(form.Encode()))) 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") assertions.AssertNoError(t, err, "Error creating request")
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
router := main.SingleUserRouter(&repo) router := main.SingleUserRouter(&repo)
@ -39,6 +44,8 @@ func TestAuthPostCorrectPassword(t *testing.T) {
repo, user := getSingleUserTestRepo() repo, user := getSingleUserTestRepo()
user.ResetPassword("testpassword") user.ResetPassword("testpassword")
csrfToken := "test_csrf_token"
// Create Request and Response // Create Request and Response
form := url.Values{} form := url.Values{}
form.Add("password", "testpassword") form.Add("password", "testpassword")
@ -46,9 +53,11 @@ func TestAuthPostCorrectPassword(t *testing.T) {
form.Add("redirect_uri", "http://example.com/response") form.Add("redirect_uri", "http://example.com/response")
form.Add("response_type", "code") form.Add("response_type", "code")
form.Add("state", "test_state") form.Add("state", "test_state")
form.Add("csrf_token", csrfToken)
req, err := http.NewRequest("POST", user.AuthUrl()+"verify/", strings.NewReader(form.Encode())) 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-Type", "application/x-www-form-urlencoded")
req.Header.Add("Content-Length", strconv.Itoa(len(form.Encode()))) 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") assertions.AssertNoError(t, err, "Error creating request")
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
router := main.SingleUserRouter(&repo) router := main.SingleUserRouter(&repo)

View File

@ -100,6 +100,15 @@ func userAuthHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Reque
return 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{ reqData := owl.AuthRequestData{
Me: me, Me: me,
ClientId: clientId, ClientId: clientId,
@ -107,6 +116,7 @@ func userAuthHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Reque
State: state, State: state,
ResponseType: responseType, ResponseType: responseType,
User: user, User: user,
CsrfToken: csrfToken,
} }
html, err := owl.RenderUserAuthPage(reqData) html, err := owl.RenderUserAuthPage(reqData)
@ -204,6 +214,23 @@ func userAuthVerifyHandler(repo *owl.Repository) func(http.ResponseWriter, *http
response_type := r.FormValue("response_type") response_type := r.FormValue("response_type")
state := r.FormValue("state") 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) password_valid := user.VerifyPassword(password)
if !password_valid { if !password_valid {
http.Redirect(w, r, http.Redirect(w, r,

View File

@ -7,5 +7,6 @@
<input type="hidden" name="redirect_uri" value="{{.RedirectUri}}"> <input type="hidden" name="redirect_uri" value="{{.RedirectUri}}">
<input type="hidden" name="response_type" value="{{.ResponseType}}"> <input type="hidden" name="response_type" value="{{.ResponseType}}">
<input type="hidden" name="state" value="{{.State}}"> <input type="hidden" name="state" value="{{.State}}">
<input type="hidden" name="csrf_token" value="{{.CsrfToken}}">
<input type="submit" value="Login"> <input type="submit" value="Login">
</form> </form>

View File

@ -27,6 +27,7 @@ type AuthRequestData struct {
State string State string
ResponseType string ResponseType string
User User User User
CsrfToken string
} }
func renderEmbedTemplate(templateFile string, data interface{}) (string, error) { func renderEmbedTemplate(templateFile string, data interface{}) (string, error) {

View File

@ -2,7 +2,6 @@ package owl
import ( import (
"fmt" "fmt"
"math/rand"
"net/url" "net/url"
"os" "os"
"path" "path"
@ -287,12 +286,7 @@ func (user User) addAuthCode(code AuthCode) error {
func (user User) GenerateAuthCode(client_id string, redirect_uri string) (string, error) { func (user User) GenerateAuthCode(client_id string, redirect_uri string) (string, error) {
// generate code // generate code
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" code := GenerateRandomString(32)
b := make([]byte, 32)
for i := range b {
b[i] = chars[rand.Intn(len(chars))]
}
code := string(b)
return code, user.addAuthCode(AuthCode{ return code, user.addAuthCode(AuthCode{
Code: code, Code: code,
ClientId: client_id, ClientId: client_id,

16
utils.go Normal file
View File

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