WIP micropub

This commit is contained in:
Niko Abeler 2022-11-08 21:22:02 +01:00
parent fbf14704e7
commit 2246cae3f7
6 changed files with 186 additions and 11 deletions

View File

@ -249,6 +249,76 @@ 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
}
// verify access token
token := r.Header.Get("Authorization")
if token == "" {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("Unauthorized"))
return
}
token = strings.TrimPrefix(token, "Bearer ")
valid, _ := user.ValidateAccessToken(token)
if !valid {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("Unauthorized"))
return
}
// parse request form
err = r.ParseForm()
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Bad request"))
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"))
return
}
if content == "" || name == "" {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Bad request. content and name are required"))
return
}
// create post
post, err := user.CreateNewPostFull(
owl.PostMeta{
Title: name,
Reply: owl.Reply{
Url: inReplyTo,
},
},
content,
)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Internal server error"))
return
}
w.WriteHeader(http.StatusCreated)
w.Header().Set("Location", post.FullUrl())
}
}
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")

View File

@ -0,0 +1,47 @@
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)
}

View File

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

View File

@ -31,6 +31,7 @@
<link rel="indieauth-metadata" href="{{ .User.IndieauthMetadataUrl }}">
<link rel="authorization_endpoint" href="{{ .User.AuthUrl}}">
<link rel="token_endpoint" href="{{ .User.TokenUrl}}">
<link rel="micropub" href="{{ .User.MicropubUrl}}">
{{ end }}
<style>
header {

41
user.go
View File

@ -88,6 +88,11 @@ func (user User) WebmentionUrl() string {
return url
}
func (user User) MicropubUrl() string {
url, _ := url.JoinPath(user.FullUrl(), "micropub/")
return url
}
func (user User) MediaUrl() string {
url, _ := url.JoinPath(user.UrlPath(), "media")
return url
@ -203,8 +208,8 @@ func (user User) GetPost(id string) (*Post, error) {
return &post, nil
}
func (user User) CreateNewPost(title string, draft bool) (*Post, error) {
folder_name := toDirectoryName(title)
func (user User) CreateNewPostFull(meta PostMeta, content string) (*Post, error) {
folder_name := toDirectoryName(meta.Title)
post_dir := path.Join(user.Dir(), "public", folder_name)
// if post already exists, add -n to the end of the name
@ -212,19 +217,13 @@ func (user User) CreateNewPost(title string, draft bool) (*Post, error) {
for {
if dirExists(post_dir) {
i++
folder_name = toDirectoryName(fmt.Sprintf("%s-%d", title, i))
folder_name = toDirectoryName(fmt.Sprintf("%s-%d", meta.Title, i))
post_dir = path.Join(user.Dir(), "public", folder_name)
} else {
break
}
}
post := Post{user: &user, id: folder_name, title: title}
meta := PostMeta{
Title: title,
Date: time.Now(),
Aliases: []string{},
Draft: draft,
}
post := Post{user: &user, id: folder_name, title: meta.Title}
initial_content := ""
initial_content += "---\n"
@ -236,7 +235,7 @@ func (user User) CreateNewPost(title string, draft bool) (*Post, error) {
initial_content += string(meta_bytes)
initial_content += "---\n"
initial_content += "\n"
initial_content += "Write your post here.\n"
initial_content += content
// create post file
os.Mkdir(post_dir, 0755)
@ -246,6 +245,16 @@ func (user User) CreateNewPost(title string, draft bool) (*Post, error) {
return &post, nil
}
func (user User) CreateNewPost(title string, draft bool) (*Post, error) {
meta := PostMeta{
Title: title,
Date: time.Now(),
Aliases: []string{},
Draft: draft,
}
return user.CreateNewPostFull(meta, title)
}
func (user User) Template() (string, error) {
// load base.html
path := path.Join(user.Dir(), "meta", "base.html")
@ -379,3 +388,13 @@ func (user User) GenerateAccessToken(authCode AuthCode) (string, int, error) {
Created: time.Now(),
})
}
func (user User) ValidateAccessToken(token string) (bool, AccessToken) {
tokens := user.getAccessTokens()
for _, t := range tokens {
if t.Token == token {
return true, t
}
}
return false, AccessToken{}
}

View File

@ -314,3 +314,39 @@ func TestVerifyPassword(t *testing.T) {
assertions.Assert(t, !user.VerifyPassword("0000000"), "Password should be incorrect")
}
func TestValidateAccessTokenWrongToken(t *testing.T) {
user := getTestUser()
code, _ := user.GenerateAuthCode(
"test", "test", "test", "test", "test",
)
user.GenerateAccessToken(owl.AuthCode{
Code: code,
ClientId: "test",
RedirectUri: "test",
CodeChallenge: "test",
CodeChallengeMethod: "test",
Scope: "test",
})
valid, _ := user.ValidateAccessToken("test")
assertions.Assert(t, !valid, "Token should be invalid")
}
func TestValidateAccessTokenCorrectToken(t *testing.T) {
user := getTestUser()
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",
})
valid, aToken := user.ValidateAccessToken(token)
assertions.Assert(t, valid, "Token should be valid")
assertions.Assert(t, aToken.ClientId == "test", "Token should be valid")
assertions.Assert(t, aToken.Token == token, "Token should be valid")
}