2022-11-29 19:36:50 +00:00
|
|
|
package web
|
|
|
|
|
|
|
|
import (
|
2022-12-04 18:03:09 +00:00
|
|
|
"fmt"
|
2022-11-29 19:36:50 +00:00
|
|
|
"h4kor/owl-blogs"
|
|
|
|
"net/http"
|
2023-01-12 20:21:29 +00:00
|
|
|
"strings"
|
2022-12-04 18:03:09 +00:00
|
|
|
"sync"
|
2022-11-29 19:36:50 +00:00
|
|
|
"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)
|
2022-12-04 18:48:46 +00:00
|
|
|
|
|
|
|
// get error from query
|
|
|
|
error_type := r.URL.Query().Get("error")
|
|
|
|
|
|
|
|
html, err := owl.RenderLoginPage(user, error_type, csrfToken)
|
2022-11-29 19:36:50 +00:00
|
|
|
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")
|
2022-12-04 18:48:46 +00:00
|
|
|
if password == "" || !user.VerifyPassword(password) {
|
|
|
|
http.Redirect(w, r, user.EditorLoginUrl()+"?error=wrong_password", http.StatusFound)
|
2022-11-29 19:36:50 +00:00
|
|
|
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")
|
2023-01-13 18:33:17 +00:00
|
|
|
content := strings.ReplaceAll(r.Form.Get("content"), "\r", "")
|
2022-11-29 19:36:50 +00:00
|
|
|
draft := r.Form.Get("draft")
|
|
|
|
|
2023-01-12 20:21:29 +00:00
|
|
|
// recipe values
|
|
|
|
recipe_yield := r.Form.Get("yield")
|
2023-01-13 18:33:17 +00:00
|
|
|
recipe_ingredients := strings.ReplaceAll(r.Form.Get("ingredients"), "\r", "")
|
2023-01-12 20:21:29 +00:00
|
|
|
recipe_duration := r.Form.Get("duration")
|
|
|
|
|
2022-12-04 14:45:51 +00:00
|
|
|
// conditional values
|
|
|
|
reply_url := r.Form.Get("reply_url")
|
|
|
|
bookmark_url := r.Form.Get("bookmark_url")
|
|
|
|
|
2022-11-29 19:36:50 +00:00
|
|
|
// validate form values
|
2022-12-04 14:45:51 +00:00
|
|
|
if post_type == "" {
|
2022-12-04 18:15:50 +00:00
|
|
|
html, _ := owl.RenderUserError(user, owl.ErrorMessage{
|
|
|
|
Error: "Missing post type",
|
|
|
|
Message: "Post type is required",
|
|
|
|
})
|
|
|
|
w.Write([]byte(html))
|
2022-11-29 19:36:50 +00:00
|
|
|
return
|
|
|
|
}
|
2023-01-12 20:21:29 +00:00
|
|
|
if (post_type == "article" || post_type == "page" || post_type == "recipe") && title == "" {
|
2022-12-04 18:15:50 +00:00
|
|
|
html, _ := owl.RenderUserError(user, owl.ErrorMessage{
|
|
|
|
Error: "Missing Title",
|
|
|
|
Message: "Articles and Pages must have a title",
|
|
|
|
})
|
|
|
|
w.Write([]byte(html))
|
2022-11-29 19:47:33 +00:00
|
|
|
return
|
|
|
|
}
|
2022-12-04 14:45:51 +00:00
|
|
|
if post_type == "reply" && reply_url == "" {
|
|
|
|
html, _ := owl.RenderUserError(user, owl.ErrorMessage{
|
|
|
|
Error: "Missing URL",
|
|
|
|
Message: "You must provide a URL to reply to",
|
|
|
|
})
|
|
|
|
w.Write([]byte(html))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if post_type == "bookmark" && bookmark_url == "" {
|
|
|
|
html, _ := owl.RenderUserError(user, owl.ErrorMessage{
|
|
|
|
Error: "Missing URL",
|
|
|
|
Message: "You must provide a URL to bookmark",
|
|
|
|
})
|
|
|
|
w.Write([]byte(html))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: scrape reply_url for title and description
|
|
|
|
// TODO: scrape bookmark_url for title and description
|
2022-11-29 19:36:50 +00:00
|
|
|
|
|
|
|
// create post
|
2022-12-05 19:47:52 +00:00
|
|
|
post, err := user.CreateNewPost(owl.PostMeta{
|
2022-11-29 19:36:50 +00:00
|
|
|
Type: post_type,
|
|
|
|
Title: title,
|
|
|
|
Description: description,
|
|
|
|
Draft: draft == "on",
|
|
|
|
Date: time.Now(),
|
2022-12-05 19:31:48 +00:00
|
|
|
Reply: owl.ReplyData{
|
2022-12-04 14:45:51 +00:00
|
|
|
Url: reply_url,
|
|
|
|
},
|
2022-12-05 19:31:48 +00:00
|
|
|
Bookmark: owl.BookmarkData{
|
2022-12-04 14:45:51 +00:00
|
|
|
Url: bookmark_url,
|
|
|
|
},
|
2023-01-12 20:21:29 +00:00
|
|
|
Recipe: owl.RecipeData{
|
|
|
|
Yield: recipe_yield,
|
|
|
|
Ingredients: strings.Split(recipe_ingredients, "\n"),
|
|
|
|
Duration: recipe_duration,
|
|
|
|
},
|
2022-11-29 19:36:50 +00:00
|
|
|
}, 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 {
|
2022-12-04 18:03:09 +00:00
|
|
|
// scan for webmentions
|
|
|
|
post.ScanForLinks()
|
|
|
|
webmentions := post.OutgoingWebmentions()
|
|
|
|
println("Found ", len(webmentions), " links")
|
|
|
|
wg := sync.WaitGroup{}
|
|
|
|
wg.Add(len(webmentions))
|
|
|
|
for _, mention := range post.OutgoingWebmentions() {
|
|
|
|
go func(mention owl.WebmentionOut) {
|
|
|
|
fmt.Printf("Sending webmention to %s", mention.Target)
|
|
|
|
defer wg.Done()
|
|
|
|
post.SendWebmention(mention)
|
|
|
|
}(mention)
|
|
|
|
}
|
|
|
|
wg.Wait()
|
2022-11-29 19:36:50 +00:00
|
|
|
http.Redirect(w, r, post.FullUrl(), http.StatusFound)
|
|
|
|
} else {
|
|
|
|
http.Redirect(w, r, user.EditorUrl(), http.StatusFound)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|