From ff563c6f05458f4f39f4316d278b6f3da6292f16 Mon Sep 17 00:00:00 2001 From: Niko Abeler Date: Sun, 6 Nov 2022 19:38:27 +0100 Subject: [PATCH] WIP access token --- cmd/owl/web/auth_test.go | 58 +++++++++++++++++++++++++++ cmd/owl/web/handler.go | 86 ++++++++++++++++++++++++++++++---------- cmd/owl/web/server.go | 2 + embed/initial/base.html | 1 + user.go | 38 ++++++++++++++++++ 5 files changed, 165 insertions(+), 20 deletions(-) diff --git a/cmd/owl/web/auth_test.go b/cmd/owl/web/auth_test.go index f23019f..ba1fe95 100644 --- a/cmd/owl/web/auth_test.go +++ b/cmd/owl/web/auth_test.go @@ -339,3 +339,61 @@ func TestAuthRedirectUriSameHost(t *testing.T) { assertions.AssertStatus(t, rr, http.StatusOK) } + +func TestAccessTokenCorrectPassword(t *testing.T) { + repo, user := getSingleUserTestRepo() + user.ResetPassword("testpassword") + code, _ := user.GenerateAuthCode("http://example.com", "http://example.com/response", "", "") + + // Create Request and Response + form := url.Values{} + form.Add("code", code) + form.Add("client_id", "http://example.com") + form.Add("redirect_uri", "http://example.com/response") + form.Add("grant_type", "authorization_code") + req, err := http.NewRequest("POST", user.AuthUrl()+"token/", strings.NewReader(form.Encode())) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("Content-Length", strconv.Itoa(len(form.Encode()))) + assertions.AssertNoError(t, err, "Error creating request") + rr := httptest.NewRecorder() + router := main.SingleUserRouter(&repo) + router.ServeHTTP(rr, req) + + assertions.AssertStatus(t, rr, http.StatusOK) + // parse response as json + type responseType struct { + Me string `json:"me"` + TokenType string `json:"token_type"` + AccessToken string `json:"access_token"` + ExpiresIn int `json:"expires_in"` + RefreshToken string `json:"refresh_token"` + } + var response responseType + json.Unmarshal(rr.Body.Bytes(), &response) + assertions.AssertEqual(t, response.Me, user.FullUrl()) + assertions.AssertEqual(t, response.TokenType, "Bearer") + assertions.Assert(t, response.ExpiresIn > 0, "ExpiresIn should be greater than 0") + assertions.Assert(t, len(response.AccessToken) > 0, "AccessToken should be greater than 0") +} + +func TestAccessTokenWithIncorrectCode(t *testing.T) { + repo, user := getSingleUserTestRepo() + user.ResetPassword("testpassword") + user.GenerateAuthCode("http://example.com", "http://example.com/response", "", "") + + // Create Request and Response + form := url.Values{} + form.Add("code", "wrongcode") + form.Add("client_id", "http://example.com") + form.Add("redirect_uri", "http://example.com/response") + form.Add("grant_type", "authorization_code") + req, err := http.NewRequest("POST", user.AuthUrl()+"token/", strings.NewReader(form.Encode())) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("Content-Length", strconv.Itoa(len(form.Encode()))) + assertions.AssertNoError(t, err, "Error creating request") + rr := httptest.NewRecorder() + router := main.SingleUserRouter(&repo) + router.ServeHTTP(rr, req) + + assertions.AssertStatus(t, rr, http.StatusUnauthorized) +} diff --git a/cmd/owl/web/handler.go b/cmd/owl/web/handler.go index 0a07aba..b5ca022 100644 --- a/cmd/owl/web/handler.go +++ b/cmd/owl/web/handler.go @@ -171,6 +171,30 @@ func userAuthHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Reque } } +func verifyAuthCodeRequest(user owl.User, w http.ResponseWriter, r *http.Request) bool { + // get form data from post request + err := r.ParseForm() + if err != nil { + println("Error parsing form: ", err.Error()) + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("Error parsing form")) + return false + } + code := r.Form.Get("code") + client_id := r.Form.Get("client_id") + redirect_uri := r.Form.Get("redirect_uri") + code_verifier := r.Form.Get("code_verifier") + + // check if request is valid + valid := user.VerifyAuthCode(code, client_id, redirect_uri, code_verifier) + if !valid { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte("Invalid code")) + return false + } + return true +} + func userAuthProfileHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Request, httprouter.Params) { return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { user, err := getUserFromRepo(repo, ps) @@ -180,26 +204,7 @@ func userAuthProfileHandler(repo *owl.Repository) func(http.ResponseWriter, *htt return } - // get form data from post request - err = r.ParseForm() - if err != nil { - println("Error parsing form: ", err.Error()) - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("Error parsing form")) - return - } - code := r.Form.Get("code") - client_id := r.Form.Get("client_id") - redirect_uri := r.Form.Get("redirect_uri") - code_verifier := r.Form.Get("code_verifier") - - // check if request is valid - valid := user.VerifyAuthCode(code, client_id, redirect_uri, code_verifier) - if !valid { - w.WriteHeader(http.StatusUnauthorized) - w.Write([]byte("Invalid code")) - return - } else { + if verifyAuthCodeRequest(user, w, r) { w.WriteHeader(http.StatusOK) type ResponseProfile struct { Name string `json:"name"` @@ -227,7 +232,48 @@ func userAuthProfileHandler(repo *owl.Repository) func(http.ResponseWriter, *htt w.Write(jsonData) return } + } +} +func userAuthTokenHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Request, httprouter.Params) { + return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + user, err := getUserFromRepo(repo, ps) + if err != nil { + println("Error getting user: ", err.Error()) + notFoundHandler(repo)(w, r) + return + } + + if verifyAuthCodeRequest(user, w, r) { + type Response struct { + Me string `json:"me"` + TokenType string `json:"token_type"` + AccessToken string `json:"access_token"` + ExpiresIn int `json:"expires_in"` + RefreshToken string `json:"refresh_token"` + } + accessToken, duration, err := user.GenerateAccessToken() + if err != nil { + println("Error generating access token: ", err.Error()) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Internal server error")) + return + } + response := Response{ + Me: user.FullUrl(), + TokenType: "Bearer", + AccessToken: accessToken, + ExpiresIn: duration, + } + jsonData, err := json.Marshal(response) + if err != nil { + println("Error marshalling json: ", err.Error()) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Internal server error")) + } + w.Write(jsonData) + return + } } } diff --git a/cmd/owl/web/server.go b/cmd/owl/web/server.go index 628df4e..02937c1 100644 --- a/cmd/owl/web/server.go +++ b/cmd/owl/web/server.go @@ -17,6 +17,7 @@ func Router(repo *owl.Repository) http.Handler { router.GET("/user/:user/auth/", userAuthHandler(repo)) router.POST("/user/:user/auth/", userAuthProfileHandler(repo)) router.POST("/user/:user/auth/verify/", userAuthVerifyHandler(repo)) + router.POST("/user/:user/auth/token/", userAuthTokenHandler(repo)) router.GET("/user/:user/media/*filepath", userMediaHandler(repo)) router.GET("/user/:user/index.xml", userRSSHandler(repo)) router.GET("/user/:user/posts/:post/", postHandler(repo)) @@ -33,6 +34,7 @@ func SingleUserRouter(repo *owl.Repository) http.Handler { router.GET("/auth/", userAuthHandler(repo)) router.POST("/auth/", userAuthProfileHandler(repo)) router.POST("/auth/verify/", userAuthVerifyHandler(repo)) + router.POST("/auth/token/", userAuthTokenHandler(repo)) router.GET("/media/*filepath", userMediaHandler(repo)) router.GET("/index.xml", userRSSHandler(repo)) router.GET("/posts/:post/", postHandler(repo)) diff --git a/embed/initial/base.html b/embed/initial/base.html index 003f0bb..ff99bb4 100644 --- a/embed/initial/base.html +++ b/embed/initial/base.html @@ -29,6 +29,7 @@ {{ if .User.AuthUrl }} + {{ end }}