aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--activitypub/activityPubStruct.go12
-rw-r--r--cacheDatabase.go314
-rw-r--r--client.go12
-rw-r--r--db/cache.go325
-rw-r--r--db/database.go47
-rw-r--r--db/follow.go481
-rw-r--r--db/pem.go313
-rw-r--r--db/verification.go342
-rw-r--r--follow.go331
-rw-r--r--main.go298
-rw-r--r--util/proxy.go39
-rw-r--r--util/util.go37
-rw-r--r--webfinger.go12
-rw-r--r--webfinger/webfinger.go145
14 files changed, 1408 insertions, 1300 deletions
diff --git a/activitypub/activityPubStruct.go b/activitypub/activityPubStruct.go
index 002d514..637982d 100644
--- a/activitypub/activityPubStruct.go
+++ b/activitypub/activityPubStruct.go
@@ -204,3 +204,15 @@ type Collection struct {
AtContext
CollectionBase
}
+
+type ObjectBaseSortDesc []ObjectBase
+
+func (a ObjectBaseSortDesc) Len() int { return len(a) }
+func (a ObjectBaseSortDesc) Less(i, j int) bool { return a[i].Updated.After(a[j].Updated) }
+func (a ObjectBaseSortDesc) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+
+type ObjectBaseSortAsc []ObjectBase
+
+func (a ObjectBaseSortAsc) Len() int { return len(a) }
+func (a ObjectBaseSortAsc) Less(i, j int) bool { return a[i].Published.Before(a[j].Published) }
+func (a ObjectBaseSortAsc) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
diff --git a/cacheDatabase.go b/cacheDatabase.go
deleted file mode 100644
index b50065f..0000000
--- a/cacheDatabase.go
+++ /dev/null
@@ -1,314 +0,0 @@
-package main
-
-import (
- "database/sql"
- "fmt"
-
- _ "github.com/lib/pq"
-)
-
-func WriteObjectToCache(db *sql.DB, obj ObjectBase) ObjectBase {
-
- if IsPostBlacklist(db, obj.Content) {
- fmt.Println("\n\nBlacklist post blocked\n\n")
- return obj
- }
-
- if len(obj.Attachment) > 0 {
- if obj.Preview.Href != "" {
- WritePreviewToCache(db, *obj.Preview)
- }
-
- for i, _ := range obj.Attachment {
- WriteAttachmentToCache(db, obj.Attachment[i])
- WriteActivitytoCacheWithAttachment(db, obj, obj.Attachment[i], *obj.Preview)
- }
-
- } else {
- WriteActivitytoCache(db, obj)
- }
-
- WriteObjectReplyToDB(db, obj)
-
- if obj.Replies != nil {
- for _, e := range obj.Replies.OrderedItems {
- WriteObjectToCache(db, e)
- }
- }
-
- return obj
-}
-
-func WriteActorObjectToCache(db *sql.DB, obj ObjectBase) ObjectBase {
-
- if IsPostBlacklist(db, obj.Content) {
- return obj
- }
-
- if len(obj.Attachment) > 0 {
-
- if IsIDLocal(db, obj.Id) {
- return obj
- }
- if obj.Preview.Href != "" {
- WritePreviewToCache(db, *obj.Preview)
- }
-
- for i, _ := range obj.Attachment {
- WriteAttachmentToCache(db, obj.Attachment[i])
- WriteActivitytoCacheWithAttachment(db, obj, obj.Attachment[i], *obj.Preview)
- }
-
- } else {
- WriteActivitytoCache(db, obj)
- }
-
- WriteActorObjectReplyToDB(db, obj)
-
- if obj.Replies != nil {
- for _, e := range obj.Replies.OrderedItems {
- WriteActorObjectToCache(db, e)
- }
- }
-
- return obj
-}
-
-func WriteActivitytoCache(db *sql.DB, obj ObjectBase) {
-
- obj.Name = EscapeString(obj.Name)
- obj.Content = EscapeString(obj.Content)
- obj.AttributedTo = EscapeString(obj.AttributedTo)
-
- query := `select id from cacheactivitystream where id=$1`
-
- rows, err := db.Query(query, obj.Id)
-
- CheckError(err, "error selecting obj id from cache")
-
- var id string
- defer rows.Close()
- rows.Next()
- rows.Scan(&id)
-
- if id != "" {
- return
- }
-
- if obj.Updated.IsZero() {
- obj.Updated = obj.Published
- }
-
- query = `insert into cacheactivitystream (id, type, name, content, published, updated, attributedto, actor, tripcode, sensitive) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`
-
- _, e := db.Exec(query, obj.Id, obj.Type, obj.Name, obj.Content, obj.Published, obj.Updated, obj.AttributedTo, obj.Actor, obj.TripCode, obj.Sensitive)
-
- if e != nil {
- fmt.Println("error inserting new activity cache")
- panic(e)
- }
-}
-
-func WriteActivitytoCacheWithAttachment(db *sql.DB, obj ObjectBase, attachment ObjectBase, preview NestedObjectBase) {
-
- obj.Name = EscapeString(obj.Name)
- obj.Content = EscapeString(obj.Content)
- obj.AttributedTo = EscapeString(obj.AttributedTo)
-
- query := `select id from cacheactivitystream where id=$1`
-
- rows, err := db.Query(query, obj.Id)
-
- CheckError(err, "error selecting activity with attachment obj id cache")
-
- var id string
- defer rows.Close()
- rows.Next()
- rows.Scan(&id)
-
- if id != "" {
- return
- }
-
- if obj.Updated.IsZero() {
- obj.Updated = obj.Published
- }
-
- query = `insert into cacheactivitystream (id, type, name, content, attachment, preview, published, updated, attributedto, actor, tripcode, sensitive) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`
-
- _, e := db.Exec(query, obj.Id, obj.Type, obj.Name, obj.Content, attachment.Id, preview.Id, obj.Published, obj.Updated, obj.AttributedTo, obj.Actor, obj.TripCode, obj.Sensitive)
-
- if e != nil {
- fmt.Println("error inserting new activity with attachment cache")
- panic(e)
- }
-}
-
-func WriteAttachmentToCache(db *sql.DB, obj ObjectBase) {
-
- query := `select id from cacheactivitystream where id=$1`
-
- rows, err := db.Query(query, obj.Id)
-
- CheckError(err, "error selecting attachment obj id cache")
-
- var id string
- defer rows.Close()
- rows.Next()
- rows.Scan(&id)
-
- if id != "" {
- return
- }
-
- if obj.Updated.IsZero() {
- obj.Updated = obj.Published
- }
-
- query = `insert into cacheactivitystream (id, type, name, href, published, updated, attributedTo, mediatype, size) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)`
-
- _, e := db.Exec(query, obj.Id, obj.Type, obj.Name, obj.Href, obj.Published, obj.Updated, obj.AttributedTo, obj.MediaType, obj.Size)
-
- if e != nil {
- fmt.Println("error inserting new attachment cache")
- panic(e)
- }
-}
-
-func WritePreviewToCache(db *sql.DB, obj NestedObjectBase) {
-
- query := `select id from cacheactivitystream where id=$1`
-
- rows, err := db.Query(query, obj.Id)
-
- CheckError(err, "error selecting preview obj id cache")
-
- var id string
- defer rows.Close()
- rows.Next()
- rows.Scan(&id)
-
- if id != "" {
- return
- }
-
- if obj.Updated.IsZero() {
- obj.Updated = obj.Published
- }
-
- query = `insert into cacheactivitystream (id, type, name, href, published, updated, attributedTo, mediatype, size) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)`
-
- _, e := db.Exec(query, obj.Id, obj.Type, obj.Name, obj.Href, obj.Published, obj.Updated, obj.AttributedTo, obj.MediaType, obj.Size)
-
- if e != nil {
- fmt.Println("error inserting new preview cache")
- panic(e)
- }
-}
-
-func WriteObjectReplyToCache(db *sql.DB, obj ObjectBase) {
-
- for i, e := range obj.InReplyTo {
- if i == 0 || IsReplyInThread(db, obj.InReplyTo[0].Id, e.Id) {
-
- query := `select id from replies where id=$1`
-
- rows, err := db.Query(query, obj.Id)
-
- CheckError(err, "error selecting obj id cache reply")
-
- var id string
- defer rows.Close()
- rows.Next()
- rows.Scan(&id)
-
- if id != "" {
- return
- }
-
- query = `insert into cachereplies (id, inreplyto) values ($1, $2)`
-
- _, err = db.Exec(query, obj.Id, e.Id)
-
- if err != nil {
- fmt.Println("error inserting replies cache")
- panic(err)
- }
- }
- }
-
- if len(obj.InReplyTo) < 1 {
- query := `insert into cachereplies (id, inreplyto) values ($1, $2)`
-
- _, err := db.Exec(query, obj.Id, "")
-
- if err != nil {
- fmt.Println("error inserting replies cache")
- panic(err)
- }
- }
-}
-
-func WriteObjectReplyCache(db *sql.DB, obj ObjectBase) {
-
- if obj.Replies != nil {
- for _, e := range obj.Replies.OrderedItems {
-
- query := `select inreplyto from cachereplies where id=$1`
-
- rows, err := db.Query(query, obj.Id)
-
- CheckError(err, "error selecting obj id cache reply")
-
- var inreplyto string
- defer rows.Close()
- rows.Next()
- rows.Scan(&inreplyto)
-
- if inreplyto != "" {
- return
- }
-
- query = `insert into cachereplies (id, inreplyto) values ($1, $2)`
-
- _, err = db.Exec(query, e.Id, obj.Id)
-
- if err != nil {
- fmt.Println("error inserting replies cache")
- panic(err)
- }
-
- if !IsObjectLocal(db, e.Id) {
- WriteObjectToCache(db, e)
- }
-
- }
- return
- }
-}
-
-func WriteActorToCache(db *sql.DB, actorID string) {
- actor := FingerActor(actorID)
- collection := GetActorCollection(actor.Outbox)
-
- for _, e := range collection.OrderedItems {
- WriteActorObjectToCache(db, e)
- }
-}
-
-func DeleteActorCache(db *sql.DB, actorID string) {
- query := `select id from cacheactivitystream where id in (select id from cacheactivitystream where actor=$1)`
-
- rows, err := db.Query(query, actorID)
-
- CheckError(err, "error selecting actors activity from cache")
-
- defer rows.Close()
-
- for rows.Next() {
- var id string
- rows.Scan(&id)
-
- DeleteObject(db, id)
- }
-}
diff --git a/client.go b/client.go
index b4323d3..b0225f4 100644
--- a/client.go
+++ b/client.go
@@ -689,18 +689,6 @@ func GetActorsFollowPostFromId(db *sql.DB, actors []string, id string) Collectio
return collection
}
-type ObjectBaseSortDesc []ObjectBase
-
-func (a ObjectBaseSortDesc) Len() int { return len(a) }
-func (a ObjectBaseSortDesc) Less(i, j int) bool { return a[i].Updated.After(a[j].Updated) }
-func (a ObjectBaseSortDesc) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
-
-type ObjectBaseSortAsc []ObjectBase
-
-func (a ObjectBaseSortAsc) Len() int { return len(a) }
-func (a ObjectBaseSortAsc) Less(i, j int) bool { return a[i].Published.Before(a[j].Published) }
-func (a ObjectBaseSortAsc) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
-
type BoardSortAsc []Board
func (a BoardSortAsc) Len() int { return len(a) }
diff --git a/db/cache.go b/db/cache.go
new file mode 100644
index 0000000..0831f02
--- /dev/null
+++ b/db/cache.go
@@ -0,0 +1,325 @@
+package db
+
+import (
+ "fmt"
+
+ "github.com/FChannel0/FChannel-Server/activitypub"
+ "github.com/FChannel0/FChannel-Server/webfinger"
+ _ "github.com/lib/pq"
+)
+
+func WriteObjectToCache(obj activitypub.ObjectBase) (activitypub.ObjectBase, error) {
+ if res, err := IsPostBlacklist(obj.Content); err == nil && res {
+ fmt.Println("\n\nBlacklist post blocked\n\n")
+ return obj, nil
+ } else {
+ return obj, err
+ }
+
+ if len(obj.Attachment) > 0 {
+ if obj.Preview.Href != "" {
+ WritePreviewToCache(*obj.Preview)
+ }
+
+ for i, _ := range obj.Attachment {
+ WriteAttachmentToCache(obj.Attachment[i])
+ WriteActivitytoCacheWithAttachment(obj, obj.Attachment[i], *obj.Preview)
+ }
+
+ } else {
+ WriteActivitytoCache(obj)
+ }
+
+ WriteObjectReplyToDB(obj)
+
+ if obj.Replies != nil {
+ for _, e := range obj.Replies.OrderedItems {
+ WriteObjectToCache(e)
+ }
+ }
+
+ return obj, nil
+}
+
+func WriteActorObjectToCache(obj activitypub.ObjectBase) (activitypub.ObjectBase, error) {
+ if res, err := IsPostBlacklist(obj.Content); err == nil && res {
+ fmt.Println("\n\nBlacklist post blocked\n\n")
+ return obj, nil
+ } else if err != nil {
+ return obj, err
+ }
+
+ if len(obj.Attachment) > 0 {
+ if res, err := IsIDLocal(obj.Id); err == nil && res {
+ return obj, err
+ } else if err != nil {
+ return obj, err
+ }
+
+ if obj.Preview.Href != "" {
+ WritePreviewToCache(*obj.Preview)
+ }
+
+ for i, _ := range obj.Attachment {
+ WriteAttachmentToCache(obj.Attachment[i])
+ WriteActivitytoCacheWithAttachment(obj, obj.Attachment[i], *obj.Preview)
+ }
+
+ } else {
+ WriteActivitytoCache(obj)
+ }
+
+ WriteActorObjectReplyToDB(obj)
+
+ if obj.Replies != nil {
+ for _, e := range obj.Replies.OrderedItems {
+ WriteActorObjectToCache(e)
+ }
+ }
+
+ return obj, nil
+}
+
+func WriteActivitytoCache(obj activitypub.ObjectBase) error {
+ obj.Name = EscapeString(obj.Name)
+ obj.Content = EscapeString(obj.Content)
+ obj.AttributedTo = EscapeString(obj.AttributedTo)
+
+ query := `select id from cacheactivitystream where id=$1`
+
+ rows, err := db.Query(query, obj.Id)
+ if err != nil {
+ return err
+ }
+ defer rows.Close()
+
+ var id string
+ rows.Next()
+ err = rows.Scan(&id)
+ if err != nil {
+ return err
+ } else if id != "" {
+ return nil // TODO: error?
+ }
+
+ if obj.Updated.IsZero() {
+ obj.Updated = obj.Published
+ }
+
+ query = `insert into cacheactivitystream (id, type, name, content, published, updated, attributedto, actor, tripcode, sensitive) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`
+
+ _, err = db.Exec(query, obj.Id, obj.Type, obj.Name, obj.Content, obj.Published, obj.Updated, obj.AttributedTo, obj.Actor, obj.TripCode, obj.Sensitive)
+ return err
+}
+
+func WriteActivitytoCacheWithAttachment(obj activitypub.ObjectBase, attachment activitypub.ObjectBase, preview activitypub.NestedObjectBase) error {
+ obj.Name = EscapeString(obj.Name)
+ obj.Content = EscapeString(obj.Content)
+ obj.AttributedTo = EscapeString(obj.AttributedTo)
+
+ query := `select id from cacheactivitystream where id=$1`
+
+ rows, err := db.Query(query, obj.Id)
+ if err != nil {
+ return err
+ }
+ defer rows.Close()
+
+ var id string
+ rows.Next()
+ err = rows.Scan(&id)
+ if err != nil {
+ return err
+ } else if id != "" {
+ return nil // TODO: error?
+ }
+
+ if obj.Updated.IsZero() {
+ obj.Updated = obj.Published
+ }
+
+ query = `insert into cacheactivitystream (id, type, name, content, attachment, preview, published, updated, attributedto, actor, tripcode, sensitive) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`
+
+ _, err = db.Exec(query, obj.Id, obj.Type, obj.Name, obj.Content, attachment.Id, preview.Id, obj.Published, obj.Updated, obj.AttributedTo, obj.Actor, obj.TripCode, obj.Sensitive)
+ return err
+}
+
+func WriteAttachmentToCache(obj activitypub.ObjectBase) error {
+ query := `select id from cacheactivitystream where id=$1`
+
+ rows, err := db.Query(query, obj.Id)
+ if err != nil {
+ return err
+ }
+ defer rows.Close()
+
+ var id string
+ rows.Next()
+ err = rows.Scan(&id)
+ if err != nil {
+ return err
+ } else if id != "" {
+ return nil // TODO: error?
+ }
+
+ if obj.Updated.IsZero() {
+ obj.Updated = obj.Published
+ }
+
+ query = `insert into cacheactivitystream (id, type, name, href, published, updated, attributedTo, mediatype, size) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)`
+
+ _, err = db.Exec(query, obj.Id, obj.Type, obj.Name, obj.Href, obj.Published, obj.Updated, obj.AttributedTo, obj.MediaType, obj.Size)
+ return err
+}
+
+func WritePreviewToCache(obj activitypub.NestedObjectBase) error {
+ query := `select id from cacheactivitystream where id=$1`
+
+ rows, err := db.Query(query, obj.Id)
+ if err != nil {
+ return err
+ }
+ defer rows.Close()
+
+ var id string
+ rows.Next()
+ err = rows.Scan(&id)
+ if err != nil {
+ return err
+ } else if id != "" {
+ return nil // TODO: error?
+ }
+
+ if obj.Updated.IsZero() {
+ obj.Updated = obj.Published
+ }
+
+ query = `insert into cacheactivitystream (id, type, name, href, published, updated, attributedTo, mediatype, size) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)`
+
+ _, err = db.Exec(query, obj.Id, obj.Type, obj.Name, obj.Href, obj.Published, obj.Updated, obj.AttributedTo, obj.MediaType, obj.Size)
+ return err
+}
+
+func WriteObjectReplyToCache(obj activitypub.ObjectBase) error {
+ for i, e := range obj.InReplyTo {
+ res, err := IsReplyInThread(obj.InReplyTo[0].Id, e.Id)
+ if err != nil {
+ return err
+ }
+
+ if i == 0 || res {
+ query := `select id from replies where id=$1`
+
+ rows, err := db.Query(query, obj.Id)
+ if err != nil {
+ return err
+ }
+ defer rows.Close()
+
+ var id string
+ rows.Next()
+ err = rows.Scan(&id)
+ if err != nil {
+ return err
+ } else if id != "" {
+ return nil // TODO: error?
+ }
+
+ query = `insert into cachereplies (id, inreplyto) values ($1, $2)`
+
+ _, err = db.Exec(query, obj.Id, e.Id)
+ if err != nil {
+ return err
+ }
+ }
+ }
+
+ if len(obj.InReplyTo) < 1 {
+ query := `insert into cachereplies (id, inreplyto) values ($1, $2)`
+
+ _, err := db.Exec(query, obj.Id, "")
+ return err
+ }
+
+ return nil
+}
+
+func WriteObjectReplyCache(obj activitypub.ObjectBase) error {
+ if obj.Replies != nil {
+ for _, e := range obj.Replies.OrderedItems {
+
+ query := `select inreplyto from cachereplies where id=$1`
+
+ rows, err := db.Query(query, obj.Id)
+ if err != nil {
+ return err
+ }
+ defer rows.Close()
+
+ var inreplyto string
+ rows.Next()
+ err = rows.Scan(&inreplyto)
+ if err != nil {
+ return err
+ } else if inreplyto != "" {
+ return nil // TODO: error?
+ }
+
+ query = `insert into cachereplies (id, inreplyto) values ($1, $2)`
+
+ if _, err := db.Exec(query, e.Id, obj.Id); err != nil {
+ return err
+ }
+
+ if res, err := IsObjectLocal(e.Id); err == nil && !res {
+ if _, err := WriteObjectToCache(e); err != nil {
+ return err
+ }
+ } else if err != nil {
+ return err
+ }
+
+ }
+ }
+
+ return nil
+}
+
+func WriteActorToCache(actorID string) error {
+ actor, err := webfinger.FingerActor(actorID)
+ if err != nil {
+ return err
+ }
+ collection := GetActorCollection(actor.Outbox)
+
+ for _, e := range collection.OrderedItems {
+ if _, err := WriteActorObjectToCache(e); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func DeleteActorCache(actorID string) error {
+ query := `select id from cacheactivitystream where id in (select id from cacheactivitystream where actor=$1)`
+
+ rows, err := db.Query(query, actorID)
+ if err != nil {
+ return err
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ var id string
+ if err := rows.Scan(&id); err != nil {
+ return err
+ }
+
+ if err := DeleteObject(id); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/db/database.go b/db/database.go
index b06bbf2..8bb7568 100644
--- a/db/database.go
+++ b/db/database.go
@@ -4,6 +4,7 @@ import (
"database/sql"
"fmt"
"html/template"
+ "io/ioutil"
"os"
"regexp"
"sort"
@@ -55,7 +56,7 @@ func Close() error {
return db.Close()
}
-func CreateUniqueID(db *sql.DB, actor string) (string, error) {
+func CreateUniqueID(actor string) (string, error) {
var newID string
isUnique := false
for !isUnique {
@@ -83,6 +84,16 @@ func CreateUniqueID(db *sql.DB, actor string) (string, error) {
return newID, nil
}
+func RunDatabaseSchema() error {
+ query, err := ioutil.ReadFile("databaseschema.psql")
+ if err != nil {
+ return err
+ }
+
+ _, err = db.Exec(string(query))
+ return err
+}
+
func GetActorFromDB(id string) (activitypub.Actor, error) {
var nActor activitypub.Actor
@@ -222,8 +233,12 @@ func CreateNewBoardDB(actor activitypub.Actor) (activitypub.Actor, error) {
nActivity.To = append(nActivity.To, actor.Id)
response := AcceptFollow(nActivity)
- SetActorFollowingDB(db, response)
- MakeActivityRequest(db, nActivity)
+ if _, err := SetActorFollowingDB(response); err != nil {
+ return actor, err
+ }
+ if err := MakeActivityRequest(nActivity); err != nil {
+ return actor, err
+ }
}
}
@@ -256,7 +271,7 @@ func GetBoards() ([]activitypub.Actor, error) {
}
func WriteObjectToDB(obj activitypub.ObjectBase) (activitypub.ObjectBase, error) {
- id, err := CreateUniqueID(db, obj.Actor)
+ id, err := CreateUniqueID(obj.Actor)
if err != nil {
return obj, err
}
@@ -264,7 +279,7 @@ func WriteObjectToDB(obj activitypub.ObjectBase) (activitypub.ObjectBase, error)
obj.Id = fmt.Sprintf("%s/%s", obj.Actor, id)
if len(obj.Attachment) > 0 {
if obj.Preview.Href != "" {
- id, err := CreateUniqueID(db, obj.Actor)
+ id, err := CreateUniqueID(obj.Actor)
if err != nil {
return obj, err
}
@@ -277,7 +292,7 @@ func WriteObjectToDB(obj activitypub.ObjectBase) (activitypub.ObjectBase, error)
}
for i, _ := range obj.Attachment {
- id, err := CreateUniqueID(db, obj.Actor)
+ id, err := CreateUniqueID(obj.Actor)
if err != nil {
return obj, err
}
@@ -1023,7 +1038,7 @@ func GetObjectRepliesDBLimit(parent activitypub.ObjectBase, limit int) (*activit
nColl.OrderedItems = result
- sort.Sort(ObjectBaseSortAsc(nColl.OrderedItems))
+ sort.Sort(activitypub.ObjectBaseSortAsc(nColl.OrderedItems))
return &nColl, postCount, attachCount, nil
}
@@ -2244,6 +2259,9 @@ func UpdateObjectTypeDB(id string, nType string) error {
func UnArchiveLast(actorId string) error {
col, err := GetActorCollectionDBTypeLimit(actorId, "Archive", 1)
+ if err != nil {
+ return err
+ }
for _, e := range col.OrderedItems {
for _, k := range e.Replies.OrderedItems {
@@ -2296,3 +2314,18 @@ func GetObjectTypeDB(id string) (string, error) {
return nType, nil
}
+
+func IsReplyInThread(inReplyTo string, id string) (bool, error) {
+ obj, _, err := CheckValidActivity(inReplyTo)
+ if err != nil {
+ return false, err
+ }
+
+ for _, e := range obj.OrderedItems[0].Replies.OrderedItems {
+ if e.Id == id {
+ return true, nil
+ }
+ }
+
+ return false, nil
+}
diff --git a/db/follow.go b/db/follow.go
new file mode 100644
index 0000000..386de2b
--- /dev/null
+++ b/db/follow.go
@@ -0,0 +1,481 @@
+package db
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/http"
+ "regexp"
+ "strings"
+ "time"
+
+ "github.com/FChannel0/FChannel-Server/activitypub"
+ "github.com/FChannel0/FChannel-Server/config"
+ "github.com/FChannel0/FChannel-Server/util"
+ "github.com/FChannel0/FChannel-Server/webfinger"
+ _ "github.com/lib/pq"
+)
+
+func GetActorFollowing(w http.ResponseWriter, id string) error {
+ var following activitypub.Collection
+ var err error
+
+ following.AtContext.Context = "https://www.w3.org/ns/activitystreams"
+ following.Type = "Collection"
+ following.TotalItems, _, err = GetActorFollowTotal(id)
+ if err != nil {
+ return err
+ }
+
+ following.Items, err = GetActorFollowingDB(id)
+ if err != nil {
+ return err
+ }
+
+ enc, _ := json.MarshalIndent(following, "", "\t")
+ w.Header().Set("Content-Type", config.ActivityStreams)
+ _, err = w.Write(enc)
+
+ return err
+}
+
+func GetActorFollowers(w http.ResponseWriter, id string) error {
+ var following activitypub.Collection
+ var err error
+
+ following.AtContext.Context = "https://www.w3.org/ns/activitystreams"
+ following.Type = "Collection"
+ _, following.TotalItems, err = GetActorFollowTotal(id)
+ if err != nil {
+ return err
+ }
+
+ following.Items, err = GetActorFollowDB(id)
+ if err != nil {
+ return err
+ }
+
+ enc, _ := json.MarshalIndent(following, "", "\t")
+ w.Header().Set("Content-Type", config.ActivityStreams)
+ _, err = w.Write(enc)
+ return err
+}
+
+func GetActorFollowingDB(id string) ([]activitypub.ObjectBase, error) {
+ var followingCollection []activitypub.ObjectBase
+ query := `select following from following where id=$1`
+
+ rows, err := db.Query(query, id)
+ if err != nil {
+ return followingCollection, err
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ var obj activitypub.ObjectBase
+
+ if err := rows.Scan(&obj.Id); err != nil {
+ return followingCollection, err
+ }
+
+ followingCollection = append(followingCollection, obj)
+ }
+
+ return followingCollection, nil
+}
+
+func GetActorFollowDB(id string) ([]activitypub.ObjectBase, error) {
+ var followerCollection []activitypub.ObjectBase
+
+ query := `select follower from follower where id=$1`
+
+ rows, err := db.Query(query, id)
+ if err != nil {
+ return followerCollection, err
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ var obj activitypub.ObjectBase
+
+ if err := rows.Scan(&obj.Id); err != nil {
+ return followerCollection, err
+ }
+
+ followerCollection = append(followerCollection, obj)
+ }
+
+ return followerCollection, nil
+}
+
+func GetActorFollowTotal(id string) (int, int, error) {
+ var following int
+ var followers int
+
+ query := `select count(following) from following where id=$1`
+
+ rows, err := db.Query(query, id)
+ if err != nil {
+ return 0, 0, err
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ if err := rows.Scan(&following); err != nil {
+ return following, 0, err
+ }
+ }
+
+ query = `select count(follower) from follower where id=$1`
+
+ rows, err = db.Query(query, id)
+ if err != nil {
+ return 0, 0, err
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ if err := rows.Scan(&followers); err != nil {
+ return following, followers, err
+ }
+
+ }
+
+ return following, followers, nil
+}
+
+func AcceptFollow(activity activitypub.Activity) activitypub.Activity {
+ var accept activitypub.Activity
+ accept.AtContext.Context = activity.AtContext.Context
+ accept.Type = "Accept"
+ var nActor activitypub.Actor
+ accept.Actor = &nActor
+ accept.Actor.Id = activity.Object.Actor
+ var nObj activitypub.ObjectBase
+ accept.Object = &nObj
+ accept.Object.Actor = activity.Actor.Id
+ var nNested activitypub.NestedObjectBase
+ accept.Object.Object = &nNested
+ accept.Object.Object.Actor = activity.Object.Actor
+ accept.Object.Object.Type = "Follow"
+ accept.To = append(accept.To, activity.Object.Actor)
+
+ return accept
+}
+
+func RejectActivity(activity activitypub.Activity) activitypub.Activity {
+ var accept activitypub.Activity
+ accept.AtContext.Context = activity.AtContext.Context
+ accept.Type = "Reject"
+ var nObj activitypub.ObjectBase
+ accept.Object = &nObj
+ var nActor activitypub.Actor
+ accept.Actor = &nActor
+ accept.Actor.Id = activity.Object.Actor
+ accept.Object.Actor = activity.Actor.Id
+ var nNested activitypub.NestedObjectBase
+ accept.Object.Object = &nNested
+ accept.Object.Object.Actor = activity.Object.Actor
+ accept.Object.Object.Type = "Follow"
+ accept.To = append(accept.To, activity.Actor.Id)
+
+ return accept
+}
+
+func IsAlreadyFollowing(actor string, follow string) (bool, error) {
+ followers, err := GetActorFollowingDB(actor)
+ if err != nil {
+ return false, err
+ }
+
+ for _, e := range followers {
+ if e.Id == follow {
+ return true, nil
+ }
+ }
+
+ return false, nil
+}
+
+func IsAlreadyFollower(actor string, follow string) (bool, error) {
+ followers, err := GetActorFollowDB(actor)
+ if err != nil {
+ return false, err
+ }
+
+ for _, e := range followers {
+ if e.Id == follow {
+ return true, nil
+ }
+ }
+
+ return false, nil
+}
+
+func SetActorFollowerDB(activity activitypub.Activity) (activitypub.Activity, error) {
+ var query string
+ alreadyFollow, err := IsAlreadyFollower(activity.Actor.Id, activity.Object.Actor)
+ if err != nil {
+ return activity, err
+ }
+
+ activity.Type = "Reject"
+ if activity.Actor.Id == activity.Object.Actor {
+ return activity, nil
+ }
+
+ if alreadyFollow {
+ query = `delete from follower where id=$1 and follower=$2`
+ activity.Summary = activity.Object.Actor + " Unfollow " + activity.Actor.Id
+
+ if _, err := db.Exec(query, activity.Actor.Id, activity.Object.Actor); err != nil {
+ return activity, err
+ }
+
+ activity.Type = "Accept"
+ return activity, err
+ }
+
+ query = `insert into follower (id, follower) values ($1, $2)`
+ activity.Summary = activity.Object.Actor + " Follow " + activity.Actor.Id
+
+ if _, err := db.Exec(query, activity.Actor.Id, activity.Object.Actor); err != nil {
+ return activity, err
+ }
+
+ activity.Type = "Accept"
+ return activity, nil
+}
+
+func SetActorFollowingDB(activity activitypub.Activity) (activitypub.Activity, error) {
+ var query string
+ alreadyFollowing := false
+ alreadyFollower := false
+ following, err := GetActorFollowingDB(activity.Object.Actor)
+ if err != nil {
+ return activity, err
+ }
+
+ actor, err := webfinger.FingerActor(activity.Actor.Id)
+ if err != nil {
+ return activity, err
+ }
+
+ remoteActorFollowerCol := GetCollectionFromReq(actor.Followers)
+
+ for _, e := range following {
+ if e.Id == activity.Actor.Id {
+ alreadyFollowing = true
+ }
+ }
+
+ for _, e := range remoteActorFollowerCol.Items {
+ if e.Id == activity.Object.Actor {
+ alreadyFollower = true
+ }
+ }
+
+ activity.Type = "Reject"
+
+ if activity.Actor.Id == activity.Object.Actor {
+ return activity, nil
+ }
+
+ if alreadyFollowing && alreadyFollower {
+ query = `delete from following where id=$1 and following=$2`
+ activity.Summary = activity.Object.Actor + " Unfollowing " + activity.Actor.Id
+ if res, err := IsActorLocal(activity.Actor.Id); err == nil && !res {
+ go DeleteActorCache(activity.Actor.Id)
+ } else {
+ return activity, err
+ }
+
+ if _, err := db.Exec(query, activity.Object.Actor, activity.Actor.Id); err != nil {
+ return activity, err
+ }
+
+ activity.Type = "Accept"
+ return activity, nil
+ }
+
+ if !alreadyFollowing && !alreadyFollower {
+
+ query = `insert into following (id, following) values ($1, $2)`
+ activity.Summary = activity.Object.Actor + " Following " + activity.Actor.Id
+ if res, err := IsActorLocal(activity.Actor.Id); err == nil && !res {
+ go WriteActorToCache(activity.Actor.Id)
+ }
+ if _, err := db.Exec(query, activity.Object.Actor, activity.Actor.Id); err != nil {
+ return activity, err
+ }
+
+ activity.Type = "Accept"
+ return activity, nil
+ }
+
+ return activity, nil
+}
+
+func AutoFollow(actor string) error {
+ following, err := GetActorFollowingDB(actor)
+ if err != nil {
+ return err
+ }
+
+ follower, err := GetActorFollowDB(actor)
+ if err != nil {
+ return err
+ }
+
+ isFollowing := false
+
+ for _, e := range follower {
+ for _, k := range following {
+ if e.Id == k.Id {
+ isFollowing = true
+ }
+ }
+
+ if !isFollowing && e.Id != config.Domain && e.Id != actor {
+ followActivity, err := MakeFollowActivity(actor, e.Id)
+ if err != nil {
+ return err
+ }
+
+ nActor, err := webfinger.FingerActor(e.Id)
+ if err != nil {
+ return err
+ }
+
+ if nActor.Id != "" {
+ MakeActivityRequestOutbox(followActivity)
+ }
+ }
+ }
+
+ return nil
+}
+
+func MakeFollowActivity(actor string, follow string) (activitypub.Activity, error) {
+ var followActivity activitypub.Activity
+ var err error
+
+ followActivity.AtContext.Context = "https://www.w3.org/ns/activitystreams"
+ followActivity.Type = "Follow"
+
+ var obj activitypub.ObjectBase
+ var nactor activitypub.Actor
+ if actor == config.Domain {
+ nactor, err = GetActorFromDB(actor)
+ } else {
+ nactor, err = webfinger.FingerActor(actor)
+ }
+
+ if err != nil {
+ return followActivity, err
+ }
+
+ followActivity.Actor = &nactor
+ followActivity.Object = &obj
+
+ followActivity.Object.Actor = follow
+ followActivity.To = append(followActivity.To, follow)
+
+ return followActivity, nil
+}
+
+func MakeActivityRequestOutbox(activity activitypub.Activity) error {
+ j, _ := json.Marshal(activity)
+
+ if activity.Actor.Outbox == "" {
+ // TODO: good enough?
+ return errors.New("invalid outbox")
+ }
+
+ req, err := http.NewRequest("POST", activity.Actor.Outbox, bytes.NewBuffer(j))
+ if err != nil {
+ return err
+ }
+
+ re := regexp.MustCompile("https?://(www.)?")
+
+ var instance string
+ if activity.Actor.Id == config.Domain {
+ instance = re.ReplaceAllString(config.Domain, "")
+ } else {
+ _, instance = util.GetActorInstance(activity.Actor.Id)
+ }
+
+ date := time.Now().UTC().Format(time.RFC1123)
+ path := strings.Replace(activity.Actor.Outbox, instance, "", 1)
+
+ path = re.ReplaceAllString(path, "")
+
+ sig := fmt.Sprintf("(request-target): %s %s\nhost: %s\ndate: %s", "post", path, instance, date)
+ encSig, err := ActivitySign(*activity.Actor, sig)
+ if err != nil {
+ return err
+ }
+
+ signature := fmt.Sprintf("keyId=\"%s\",headers=\"(request-target) host date\",signature=\"%s\"", activity.Actor.PublicKey.Id, encSig)
+
+ req.Header.Set("Content-Type", config.ActivityStreams)
+ req.Header.Set("Date", date)
+ req.Header.Set("Signature", signature)
+ req.Host = instance
+
+ _, err = util.RouteProxy(req)
+ return err
+}
+
+func MakeActivityRequest(activity activitypub.Activity) error {
+ j, _ := json.MarshalIndent(activity, "", "\t")
+
+ for _, e := range activity.To {
+ if e != activity.Actor.Id {
+ actor, err := webfinger.FingerActor(e)
+ if err != nil {
+ return err
+ }
+
+ if actor.Id != "" {
+ _, instance := util.GetActorInstance(actor.Id)
+
+ if actor.Inbox != "" {
+ req, err := http.NewRequest("POST", actor.Inbox, bytes.NewBuffer(j))
+ if err != nil {
+ return err
+ }
+
+ date := time.Now().UTC().Format(time.RFC1123)
+ path := strings.Replace(actor.Inbox, instance, "", 1)
+
+ re := regexp.MustCompile("https?://(www.)?")
+ path = re.ReplaceAllString(path, "")
+
+ sig := fmt.Sprintf("(request-target): %s %s\nhost: %s\ndate: %s", "post", path, instance, date)
+ encSig, err := ActivitySign(*activity.Actor, sig)
+ if err != nil {
+ return err
+ }
+
+ signature := fmt.Sprintf("keyId=\"%s\",headers=\"(request-target) host date\",signature=\"%s\"", activity.Actor.PublicKey.Id, encSig)
+
+ req.Header.Set("Content-Type", config.ActivityStreams)
+ req.Header.Set("Date", date)
+ req.Header.Set("Signature", signature)
+ req.Host = instance
+
+ _, err = util.RouteProxy(req)
+ if err != nil {
+ fmt.Println("error with sending activity resp to actor " + instance)
+ return err // TODO: needs further testing
+ }
+ }
+ }
+ }
+ }
+
+ return nil
+}
diff --git a/db/pem.go b/db/pem.go
index d9bcee2..dedb137 100644
--- a/db/pem.go
+++ b/db/pem.go
@@ -1,10 +1,24 @@
package db
import (
+ "crypto"
+ crand "crypto/rand"
+ "crypto/rsa"
+ "crypto/sha256"
+ "crypto/x509"
+ "encoding/base64"
+ "encoding/pem"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "net/http"
"os"
+ "regexp"
"strings"
+ "time"
"github.com/FChannel0/FChannel-Server/activitypub"
+ "github.com/FChannel0/FChannel-Server/webfinger"
)
func GetActorPemFromDB(pemID string) (activitypub.PublicKeyPem, error) {
@@ -46,3 +60,302 @@ func GetActorPemFileFromDB(pemID string) (string, error) {
return file, nil
}
+
+func CreatePem(actor activitypub.Actor) error {
+ privatekey, err := rsa.GenerateKey(crand.Reader, 2048)
+ if err != nil {
+ return err
+ }
+
+ privateKeyBytes := x509.MarshalPKCS1PrivateKey(privatekey)
+
+ privateKeyBlock := &pem.Block{
+ Type: "RSA PRIVATE KEY",
+ Bytes: privateKeyBytes,
+ }
+
+ privatePem, err := os.Create("./pem/board/" + actor.Name + "-private.pem")
+ if err != nil {
+ return err
+ }
+
+ if err := pem.Encode(privatePem, privateKeyBlock); err != nil {
+ return err
+ }
+
+ publickey := &privatekey.PublicKey
+ publicKeyBytes, err := x509.MarshalPKIXPublicKey(publickey)
+ if err != nil {
+ return err
+ }
+
+ publicKeyBlock := &pem.Block{
+ Type: "PUBLIC KEY",
+ Bytes: publicKeyBytes,
+ }
+
+ publicPem, err := os.Create("./pem/board/" + actor.Name + "-public.pem")
+ if err != nil {
+ return err
+ }
+
+ if err := pem.Encode(publicPem, publicKeyBlock); err != nil {
+ return err
+ }
+
+ _, err = os.Stat("./pem/board/" + actor.Name + "-public.pem")
+ if os.IsNotExist(err) {
+ return err
+ } else {
+ return StorePemToDB(actor)
+ }
+
+ fmt.Println(`Created PEM keypair for the "` + actor.Name + `" board. Please keep in mind that
+the PEM key is crucial in identifying yourself as the legitimate owner of the board,
+so DO NOT LOSE IT!!! If you lose it, YOU WILL LOSE ACCESS TO YOUR BOARD!`)
+
+ return nil
+}
+
+func CreatePublicKeyFromPrivate(actor *activitypub.Actor, publicKeyPem string) error {
+ publicFilename, err := GetActorPemFileFromDB(publicKeyPem)
+ if err != nil {
+ return err
+ }
+
+ privateFilename := strings.ReplaceAll(publicFilename, "public.pem", "private.pem")
+ if _, err := os.Stat(privateFilename); err == nil {
+ // Not a lost cause
+ priv, err := ioutil.ReadFile(privateFilename)
+ if err != nil {
+ return err
+ }
+
+ block, _ := pem.Decode([]byte(priv))
+ if block == nil || block.Type != "RSA PRIVATE KEY" {
+ return errors.New("failed to decode PEM block containing public key")
+ }
+
+ key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
+ if err != nil {
+ return err
+ }
+
+ publicKeyDer, err := x509.MarshalPKIXPublicKey(&key.PublicKey)
+ if err != nil {
+ return err
+ }
+
+ pubKeyBlock := pem.Block{
+ Type: "PUBLIC KEY",
+ Headers: nil,
+ Bytes: publicKeyDer,
+ }
+
+ publicFileWriter, err := os.Create(publicFilename)
+ if err != nil {
+ return err
+ }
+
+ if err := pem.Encode(publicFileWriter, &pubKeyBlock); err != nil {
+ return err
+ }
+ } else {
+ fmt.Println(`\nUnable to locate private key from public key generation. Now,
+this means that you are now missing the proof that you are the
+owner of the "` + actor.Name + `" board. If you are the developer,
+then your job is just as easy as generating a new keypair, but
+if this board is live, then you'll also have to convince the other
+owners to switch their public keys for you so that they will start
+accepting your posts from your board from this site. Good luck ;)`)
+ return errors.New("unable to locate private key")
+ }
+ return nil
+}
+
+func StorePemToDB(actor activitypub.Actor) error {
+ query := "select publicKeyPem from actor where id=$1"
+ rows, err := db.Query(query, actor.Id)
+ if err != nil {
+ return err
+ }
+
+ defer rows.Close()
+
+ var result string
+ rows.Next()
+ rows.Scan(&result)
+
+ if result != "" {
+ return errors.New("already storing public key for actor")
+ }
+
+ publicKeyPem := actor.Id + "#main-key"
+ query = "update actor set publicKeyPem=$1 where id=$2"
+ if _, err := db.Exec(query, publicKeyPem, actor.Id); err != nil {
+ return err
+ }
+
+ file := "./pem/board/" + actor.Name + "-public.pem"
+ query = "insert into publicKeyPem (id, owner, file) values($1, $2, $3)"
+ _, err = db.Exec(query, publicKeyPem, actor.Id, file)
+ return err
+}
+func ActivitySign(actor activitypub.Actor, signature string) (string, error) {
+ query := `select file from publicKeyPem where id=$1 `
+
+ rows, err := db.Query(query, actor.PublicKey.Id)
+ if err != nil {
+ return "", err
+ }
+
+ defer rows.Close()
+
+ var file string
+ rows.Next()
+ rows.Scan(&file)
+
+ file = strings.ReplaceAll(file, "public.pem", "private.pem")
+ _, err = os.Stat(file)
+ if err != nil {
+ fmt.Println(`\n Unable to locate private key. Now,
+this means that you are now missing the proof that you are the
+owner of the "` + actor.Name + `" board. If you are the developer,
+then your job is just as easy as generating a new keypair, but
+if this board is live, then you'll also have to convince the other
+owners to switch their public keys for you so that they will start
+accepting your posts from your board from this site. Good luck ;)`)
+ return "", errors.New("unable to locate private key")
+ }
+
+ publickey, err := ioutil.ReadFile(file)
+ if err != nil {
+ return "", err
+ }
+
+ block, _ := pem.Decode(publickey)
+
+ pub, _ := x509.ParsePKCS1PrivateKey(block.Bytes)
+ rng := crand.Reader
+ hashed := sha256.New()
+ hashed.Write([]byte(signature))
+ cipher, _ := rsa.SignPKCS1v15(rng, pub, crypto.SHA256, hashed.Sum(nil))
+
+ return base64.StdEncoding.EncodeToString(cipher), nil
+}
+
+func ActivityVerify(actor activitypub.Actor, signature string, verify string) error {
+ sig, _ := base64.StdEncoding.DecodeString(signature)
+
+ if actor.PublicKey.PublicKeyPem == "" {
+ _actor, err := webfinger.FingerActor(actor.Id)
+ if err != nil {
+ return err
+ }
+ actor = _actor
+ }
+
+ block, _ := pem.Decode([]byte(actor.PublicKey.PublicKeyPem))
+ pub, _ := x509.ParsePKIXPublicKey(block.Bytes)
+
+ hashed := sha256.New()
+ hashed.Write([]byte(verify))
+
+ return rsa.VerifyPKCS1v15(pub.(*rsa.PublicKey), crypto.SHA256, hashed.Sum(nil), sig)
+}
+
+func VerifyHeaderSignature(r *http.Request, actor activitypub.Actor) bool {
+ s := ParseHeaderSignature(r.Header.Get("Signature"))
+
+ var method string
+ var path string
+ var host string
+ var date string
+ var digest string
+ var contentLength string
+
+ var sig string
+ for i, e := range s.Headers {
+ var nl string
+ if i < len(s.Headers)-1 {
+ nl = "\n"
+ }
+
+ switch e {
+ case "(request-target)":
+ method = strings.ToLower(r.Method)
+ path = r.URL.Path
+ sig += "(request-target): " + method + " " + path + "" + nl
+ break
+ case "host":
+ host = r.Host
+ sig += "host: " + host + "" + nl
+ break
+ case "date":
+ date = r.Header.Get("date")
+ sig += "date: " + date + "" + nl
+ break
+ case "digest":
+ digest = r.Header.Get("digest")
+ sig += "digest: " + digest + "" + nl
+ break
+ case "content-length":
+ contentLength = r.Header.Get("content-length")
+ sig += "content-length: " + contentLength + "" + nl
+ break
+ }
+ }
+
+ if s.KeyId != actor.PublicKey.Id {
+ return false
+ }
+
+ t, _ := time.Parse(time.RFC1123, date)
+
+ if time.Now().UTC().Sub(t).Seconds() > 75 {
+ return false
+ }
+
+ if ActivityVerify(actor, s.Signature, sig) != nil {
+ return false
+ }
+
+ return true
+}
+
+func ParseHeaderSignature(signature string) Signature {
+ var nsig Signature
+
+ keyId := regexp.MustCompile(`keyId=`)
+ headers := regexp.MustCompile(`headers=`)
+ sig := regexp.MustCompile(`signature=`)
+ algo := regexp.MustCompile(`algorithm=`)
+
+ signature = strings.ReplaceAll(signature, "\"", "")
+ parts := strings.Split(signature, ",")
+
+ for _, e := range parts {
+ if keyId.MatchString(e) {
+ nsig.KeyId = keyId.ReplaceAllString(e, "")
+ continue
+ }
+
+ if headers.MatchString(e) {
+ header := headers.ReplaceAllString(e, "")
+ nsig.Headers = strings.Split(header, " ")
+ continue
+ }
+
+ if sig.MatchString(e) {
+ nsig.Signature = sig.ReplaceAllString(e, "")
+ continue
+ }
+
+ if algo.MatchString(e) {
+ nsig.Algorithm = algo.ReplaceAllString(e, "")
+ continue
+ }
+ }
+
+ return nsig
+}
diff --git a/db/verification.go b/db/verification.go
index b976908..8be2ffe 100644
--- a/db/verification.go
+++ b/db/verification.go
@@ -1,13 +1,6 @@
package db
import (
- "crypto"
- "crypto/rsa"
- "crypto/sha256"
- "crypto/x509"
- "encoding/base64"
- "encoding/pem"
- "errors"
"fmt"
"math/rand"
"net/smtp"
@@ -15,14 +8,9 @@ import (
"os/exec"
"time"
- "github.com/FChannel0/FChannel-Server/activitypub"
+ "github.com/FChannel0/FChannel-Server/config"
+ "github.com/FChannel0/FChannel-Server/util"
_ "github.com/lib/pq"
-
- crand "crypto/rand"
- "io/ioutil"
- "net/http"
- "regexp"
- "strings"
)
type Verify struct {
@@ -97,7 +85,7 @@ func GetBoardMod(identifier string) (Verify, error) {
}
func CreateBoardMod(verify Verify) error {
- pass := CreateKey(50)
+ pass := util.CreateKey(50)
query := `select code from verification where identifier=$1 and type=$2`
@@ -307,8 +295,8 @@ func VerficationCooldownRemove() error {
func SendVerification(verify Verify) error {
fmt.Println("sending email")
- from := SiteEmail
- pass := SiteEmailPassword
+ from := config.SiteEmail
+ pass := config.SiteEmailPassword
to := verify.Identifier
body := fmt.Sprintf("You can use either\r\nEmail: %s \r\n Verfication Code: %s\r\n for the board %s", verify.Identifier, verify.Code, verify.Board)
@@ -317,23 +305,13 @@ func SendVerification(verify Verify) error {
"Subject: Image Board Verification\n\n" +
body
- return smtp.SendMail(SiteEmailServer+":"+SiteEmailPort,
- smtp.PlainAuth("", from, pass, SiteEmailServer),
+ return smtp.SendMail(config.SiteEmailServer+":"+config.SiteEmailPort,
+ smtp.PlainAuth("", from, pass, config.SiteEmailServer),
from, []string{to}, []byte(msg))
}
func IsEmailSetup() bool {
- if SiteEmail == "" {
- return false
- } else if SiteEmailPassword == "" {
- return false
- } else if SiteEmailServer == "" {
- return false
- } else if SiteEmailPort == "" {
- return false
- }
-
- return true
+ return config.SiteEmail != "" || config.SiteEmailPassword != "" || config.SiteEmailServer != "" || config.SiteEmailPort != ""
}
func HasAuth(code string, board string) (bool, error) {
@@ -342,8 +320,10 @@ func HasAuth(code string, board string) (bool, error) {
return false, err
}
- if verify.Board == Domain || (HasBoardAccess(db, verify) && verify.Board == board) {
+ if res, err := HasBoardAccess(verify); err != nil && (verify.Board == config.Domain || (res && verify.Board == board)) {
return true, nil
+ } else {
+ return false, err
}
return false, nil
@@ -377,12 +357,12 @@ func GetVerify(access string) (Verify, error) {
}
func CreateNewCaptcha() error {
- id := RandomID(8)
+ id := util.RandomID(8)
file := "public/" + id + ".png"
for true {
if _, err := os.Stat("./" + file); err == nil {
- id = RandomID(8)
+ id = util.RandomID(8)
file = "public/" + id + ".png"
} else {
break
@@ -506,299 +486,3 @@ func Captcha() string {
return newID
}
-
-func CreatePem(actor Actor) error {
- privatekey, err := rsa.GenerateKey(crand.Reader, 2048)
- if err != nil {
- return err
- }
-
- privateKeyBytes := x509.MarshalPKCS1PrivateKey(privatekey)
-
- privateKeyBlock := &pem.Block{
- Type: "RSA PRIVATE KEY",
- Bytes: privateKeyBytes,
- }
-
- privatePem, err := os.Create("./pem/board/" + actor.Name + "-private.pem")
- if err != nil {
- return err
- }
-
- if err := pem.Encode(privatePem, privateKeyBlock); err != nil {
- return err
- }
-
- publickey := &privatekey.PublicKey
- publicKeyBytes, err := x509.MarshalPKIXPublicKey(publickey)
- if err != nil {
- return err
- }
-
- publicKeyBlock := &pem.Block{
- Type: "PUBLIC KEY",
- Bytes: publicKeyBytes,
- }
-
- publicPem, err := os.Create("./pem/board/" + actor.Name + "-public.pem")
- if err != nil {
- return err
- }
-
- if err := pem.Encode(publicPem, publicKeyBlock); err != nil {
- return err
- }
-
- _, err = os.Stat("./pem/board/" + actor.Name + "-public.pem")
- if os.IsNotExist(err) {
- return err
- } else {
- return StorePemToDB(actor)
- }
-
- fmt.Println(`Created PEM keypair for the "` + actor.Name + `" board. Please keep in mind that
-the PEM key is crucial in identifying yourself as the legitimate owner of the board,
-so DO NOT LOSE IT!!! If you lose it, YOU WILL LOSE ACCESS TO YOUR BOARD!`)
-
- return nil
-}
-
-func CreatePublicKeyFromPrivate(actor *activitypub.Actor, publicKeyPem string) error {
- publicFilename, err := GetActorPemFileFromDB(publicKeyPem)
- if err != nil {
- return err
- }
-
- privateFilename := strings.ReplaceAll(publicFilename, "public.pem", "private.pem")
- if _, err := os.Stat(privateFilename); err == nil {
- // Not a lost cause
- priv, err := ioutil.ReadFile(privateFilename)
- if err != nil {
- return err
- }
-
- block, _ := pem.Decode([]byte(priv))
- if block == nil || block.Type != "RSA PRIVATE KEY" {
- return errors.New("failed to decode PEM block containing public key")
- }
-
- key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
- if err != nil {
- return err
- }
-
- publicKeyDer, err := x509.MarshalPKIXPublicKey(&key.PublicKey)
- if err != nil {
- return err
- }
-
- pubKeyBlock := pem.Block{
- Type: "PUBLIC KEY",
- Headers: nil,
- Bytes: publicKeyDer,
- }
-
- publicFileWriter, err := os.Create(publicFilename)
- if err != nil {
- return err
- }
-
- if err := pem.Encode(publicFileWriter, &pubKeyBlock); err != nil {
- return err
- }
- } else {
- fmt.Println(`\nUnable to locate private key from public key generation. Now,
-this means that you are now missing the proof that you are the
-owner of the "` + actor.Name + `" board. If you are the developer,
-then your job is just as easy as generating a new keypair, but
-if this board is live, then you'll also have to convince the other
-owners to switch their public keys for you so that they will start
-accepting your posts from your board from this site. Good luck ;)`)
- return errors.New("unable to locate private key")
- }
- return nil
-}
-
-func StorePemToDB(actor activitypub.Actor) error {
- query := "select publicKeyPem from actor where id=$1"
- rows, err := db.Query(query, actor.Id)
- if err != nil {
- return err
- }
-
- defer rows.Close()
-
- var result string
- rows.Next()
- rows.Scan(&result)
-
- if result != "" {
- return errors.New("already storing public key for actor")
- }
-
- publicKeyPem := actor.Id + "#main-key"
- query = "update actor set publicKeyPem=$1 where id=$2"
- if _, err := db.Exec(query, publicKeyPem, actor.Id); err != nil {
- return err
- }
-
- file := "./pem/board/" + actor.Name + "-public.pem"
- query = "insert into publicKeyPem (id, owner, file) values($1, $2, $3)"
- _, err = db.Exec(query, publicKeyPem, actor.Id, file)
- return err
-}
-
-func ActivitySign(actor activitypub.Actor, signature string) (string, error) {
- query := `select file from publicKeyPem where id=$1 `
-
- rows, err := db.Query(query, actor.PublicKey.Id)
- if err != nil {
- return "", err
- }
-
- defer rows.Close()
-
- var file string
- rows.Next()
- rows.Scan(&file)
-
- file = strings.ReplaceAll(file, "public.pem", "private.pem")
- _, err = os.Stat(file)
- if err != nil {
- fmt.Println(`\n Unable to locate private key. Now,
-this means that you are now missing the proof that you are the
-owner of the "` + actor.Name + `" board. If you are the developer,
-then your job is just as easy as generating a new keypair, but
-if this board is live, then you'll also have to convince the other
-owners to switch their public keys for you so that they will start
-accepting your posts from your board from this site. Good luck ;)`)
- return "", errors.New("unable to locate private key")
- }
-
- publickey, err := ioutil.ReadFile(file)
- if err != nil {
- return "", err
- }
-
- block, _ := pem.Decode(publickey)
-
- pub, _ := x509.ParsePKCS1PrivateKey(block.Bytes)
- rng := crand.Reader
- hashed := sha256.New()
- hashed.Write([]byte(signature))
- cipher, _ := rsa.SignPKCS1v15(rng, pub, crypto.SHA256, hashed.Sum(nil))
-
- return base64.StdEncoding.EncodeToString(cipher), nil
-}
-
-func ActivityVerify(actor activitypub.Actor, signature string, verify string) error {
- sig, _ := base64.StdEncoding.DecodeString(signature)
-
- if actor.PublicKey.PublicKeyPem == "" {
- actor = FingerActor(actor.Id)
- }
-
- block, _ := pem.Decode([]byte(actor.PublicKey.PublicKeyPem))
- pub, _ := x509.ParsePKIXPublicKey(block.Bytes)
-
- hashed := sha256.New()
- hashed.Write([]byte(verify))
-
- return rsa.VerifyPKCS1v15(pub.(*rsa.PublicKey), crypto.SHA256, hashed.Sum(nil), sig)
-}
-
-func VerifyHeaderSignature(r *http.Request, actor activitypub.Actor) bool {
- s := ParseHeaderSignature(r.Header.Get("Signature"))
-
- var method string
- var path string
- var host string
- var date string
- var digest string
- var contentLength string
-
- var sig string
- for i, e := range s.Headers {
- var nl string
- if i < len(s.Headers)-1 {
- nl = "\n"
- }
-
- switch e {
- case "(request-target)":
- method = strings.ToLower(r.Method)
- path = r.URL.Path
- sig += "(request-target): " + method + " " + path + "" + nl
- break
- case "host":
- host = r.Host
- sig += "host: " + host + "" + nl
- break
- case "date":
- date = r.Header.Get("date")
- sig += "date: " + date + "" + nl
- break
- case "digest":
- digest = r.Header.Get("digest")
- sig += "digest: " + digest + "" + nl
- break
- case "content-length":
- contentLength = r.Header.Get("content-length")
- sig += "content-length: " + contentLength + "" + nl
- break
- }
- }
-
- if s.KeyId != actor.PublicKey.Id {
- return false
- }
-
- t, _ := time.Parse(time.RFC1123, date)
-
- if time.Now().UTC().Sub(t).Seconds() > 75 {
- return false
- }
-
- if ActivityVerify(actor, s.Signature, sig) != nil {
- return false
- }
-
- return true
-}
-
-func ParseHeaderSignature(signature string) Signature {
- var nsig Signature
-
- keyId := regexp.MustCompile(`keyId=`)
- headers := regexp.MustCompile(`headers=`)
- sig := regexp.MustCompile(`signature=`)
- algo := regexp.MustCompile(`algorithm=`)
-
- signature = strings.ReplaceAll(signature, "\"", "")
- parts := strings.Split(signature, ",")
-
- for _, e := range parts {
- if keyId.MatchString(e) {
- nsig.KeyId = keyId.ReplaceAllString(e, "")
- continue
- }
-
- if headers.MatchString(e) {
- header := headers.ReplaceAllString(e, "")
- nsig.Headers = strings.Split(header, " ")
- continue
- }
-
- if sig.MatchString(e) {
- nsig.Signature = sig.ReplaceAllString(e, "")
- continue
- }
-
- if algo.MatchString(e) {
- nsig.Algorithm = algo.ReplaceAllString(e, "")
- continue
- }
- }
-
- return nsig
-}
diff --git a/follow.go b/follow.go
deleted file mode 100644
index 49558cd..0000000
--- a/follow.go
+++ /dev/null
@@ -1,331 +0,0 @@
-package main
-
-import (
- "database/sql"
- "encoding/json"
- "net/http"
-
- _ "github.com/lib/pq"
-)
-
-func GetActorFollowing(w http.ResponseWriter, db *sql.DB, id string) {
- var following Collection
-
- following.AtContext.Context = "https://www.w3.org/ns/activitystreams"
- following.Type = "Collection"
- following.TotalItems, _ = GetActorFollowTotal(db, id)
- following.Items = GetActorFollowingDB(db, id)
-
- enc, _ := json.MarshalIndent(following, "", "\t")
- w.Header().Set("Content-Type", activitystreams)
- w.Write(enc)
-}
-
-func GetActorFollowers(w http.ResponseWriter, db *sql.DB, id string) {
- var following Collection
-
- following.AtContext.Context = "https://www.w3.org/ns/activitystreams"
- following.Type = "Collection"
- _, following.TotalItems = GetActorFollowTotal(db, id)
- following.Items = GetActorFollowDB(db, id)
-
- enc, _ := json.MarshalIndent(following, "", "\t")
- w.Header().Set("Content-Type", activitystreams)
- w.Write(enc)
-}
-
-func GetActorFollowingDB(db *sql.DB, id string) []ObjectBase {
- var followingCollection []ObjectBase
- query := `select following from following where id=$1`
-
- rows, err := db.Query(query, id)
-
- CheckError(err, "error with following db query")
-
- defer rows.Close()
-
- for rows.Next() {
- var obj ObjectBase
-
- err := rows.Scan(&obj.Id)
-
- CheckError(err, "error with following db scan")
-
- followingCollection = append(followingCollection, obj)
- }
-
- return followingCollection
-}
-
-func GetActorFollowDB(db *sql.DB, id string) []ObjectBase {
- var followerCollection []ObjectBase
-
- query := `select follower from follower where id=$1`
-
- rows, err := db.Query(query, id)
-
- CheckError(err, "error with follower db query")
-
- defer rows.Close()
-
- for rows.Next() {
- var obj ObjectBase
-
- err := rows.Scan(&obj.Id)
-
- CheckError(err, "error with followers db scan")
-
- followerCollection = append(followerCollection, obj)
- }
-
- return followerCollection
-}
-
-func GetActorFollowTotal(db *sql.DB, id string) (int, int) {
- var following int
- var followers int
-
- query := `select count(following) from following where id=$1`
-
- rows, err := db.Query(query, id)
-
- CheckError(err, "error with following total db query")
-
- defer rows.Close()
-
- for rows.Next() {
- err := rows.Scan(&following)
-
- CheckError(err, "error with following total db scan")
- }
-
- query = `select count(follower) from follower where id=$1`
-
- rows, err = db.Query(query, id)
-
- CheckError(err, "error with followers total db query")
-
- defer rows.Close()
-
- for rows.Next() {
- err := rows.Scan(&followers)
-
- CheckError(err, "error with followers total db scan")
- }
-
- return following, followers
-}
-
-func AcceptFollow(activity Activity) Activity {
- var accept Activity
- accept.AtContext.Context = activity.AtContext.Context
- accept.Type = "Accept"
- var nActor Actor
- accept.Actor = &nActor
- accept.Actor.Id = activity.Object.Actor
- var nObj ObjectBase
- accept.Object = &nObj
- accept.Object.Actor = activity.Actor.Id
- var nNested NestedObjectBase
- accept.Object.Object = &nNested
- accept.Object.Object.Actor = activity.Object.Actor
- accept.Object.Object.Type = "Follow"
- accept.To = append(accept.To, activity.Object.Actor)
-
- return accept
-}
-
-func RejectActivity(activity Activity) Activity {
- var accept Activity
- accept.AtContext.Context = activity.AtContext.Context
- accept.Type = "Reject"
- var nObj ObjectBase
- accept.Object = &nObj
- var nActor Actor
- accept.Actor = &nActor
- accept.Actor.Id = activity.Object.Actor
- accept.Object.Actor = activity.Actor.Id
- var nNested NestedObjectBase
- accept.Object.Object = &nNested
- accept.Object.Object.Actor = activity.Object.Actor
- accept.Object.Object.Type = "Follow"
- accept.To = append(accept.To, activity.Actor.Id)
-
- return accept
-}
-
-func IsAlreadyFollowing(db *sql.DB, actor string, follow string) bool {
- followers := GetActorFollowingDB(db, actor)
-
- for _, e := range followers {
- if e.Id == follow {
- return true
- }
- }
-
- return false
-}
-
-func IsAlreadyFollower(db *sql.DB, actor string, follow string) bool {
- followers := GetActorFollowDB(db, actor)
-
- for _, e := range followers {
- if e.Id == follow {
- return true
- }
- }
-
- return false
-}
-
-func SetActorFollowerDB(db *sql.DB, activity Activity) Activity {
- var query string
- alreadyFollow := IsAlreadyFollower(db, activity.Actor.Id, activity.Object.Actor)
-
- activity.Type = "Reject"
- if activity.Actor.Id == activity.Object.Actor {
- return activity
- }
-
- if alreadyFollow {
- query = `delete from follower where id=$1 and follower=$2`
- activity.Summary = activity.Object.Actor + " Unfollow " + activity.Actor.Id
-
- _, err := db.Exec(query, activity.Actor.Id, activity.Object.Actor)
-
- if CheckError(err, "error with follower db delete") != nil {
- activity.Type = "Reject"
- return activity
- }
-
- activity.Type = "Accept"
- return activity
- } else {
- query = `insert into follower (id, follower) values ($1, $2)`
- activity.Summary = activity.Object.Actor + " Follow " + activity.Actor.Id
-
- _, err := db.Exec(query, activity.Actor.Id, activity.Object.Actor)
-
- if CheckError(err, "error with follower db insert") != nil {
- activity.Type = "Reject"
- return activity
- }
-
- activity.Type = "Accept"
- return activity
- }
-}
-
-func SetActorFollowingDB(db *sql.DB, activity Activity) Activity {
- var query string
- alreadyFollowing := false
- alreadyFollower := false
- following := GetActorFollowingDB(db, activity.Object.Actor)
-
- actor := FingerActor(activity.Actor.Id)
-
- remoteActorFollowerCol := GetCollectionFromReq(actor.Followers)
-
- for _, e := range following {
- if e.Id == activity.Actor.Id {
- alreadyFollowing = true
- }
- }
-
- for _, e := range remoteActorFollowerCol.Items {
- if e.Id == activity.Object.Actor {
- alreadyFollower = true
- }
- }
-
- activity.Type = "Reject"
-
- if activity.Actor.Id == activity.Object.Actor {
- return activity
- }
-
- if alreadyFollowing && alreadyFollower {
- query = `delete from following where id=$1 and following=$2`
- activity.Summary = activity.Object.Actor + " Unfollowing " + activity.Actor.Id
- if !IsActorLocal(db, activity.Actor.Id) {
- go DeleteActorCache(db, activity.Actor.Id)
- }
- _, err := db.Exec(query, activity.Object.Actor, activity.Actor.Id)
-
- if CheckError(err, "error with following db delete") != nil {
- activity.Type = "Reject"
- return activity
- }
-
- activity.Type = "Accept"
- return activity
- }
-
- if !alreadyFollowing && !alreadyFollower {
-
- query = `insert into following (id, following) values ($1, $2)`
- activity.Summary = activity.Object.Actor + " Following " + activity.Actor.Id
- if !IsActorLocal(db, activity.Actor.Id) {
- go WriteActorToCache(db, activity.Actor.Id)
- }
- _, err := db.Exec(query, activity.Object.Actor, activity.Actor.Id)
-
- if CheckError(err, "error with following db insert") != nil {
- activity.Type = "Reject"
- return activity
- }
-
- activity.Type = "Accept"
- return activity
- }
-
- return activity
-}
-
-func AutoFollow(db *sql.DB, actor string) {
- following := GetActorFollowingDB(db, actor)
- follower := GetActorFollowDB(db, actor)
-
- isFollowing := false
-
- for _, e := range follower {
- for _, k := range following {
- if e.Id == k.Id {
- isFollowing = true
- }
- }
-
- if !isFollowing && e.Id != Domain && e.Id != actor {
- followActivity := MakeFollowActivity(db, actor, e.Id)
-
- nActor := FingerActor(e.Id)
-
- if nActor.Id != "" {
- MakeActivityRequestOutbox(db, followActivity)
- }
- }
- }
-}
-
-func MakeFollowActivity(db *sql.DB, actor string, follow string) Activity {
- var followActivity Activity
-
- followActivity.AtContext.Context = "https://www.w3.org/ns/activitystreams"
- followActivity.Type = "Follow"
-
- var obj ObjectBase
- var nactor Actor
- if actor == Domain {
- nactor = GetActorFromDB(db, actor)
- } else {
- nactor = FingerActor(actor)
- }
-
- followActivity.Actor = &nactor
- followActivity.Object = &obj
-
- followActivity.Object.Actor = follow
- followActivity.To = append(followActivity.To, follow)
-
- return followActivity
-}
diff --git a/main.go b/main.go
index 4cceade..c056f38 100644
--- a/main.go
+++ b/main.go
@@ -1,7 +1,6 @@
package main
import (
- "bytes"
"crypto/sha256"
"database/sql"
"encoding/hex"
@@ -26,7 +25,6 @@ import (
"math/rand"
"mime/multipart"
"net/http"
- "net/url"
"os"
"os/exec"
"path"
@@ -37,14 +35,8 @@ import (
var authReq = []string{"captcha", "email", "passphrase"}
-var supportedFiles = []string{"image/gif", "image/jpeg", "image/png", "image/webp", "image/apng", "video/mp4", "video/ogg", "video/webm", "audio/mpeg", "audio/ogg", "audio/wav", "audio/wave", "audio/x-wav"}
-
-var activitystreams = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
-
var MediaHashs = make(map[string]string)
-var ActorCache = make(map[string]activitypub.Actor)
-
var Themes []string
func init() {
@@ -60,7 +52,7 @@ func main() {
db.ConnectDB()
defer db.Close()
- RunDatabaseSchema(DB)
+ db.RunDatabaseSchema()
go MakeCaptchas(DB, 100)
@@ -573,49 +565,8 @@ func CheckValidActivity(id string) (Collection, bool) {
return respCollection, false
}
-func GetActor(id string) Actor {
-
- var respActor Actor
-
- if id == "" {
- return respActor
- }
-
- actor, instance := GetActorInstance(id)
-
- if ActorCache[actor+"@"+instance].Id != "" {
- respActor = ActorCache[actor+"@"+instance]
- } else {
- req, err := http.NewRequest("GET", strings.TrimSpace(id), nil)
-
- CheckError(err, "error with getting actor req")
-
- req.Header.Set("Accept", activitystreams)
-
- resp, err := RouteProxy(req)
-
- if err != nil {
- return respActor
- }
-
- defer resp.Body.Close()
-
- body, _ := ioutil.ReadAll(resp.Body)
-
- err = json.Unmarshal(body, &respActor)
-
- if err != nil {
- return respActor
- }
-
- ActorCache[actor+"@"+instance] = respActor
- }
-
- return respActor
-}
-
-func GetActorCollection(collection string) Collection {
- var nCollection Collection
+func GetActorCollection(collection string) activitypub.Collection {
+ var nCollection activitypub.Collection
if collection == "" {
return nCollection
@@ -708,17 +659,6 @@ func GetFileContentType(out multipart.File) (string, error) {
return contentType, nil
}
-func IsReplyInThread(db *sql.DB, inReplyTo string, id string) bool {
- obj, _ := CheckValidActivity(inReplyTo)
-
- for _, e := range obj.OrderedItems[0].Replies.OrderedItems {
- if e.Id == id {
- return true
- }
- }
- return false
-}
-
func SupportedMIMEType(mime string) bool {
for _, e := range supportedFiles {
if e == mime {
@@ -758,91 +698,6 @@ func GetActorReported(w http.ResponseWriter, r *http.Request, db *sql.DB, id str
w.Write(enc)
}
-func MakeActivityRequestOutbox(db *sql.DB, activity Activity) {
- j, _ := json.Marshal(activity)
-
- if activity.Actor.Outbox == "" {
- return
- }
-
- req, err := http.NewRequest("POST", activity.Actor.Outbox, bytes.NewBuffer(j))
-
- CheckError(err, "error with sending activity req to outbox")
-
- re := regexp.MustCompile("https?://(www.)?")
-
- var instance string
- if activity.Actor.Id == Domain {
- instance = re.ReplaceAllString(Domain, "")
- } else {
- _, instance = GetActorInstance(activity.Actor.Id)
- }
-
- date := time.Now().UTC().Format(time.RFC1123)
- path := strings.Replace(activity.Actor.Outbox, instance, "", 1)
-
- path = re.ReplaceAllString(path, "")
-
- sig := fmt.Sprintf("(request-target): %s %s\nhost: %s\ndate: %s", "post", path, instance, date)
- encSig, err := ActivitySign(db, *activity.Actor, sig)
- CheckError(err, "unable to sign activity response")
- signature := fmt.Sprintf("keyId=\"%s\",headers=\"(request-target) host date\",signature=\"%s\"", activity.Actor.PublicKey.Id, encSig)
-
- req.Header.Set("Content-Type", activitystreams)
- req.Header.Set("Date", date)
- req.Header.Set("Signature", signature)
- req.Host = instance
-
- _, err = RouteProxy(req)
-
- CheckError(err, "error with sending activity resp to")
-}
-
-func MakeActivityRequest(db *sql.DB, activity Activity) {
-
- j, _ := json.MarshalIndent(activity, "", "\t")
-
- for _, e := range activity.To {
- if e != activity.Actor.Id {
-
- actor := FingerActor(e)
-
- if actor.Id != "" {
- _, instance := GetActorInstance(actor.Id)
-
- if actor.Inbox != "" {
-
- req, err := http.NewRequest("POST", actor.Inbox, bytes.NewBuffer(j))
-
- CheckError(err, "error with sending activity req to")
-
- date := time.Now().UTC().Format(time.RFC1123)
- path := strings.Replace(actor.Inbox, instance, "", 1)
-
- re := regexp.MustCompile("https?://(www.)?")
- path = re.ReplaceAllString(path, "")
-
- sig := fmt.Sprintf("(request-target): %s %s\nhost: %s\ndate: %s", "post", path, instance, date)
- encSig, err := ActivitySign(db, *activity.Actor, sig)
- CheckError(err, "unable to sign activity response")
- signature := fmt.Sprintf("keyId=\"%s\",headers=\"(request-target) host date\",signature=\"%s\"", activity.Actor.PublicKey.Id, encSig)
-
- req.Header.Set("Content-Type", activitystreams)
- req.Header.Set("Date", date)
- req.Header.Set("Signature", signature)
- req.Host = instance
-
- _, err = RouteProxy(req)
-
- if err != nil {
- fmt.Println("error with sending activity resp to actor " + instance)
- }
- }
- }
- }
- }
-}
-
func GetCollectionFromID(id string) Collection {
var nColl Collection
@@ -1178,43 +1033,6 @@ func remoteShort(url string) string {
return "f" + actorname + "-" + id
}
-func RouteProxy(req *http.Request) (*http.Response, error) {
-
- var proxyType = GetPathProxyType(req.URL.Host)
-
- if proxyType == "tor" {
- proxyUrl, err := url.Parse("socks5://" + TorProxy)
-
- CheckError(err, "error parsing tor proxy url")
-
- proxyTransport := &http.Transport{Proxy: http.ProxyURL(proxyUrl)}
- client := &http.Client{Transport: proxyTransport, Timeout: time.Second * 15}
- return client.Do(req)
- }
-
- return http.DefaultClient.Do(req)
-}
-
-func GetPathProxyType(path string) string {
- if TorProxy != "" {
- re := regexp.MustCompile(`(http://|http://)?(www.)?\w+\.onion`)
- onion := re.MatchString(path)
- if onion {
- return "tor"
- }
- }
-
- return "clearnet"
-}
-
-func RunDatabaseSchema(db *sql.DB) {
- query, err := ioutil.ReadFile("databaseschema.psql")
- CheckError(err, "could not read databaseschema.psql file")
- if _, err := db.Exec(string(query)); err != nil {
- CheckError(err, "could not exec databaseschema.psql")
- }
-}
-
func CreatedNeededDirectories() {
if _, err := os.Stat("./public"); os.IsNotExist(err) {
os.Mkdir("./public", 0755)
@@ -1225,116 +1043,6 @@ func CreatedNeededDirectories() {
}
}
-//looks for actor with pattern of board@instance
-func FingerActor(path string) Actor {
-
- var nActor Actor
-
- actor, instance := GetActorInstance(path)
-
- if actor == "" && instance == "" {
- return nActor
- }
-
- if ActorCache[actor+"@"+instance].Id != "" {
- nActor = ActorCache[actor+"@"+instance]
- } else {
- r := FingerRequest(actor, instance)
- if r != nil && r.StatusCode == 200 {
- defer r.Body.Close()
-
- body, _ := ioutil.ReadAll(r.Body)
-
- err := json.Unmarshal(body, &nActor)
-
- CheckError(err, "error getting fingerrequet resp from json body")
-
- ActorCache[actor+"@"+instance] = nActor
- }
- }
-
- return nActor
-}
-
-func FingerRequest(actor string, instance string) *http.Response {
- acct := "acct:" + actor + "@" + instance
- req, err := http.NewRequest("GET", "http://"+instance+"/.well-known/webfinger?resource="+acct, nil)
-
- CheckError(err, "could not get finger request from id req")
-
- resp, err := RouteProxy(req)
-
- var finger Webfinger
-
- if err != nil {
- return resp
- }
-
- if resp.StatusCode == 200 {
- defer resp.Body.Close()
-
- body, _ := ioutil.ReadAll(resp.Body)
-
- err := json.Unmarshal(body, &finger)
-
- CheckError(err, "error getting fingerrequet resp from json body")
- }
-
- if len(finger.Links) > 0 {
- for _, e := range finger.Links {
- if e.Type == "application/activity+json" {
- req, err := http.NewRequest("GET", e.Href, nil)
-
- CheckError(err, "could not get finger request from id req")
-
- req.Header.Set("Accept", activitystreams)
-
- resp, err := RouteProxy(req)
- return resp
- }
- }
- }
-
- return resp
-}
-
-func GetActorInstance(path string) (string, string) {
- re := regexp.MustCompile(`([@]?([\w\d.-_]+)[@](.+))`)
- atFormat := re.MatchString(path)
-
- if atFormat {
- match := re.FindStringSubmatch(path)
- if len(match) > 2 {
- return match[2], match[3]
- }
- }
-
- re = regexp.MustCompile(`(https?://)(www)?([\w\d-_.:]+)(/|\s+|\r|\r\n)?$`)
- mainActor := re.MatchString(path)
- if mainActor {
- match := re.FindStringSubmatch(path)
- if len(match) > 2 {
- return "main", match[3]
- }
- }
-
- re = regexp.MustCompile(`(https?://)?(www)?([\w\d-_.:]+)\/([\w\d-_.]+)(\/([\w\d-_.]+))?`)
- httpFormat := re.MatchString(path)
-
- if httpFormat {
- match := re.FindStringSubmatch(path)
- if len(match) > 3 {
- if match[4] == "users" {
- return match[6], match[3]
- }
-
- return match[4], match[3]
- }
- }
-
- return "", ""
-}
-
func AddInstanceToIndex(actor string) {
// if local testing enviroment do not add to index
re := regexp.MustCompile(`(.+)?(localhost|\d+\.\d+\.\d+\.\d+)(.+)?`)
diff --git a/util/proxy.go b/util/proxy.go
new file mode 100644
index 0000000..1fc9b03
--- /dev/null
+++ b/util/proxy.go
@@ -0,0 +1,39 @@
+package util
+
+import (
+ "net/http"
+ "net/url"
+ "regexp"
+ "time"
+
+ "github.com/FChannel0/FChannel-Server/config"
+)
+
+func RouteProxy(req *http.Request) (*http.Response, error) {
+ var proxyType = GetPathProxyType(req.URL.Host)
+
+ if proxyType == "tor" {
+ proxyUrl, err := url.Parse("socks5://" + config.TorProxy)
+ if err != nil {
+ return nil, err
+ }
+
+ proxyTransport := &http.Transport{Proxy: http.ProxyURL(proxyUrl)}
+ client := &http.Client{Transport: proxyTransport, Timeout: time.Second * 15}
+ return client.Do(req)
+ }
+
+ return http.DefaultClient.Do(req)
+}
+
+func GetPathProxyType(path string) string {
+ if config.TorProxy != "" {
+ re := regexp.MustCompile(`(http://|http://)?(www.)?\w+\.onion`)
+ onion := re.MatchString(path)
+ if onion {
+ return "tor"
+ }
+ }
+
+ return "clearnet"
+}
diff --git a/util/util.go b/util/util.go
index 7164937..4ac8267 100644
--- a/util/util.go
+++ b/util/util.go
@@ -12,3 +12,40 @@ func IsOnion(url string) bool {
return false
}
+
+func GetActorInstance(path string) (string, string) {
+ re := regexp.MustCompile(`([@]?([\w\d.-_]+)[@](.+))`)
+ atFormat := re.MatchString(path)
+
+ if atFormat {
+ match := re.FindStringSubmatch(path)
+ if len(match) > 2 {
+ return match[2], match[3]
+ }
+ }
+
+ re = regexp.MustCompile(`(https?://)(www)?([\w\d-_.:]+)(/|\s+|\r|\r\n)?$`)
+ mainActor := re.MatchString(path)
+ if mainActor {
+ match := re.FindStringSubmatch(path)
+ if len(match) > 2 {
+ return "main", match[3]
+ }
+ }
+
+ re = regexp.MustCompile(`(https?://)?(www)?([\w\d-_.:]+)\/([\w\d-_.]+)(\/([\w\d-_.]+))?`)
+ httpFormat := re.MatchString(path)
+
+ if httpFormat {
+ match := re.FindStringSubmatch(path)
+ if len(match) > 3 {
+ if match[4] == "users" {
+ return match[6], match[3]
+ }
+
+ return match[4], match[3]
+ }
+ }
+
+ return "", ""
+}
diff --git a/webfinger.go b/webfinger.go
deleted file mode 100644
index c8fd0ae..0000000
--- a/webfinger.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package main
-
-type Webfinger struct {
- Subject string `json:"subject,omitempty"`
- Links []WebfingerLink `json:"links,omitempty"`
-}
-
-type WebfingerLink struct {
- Rel string `json:"rel,omitempty"`
- Type string `json:"type,omitempty"`
- Href string `json:"href,omitempty"`
-}
diff --git a/webfinger/webfinger.go b/webfinger/webfinger.go
new file mode 100644
index 0000000..ffe7c6d
--- /dev/null
+++ b/webfinger/webfinger.go
@@ -0,0 +1,145 @@
+package webfinger
+
+import (
+ "encoding/json"
+ "io/ioutil"
+ "net/http"
+ "strings"
+
+ "github.com/FChannel0/FChannel-Server/activitypub"
+ "github.com/FChannel0/FChannel-Server/config"
+ "github.com/FChannel0/FChannel-Server/util"
+)
+
+type Webfinger struct {
+ Subject string `json:"subject,omitempty"`
+ Links []WebfingerLink `json:"links,omitempty"`
+}
+
+type WebfingerLink struct {
+ Rel string `json:"rel,omitempty"`
+ Type string `json:"type,omitempty"`
+ Href string `json:"href,omitempty"`
+}
+
+var ActorCache = make(map[string]activitypub.Actor)
+
+func GetActor(id string) (activitypub.Actor, error) {
+ var respActor activitypub.Actor
+
+ if id == "" {
+ return respActor, nil
+ }
+
+ actor, instance := util.GetActorInstance(id)
+
+ if ActorCache[actor+"@"+instance].Id != "" {
+ respActor = ActorCache[actor+"@"+instance]
+ return respActor, nil
+ }
+
+ req, err := http.NewRequest("GET", strings.TrimSpace(id), nil)
+ if err != nil {
+ return respActor, err
+ }
+ req.Header.Set("Accept", config.ActivityStreams)
+
+ resp, err := util.RouteProxy(req)
+
+ if err != nil {
+ return respActor, err
+ }
+
+ defer resp.Body.Close()
+
+ body, _ := ioutil.ReadAll(resp.Body)
+
+ if err := json.Unmarshal(body, &respActor); err != nil {
+ return respActor, err
+ }
+
+ ActorCache[actor+"@"+instance] = respActor
+
+ return respActor, nil
+}
+
+//looks for actor with pattern of board@instance
+func FingerActor(path string) (activitypub.Actor, error) {
+ var nActor activitypub.Actor
+
+ actor, instance := util.GetActorInstance(path)
+
+ if actor == "" && instance == "" {
+ return nActor, nil
+ }
+
+ if ActorCache[actor+"@"+instance].Id != "" {
+ nActor = ActorCache[actor+"@"+instance]
+ return nActor, nil
+ }
+
+ r, err := FingerRequest(actor, instance)
+ if err != nil {
+ return nActor, err
+ }
+
+ if r != nil && r.StatusCode == 200 {
+ defer r.Body.Close()
+
+ body, _ := ioutil.ReadAll(r.Body)
+
+ if err := json.Unmarshal(body, &nActor); err != nil {
+ return nActor, err
+ }
+
+ ActorCache[actor+"@"+instance] = nActor
+ }
+
+ // TODO: this just falls through and returns a blank Actor object. do something?
+ return nActor, nil
+}
+
+func FingerRequest(actor string, instance string) (*http.Response, error) {
+ acct := "acct:" + actor + "@" + instance
+
+ // TODO: respect https
+ req, err := http.NewRequest("GET", "http://"+instance+"/.well-known/webfinger?resource="+acct, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ resp, err := util.RouteProxy(req)
+ if err != nil {
+ return resp, err
+ }
+
+ var finger Webfinger
+
+ if resp.StatusCode == 200 {
+ defer resp.Body.Close()
+
+ body, _ := ioutil.ReadAll(resp.Body)
+
+ if err := json.Unmarshal(body, &finger); err != nil {
+ return resp, err
+ }
+ }
+
+ if len(finger.Links) > 0 {
+ for _, e := range finger.Links {
+ if e.Type == "application/activity+json" {
+ req, err := http.NewRequest("GET", e.Href, nil)
+ if err != nil {
+ return resp, err
+ }
+
+ req.Header.Set("Accept", config.ActivityStreams)
+
+ resp, err := util.RouteProxy(req)
+ return resp, err
+ }
+ }
+ }
+
+ return resp, nil
+}