diff --git a/cmd/owl/web/editor_handler.go b/cmd/owl/web/editor_handler.go
new file mode 100644
index 0000000..77b6f1e
--- /dev/null
+++ b/cmd/owl/web/editor_handler.go
@@ -0,0 +1,237 @@
+package web
+
+import (
+ "h4kor/owl-blogs"
+ "net/http"
+ "time"
+
+ "github.com/julienschmidt/httprouter"
+)
+
+func isUserLoggedIn(user *owl.User, r *http.Request) bool {
+ sessionCookie, err := r.Cookie("session")
+ if err != nil {
+ return false
+ }
+ return user.ValidateSession(sessionCookie.Value)
+}
+
+func setCSRFCookie(w http.ResponseWriter) string {
+ csrfToken := owl.GenerateRandomString(32)
+ cookie := http.Cookie{
+ Name: "csrf_token",
+ Value: csrfToken,
+ HttpOnly: true,
+ SameSite: http.SameSiteStrictMode,
+ }
+ http.SetCookie(w, &cookie)
+ return csrfToken
+}
+
+func checkCSRF(r *http.Request) bool {
+ // 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())
+ return false
+ }
+ if formCsrfToken != cookieCsrfToken.Value {
+ println("Invalid csrf token")
+ return false
+ }
+ return true
+}
+
+func userLoginGetHandler(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 isUserLoggedIn(&user, r) {
+ http.Redirect(w, r, user.EditorUrl(), http.StatusFound)
+ return
+ }
+ csrfToken := setCSRFCookie(w)
+ html, err := owl.RenderLoginPage(user, csrfToken)
+ if err != nil {
+ println("Error rendering login page: ", err.Error())
+ w.WriteHeader(http.StatusInternalServerError)
+ html, _ := owl.RenderUserError(user, owl.ErrorMessage{
+ Error: "Internal server error",
+ Message: "Internal server error",
+ })
+ w.Write([]byte(html))
+ return
+ }
+ w.Write([]byte(html))
+ }
+}
+
+func userLoginPostHandler(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
+ }
+ err = r.ParseForm()
+ if err != nil {
+ println("Error parsing form: ", err.Error())
+ w.WriteHeader(http.StatusInternalServerError)
+ html, _ := owl.RenderUserError(user, owl.ErrorMessage{
+ Error: "Internal server error",
+ Message: "Internal server error",
+ })
+ w.Write([]byte(html))
+ return
+ }
+
+ // CSRF check
+ if !checkCSRF(r) {
+ w.WriteHeader(http.StatusBadRequest)
+ html, _ := owl.RenderUserError(user, owl.ErrorMessage{
+ Error: "CSRF Error",
+ Message: "Invalid csrf token",
+ })
+ w.Write([]byte(html))
+ return
+ }
+
+ password := r.Form.Get("password")
+ if password == "" {
+ userLoginGetHandler(repo)(w, r, ps)
+ return
+ }
+ if !user.VerifyPassword(password) {
+ userLoginGetHandler(repo)(w, r, ps)
+ return
+ }
+
+ // set session cookie
+ cookie := http.Cookie{
+ Name: "session",
+ Value: user.CreateNewSession(),
+ Path: "/",
+ Expires: time.Now().Add(30 * 24 * time.Hour),
+ HttpOnly: true,
+ }
+ http.SetCookie(w, &cookie)
+ http.Redirect(w, r, user.EditorUrl(), http.StatusFound)
+ }
+}
+
+func userEditorGetHandler(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 !isUserLoggedIn(&user, r) {
+ http.Redirect(w, r, user.EditorLoginUrl(), http.StatusFound)
+ return
+ }
+
+ csrfToken := setCSRFCookie(w)
+ html, err := owl.RenderEditorPage(user, csrfToken)
+ if err != nil {
+ println("Error rendering editor page: ", err.Error())
+ w.WriteHeader(http.StatusInternalServerError)
+ html, _ := owl.RenderUserError(user, owl.ErrorMessage{
+ Error: "Internal server error",
+ Message: "Internal server error",
+ })
+ w.Write([]byte(html))
+ return
+ }
+ w.Write([]byte(html))
+ }
+}
+
+func userEditorPostHandler(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 !isUserLoggedIn(&user, r) {
+ http.Redirect(w, r, user.EditorLoginUrl(), http.StatusFound)
+ return
+ }
+
+ err = r.ParseForm()
+ if err != nil {
+ println("Error parsing form: ", err.Error())
+ w.WriteHeader(http.StatusInternalServerError)
+ html, _ := owl.RenderUserError(user, owl.ErrorMessage{
+ Error: "Internal server error",
+ Message: "Internal server error",
+ })
+ w.Write([]byte(html))
+ return
+ }
+
+ // CSRF check
+ if !checkCSRF(r) {
+ w.WriteHeader(http.StatusBadRequest)
+ html, _ := owl.RenderUserError(user, owl.ErrorMessage{
+ Error: "CSRF Error",
+ Message: "Invalid csrf token",
+ })
+ w.Write([]byte(html))
+ return
+ }
+
+ // get form values
+ post_type := r.Form.Get("type")
+ title := r.Form.Get("title")
+ description := r.Form.Get("description")
+ content := r.Form.Get("content")
+ draft := r.Form.Get("draft")
+
+ // validate form values
+ if post_type == "article" && title == "" {
+ userEditorGetHandler(repo)(w, r, ps)
+ return
+ }
+
+ // create post
+ post, err := user.CreateNewPostFull(owl.PostMeta{
+ Type: post_type,
+ Title: title,
+ Description: description,
+ Draft: draft == "on",
+ Date: time.Now(),
+ }, content)
+
+ if err != nil {
+ println("Error creating post: ", err.Error())
+ w.WriteHeader(http.StatusInternalServerError)
+ html, _ := owl.RenderUserError(user, owl.ErrorMessage{
+ Error: "Internal server error",
+ Message: "Internal server error",
+ })
+ w.Write([]byte(html))
+ return
+ }
+
+ // redirect to post
+ if !post.Meta().Draft {
+ http.Redirect(w, r, post.FullUrl(), http.StatusFound)
+ } else {
+ http.Redirect(w, r, user.EditorUrl(), http.StatusFound)
+ }
+ }
+}
diff --git a/cmd/owl/web/server.go b/cmd/owl/web/server.go
index d828508..b14f3a7 100644
--- a/cmd/owl/web/server.go
+++ b/cmd/owl/web/server.go
@@ -14,16 +14,27 @@ func Router(repo *owl.Repository) http.Handler {
router.ServeFiles("/static/*filepath", http.Dir(repo.StaticDir()))
router.GET("/", repoIndexHandler(repo))
router.GET("/user/:user/", userIndexHandler(repo))
+ // Editor
+ router.GET("/user/:user/editor/auth/", userLoginGetHandler(repo))
+ router.POST("/user/:user/editor/auth/", userLoginPostHandler(repo))
+ router.GET("/user/:user/editor/", userEditorGetHandler(repo))
+ router.POST("/user/:user/editor/", userEditorPostHandler(repo))
+ // Media
+ router.GET("/user/:user/media/*filepath", userMediaHandler(repo))
+ // RSS
+ router.GET("/user/:user/index.xml", userRSSHandler(repo))
+ // Posts
+ router.GET("/user/:user/posts/:post/", postHandler(repo))
+ router.GET("/user/:user/posts/:post/media/*filepath", postMediaHandler(repo))
+ // Webmention
+ router.POST("/user/:user/webmention/", userWebmentionHandler(repo))
+ // Micropub
+ router.POST("/user/:user/micropub/", userMicropubHandler(repo))
+ // IndieAuth
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))
- 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
@@ -33,16 +44,27 @@ func SingleUserRouter(repo *owl.Repository) http.Handler {
router := httprouter.New()
router.ServeFiles("/static/*filepath", http.Dir(repo.StaticDir()))
router.GET("/", userIndexHandler(repo))
+ // Editor
+ router.GET("/editor/auth/", userLoginGetHandler(repo))
+ router.POST("/editor/auth/", userLoginPostHandler(repo))
+ router.GET("/editor/", userEditorGetHandler(repo))
+ router.POST("/editor/", userEditorPostHandler(repo))
+ // Media
+ router.GET("/media/*filepath", userMediaHandler(repo))
+ // RSS
+ router.GET("/index.xml", userRSSHandler(repo))
+ // Posts
+ router.GET("/posts/:post/", postHandler(repo))
+ router.GET("/posts/:post/media/*filepath", postMediaHandler(repo))
+ // Webmention
+ router.POST("/webmention/", userWebmentionHandler(repo))
+ // Micropub
+ router.POST("/micropub/", userMicropubHandler(repo))
+ // IndieAuth
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))
- 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/editor/editor.html b/embed/editor/editor.html
new file mode 100644
index 0000000..237a463
--- /dev/null
+++ b/embed/editor/editor.html
@@ -0,0 +1,23 @@
+Write Article
+
+Write Note
+ TODO
+