diff --git a/cmd/owl/web/handler.go b/cmd/owl/web/handler.go index 4c5a160..5b0355a 100644 --- a/cmd/owl/web/handler.go +++ b/cmd/owl/web/handler.go @@ -8,6 +8,7 @@ import ( "os" "path" "strings" + "time" "github.com/julienschmidt/httprouter" ) @@ -249,6 +250,78 @@ func postMediaHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Requ } } +func userMicropubHandler(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 + } + + // parse request form + err = r.ParseForm() + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("Bad request")) + return + } + + // verify access token + token := r.Header.Get("Authorization") + if token == "" { + token = r.Form.Get("access_token") + } else { + token = strings.TrimPrefix(token, "Bearer ") + } + + valid, _ := user.ValidateAccessToken(token) + if !valid { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte("Unauthorized")) + return + } + + h := r.Form.Get("h") + content := r.Form.Get("content") + name := r.Form.Get("name") + inReplyTo := r.Form.Get("in-reply-to") + + if h != "entry" { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("Bad request. h must be entry. Got " + h)) + return + } + if content == "" { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("Bad request. content is required")) + return + } + + // create post + post, err := user.CreateNewPostFull( + owl.PostMeta{ + Title: name, + Reply: owl.Reply{ + Url: inReplyTo, + }, + Date: time.Now(), + }, + content, + ) + + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Internal server error")) + return + } + + w.Header().Add("Location", post.FullUrl()) + w.WriteHeader(http.StatusCreated) + + } +} + func userMediaHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Request, httprouter.Params) { return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { filepath := ps.ByName("filepath") diff --git a/cmd/owl/web/micropub_test.go b/cmd/owl/web/micropub_test.go new file mode 100644 index 0000000..d9574ff --- /dev/null +++ b/cmd/owl/web/micropub_test.go @@ -0,0 +1,183 @@ +package web_test + +import ( + "h4kor/owl-blogs" + main "h4kor/owl-blogs/cmd/owl/web" + "h4kor/owl-blogs/test/assertions" + "net/http" + "net/http/httptest" + "net/url" + "strconv" + "strings" + "testing" +) + +func TestMicropubMinimalArticle(t *testing.T) { + repo, user := getSingleUserTestRepo() + user.ResetPassword("testpassword") + + code, _ := user.GenerateAuthCode( + "test", "test", "test", "test", "test", + ) + token, _, _ := user.GenerateAccessToken(owl.AuthCode{ + Code: code, + ClientId: "test", + RedirectUri: "test", + CodeChallenge: "test", + CodeChallengeMethod: "test", + Scope: "test", + }) + + // Create Request and Response + form := url.Values{} + form.Add("h", "entry") + form.Add("name", "Test Article") + form.Add("content", "Test Content") + + req, err := http.NewRequest("POST", user.MicropubUrl(), 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.Header.Add("Authorization", "Bearer "+token) + assertions.AssertNoError(t, err, "Error creating request") + rr := httptest.NewRecorder() + router := main.SingleUserRouter(&repo) + router.ServeHTTP(rr, req) + + assertions.AssertStatus(t, rr, http.StatusCreated) +} + +func TestMicropubWithoutName(t *testing.T) { + repo, user := getSingleUserTestRepo() + user.ResetPassword("testpassword") + + code, _ := user.GenerateAuthCode( + "test", "test", "test", "test", "test", + ) + token, _, _ := user.GenerateAccessToken(owl.AuthCode{ + Code: code, + ClientId: "test", + RedirectUri: "test", + CodeChallenge: "test", + CodeChallengeMethod: "test", + Scope: "test", + }) + + // Create Request and Response + form := url.Values{} + form.Add("h", "entry") + form.Add("content", "Test Content") + + req, err := http.NewRequest("POST", user.MicropubUrl(), 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.Header.Add("Authorization", "Bearer "+token) + assertions.AssertNoError(t, err, "Error creating request") + rr := httptest.NewRecorder() + router := main.SingleUserRouter(&repo) + router.ServeHTTP(rr, req) + + assertions.AssertStatus(t, rr, http.StatusCreated) + loc_header := rr.Header().Get("Location") + assertions.Assert(t, loc_header != "", "Location header should be set") +} + +func TestMicropubAccessTokenInBody(t *testing.T) { + repo, user := getSingleUserTestRepo() + user.ResetPassword("testpassword") + + code, _ := user.GenerateAuthCode( + "test", "test", "test", "test", "test", + ) + token, _, _ := user.GenerateAccessToken(owl.AuthCode{ + Code: code, + ClientId: "test", + RedirectUri: "test", + CodeChallenge: "test", + CodeChallengeMethod: "test", + Scope: "test", + }) + + // Create Request and Response + form := url.Values{} + form.Add("h", "entry") + form.Add("content", "Test Content") + form.Add("access_token", token) + + req, err := http.NewRequest("POST", user.MicropubUrl(), 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.StatusCreated) + loc_header := rr.Header().Get("Location") + assertions.Assert(t, loc_header != "", "Location header should be set") +} + +// func TestMicropubAccessTokenInBoth(t *testing.T) { +// repo, user := getSingleUserTestRepo() +// user.ResetPassword("testpassword") + +// code, _ := user.GenerateAuthCode( +// "test", "test", "test", "test", "test", +// ) +// token, _, _ := user.GenerateAccessToken(owl.AuthCode{ +// Code: code, +// ClientId: "test", +// RedirectUri: "test", +// CodeChallenge: "test", +// CodeChallengeMethod: "test", +// Scope: "test", +// }) + +// // Create Request and Response +// form := url.Values{} +// form.Add("h", "entry") +// form.Add("content", "Test Content") +// form.Add("access_token", token) + +// req, err := http.NewRequest("POST", user.MicropubUrl(), 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.Header.Add("Authorization", "Bearer "+token) +// assertions.AssertNoError(t, err, "Error creating request") +// rr := httptest.NewRecorder() +// router := main.SingleUserRouter(&repo) +// router.ServeHTTP(rr, req) + +// assertions.AssertStatus(t, rr, http.StatusBadRequest) +// } + +func TestMicropubNoAccessToken(t *testing.T) { + repo, user := getSingleUserTestRepo() + user.ResetPassword("testpassword") + + code, _ := user.GenerateAuthCode( + "test", "test", "test", "test", "test", + ) + user.GenerateAccessToken(owl.AuthCode{ + Code: code, + ClientId: "test", + RedirectUri: "test", + CodeChallenge: "test", + CodeChallengeMethod: "test", + Scope: "test", + }) + + // Create Request and Response + form := url.Values{} + form.Add("h", "entry") + form.Add("content", "Test Content") + + req, err := http.NewRequest("POST", user.MicropubUrl(), 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/server.go b/cmd/owl/web/server.go index 4967735..d828508 100644 --- a/cmd/owl/web/server.go +++ b/cmd/owl/web/server.go @@ -23,6 +23,7 @@ func Router(repo *owl.Repository) http.Handler { router.GET("/user/:user/posts/:post/", postHandler(repo)) router.GET("/user/:user/posts/:post/media/*filepath", postMediaHandler(repo)) router.POST("/user/:user/webmention/", userWebmentionHandler(repo)) + router.POST("/user/:user/micropub/", userMicropubHandler(repo)) router.GET("/user/:user/.well-known/oauth-authorization-server", userAuthMetadataHandler(repo)) router.NotFound = http.HandlerFunc(notFoundHandler(repo)) return router @@ -41,6 +42,7 @@ func SingleUserRouter(repo *owl.Repository) http.Handler { router.GET("/posts/:post/", postHandler(repo)) router.GET("/posts/:post/media/*filepath", postMediaHandler(repo)) router.POST("/webmention/", userWebmentionHandler(repo)) + router.POST("/micropub/", userMicropubHandler(repo)) router.GET("/.well-known/oauth-authorization-server", userAuthMetadataHandler(repo)) router.NotFound = http.HandlerFunc(notFoundHandler(repo)) return router diff --git a/embed/initial/base.html b/embed/initial/base.html index 78ad250..5fee043 100644 --- a/embed/initial/base.html +++ b/embed/initial/base.html @@ -31,6 +31,7 @@ + {{ end }}