mirror of https://github.com/jlelse/GoBlog
Verify ActivityPub requests
This commit is contained in:
parent
0cb142bd20
commit
9e5af4b8a7
|
@ -85,13 +85,31 @@ func apHandleInbox(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
blogIri := blog.apIri()
|
blogIri := blog.apIri()
|
||||||
|
// Verify request
|
||||||
|
requestActor, err := apVerifySignature(r)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Parse activity
|
||||||
activity := make(map[string]interface{})
|
activity := make(map[string]interface{})
|
||||||
err := json.NewDecoder(r.Body).Decode(&activity)
|
err = json.NewDecoder(r.Body).Decode(&activity)
|
||||||
_ = r.Body.Close()
|
_ = r.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Failed to decode body", http.StatusBadRequest)
|
http.Error(w, "Failed to decode body", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Get and check activity actor
|
||||||
|
activityActor, ok := activity["actor"].(string)
|
||||||
|
if !ok {
|
||||||
|
http.Error(w, "actor in activity is no string", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if activityActor != requestActor.ID {
|
||||||
|
http.Error(w, "Request actor isn't activity actor", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Do
|
||||||
switch activity["type"] {
|
switch activity["type"] {
|
||||||
case "Follow":
|
case "Follow":
|
||||||
apAccept(blogName, blog, activity)
|
apAccept(blogName, blog, activity)
|
||||||
|
@ -99,8 +117,8 @@ func apHandleInbox(w http.ResponseWriter, r *http.Request) {
|
||||||
{
|
{
|
||||||
if object, ok := activity["object"].(map[string]interface{}); ok {
|
if object, ok := activity["object"].(map[string]interface{}); ok {
|
||||||
if objectType, ok := object["type"].(string); ok && objectType == "Follow" {
|
if objectType, ok := object["type"].(string); ok && objectType == "Follow" {
|
||||||
if iri, ok := object["actor"].(string); ok && iri == activity["actor"] {
|
if iri, ok := object["actor"].(string); ok && iri == activityActor {
|
||||||
_ = apRemoveFollower(blogName, iri)
|
_ = apRemoveFollower(blogName, activityActor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,30 +146,54 @@ func apHandleInbox(w http.ResponseWriter, r *http.Request) {
|
||||||
case "Delete":
|
case "Delete":
|
||||||
case "Block":
|
case "Block":
|
||||||
{
|
{
|
||||||
if object, ok := activity["object"].(string); ok && len(object) > 0 && activity["actor"] == object {
|
if object, ok := activity["object"].(string); ok && len(object) > 0 && object == activityActor {
|
||||||
_ = apRemoveFollower(blogName, object)
|
_ = apRemoveFollower(blogName, activityActor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "Like":
|
case "Like":
|
||||||
{
|
{
|
||||||
likeActor, likeActorOk := activity["actor"].(string)
|
|
||||||
likeObject, likeObjectOk := activity["object"].(string)
|
likeObject, likeObjectOk := activity["object"].(string)
|
||||||
if likeActorOk && likeObjectOk && len(likeActor) > 0 && len(likeObject) > 0 && strings.Contains(likeObject, blogIri) {
|
if likeObjectOk && len(likeObject) > 0 && strings.Contains(likeObject, blogIri) {
|
||||||
sendNotification(fmt.Sprintf("%s liked %s", likeActor, likeObject))
|
sendNotification(fmt.Sprintf("%s liked %s", activityActor, likeObject))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "Announce":
|
case "Announce":
|
||||||
{
|
{
|
||||||
announceActor, announceActorOk := activity["actor"].(string)
|
|
||||||
announceObject, announceObjectOk := activity["object"].(string)
|
announceObject, announceObjectOk := activity["object"].(string)
|
||||||
if announceActorOk && announceObjectOk && len(announceActor) > 0 && len(announceObject) > 0 && strings.Contains(announceObject, blogIri) {
|
if announceObjectOk && len(announceObject) > 0 && strings.Contains(announceObject, blogIri) {
|
||||||
sendNotification(fmt.Sprintf("%s announced %s", announceActor, announceObject))
|
sendNotification(fmt.Sprintf("%s announced %s", activityActor, announceObject))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Return 201
|
// Return 201
|
||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
}
|
||||||
|
|
||||||
|
func apVerifySignature(r *http.Request) (*asPerson, error) {
|
||||||
|
verifier, err := httpsig.NewVerifier(r)
|
||||||
|
if err != nil {
|
||||||
|
// Error with signature header etc.
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
keyID := verifier.KeyId()
|
||||||
|
actor, err := apGetRemoteActor(keyID)
|
||||||
|
if err != nil {
|
||||||
|
// Actor not found or something else bad
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if actor.PublicKey == nil {
|
||||||
|
return nil, errors.New("Actor has no public key")
|
||||||
|
}
|
||||||
|
block, _ := pem.Decode([]byte(actor.PublicKey.PublicKeyPem))
|
||||||
|
if block == nil {
|
||||||
|
return nil, errors.New("Public key invalid")
|
||||||
|
}
|
||||||
|
pubKey, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
// Unable to parse public key
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return actor, verifier.Verify(pubKey, httpsig.RSA_SHA256)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleWellKnownHostMeta(w http.ResponseWriter, r *http.Request) {
|
func handleWellKnownHostMeta(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -222,6 +264,11 @@ func (p *post) apPost() {
|
||||||
createActivity["type"] = "Create"
|
createActivity["type"] = "Create"
|
||||||
createActivity["object"] = n
|
createActivity["object"] = n
|
||||||
apSendToAllFollowers(p.Blog, createActivity)
|
apSendToAllFollowers(p.Blog, createActivity)
|
||||||
|
if n.InReplyTo != "" {
|
||||||
|
// Is reply, so announce it
|
||||||
|
time.Sleep(30 * time.Second)
|
||||||
|
p.apAnnounce()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *post) apUpdate() {
|
func (p *post) apUpdate() {
|
||||||
|
@ -233,11 +280,26 @@ func (p *post) apUpdate() {
|
||||||
updateActivity["@context"] = asContext
|
updateActivity["@context"] = asContext
|
||||||
updateActivity["actor"] = appConfig.Blogs[p.Blog].apIri()
|
updateActivity["actor"] = appConfig.Blogs[p.Blog].apIri()
|
||||||
updateActivity["id"] = appConfig.Server.PublicAddress + p.Path
|
updateActivity["id"] = appConfig.Server.PublicAddress + p.Path
|
||||||
|
updateActivity["published"] = time.Now().Format("2006-01-02T15:04:05-07:00")
|
||||||
updateActivity["type"] = "Update"
|
updateActivity["type"] = "Update"
|
||||||
updateActivity["object"] = n
|
updateActivity["object"] = n
|
||||||
apSendToAllFollowers(p.Blog, updateActivity)
|
apSendToAllFollowers(p.Blog, updateActivity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *post) apAnnounce() {
|
||||||
|
if !appConfig.ActivityPub.Enabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
announceActivity := make(map[string]interface{})
|
||||||
|
announceActivity["@context"] = asContext
|
||||||
|
announceActivity["actor"] = appConfig.Blogs[p.Blog].apIri()
|
||||||
|
announceActivity["id"] = appConfig.Server.PublicAddress + p.Path + "#announce"
|
||||||
|
announceActivity["published"] = p.toASNote().Published
|
||||||
|
announceActivity["type"] = "Announce"
|
||||||
|
announceActivity["object"] = appConfig.Server.PublicAddress + p.Path
|
||||||
|
apSendToAllFollowers(p.Blog, announceActivity)
|
||||||
|
}
|
||||||
|
|
||||||
func (p *post) apDelete() {
|
func (p *post) apDelete() {
|
||||||
if !appConfig.ActivityPub.Enabled {
|
if !appConfig.ActivityPub.Enabled {
|
||||||
return
|
return
|
||||||
|
|
|
@ -88,7 +88,7 @@ func (p *post) toASNote() *asNote {
|
||||||
// Content
|
// Content
|
||||||
as.Content = string(p.html())
|
as.Content = string(p.html())
|
||||||
// Attachments
|
// Attachments
|
||||||
if images := p.Parameters["images"]; len(images) > 0 {
|
if images := p.Parameters[appConfig.Micropub.PhotoParam]; len(images) > 0 {
|
||||||
for _, image := range images {
|
for _, image := range images {
|
||||||
as.Attachment = append(as.Attachment, &asAttachment{
|
as.Attachment = append(as.Attachment, &asAttachment{
|
||||||
Type: "Image",
|
Type: "Image",
|
||||||
|
@ -109,7 +109,7 @@ func (p *post) toASNote() *asNote {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Reply
|
// Reply
|
||||||
if replyLink := p.firstParameter("replylink"); replyLink != "" {
|
if replyLink := p.firstParameter(appConfig.Micropub.ReplyParam); replyLink != "" {
|
||||||
as.InReplyTo = replyLink
|
as.InReplyTo = replyLink
|
||||||
}
|
}
|
||||||
return as
|
return as
|
||||||
|
|
Loading…
Reference in New Issue