From 580dec5b89215310ce34341e11ff17fe38bdb63a Mon Sep 17 00:00:00 2001 From: FChannel <> Date: Sun, 8 May 2022 14:57:40 -0700 Subject: more cleanup, logging and error logging everywhere things are mostly in place can work on "features" and polish --- activitypub/activity.go | 343 ++++++++++++++++++++- activitypub/actor.go | 774 +++++++++++++++++++++++++++++++++-------------- activitypub/object.go | 201 ++++++++++-- activitypub/pem.go | 5 +- activitypub/util.go | 12 + activitypub/webfinger.go | 180 +++++++++++ config/config.go | 3 +- db/cache.go | 72 ----- db/database.go | 584 +++++++++-------------------------- db/follow.go | 306 ------------------- db/pem.go | 95 ------ db/report.go | 112 +++---- db/verification.go | 514 ------------------------------- main.go | 48 +-- post/tripcode.go | 106 +++---- post/util.go | 111 +++---- routes/actor.go | 86 +++--- routes/admin.go | 83 ++--- routes/api.go | 9 +- routes/archive.go | 11 +- routes/index.go | 14 +- routes/news.go | 18 +- routes/outbox.go | 16 +- routes/post.go | 36 +-- routes/util.go | 90 +++--- routes/webfinger.go | 8 +- util/blacklist.go | 33 +- util/key.go | 30 +- util/proxy.go | 40 +-- util/util.go | 79 ++--- util/verification.go | 482 +++++++++++++++++++++++++++++ webfinger/comm.go | 88 ------ webfinger/util.go | 36 ++- webfinger/webfinger.go | 313 ------------------- 34 files changed, 2345 insertions(+), 2593 deletions(-) create mode 100644 activitypub/webfinger.go delete mode 100644 db/cache.go delete mode 100644 db/follow.go delete mode 100644 db/pem.go delete mode 100644 db/verification.go create mode 100644 util/verification.go delete mode 100644 webfinger/comm.go delete mode 100644 webfinger/webfinger.go diff --git a/activitypub/activity.go b/activitypub/activity.go index 06e061b..1650f14 100644 --- a/activitypub/activity.go +++ b/activitypub/activity.go @@ -1,26 +1,135 @@ package activitypub import ( + "bytes" + "encoding/json" "errors" "fmt" + "io/ioutil" + "net/http" + "regexp" "strings" + "time" "github.com/FChannel0/FChannel-Server/config" "github.com/FChannel0/FChannel-Server/util" ) -func AcceptActivity(header string) bool { - accept := false - if strings.Contains(header, ";") { - split := strings.Split(header, ";") - accept = accept || activityRegexp.MatchString(split[0]) - accept = accept || strings.Contains(split[len(split)-1], "profile=\"https://www.w3.org/ns/activitystreams\"") - } else { - accept = accept || activityRegexp.MatchString(header) - } +func (activity Activity) AcceptFollow() 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 (activity Activity) AddFollowersTo() (Activity, error) { + activity.To = append(activity.To, activity.Actor.Id) + + for _, e := range activity.To { + reqActivity := Activity{Id: e + "/followers"} + aFollowers, err := reqActivity.GetCollection() + if err != nil { + return activity, util.MakeError(err, "AddFollowersTo") + } + + for _, k := range aFollowers.Items { + activity.To = append(activity.To, k.Id) + } + } + + var nActivity Activity + + for _, e := range activity.To { + var alreadyTo = false + for _, k := range nActivity.To { + if e == k || e == activity.Actor.Id { + alreadyTo = true + } + } + + if !alreadyTo { + nActivity.To = append(nActivity.To, e) + } + } + + activity.To = nActivity.To + + return activity, nil +} + +func (activity Activity) CheckValid() (Collection, bool, error) { + var respCollection Collection + + re := regexp.MustCompile(`.+\.onion(.+)?`) + if re.MatchString(activity.Id) { + activity.Id = strings.Replace(activity.Id, "https", "http", 1) + } + + req, err := http.NewRequest("GET", activity.Id, nil) + if err != nil { + return respCollection, false, util.MakeError(err, "CheckValid") + } + + req.Header.Set("Accept", config.ActivityStreams) + + resp, err := util.RouteProxy(req) + if err != nil { + return respCollection, false, util.MakeError(err, "CheckValid") + } + defer resp.Body.Close() + + body, _ := ioutil.ReadAll(resp.Body) + + if err := json.Unmarshal(body, &respCollection); err != nil { + return respCollection, false, util.MakeError(err, "CheckValid") + } + + if respCollection.AtContext.Context == "https://www.w3.org/ns/activitystreams" && respCollection.OrderedItems[0].Id != "" { + return respCollection, true, nil + } + + return respCollection, false, nil +} + +func (activity Activity) GetCollection() (Collection, error) { + var nColl Collection + + req, err := http.NewRequest("GET", activity.Id, nil) + if err != nil { + return nColl, util.MakeError(err, "GetCollection") + } + + req.Header.Set("Accept", config.ActivityStreams) + resp, err := util.RouteProxy(req) + if err != nil { + return nColl, util.MakeError(err, "GetCollection") + } + + if resp.StatusCode == 200 { + defer resp.Body.Close() + body, _ := ioutil.ReadAll(resp.Body) + if len(body) > 0 { + if err := json.Unmarshal(body, &nColl); err != nil { + return nColl, util.MakeError(err, "GetCollection") + } + } + } + + return nColl, nil +} + func (activity Activity) IsLocal() (bool, error) { for _, e := range activity.To { @@ -50,11 +159,11 @@ func (activity Activity) Process() error { if activityType == "Create" { for _, e := range activity.To { if res, err := GetActorFromDB(e); res.Id != "" { - fmt.Println("actor is in the database") + config.Log.Println("actor is in the database") } else if err != nil { return util.MakeError(err, "Process") } else { - fmt.Println("actor is NOT in the database") + config.Log.Println("actor is NOT in the database") } } } else if activityType == "Follow" { @@ -91,7 +200,9 @@ func (activity Activity) Report(reason string) (bool, error) { return false, util.MakeError(err, "Report") } - activityCol, err := activity.Object.GetCollection() + reqActivity := Activity{Id: activity.Object.Id} + activityCol, err := reqActivity.GetCollection() + if err != nil { return false, util.MakeError(err, "Report") } @@ -104,7 +215,7 @@ func (activity Activity) Report(reason string) (bool, error) { return true, nil } -func (activity Activity) SetFollower() (Activity, error) { +func (activity Activity) SetActorFollower() (Activity, error) { var query string alreadyFollow, err := activity.Actor.IsAlreadyFollower(activity.Object.Actor) @@ -140,3 +251,209 @@ func (activity Activity) SetFollower() (Activity, error) { activity.Type = "Accept" return activity, nil } + +func (activity Activity) SetActorFollowing() (Activity, error) { + var query string + + alreadyFollowing := false + alreadyFollower := false + objActor, _ := GetActor(activity.Object.Actor) + following, err := objActor.GetFollowing() + + if err != nil { + return activity, util.MakeError(err, "SetActorFollowing") + } + + actor, err := FingerActor(activity.Actor.Id) + + if err != nil { + return activity, util.MakeError(err, "SetActorFollowing") + } + + reqActivity := Activity{Id: actor.Followers} + remoteActorFollowerCol, err := reqActivity.GetCollection() + + if err != nil { + return activity, util.MakeError(err, "SetActorFollowing") + } + + 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 := activity.Actor.IsLocal(); err == nil && !res { + go activity.Actor.DeleteCache() + } else { + return activity, util.MakeError(err, "SetActorFollowing") + } + + if _, err := config.DB.Exec(query, activity.Object.Actor, activity.Actor.Id); err != nil { + return activity, util.MakeError(err, "SetActorFollowing") + } + + 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 := activity.Actor.IsLocal(); err == nil && !res { + go activity.Actor.WriteCache() + } + if _, err := config.DB.Exec(query, activity.Object.Actor, activity.Actor.Id); err != nil { + return activity, util.MakeError(err, "SetActorFollowing") + } + + activity.Type = "Accept" + + return activity, nil + } + + return activity, nil +} + +func (activity Activity) MakeFollowingReq() (bool, error) { + actor, err := GetActor(activity.Object.Id) + + if err != nil { + return false, util.MakeError(err, "MakeFollowingReq") + } + + req, err := http.NewRequest("POST", actor.Inbox, nil) + + if err != nil { + return false, util.MakeError(err, "MakeFollowingReq") + } + + resp, err := util.RouteProxy(req) + + if err != nil { + return false, util.MakeError(err, "MakeFollowingReq") + } + + defer resp.Body.Close() + body, _ := ioutil.ReadAll(resp.Body) + + var respActivity Activity + err = json.Unmarshal(body, &respActivity) + + return respActivity.Type == "Accept", util.MakeError(err, "MakeFollowingReq") +} + +func (activity Activity) MakeRequestInbox() error { + j, _ := json.MarshalIndent(activity, "", "\t") + + for _, e := range activity.To { + if e != activity.Actor.Id { + actor, err := FingerActor(e) + + if err != nil { + return util.MakeError(err, "MakeRequest") + } + + if actor.Id != "" { + _, instance := GetActorAndInstance(actor.Id) + + if actor.Inbox != "" { + req, err := http.NewRequest("POST", actor.Inbox, bytes.NewBuffer(j)) + + if err != nil { + return util.MakeError(err, "MakeRequest") + } + + 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 := activity.Actor.ActivitySign(sig) + + if err != nil { + return util.MakeError(err, "MakeRequest") + } + + 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 { + return util.MakeError(err, "MakeRequest") + } + } + } + } + } + + return nil +} + +func (activity Activity) MakeRequestOutbox() error { + j, _ := json.Marshal(activity) + + if activity.Actor.Outbox == "" { + return util.MakeError(errors.New("invalid outbox"), "MakeRequestOutbox") + } + + req, err := http.NewRequest("POST", activity.Actor.Outbox, bytes.NewBuffer(j)) + + if err != nil { + return util.MakeError(err, "MakeRequestOutbox") + } + + re := regexp.MustCompile("https?://(www.)?") + + var instance string + if activity.Actor.Id == config.Domain { + instance = re.ReplaceAllString(config.Domain, "") + } else { + _, instance = GetActorAndInstance(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 := activity.Actor.ActivitySign(sig) + + if err != nil { + return util.MakeError(err, "MakeRequestOutbox") + } + + 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 util.MakeError(err, "MakeRequestOutbox") +} diff --git a/activitypub/actor.go b/activitypub/actor.go index 120c9fd..bc7ba8a 100644 --- a/activitypub/actor.go +++ b/activitypub/actor.go @@ -11,16 +11,20 @@ import ( "encoding/json" "encoding/pem" "errors" - "fmt" "io/ioutil" + "net/http" "os" + "regexp" "strings" + "time" "github.com/FChannel0/FChannel-Server/config" "github.com/FChannel0/FChannel-Server/util" "github.com/gofiber/fiber/v2" ) +var ActorCache = make(map[string]Actor) + func (actor Actor) AddFollower(follower string) error { query := `insert into follower (id, follower) values ($1, $2)` _, err := config.DB.Exec(query, actor.Id, follower) @@ -39,7 +43,7 @@ func (actor Actor) ActivitySign(signature string) (string, error) { _, err := os.Stat(file) if err != nil { - fmt.Println(`\n Unable to locate private key. Now, + config.Log.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 @@ -65,6 +69,75 @@ accepting your posts from your board from this site. Good luck ;)`) return base64.StdEncoding.EncodeToString(cipher), nil } +func (actor Actor) ArchivePosts() error { + if actor.Id != "" && actor.Id != config.Domain { + col, err := actor.GetAllArchive(165) + + if err != nil { + return util.MakeError(err, "ArchivePosts") + } + + for _, e := range col.OrderedItems { + for _, k := range e.Replies.OrderedItems { + if err := k.UpdateType("Archive"); err != nil { + return util.MakeError(err, "ArchivePosts") + } + } + + if err := e.UpdateType("Archive"); err != nil { + return util.MakeError(err, "ArchivePosts") + } + } + } + + return nil +} + +func (actor Actor) AutoFollow() error { + nActor, _ := GetActor(actor.Id) + following, err := nActor.GetFollowing() + + if err != nil { + return util.MakeError(err, "AutoFollow") + } + + follower, err := nActor.GetFollow() + + if err != nil { + return util.MakeError(err, "AutoFollow") + } + + 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 != nActor.Id { + followActivity, err := nActor.MakeFollowActivity(e.Id) + + if err != nil { + return util.MakeError(err, "AutoFollow") + } + + nActor, err := FingerActor(e.Id) + + if err != nil { + return util.MakeError(err, "AutoFollow") + } + + if nActor.Id != "" { + followActivity.MakeRequestOutbox() + } + } + } + + return nil +} + func (actor Actor) DeleteCache() error { query := `select id from cacheactivitystream where id in (select id from cacheactivitystream where actor=$1)` rows, err := config.DB.Query(query, actor.Id) @@ -88,6 +161,37 @@ func (actor Actor) DeleteCache() error { return nil } +func (actor Actor) GetAllArchive(offset int) (Collection, error) { + var nColl Collection + var result []ObjectBase + + query := `select x.id, x.updated from (select id, updated from activitystream where actor=$1 and id in (select id from replies where inreplyto='') and type='Note' union select id, updated from activitystream where actor in (select following from following where id=$1) and id in (select id from replies where inreplyto='') and type='Note' union select id, updated from cacheactivitystream where actor in (select following from following where id=$1) and id in (select id from replies where inreplyto='') and type='Note') as x order by x.updated desc offset $2` + rows, err := config.DB.Query(query, actor.Id, offset) + + if err != nil { + return nColl, util.MakeError(err, "GetAllArchive") + } + + defer rows.Close() + for rows.Next() { + var post ObjectBase + + if err := rows.Scan(&post.Id, &post.Updated); err != nil { + return nColl, util.MakeError(err, "GetAllArchive") + } + + post.Replies, _, _, err = post.GetReplies() + + result = append(result, post) + } + + nColl.AtContext.Context = "https://www.w3.org/ns/activitystreams" + + nColl.OrderedItems = result + + return nColl, nil +} + func (actor Actor) GetAutoSubscribe() (bool, error) { var subscribed bool @@ -99,6 +203,188 @@ func (actor Actor) GetAutoSubscribe() (bool, error) { return subscribed, nil } +func (actor Actor) GetCatalogCollection() (Collection, error) { + var nColl Collection + var result []ObjectBase + + var err error + var rows *sql.Rows + + query := `select x.id, x.name, x.content, x.type, x.published, x.updated, x.attributedto, x.attachment, x.preview, x.actor, x.tripcode, x.sensitive from (select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from activitystream where actor=$1 and id in (select id from replies where inreplyto='') and type='Note' union select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from activitystream where actor in (select following from following where id=$1) and id in (select id from replies where inreplyto='') and type='Note' union select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from cacheactivitystream where actor in (select following from following where id=$1) and id in (select id from replies where inreplyto='') and type='Note') as x order by x.updated desc limit 165` + if rows, err = config.DB.Query(query, actor.Id); err != nil { + return nColl, util.MakeError(err, "GetCatalogCollection") + } + + defer rows.Close() + for rows.Next() { + var post ObjectBase + var actor Actor + + var attch ObjectBase + post.Attachment = append(post.Attachment, attch) + + var prev NestedObjectBase + post.Preview = &prev + + err = rows.Scan(&post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.Updated, &post.AttributedTo, &post.Attachment[0].Id, &post.Preview.Id, &actor.Id, &post.TripCode, &post.Sensitive) + + if err != nil { + return nColl, util.MakeError(err, "GetCatalogCollection") + } + + post.Actor = actor.Id + + var replies CollectionBase + + post.Replies = replies + + post.Replies.TotalItems, post.Replies.TotalImgs, err = post.GetRepliesCount() + + if err != nil { + return nColl, util.MakeError(err, "GetCatalogCollection") + } + + post.Attachment, err = post.Attachment[0].GetAttachment() + + if err != nil { + return nColl, util.MakeError(err, "GetCatalogCollection") + } + + post.Preview, err = post.Preview.GetPreview() + + if err != nil { + return nColl, util.MakeError(err, "GetCatalogCollection") + } + + result = append(result, post) + } + + nColl.AtContext.Context = "https://www.w3.org/ns/activitystreams" + + nColl.OrderedItems = result + + return nColl, nil +} + +func (actor Actor) GetCollectionPage(page int) (Collection, error) { + var nColl Collection + var result []ObjectBase + + var err error + var rows *sql.Rows + + query := `select count (x.id) over(), x.id, x.name, x.content, x.type, x.published, x.updated, x.attributedto, x.attachment, x.preview, x.actor, x.tripcode, x.sensitive from (select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from activitystream where actor=$1 and id in (select id from replies where inreplyto='') and type='Note' union select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from activitystream where actor in (select following from following where id=$1) and id in (select id from replies where inreplyto='') and type='Note' union select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from cacheactivitystream where actor in (select following from following where id=$1) and id in (select id from replies where inreplyto='') and type='Note') as x order by x.updated desc limit 15 offset $2` + + if rows, err = config.DB.Query(query, actor.Id, page*15); err != nil { + return nColl, util.MakeError(err, "GetCollectionPage") + } + + var count int + defer rows.Close() + for rows.Next() { + var post ObjectBase + var actor Actor + + var attch ObjectBase + post.Attachment = append(post.Attachment, attch) + + var prev NestedObjectBase + post.Preview = &prev + + err = rows.Scan(&count, &post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.Updated, &post.AttributedTo, &post.Attachment[0].Id, &post.Preview.Id, &actor.Id, &post.TripCode, &post.Sensitive) + + if err != nil { + return nColl, util.MakeError(err, "GetCollectionPage") + } + + post.Actor = actor.Id + + post.Replies, post.Replies.TotalItems, post.Replies.TotalImgs, err = post.GetRepliesLimit(5) + + if err != nil { + return nColl, util.MakeError(err, "GetCollectionPage") + } + + post.Attachment, err = post.Attachment[0].GetAttachment() + + if err != nil { + return nColl, util.MakeError(err, "GetCollectionPage") + } + + post.Preview, err = post.Preview.GetPreview() + + if err != nil { + return nColl, util.MakeError(err, "GetCollectionPage") + } + + result = append(result, post) + } + + nColl.AtContext.Context = "https://www.w3.org/ns/activitystreams" + + nColl.TotalItems = count + + nColl.OrderedItems = result + + return nColl, nil +} + +func (actor Actor) GetCollection() (Collection, error) { + var nColl Collection + var result []ObjectBase + + query := `select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from activitystream where actor=$1 and id in (select id from replies where inreplyto='') and type='Note' order by updated desc` + rows, err := config.DB.Query(query, actor.Id) + + if err != nil { + return nColl, util.MakeError(err, "GetCollection") + } + + defer rows.Close() + for rows.Next() { + var post ObjectBase + var actor Actor + + var attch ObjectBase + post.Attachment = append(post.Attachment, attch) + + var prev NestedObjectBase + post.Preview = &prev + + if err := rows.Scan(&post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.Updated, &post.AttributedTo, &post.Attachment[0].Id, &post.Preview.Id, &actor.Id, &post.TripCode, &post.Sensitive); err != nil { + return nColl, util.MakeError(err, "GetCollection") + } + + post.Actor = actor.Id + + post.Replies, post.Replies.TotalItems, post.Replies.TotalImgs, err = post.GetReplies() + + if err != nil { + return nColl, util.MakeError(err, "GetCollection") + } + + post.Attachment, err = post.Attachment[0].GetAttachment() + + if err != nil { + return nColl, util.MakeError(err, "GetCollection") + } + + post.Preview, err = post.Preview.GetPreview() + + if err != nil { + return nColl, util.MakeError(err, "GetCollection") + } + + result = append(result, post) + } + + nColl.AtContext.Context = "https://www.w3.org/ns/activitystreams" + + nColl.OrderedItems = result + + return nColl, nil +} + func (actor Actor) GetCollectionType(nType string) (Collection, error) { var nColl Collection var result []ObjectBase @@ -232,6 +518,50 @@ func (actor Actor) GetFollow() ([]ObjectBase, error) { return followerCollection, nil } +func (actor Actor) GetFollowing() ([]ObjectBase, error) { + var followingCollection []ObjectBase + + query := `select following from following where id=$1` + rows, err := config.DB.Query(query, actor.Id) + + if err != nil { + return followingCollection, util.MakeError(err, "GetFollowing") + } + + defer rows.Close() + for rows.Next() { + var obj ObjectBase + + if err := rows.Scan(&obj.Id); err != nil { + return followingCollection, util.MakeError(err, "GetFollowing") + } + + followingCollection = append(followingCollection, obj) + } + + return followingCollection, nil +} + +func (actor Actor) GetFollowFromName(name string) ([]string, error) { + var followingActors []string + + activity := Activity{Id: actor.Following} + follow, err := activity.GetCollection() + if err != nil { + return followingActors, util.MakeError(err, "GetFollowFromName") + } + + re := regexp.MustCompile("\\w+?$") + + for _, e := range follow.Items { + if re.FindString(e.Id) == name { + followingActors = append(followingActors, e.Id) + } + } + + return followingActors, nil +} + func (actor Actor) GetFollowingTotal() (int, error) { var following int @@ -276,7 +606,7 @@ func (actor Actor) GetFollowersResp(ctx *fiber.Ctx) error { ctx.Response().Header.Set("Content-Type", config.ActivityStreams) _, err = ctx.Write(enc) - return err + return util.MakeError(err, "") } func (actor Actor) GetFollowingResp(ctx *fiber.Ctx) error { @@ -304,28 +634,15 @@ func (actor Actor) GetFollowingResp(ctx *fiber.Ctx) error { return util.MakeError(err, "GetFollowingResp") } -func (actor Actor) GetFollowing() ([]ObjectBase, error) { - var followingCollection []ObjectBase - - query := `select following from following where id=$1` - rows, err := config.DB.Query(query, actor.Id) - - if err != nil { - return followingCollection, util.MakeError(err, "GetFollowing") - } - - defer rows.Close() - for rows.Next() { - var obj ObjectBase - - if err := rows.Scan(&obj.Id); err != nil { - return followingCollection, util.MakeError(err, "GetFollowing") - } +func (actor Actor) GetImgTotal() (int, error) { + var count int - followingCollection = append(followingCollection, obj) + query := `select count(attachment) from activitystream where actor=$1 and id in (select id from replies where inreplyto='' and type='Note' )` + if err := config.DB.QueryRow(query, actor.Id).Scan(&count); err != nil { + return count, util.MakeError(err, "GetImgTotal") } - return followingCollection, nil + return count, nil } func (actor Actor) GetInfoResp(ctx *fiber.Ctx) error { @@ -337,60 +654,47 @@ func (actor Actor) GetInfoResp(ctx *fiber.Ctx) error { return util.MakeError(err, "GetInfoResp") } -func (actor Actor) GetCollection() (Collection, error) { - var nColl Collection - var result []ObjectBase - - query := `select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from activitystream where actor=$1 and id in (select id from replies where inreplyto='') and type='Note' order by updated desc` - rows, err := config.DB.Query(query, actor.Id) +func (actor Actor) GetPostTotal() (int, error) { + var count int - if err != nil { - return nColl, util.MakeError(err, "GetCollection") + query := `select count(id) from activitystream where actor=$1 and id in (select id from replies where inreplyto='' and type='Note')` + if err := config.DB.QueryRow(query, actor.Id).Scan(&count); err != nil { + return count, util.MakeError(err, "GetPostTotal") } - defer rows.Close() - for rows.Next() { - var post ObjectBase - var actor Actor - - var attch ObjectBase - post.Attachment = append(post.Attachment, attch) - - var prev NestedObjectBase - post.Preview = &prev - - if err := rows.Scan(&post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.Updated, &post.AttributedTo, &post.Attachment[0].Id, &post.Preview.Id, &actor.Id, &post.TripCode, &post.Sensitive); err != nil { - return nColl, util.MakeError(err, "GetCollection") - } - - post.Actor = actor.Id - - post.Replies, post.Replies.TotalItems, post.Replies.TotalImgs, err = post.GetReplies() + return count, nil +} - if err != nil { - return nColl, util.MakeError(err, "GetCollection") - } +func (actor Actor) GetOutbox(ctx *fiber.Ctx) error { + var collection Collection - post.Attachment, err = post.Attachment[0].GetAttachment() + c, err := actor.GetCollection() - if err != nil { - return nColl, util.MakeError(err, "GetCollection") - } + if err != nil { + return util.MakeError(err, "GetOutbox") + } - post.Preview, err = post.Preview.GetPreview() + collection.OrderedItems = c.OrderedItems + collection.AtContext.Context = "https://www.w3.org/ns/activitystreams" + collection.Actor = actor - if err != nil { - return nColl, util.MakeError(err, "GetCollection") - } + collection.TotalItems, err = actor.GetPostTotal() - result = append(result, post) + if err != nil { + return util.MakeError(err, "GetOutbox") } - nColl.AtContext.Context = "https://www.w3.org/ns/activitystreams" + collection.TotalImgs, err = actor.GetImgTotal() - nColl.OrderedItems = result + if err != nil { + return util.MakeError(err, "GetOutbox") + } - return nColl, nil + enc, _ := json.Marshal(collection) + ctx.Response().Header.Set("Content-Type", config.ActivityStreams) + _, err = ctx.Write(enc) + + return util.MakeError(err, "GetOutbox") } func (actor Actor) GetRecentPosts() ([]ObjectBase, error) { @@ -467,35 +771,14 @@ func (actor Actor) GetReportedTotal() (int, error) { return count, nil } -func (actor Actor) GetAllArchive(offset int) (Collection, error) { - var nColl Collection - var result []ObjectBase - - query := `select x.id, x.updated from (select id, updated from activitystream where actor=$1 and id in (select id from replies where inreplyto='') and type='Note' union select id, updated from activitystream where actor in (select following from following where id=$1) and id in (select id from replies where inreplyto='') and type='Note' union select id, updated from cacheactivitystream where actor in (select following from following where id=$1) and id in (select id from replies where inreplyto='') and type='Note') as x order by x.updated desc offset $2` - rows, err := config.DB.Query(query, actor.Id, offset) - - if err != nil { - return nColl, util.MakeError(err, "GetAllArchive") - } - - defer rows.Close() - for rows.Next() { - var post ObjectBase - - if err := rows.Scan(&post.Id, &post.Updated); err != nil { - return nColl, util.MakeError(err, "GetAllArchive") - } - - post.Replies, _, _, err = post.GetReplies() +func (actor Actor) HasValidation(ctx *fiber.Ctx) bool { + id, _ := util.GetPasswordFromSession(ctx) - result = append(result, post) + if id == "" || (id != actor.Id && id != config.Domain) { + return false } - nColl.AtContext.Context = "https://www.w3.org/ns/activitystreams" - - nColl.OrderedItems = result - - return nColl, nil + return true } func (actor Actor) IsAlreadyFollowing(follow string) (bool, error) { @@ -530,6 +813,70 @@ func (actor Actor) IsAlreadyFollower(follow string) (bool, error) { return false, nil } +func (actor Actor) IsLocal() (bool, error) { + actor, err := GetActorFromDB(actor.Id) + return actor.Id != "", util.MakeError(err, "IsLocal") +} + +func (actor Actor) IsValid() (Actor, bool, error) { + actor, err := FingerActor(actor.Id) + return actor, actor.Id != "", util.MakeError(err, "IsValid") +} + +func (actor Actor) ReportedResp(ctx *fiber.Ctx) error { + var err error + + auth := ctx.Get("Authorization") + verification := strings.Split(auth, " ") + + if len(verification) < 2 { + ctx.Response().Header.SetStatusCode(http.StatusBadRequest) + _, err := ctx.Write([]byte("")) + return util.MakeError(err, "GetReported") + } + + if hasAuth, err := util.HasAuth(verification[1], actor.Id); !hasAuth { + ctx.Response().Header.SetStatusCode(http.StatusBadRequest) + _, err := ctx.Write([]byte("")) + return util.MakeError(err, "GetReported") + } else if err != nil { + return util.MakeError(err, "GetReported") + } + + actor, err = GetActorFromDB(actor.Id) + + if err != nil { + return util.MakeError(err, "GetReported") + } + + var following Collection + + following.AtContext.Context = "https://www.w3.org/ns/activitystreams" + following.Type = "Collection" + following.TotalItems, err = actor.GetReportedTotal() + + if err != nil { + return util.MakeError(err, "GetReported") + } + + following.Items, err = actor.GetReported() + + if err != nil { + return util.MakeError(err, "GetReported") + } + + enc, err := json.MarshalIndent(following, "", "\t") + + if err != nil { + return util.MakeError(err, "GetReported") + } + + ctx.Response().Header.Set("Content-Type", config.ActivityStreams) + _, err = ctx.Write(enc) + + return util.MakeError(err, "GetReported") +} + func (actor Actor) SetActorAutoSubscribeDB() error { current, err := actor.GetAutoSubscribe() @@ -543,36 +890,37 @@ func (actor Actor) SetActorAutoSubscribeDB() error { return util.MakeError(err, "SetActorAutoSubscribeDB") } -func (actor Actor) GetOutbox(ctx *fiber.Ctx) error { - var collection Collection - - c, err := actor.GetCollection() +func (actor Actor) SendToFollowers(activity Activity) error { + nActor, err := GetActorFromDB(actor.Id) if err != nil { - return util.MakeError(err, "GetOutbox") + return util.MakeError(err, "SendToFollowers") } - collection.OrderedItems = c.OrderedItems - collection.AtContext.Context = "https://www.w3.org/ns/activitystreams" - collection.Actor = actor - - collection.TotalItems, err = actor.GetPostTotal() + activity.Actor = &nActor + followers, err := nActor.GetFollow() if err != nil { - return util.MakeError(err, "GetOutbox") + return util.MakeError(err, "SendToFollowers") } - collection.TotalImgs, err = actor.GetImgTotal() + var to []string - if err != nil { - return util.MakeError(err, "GetOutbox") + for _, e := range followers { + for _, k := range activity.To { + if e.Id != k { + to = append(to, e.Id) + } + } } - enc, _ := json.Marshal(collection) - ctx.Response().Header.Set("Content-Type", config.ActivityStreams) - _, err = ctx.Write(enc) + activity.To = to - return util.MakeError(err, "GetOutbox") + if len(activity.Object.InReplyTo) > 0 { + err = activity.MakeRequestInbox() + } + + return util.MakeError(err, "SendToFollowers") } func (actor Actor) UnArchiveLast() error { @@ -597,135 +945,135 @@ func (actor Actor) UnArchiveLast() error { return nil } -func (actor Actor) IsLocal() (bool, error) { - actor, err := GetActorFromDB(actor.Id) - return actor.Id != "", util.MakeError(err, "IsLocal") -} - -func (actor Actor) GetCatalogCollection() (Collection, error) { - var nColl Collection - var result []ObjectBase - - var err error - var rows *sql.Rows - - query := `select x.id, x.name, x.content, x.type, x.published, x.updated, x.attributedto, x.attachment, x.preview, x.actor, x.tripcode, x.sensitive from (select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from activitystream where actor=$1 and id in (select id from replies where inreplyto='') and type='Note' union select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from activitystream where actor in (select following from following where id=$1) and id in (select id from replies where inreplyto='') and type='Note' union select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from cacheactivitystream where actor in (select following from following where id=$1) and id in (select id from replies where inreplyto='') and type='Note') as x order by x.updated desc limit 165` - if rows, err = config.DB.Query(query, actor.Id); err != nil { - return nColl, util.MakeError(err, "GetCatalogCollection") - } - - defer rows.Close() - for rows.Next() { - var post ObjectBase - var actor Actor - - var attch ObjectBase - post.Attachment = append(post.Attachment, attch) - - var prev NestedObjectBase - post.Preview = &prev - - err = rows.Scan(&post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.Updated, &post.AttributedTo, &post.Attachment[0].Id, &post.Preview.Id, &actor.Id, &post.TripCode, &post.Sensitive) +func (actor Actor) Verify(signature string, verify string) error { + sig, _ := base64.StdEncoding.DecodeString(signature) + if actor.PublicKey.PublicKeyPem == "" { + _actor, err := FingerActor(actor.Id) if err != nil { - return nColl, util.MakeError(err, "GetCatalogCollection") + return util.MakeError(err, "Verify") } + actor = _actor + } - post.Actor = actor.Id - - var replies CollectionBase + block, _ := pem.Decode([]byte(actor.PublicKey.PublicKeyPem)) + pub, _ := x509.ParsePKIXPublicKey(block.Bytes) - post.Replies = replies + hashed := sha256.New() + hashed.Write([]byte(verify)) - post.Replies.TotalItems, post.Replies.TotalImgs, err = post.GetRepliesCount() + return rsa.VerifyPKCS1v15(pub.(*rsa.PublicKey), crypto.SHA256, hashed.Sum(nil), sig) +} - if err != nil { - return nColl, util.MakeError(err, "GetCatalogCollection") +func (actor Actor) VerifyHeaderSignature(ctx *fiber.Ctx) bool { + var sig string + var path string + var host string + var date string + var method string + var digest string + var contentLength string + + s := ParseHeaderSignature(ctx.Get("Signature")) + + for i, e := range s.Headers { + var nl string + if i < len(s.Headers)-1 { + nl = "\n" } - post.Attachment, err = post.Attachment[0].GetAttachment() - - if err != nil { - return nColl, util.MakeError(err, "GetCatalogCollection") + switch e { + case "(request-target)": + method = strings.ToLower(ctx.Method()) + path = ctx.Path() + sig += "(request-target): " + method + " " + path + "" + nl + break + case "host": + host = ctx.Hostname() + sig += "host: " + host + "" + nl + break + case "date": + date = ctx.Get("date") + sig += "date: " + date + "" + nl + break + case "digest": + digest = ctx.Get("digest") + sig += "digest: " + digest + "" + nl + break + case "content-length": + contentLength = ctx.Get("content-length") + sig += "content-length: " + contentLength + "" + nl + break } + } - post.Preview, err = post.Preview.GetPreview() + if s.KeyId != actor.PublicKey.Id { + return false + } - if err != nil { - return nColl, util.MakeError(err, "GetCatalogCollection") - } + t, _ := time.Parse(time.RFC1123, date) - result = append(result, post) + if time.Now().UTC().Sub(t).Seconds() > 75 { + return false } - nColl.AtContext.Context = "https://www.w3.org/ns/activitystreams" - - nColl.OrderedItems = result + if actor.Verify(s.Signature, sig) != nil { + return false + } - return nColl, nil + return true } -func (actor Actor) GetCollectionPage(page int) (Collection, error) { - var nColl Collection - var result []ObjectBase - - var err error - var rows *sql.Rows - - query := `select count (x.id) over(), x.id, x.name, x.content, x.type, x.published, x.updated, x.attributedto, x.attachment, x.preview, x.actor, x.tripcode, x.sensitive from (select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from activitystream where actor=$1 and id in (select id from replies where inreplyto='') and type='Note' union select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from activitystream where actor in (select following from following where id=$1) and id in (select id from replies where inreplyto='') and type='Note' union select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from cacheactivitystream where actor in (select following from following where id=$1) and id in (select id from replies where inreplyto='') and type='Note') as x order by x.updated desc limit 15 offset $2` +func (actor Actor) WriteCache() error { + actor, err := FingerActor(actor.Id) - if rows, err = config.DB.Query(query, actor.Id, page*15); err != nil { - return nColl, util.MakeError(err, "GetCollectionPage") + if err != nil { + return util.MakeError(err, "WriteCache") } - var count int - defer rows.Close() - for rows.Next() { - var post ObjectBase - var actor Actor - - var attch ObjectBase - post.Attachment = append(post.Attachment, attch) - - var prev NestedObjectBase - post.Preview = &prev - - err = rows.Scan(&count, &post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.Updated, &post.AttributedTo, &post.Attachment[0].Id, &post.Preview.Id, &actor.Id, &post.TripCode, &post.Sensitive) - - if err != nil { - return nColl, util.MakeError(err, "GetCollectionPage") - } - - post.Actor = actor.Id + reqActivity := Activity{Id: actor.Outbox} + collection, err := reqActivity.GetCollection() - post.Replies, post.Replies.TotalItems, post.Replies.TotalImgs, err = post.GetRepliesLimit(5) + if err != nil { + return util.MakeError(err, "WriteCache") + } - if err != nil { - return nColl, util.MakeError(err, "GetCollectionPage") + for _, e := range collection.OrderedItems { + if err := e.WriteCache(); err != nil { + return util.MakeError(err, "WriteCache") } + } - post.Attachment, err = post.Attachment[0].GetAttachment() + return nil +} - if err != nil { - return nColl, util.MakeError(err, "GetCollectionPage") - } +func (actor Actor) MakeFollowActivity(follow string) (Activity, error) { + var followActivity Activity + var err error - post.Preview, err = post.Preview.GetPreview() + followActivity.AtContext.Context = "https://www.w3.org/ns/activitystreams" + followActivity.Type = "Follow" - if err != nil { - return nColl, util.MakeError(err, "GetCollectionPage") - } + var obj ObjectBase + var nactor Actor - result = append(result, post) + if actor.Id == config.Domain { + nactor, err = GetActorFromDB(actor.Id) + } else { + nactor, err = FingerActor(actor.Id) } - nColl.AtContext.Context = "https://www.w3.org/ns/activitystreams" + if err != nil { + return followActivity, util.MakeError(err, "MakeFollowActivity") + } - nColl.TotalItems = count + followActivity.Actor = &nactor + followActivity.Object = &obj - nColl.OrderedItems = result + followActivity.Object.Actor = follow + followActivity.To = append(followActivity.To, follow) - return nColl, nil + return followActivity, nil } func (actor Actor) WantToServePage(page int) (Collection, error) { @@ -744,25 +1092,3 @@ func (actor Actor) WantToServePage(page int) (Collection, error) { return collection, nil } - -func (actor Actor) GetImgTotal() (int, error) { - var count int - - query := `select count(attachment) from activitystream where actor=$1 and id in (select id from replies where inreplyto='' and type='Note' )` - if err := config.DB.QueryRow(query, actor.Id).Scan(&count); err != nil { - return count, util.MakeError(err, "GetImgTotal") - } - - return count, nil -} - -func (actor Actor) GetPostTotal() (int, error) { - var count int - - query := `select count(id) from activitystream where actor=$1 and id in (select id from replies where inreplyto='' and type='Note')` - if err := config.DB.QueryRow(query, actor.Id).Scan(&count); err != nil { - return count, util.MakeError(err, "GetPostTotal") - } - - return count, nil -} diff --git a/activitypub/object.go b/activitypub/object.go index a873ce0..29a17e4 100644 --- a/activitypub/object.go +++ b/activitypub/object.go @@ -2,10 +2,7 @@ package activitypub import ( "database/sql" - "encoding/json" "fmt" - "io/ioutil" - "net/http" "os" "os/exec" "regexp" @@ -17,6 +14,35 @@ import ( "github.com/FChannel0/FChannel-Server/util" ) +func (obj ObjectBase) CreateActivity(activityType string) (Activity, error) { + var newActivity Activity + + actor, err := FingerActor(obj.Actor) + if err != nil { + return newActivity, util.MakeError(err, "CreateActivity") + } + + newActivity.AtContext.Context = "https://www.w3.org/ns/activitystreams" + newActivity.Type = activityType + newActivity.Published = obj.Published + newActivity.Actor = &actor + newActivity.Object = &obj + + for _, e := range obj.To { + if obj.Actor != e { + newActivity.To = append(newActivity.To, e) + } + } + + for _, e := range obj.Cc { + if obj.Actor != e { + newActivity.Cc = append(newActivity.Cc, e) + } + } + + return newActivity, nil +} + func (obj ObjectBase) CheckIfOP() (bool, error) { var count int @@ -63,6 +89,44 @@ func (obj ObjectBase) CreatePreview() *NestedObjectBase { return &nPreview } +func (obj ObjectBase) DeleteAndRepliesRequest() error { + activity, err := obj.CreateActivity("Delete") + + if err != nil { + return util.MakeError(err, "DeleteAndRepliesRequest") + } + + nObj, err := obj.GetCollectionFromPath() + if err != nil { + return util.MakeError(err, "DeleteAndRepliesRequest") + } + + activity.Actor.Id = nObj.OrderedItems[0].Actor + activity.Object = &nObj.OrderedItems[0] + objActor, _ := GetActor(nObj.OrderedItems[0].Actor) + followers, err := objActor.GetFollow() + + if err != nil { + return util.MakeError(err, "DeleteAndRepliesRequest") + } + for _, e := range followers { + activity.To = append(activity.To, e.Id) + } + + following, err := objActor.GetFollowing() + if err != nil { + return util.MakeError(err, "DeleteAndRepliesRequest") + } + + for _, e := range following { + activity.To = append(activity.To, e.Id) + } + + err = activity.MakeRequestInbox() + + return util.MakeError(err, "DeleteAndRepliesRequest") +} + //TODO break this off into seperate for Cache func (obj ObjectBase) DeleteAttachment() error { query := `delete from activitystream where id in (select attachment from activitystream where id=$1)` @@ -167,49 +231,65 @@ func (obj ObjectBase) Delete() error { return util.MakeError(err, "Delete") } -func (obj ObjectBase) DeleteRepliedTo() error { - query := `delete from replies where id=$1` - _, err := config.DB.Exec(query, obj.Id) - return util.MakeError(err, "DeleteRepliedTo") -} - func (obj ObjectBase) DeleteInReplyTo() error { query := `delete from replies where id in (select id from replies where inreplyto=$1)` _, err := config.DB.Exec(query, obj.Id) return util.MakeError(err, "DeleteInReplyTo") } -func (obj ObjectBase) DeleteReported() error { - query := `delete from reported where id=$1` +func (obj ObjectBase) DeleteRepliedTo() error { + query := `delete from replies where id=$1` _, err := config.DB.Exec(query, obj.Id) - return util.MakeError(err, "DeleteReported") + return util.MakeError(err, "DeleteRepliedTo") } -func (obj ObjectBase) GetCollection() (Collection, error) { - var nColl Collection +func (obj ObjectBase) DeleteRequest() error { + activity, err := obj.CreateActivity("Delete") - req, err := http.NewRequest("GET", obj.Id, nil) if err != nil { - return nColl, util.MakeError(err, "GetCollection") + return util.MakeError(err, "DeleteRequest") } - req.Header.Set("Accept", config.ActivityStreams) - resp, err := util.RouteProxy(req) + nObj, err := obj.GetFromPath() + if err != nil { - return nColl, util.MakeError(err, "GetCollection") + return util.MakeError(err, "DeleteRequest") } - if resp.StatusCode == 200 { - defer resp.Body.Close() - body, _ := ioutil.ReadAll(resp.Body) - if len(body) > 0 { - if err := json.Unmarshal(body, &nColl); err != nil { - return nColl, util.MakeError(err, "GetCollection") - } - } + actor, err := FingerActor(nObj.Actor) + + if err != nil { + return util.MakeError(err, "DeleteRequest") + } + activity.Actor = &actor + objActor, _ := GetActor(nObj.Actor) + followers, err := objActor.GetFollow() + if err != nil { + return util.MakeError(err, "DeleteRequest") } - return nColl, nil + for _, e := range followers { + activity.To = append(activity.To, e.Id) + } + + following, err := objActor.GetFollowing() + if err != nil { + return util.MakeError(err, "DeleteRequest") + } + for _, e := range following { + activity.To = append(activity.To, e.Id) + } + + err = activity.MakeRequestInbox() + + return util.MakeError(err, "DeleteRequest") +} + +func (obj ObjectBase) DeleteReported() error { + query := `delete from reported where id=$1` + _, err := config.DB.Exec(query, obj.Id) + + return util.MakeError(err, "DeleteReported") } func (obj ObjectBase) GetCollectionLocal() (Collection, error) { @@ -646,6 +726,23 @@ func (obj ObjectBase) IsLocal() (bool, error) { return true, nil } +func (obj ObjectBase) IsReplyInThread(id string) (bool, error) { + reqActivity := Activity{Id: obj.InReplyTo[0].Id} + coll, _, err := reqActivity.CheckValid() + + if err != nil { + return false, util.MakeError(err, "IsReplyInThread") + } + + for _, e := range coll.OrderedItems[0].Replies.OrderedItems { + if e.Id == id { + return true, nil + } + } + + return false, nil +} + //TODO break this off into seperate for Cache func (obj ObjectBase) MarkSensitive(sensitive bool) error { var query = `update activitystream set sensitive=$1 where id=$2` @@ -1151,6 +1248,50 @@ func (obj ObjectBase) WriteReply() error { return nil } +func (obj ObjectBase) WriteReplyCache() error { + for i, e := range obj.InReplyTo { + res, err := obj.InReplyTo[0].IsReplyInThread(e.Id) + if err != nil { + return util.MakeError(err, "WriteReplyCache") + } + + if i == 0 || res { + query := `select id from replies where id=$1` + + rows, err := config.DB.Query(query, obj.Id) + if err != nil { + return util.MakeError(err, "WriteReplyCache") + } + defer rows.Close() + + var id string + rows.Next() + err = rows.Scan(&id) + if err != nil { + return util.MakeError(err, "WriteReplyCache") + } else if id != "" { + return nil // TODO: error? + } + + query = `insert into cachereplies (id, inreplyto) values ($1, $2)` + + _, err = config.DB.Exec(query, obj.Id, e.Id) + if err != nil { + return util.MakeError(err, "WriteReplyCache") + } + } + } + + if len(obj.InReplyTo) < 1 { + query := `insert into cachereplies (id, inreplyto) values ($1, $2)` + + _, err := config.DB.Exec(query, obj.Id, "") + return util.MakeError(err, "WriteReplyCache") + } + + return nil +} + func (obj ObjectBase) WriteReplyLocal(replyto string) error { var nID string @@ -1179,7 +1320,7 @@ func (obj ObjectBase) WriteReplyLocal(replyto string) error { func (obj ObjectBase) WriteObjectToCache() (ObjectBase, error) { if isBlacklisted, err := util.IsPostBlacklist(obj.Content); err != nil || isBlacklisted { - fmt.Println("\n\nBlacklist post blocked\n\n") + config.Log.Println("\n\nBlacklist post blocked\n\n") return obj, util.MakeError(err, "WriteObjectToCache") } @@ -1243,7 +1384,7 @@ func (obj ObjectBase) WriteWithAttachment(attachment ObjectBase) { _, e := config.DB.Exec(query, obj.Id, obj.Type, obj.Name, obj.Content, attachment.Id, obj.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") + config.Log.Println("error inserting new activity with attachment") panic(e) } } diff --git a/activitypub/pem.go b/activitypub/pem.go index 408be0c..f99643e 100644 --- a/activitypub/pem.go +++ b/activitypub/pem.go @@ -6,7 +6,6 @@ import ( "crypto/x509" "encoding/pem" "errors" - "fmt" "io/ioutil" "os" "regexp" @@ -72,7 +71,7 @@ func CreatePem(actor Actor) error { return StorePemToDB(actor) } - fmt.Println(`Created PEM keypair for the "` + actor.Name + `" board. Please keep in mind that + config.Log.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!`) @@ -123,7 +122,7 @@ func CreatePublicKeyFromPrivate(actor *Actor, publicKeyPem string) error { return util.MakeError(err, "CreatePublicKeyFromPrivate") } } else { - fmt.Println(`\nUnable to locate private key from public key generation. Now, + config.Log.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 diff --git a/activitypub/util.go b/activitypub/util.go index 7aee666..9dfa1ae 100644 --- a/activitypub/util.go +++ b/activitypub/util.go @@ -19,6 +19,18 @@ import ( // False positive for application/ld+ld, application/activity+ld, application/json+json var activityRegexp = regexp.MustCompile("application\\/(ld|json|activity)((\\+(ld|json))|$)") +func AcceptActivity(header string) bool { + accept := false + if strings.Contains(header, ";") { + split := strings.Split(header, ";") + accept = accept || activityRegexp.MatchString(split[0]) + accept = accept || strings.Contains(split[len(split)-1], "profile=\"https://www.w3.org/ns/activitystreams\"") + } else { + accept = accept || activityRegexp.MatchString(header) + } + return accept +} + func CreateAttachmentObject(file multipart.File, header *multipart.FileHeader) ([]ObjectBase, *os.File, error) { contentType, err := util.GetFileContentType(file) if err != nil { diff --git a/activitypub/webfinger.go b/activitypub/webfinger.go new file mode 100644 index 0000000..276a791 --- /dev/null +++ b/activitypub/webfinger.go @@ -0,0 +1,180 @@ +package activitypub + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "strings" + "time" + + "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"` +} + +func GetActor(id string) (Actor, error) { + var respActor Actor + + if id == "" { + return respActor, nil + } + + actor, instance := GetActorAndInstance(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, util.MakeError(err, "GetActor") + } + + req.Header.Set("Accept", config.ActivityStreams) + + resp, err := util.RouteProxy(req) + + if err != nil { + return respActor, util.MakeError(err, "GetActor") + } + + defer resp.Body.Close() + body, _ := ioutil.ReadAll(resp.Body) + + if err := json.Unmarshal(body, &respActor); err != nil { + return respActor, util.MakeError(err, "GetActor") + } + + ActorCache[actor+"@"+instance] = respActor + + return respActor, nil +} + +//looks for actor with pattern of board@instance +func FingerActor(path string) (Actor, error) { + var nActor Actor + + actor, instance := GetActorAndInstance(path) + + if actor == "" && instance == "" { + return nActor, nil + } + + if ActorCache[actor+"@"+instance].Id != "" { + nActor = ActorCache[actor+"@"+instance] + } else { + resp, _ := FingerRequest(actor, instance) + + if resp != nil && resp.StatusCode == 200 { + defer resp.Body.Close() + + body, _ := ioutil.ReadAll(resp.Body) + + if err := json.Unmarshal(body, &nActor); err != nil { + return nActor, util.MakeError(err, "FingerActor") + } + + ActorCache[actor+"@"+instance] = nActor + } + } + + 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, util.MakeError(err, "FingerRequest") + } + + resp, err := util.RouteProxy(req) + if err != nil { + return resp, nil + } + + 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, util.MakeError(err, "FingerRequest") + } + } + + 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, util.MakeError(err, "FingerRequest") + } + + req.Header.Set("Accept", config.ActivityStreams) + resp, _ := util.RouteProxy(req) + + return resp, nil + } + } + } + + return resp, nil +} + +func AddInstanceToIndexDB(actor string) error { + // TODO: completely disabling this until it is actually reasonable to turn it on + // only actually allow this when it more or less works, i.e. can post, make threads, manage boards, etc + return nil + + //sleep to be sure the webserver is fully initialized + //before making finger request + time.Sleep(15 * time.Second) + + nActor, err := FingerActor(actor) + if err != nil { + return util.MakeError(err, "IsValidActor") + } + + if nActor.Id == "" { + return nil + } + + // TODO: maybe allow different indexes? + reqActivity := Activity{Id: "https://fchan.xyz/followers"} + followers, err := reqActivity.GetCollection() + if err != nil { + return util.MakeError(err, "IsValidActor") + } + + var alreadyIndex = false + for _, e := range followers.Items { + if e.Id == nActor.Id { + alreadyIndex = true + } + } + + if !alreadyIndex { + actor := Actor{Id: "https://fchan.xyz"} + return actor.AddFollower(nActor.Id) + } + + return nil +} diff --git a/config/config.go b/config/config.go index 16a1261..11f4792 100644 --- a/config/config.go +++ b/config/config.go @@ -31,6 +31,7 @@ var ActivityStreams = "application/ld+json; profile=\"https://www.w3.org/ns/acti var AuthReq = []string{"captcha", "email", "passphrase"} var PostCountPerPage = 10 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 Log = log.New(os.Stdout, "", log.Ltime) var MediaHashs = make(map[string]string) var Key string var Themes []string @@ -40,7 +41,7 @@ func GetConfigValue(value string, ifnone string) string { file, err := os.Open("config/config-init") if err != nil { - log.Print(err) + Log.Println(err) return ifnone } diff --git a/db/cache.go b/db/cache.go deleted file mode 100644 index d1c1fe7..0000000 --- a/db/cache.go +++ /dev/null @@ -1,72 +0,0 @@ -package db - -import ( - "github.com/FChannel0/FChannel-Server/activitypub" - "github.com/FChannel0/FChannel-Server/config" - "github.com/FChannel0/FChannel-Server/webfinger" - _ "github.com/lib/pq" -) - -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 := config.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 = config.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 := config.DB.Exec(query, obj.Id, "") - return err - } - - return nil -} - -func WriteActorToCache(actorID string) error { - actor, err := webfinger.FingerActor(actorID) - if err != nil { - return err - } - - collection, err := webfinger.GetActorCollection(actor.Outbox) - if err != nil { - return err - } - - for _, e := range collection.OrderedItems { - if err := e.WriteCache(); err != nil { - return err - } - } - - return nil -} diff --git a/db/database.go b/db/database.go index 5d53f46..c6c2fc7 100644 --- a/db/database.go +++ b/db/database.go @@ -2,11 +2,9 @@ package db import ( "database/sql" - "encoding/json" "fmt" "html/template" "io/ioutil" - "net/http" "os" "regexp" "strings" @@ -15,7 +13,6 @@ import ( "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" ) @@ -25,8 +22,7 @@ type NewsItem struct { Time int } -// ConnectDB connects to the PostgreSQL database configured. -func ConnectDB() error { +func Connect() error { host := config.DBHost port := config.DBPort user := config.DBUser @@ -37,89 +33,98 @@ func ConnectDB() error { "dbname=%s sslmode=disable", host, port, user, password, dbname) _db, err := sql.Open("postgres", psqlInfo) + if err != nil { - return err + return util.MakeError(err, "Connect") } if err := _db.Ping(); err != nil { - return err + return util.MakeError(err, "Connect") } - fmt.Println("Successfully connected DB") + config.Log.Println("Successfully connected DB") config.DB = _db + return nil } -// Close closes the database connection. func Close() error { - return config.DB.Close() + err := config.DB.Close() + + return util.MakeError(err, "Close") } func RunDatabaseSchema() error { query, err := ioutil.ReadFile("databaseschema.psql") if err != nil { - return err + return util.MakeError(err, "RunDatabaseSchema") } _, err = config.DB.Exec(string(query)) - return err + return util.MakeError(err, "RunDatabaseSchema") } -func CreateNewBoardDB(actor activitypub.Actor) (activitypub.Actor, error) { - query := `insert into actor (type, id, name, preferedusername, inbox, outbox, following, followers, summary, restricted) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)` +func CreateNewBoard(actor activitypub.Actor) (activitypub.Actor, error) { + if _, err := activitypub.GetActorFromDB(actor.Id); err == nil { + return activitypub.Actor{}, util.MakeError(err, "CreateNewBoardDB") + } else { + query := `insert into actor (type, id, name, preferedusername, inbox, outbox, following, followers, summary, restricted) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)` + _, err := config.DB.Exec(query, actor.Type, actor.Id, actor.Name, actor.PreferredUsername, actor.Inbox, actor.Outbox, actor.Following, actor.Followers, actor.Summary, actor.Restricted) - _, err := config.DB.Exec(query, actor.Type, actor.Id, actor.Name, actor.PreferredUsername, actor.Inbox, actor.Outbox, actor.Following, actor.Followers, actor.Summary, actor.Restricted) + if err != nil { + return activitypub.Actor{}, util.MakeError(err, "CreateNewBoardDB") + } - if err != nil { - // TODO: board exists error - return activitypub.Actor{}, err - } else { - fmt.Println("board added") + config.Log.Println("board added") for _, e := range actor.AuthRequirement { query = `insert into actorauth (type, board) values ($1, $2)` - if _, err := config.DB.Exec(query, e, actor.Name); err != nil { - return activitypub.Actor{}, err + return activitypub.Actor{}, util.MakeError(err, "CreateNewBoardDB") } } - var verify Verify + var verify util.Verify - verify.Identifier = actor.Id - verify.Code = util.CreateKey(50) verify.Type = "admin" - - CreateVerification(verify) - verify.Identifier = actor.Id - verify.Code = util.CreateKey(50) - verify.Type = "janitor" - CreateVerification(verify) + if verify.Code, err = util.CreateKey(50); err != nil { + return activitypub.Actor{}, util.MakeError(err, "CreateNewBoardDB") + } + + if err := verify.Create(); err != nil { + return activitypub.Actor{}, util.MakeError(err, "CreateNewBoardDB") + } + verify.Type = "janitor" verify.Identifier = actor.Id - verify.Code = util.CreateKey(50) - verify.Type = "post" - CreateVerification(verify) + if verify.Code, err = util.CreateKey(50); err != nil { + return activitypub.Actor{}, util.MakeError(err, "CreateNewBoardDB") + } + + if err := verify.Create(); err != nil { + return activitypub.Actor{}, util.MakeError(err, "CreateNewBoardDB") + } - var nverify Verify + var nverify util.Verify nverify.Board = actor.Id nverify.Identifier = "admin" nverify.Type = "admin" - CreateBoardMod(nverify) + + if err := nverify.CreateBoardMod(); err != nil { + return activitypub.Actor{}, util.MakeError(err, "CreateNewBoardDB") + } nverify.Board = actor.Id nverify.Identifier = "janitor" nverify.Type = "janitor" - CreateBoardMod(nverify) - nverify.Board = actor.Id - nverify.Identifier = "post" - nverify.Type = "post" - CreateBoardMod(nverify) + if err := nverify.CreateBoardMod(); err != nil { + return activitypub.Actor{}, util.MakeError(err, "CreateNewBoardDB") + } activitypub.CreatePem(actor) @@ -128,165 +133,90 @@ func CreateNewBoardDB(actor activitypub.Actor) (activitypub.Actor, error) { var nActivity activitypub.Activity nActor, err := activitypub.GetActorFromDB(config.Domain) + if err != nil { - return actor, err + return actor, util.MakeError(err, "CreateNewBoardDB") } nActivity.AtContext.Context = "https://www.w3.org/ns/activitystreams" nActivity.Type = "Follow" nActivity.Actor = &nActor nActivity.Object = &nObject - mActor, err := activitypub.GetActorFromDB(actor.Id) + if err != nil { - return actor, err + return actor, util.MakeError(err, "CreateNewBoardDB") } nActivity.Object.Actor = mActor.Id nActivity.To = append(nActivity.To, actor.Id) - response := AcceptFollow(nActivity) - if _, err := SetActorFollowingDB(response); err != nil { - return actor, err + activityRequest := nActivity.AcceptFollow() + + if _, err := activityRequest.SetActorFollowing(); err != nil { + return actor, util.MakeError(err, "CreateNewBoardDB") } - if err := MakeActivityRequest(nActivity); err != nil { - return actor, err + + if err := activityRequest.MakeRequestInbox(); err != nil { + return actor, util.MakeError(err, "CreateNewBoardDB") } } - } return actor, nil } func RemovePreviewFromFile(id string) error { + var href string + query := `select href from activitystream where id in (select preview from activitystream where id=$1)` - rows, err := config.DB.Query(query, id) - if err != nil { - return err + if err := config.DB.QueryRow(query, id).Scan(&href); err != nil { + return nil } - defer rows.Close() - for rows.Next() { - var href string + href = strings.Replace(href, config.Domain+"/", "", 1) - if err := rows.Scan(&href); err != nil { - return err + if href != "static/notfound.png" { + if _, err := os.Stat(href); err != nil { + return util.MakeError(err, "RemovePreviewFromFile") } - href = strings.Replace(href, config.Domain+"/", "", 1) - - if href != "static/notfound.png" { - _, err = os.Stat(href) - if err == nil { - return os.Remove(href) - } - return err - } + err := os.Remove(href) + return util.MakeError(err, "RemovePreviewFromFile") } obj := activitypub.ObjectBase{Id: id} - return obj.DeletePreview() -} - -func GetRandomCaptcha() (string, error) { - var verify string - - query := `select identifier from verification where type='captcha' order by random() limit 1` - - rows, err := config.DB.Query(query) - if err != nil { - return verify, err - } - defer rows.Close() - - rows.Next() - if err := rows.Scan(&verify); err != nil { - return verify, err - } - - return verify, nil -} - -func GetCaptchaTotal() (int, error) { - query := `select count(*) from verification where type='captcha'` - - rows, err := config.DB.Query(query) - if err != nil { - return 0, err - } - - defer rows.Close() - - var count int - for rows.Next() { - if err := rows.Scan(&count); err != nil { - return count, err - } - } - - return count, nil -} - -func GetCaptchaCodeDB(verify string) (string, error) { - query := `select code from verification where identifier=$1 limit 1` - - rows, err := config.DB.Query(query, verify) - if err != nil { - return "", err - } - defer rows.Close() - - var code string - - rows.Next() - if err := rows.Scan(&code); err != nil { - fmt.Println("Could not get verification captcha") - } - - return code, nil -} - -func DeleteCaptchaCodeDB(verify string) error { - query := `delete from verification where identifier=$1` - - _, err := config.DB.Exec(query, verify) - if err != nil { - return err - } - - return os.Remove("./" + verify) + err := obj.DeletePreview() + return util.MakeError(err, "RemovePreviewFromFile") } //if limit less than 1 return all news items -func GetNewsFromDB(limit int) ([]NewsItem, error) { +func GetNews(limit int) ([]NewsItem, error) { var news []NewsItem - var query string - if limit > 0 { - query = `select title, content, time from newsItem order by time desc limit $1` - } else { - query = `select title, content, time from newsItem order by time desc` - } var rows *sql.Rows var err error + if limit > 0 { + query = `select title, content, time from newsItem order by time desc limit $1` rows, err = config.DB.Query(query, limit) } else { + query = `select title, content, time from newsItem order by time desc` rows, err = config.DB.Query(query) } if err != nil { - return news, nil + return news, util.MakeError(err, "GetNews") } defer rows.Close() for rows.Next() { - n := NewsItem{} var content string + n := NewsItem{} + if err := rows.Scan(&n.Title, &content, &n.Time); err != nil { - return news, err + return news, util.MakeError(err, "GetNews") } content = strings.ReplaceAll(content, "\n", "
") @@ -298,21 +228,13 @@ func GetNewsFromDB(limit int) ([]NewsItem, error) { return news, nil } -func GetNewsItemFromDB(timestamp int) (NewsItem, error) { +func GetNewsItem(timestamp int) (NewsItem, error) { var news NewsItem var content string - query := `select title, content, time from newsItem where time=$1 limit 1` - - rows, err := config.DB.Query(query, timestamp) - if err != nil { - return news, err - } - - defer rows.Close() - rows.Next() - if err := rows.Scan(&news.Title, &content, &news.Time); err != nil { - return news, err + query := `select title, content, time from newsItem where time=$1 limit 1` + if err := config.DB.QueryRow(query, timestamp).Scan(&news.Title, &content, &news.Time); err != nil { + return news, util.MakeError(err, "GetNewsItem") } content = strings.ReplaceAll(content, "\n", "
") @@ -321,65 +243,54 @@ func GetNewsItemFromDB(timestamp int) (NewsItem, error) { return news, nil } -func deleteNewsItemFromDB(timestamp int) error { +func DeleteNewsItem(timestamp int) error { query := `delete from newsItem where time=$1` _, err := config.DB.Exec(query, timestamp) - return err + + return util.MakeError(err, "DeleteNewsItem") } -func WriteNewsToDB(news NewsItem) error { +func WriteNews(news NewsItem) error { query := `insert into newsItem (title, content, time) values ($1, $2, $3)` - _, err := config.DB.Exec(query, news.Title, news.Content, time.Now().Unix()) - return err -} - -func AddInstanceToInactiveDB(instance string) error { - query := `select timestamp from inactive where instance=$1` - rows, err := config.DB.Query(query, instance) - if err != nil { - return err - } + return util.MakeError(err, "WriteNews") +} +func AddInstanceToInactive(instance string) error { var timeStamp string - defer rows.Close() - rows.Next() - rows.Scan(&timeStamp) - if timeStamp == "" { + query := `select timestamp from inactive where instance=$1` + if err := config.DB.QueryRow(query, instance).Scan(&timeStamp); err != nil { query := `insert into inactive (instance, timestamp) values ($1, $2)` - _, err := config.DB.Exec(query, instance, time.Now().UTC().Format(time.RFC3339)) - return err + + return util.MakeError(err, "AddInstanceToInactive") } if !IsInactiveTimestamp(timeStamp) { return nil } - query = `delete from following where following like $1` - if _, err := config.DB.Exec(query, "%"+instance+"%"); err != nil { - return err - } - query = `delete from follower where follower like $1` - if _, err = config.DB.Exec(query, "%"+instance+"%"); err != nil { - return err + if _, err := config.DB.Exec(query, "%"+instance+"%"); err != nil { + return util.MakeError(err, "AddInstanceToInactive") } - return DeleteInstanceFromInactiveDB(instance) + err := DeleteInstanceFromInactive(instance) + return util.MakeError(err, "AddInstanceToInactive") } -func DeleteInstanceFromInactiveDB(instance string) error { +func DeleteInstanceFromInactive(instance string) error { query := `delete from inactive where instance=$1` - _, err := config.DB.Exec(query, instance) - return err + + return util.MakeError(err, "DeleteInstanceFromInactive") } func IsInactiveTimestamp(timeStamp string) bool { stamp, _ := time.Parse(time.RFC3339, timeStamp) + if time.Now().UTC().Sub(stamp).Hours() > 48 { return true } @@ -387,45 +298,9 @@ func IsInactiveTimestamp(timeStamp string) bool { return false } -func ArchivePosts(actor activitypub.Actor) error { - if actor.Id != "" && actor.Id != config.Domain { - col, err := actor.GetAllArchive(165) - if err != nil { - return err - } - - for _, e := range col.OrderedItems { - for _, k := range e.Replies.OrderedItems { - if err := k.UpdateType("Archive"); err != nil { - return err - } - } - - if err := e.UpdateType("Archive"); err != nil { - return err - } - } - } - - return nil -} - -func IsReplyInThread(inReplyTo string, id string) (bool, error) { - obj, _, err := webfinger.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 -} - func IsReplyToOP(op string, link string) (string, bool, error) { + var id string + if op == link { return link, true, nil } @@ -440,52 +315,22 @@ func IsReplyToOP(op string, link string) (string, bool, error) { } query := `select id from replies where id like $1 and inreplyto=$2` - - rows, err := config.DB.Query(query, link, op) - if err != nil { - return op, false, err - } - - defer rows.Close() - - var id string - rows.Next() - if err := rows.Scan(&id); err != nil { - return id, false, err + if err := config.DB.QueryRow(query, link, op).Scan(&id); err != nil { + return op, false, nil } return id, id != "", nil } func GetReplyOP(link string) (string, error) { - query := `select id from replies where id in (select inreplyto from replies where id=$1) and inreplyto=''` - - rows, err := config.DB.Query(query, link) - if err != nil { - return "", err - } - defer rows.Close() - var id string - rows.Next() - err = rows.Scan(&id) - return id, err -} - -func StartupArchive() error { - for _, e := range webfinger.FollowingBoards { - actor, err := activitypub.GetActorFromDB(e.Id) - if err != nil { - return err - } - - if err := ArchivePosts(actor); err != nil { - return err - } + query := `select id from replies where id in (select inreplyto from replies where id=$1) and inreplyto=''` + if err := config.DB.QueryRow(query, link).Scan(&id); err != nil { + return "", nil } - return nil + return id, nil } func CheckInactive() { @@ -496,54 +341,59 @@ func CheckInactive() { } func CheckInactiveInstances() (map[string]string, error) { + var rows *sql.Rows + var err error + instances := make(map[string]string) - query := `select following from following` - rows, err := config.DB.Query(query) - if err != nil { - return instances, err + query := `select following from following` + if rows, err = config.DB.Query(query); err != nil { + return instances, util.MakeError(err, "CheckInactiveInstances") } - defer rows.Close() + defer rows.Close() for rows.Next() { var instance string + if err := rows.Scan(&instance); err != nil { - return instances, err + return instances, util.MakeError(err, "CheckInactiveInstances") } instances[instance] = instance } query = `select follower from follower` - rows, err = config.DB.Query(query) - if err != nil { - return instances, err + if rows, err = config.DB.Query(query); err != nil { + return instances, util.MakeError(err, "CheckInactiveInstances") } - defer rows.Close() + defer rows.Close() for rows.Next() { var instance string + if err := rows.Scan(&instance); err != nil { - return instances, err + return instances, util.MakeError(err, "CheckInactiveInstances") } instances[instance] = instance } re := regexp.MustCompile(config.Domain + `(.+)?`) + for _, e := range instances { - actor, err := webfinger.GetActor(e) + actor, err := activitypub.GetActor(e) + if err != nil { - return instances, err + return instances, util.MakeError(err, "CheckInactiveInstances") } if actor.Id == "" && !re.MatchString(e) { - if err := AddInstanceToInactiveDB(e); err != nil { - return instances, err + if err := AddInstanceToInactive(e); err != nil { + return instances, util.MakeError(err, "CheckInactiveInstances") } } else { - if err := DeleteInstanceFromInactiveDB(e); err != nil { - return instances, err + if err := DeleteInstanceFromInactive(e); err != nil { + return instances, util.MakeError(err, "CheckInactiveInstances") } } } @@ -552,12 +402,12 @@ func CheckInactiveInstances() (map[string]string, error) { } func GetAdminAuth() (string, string, error) { - query := fmt.Sprintf("select identifier, code from boardaccess where board='%s' and type='admin'", config.Domain) - var code string var identifier string - if err := config.DB.QueryRow(query).Scan(&identifier, &code); err != nil { - return "", "", err + + query := `select identifier, code from boardaccess where board=$1 and type='admin'` + if err := config.DB.QueryRow(query, config.Domain).Scan(&identifier, &code); err != nil { + return "", "", nil } return code, identifier, nil @@ -567,175 +417,27 @@ func IsHashBanned(hash string) (bool, error) { var h string query := `select hash from bannedmedia where hash=$1` - _ = config.DB.QueryRow(query, hash).Scan(&h) return h == hash, nil } -func MakeCaptchas(total int) error { - dbtotal, err := GetCaptchaTotal() - if err != nil { - return err - } - - difference := total - dbtotal - - for i := 0; i < difference; i++ { - if err := CreateNewCaptcha(); err != nil { - return err - } - } - - return nil -} - -func GetActorReported(w http.ResponseWriter, r *http.Request, id string) error { - auth := r.Header.Get("Authorization") - verification := strings.Split(auth, " ") - - if len(verification) < 2 { - w.WriteHeader(http.StatusBadRequest) - _, err := w.Write([]byte("")) - return err - } - - if res, err := HasAuth(verification[1], id); err == nil && !res { - w.WriteHeader(http.StatusBadRequest) - _, err = w.Write([]byte("")) - return err - } else if err != nil { - return err - } - - actor, _ := activitypub.GetActorFromDB(id) - - var following activitypub.Collection - var err error - - following.AtContext.Context = "https://www.w3.org/ns/activitystreams" - following.Type = "Collection" - following.TotalItems, err = actor.GetReportedTotal() - if err != nil { - return err - } - - following.Items, err = actor.GetReported() - if err != nil { - return err - } - - enc, err := json.MarshalIndent(following, "", "\t") - if err != nil { - return err - } - - w.Header().Set("Content-Type", config.ActivityStreams) - - _, err = w.Write(enc) - return err -} - func PrintAdminAuth() error { - identifier, code, err := GetAdminAuth() - if err != nil { - return err - } - - fmt.Println("Mod key: " + config.Key) - fmt.Println("Admin Login: " + identifier + ", Code: " + code) - return nil -} - -func DeleteObjectRequest(id string) error { - var nObj activitypub.ObjectBase - var nActor activitypub.Actor - nObj.Id = id - nObj.Actor = nActor.Id - - activity, err := webfinger.CreateActivity("Delete", nObj) - if err != nil { - return err - } - - obj, err := nObj.GetFromPath() - if err != nil { - return err - } + code, identifier, err := GetAdminAuth() - actor, err := webfinger.FingerActor(obj.Actor) if err != nil { - return err - } - activity.Actor = &actor - objActor, _ := webfinger.GetActor(obj.Actor) - followers, err := objActor.GetFollow() - if err != nil { - return err - } - - for _, e := range followers { - activity.To = append(activity.To, e.Id) - } - - following, err := objActor.GetFollowing() - if err != nil { - return err - } - for _, e := range following { - activity.To = append(activity.To, e.Id) + return util.MakeError(err, "PrintAdminAuth") } - return MakeActivityRequest(activity) -} - -func DeleteObjectAndRepliesRequest(id string) error { - var nObj activitypub.ObjectBase - var nActor activitypub.Actor - nObj.Id = id - nObj.Actor = nActor.Id - - activity, err := webfinger.CreateActivity("Delete", nObj) - if err != nil { - return err - } - - obj, err := nObj.GetCollectionFromPath() - if err != nil { - return err - } - - activity.Actor.Id = obj.OrderedItems[0].Actor - - activity.Object = &obj.OrderedItems[0] - - objActor, _ := webfinger.GetActor(obj.OrderedItems[0].Actor) - followers, err := objActor.GetFollow() - if err != nil { - return err - } - for _, e := range followers { - activity.To = append(activity.To, e.Id) - } - - following, err := objActor.GetFollowing() - if err != nil { - return err - } - - for _, e := range following { - activity.To = append(activity.To, e.Id) - } - - return MakeActivityRequest(activity) + config.Log.Println("Mod key: " + config.Key) + config.Log.Println("Admin Login: " + identifier + ", Code: " + code) + return nil } -// root actor is used to follow remote feeds that are not local -//name, prefname, summary, auth requirements, restricted -func InitInstance() { +func InitInstance() error { if config.InstanceName != "" { - if _, err := CreateNewBoardDB(*activitypub.CreateNewActor("", config.InstanceName, config.InstanceSummary, config.AuthReq, false)); err != nil { - //panic(err) + if _, err := CreateNewBoard(*activitypub.CreateNewActor("", config.InstanceName, config.InstanceSummary, config.AuthReq, false)); err != nil { + return util.MakeError(err, "InitInstance") } if config.PublicIndexing == "true" { @@ -743,4 +445,6 @@ func InitInstance() { //AddInstanceToIndex(config.Domain) } } + + return nil } diff --git a/db/follow.go b/db/follow.go deleted file mode 100644 index 3932ea1..0000000 --- a/db/follow.go +++ /dev/null @@ -1,306 +0,0 @@ -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 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 SetActorFollowingDB(activity activitypub.Activity) (activitypub.Activity, error) { - var query string - alreadyFollowing := false - alreadyFollower := false - objActor, _ := webfinger.GetActor(activity.Object.Actor) - following, err := objActor.GetFollowing() - if err != nil { - return activity, err - } - - actor, err := webfinger.FingerActor(activity.Actor.Id) - if err != nil { - return activity, err - } - - remoteActorFollowerCol, err := webfinger.GetCollectionFromReq(actor.Followers) - if err != nil { - return activity, err - } - - 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 := activity.Actor.IsLocal(); err == nil && !res { - go activity.Actor.DeleteCache() - } else { - return activity, err - } - - if _, err := config.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 := activity.Actor.IsLocal(); err == nil && !res { - go WriteActorToCache(activity.Actor.Id) - } - if _, err := config.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(actorID string) error { - actor, _ := webfinger.GetActor(actorID) - following, err := actor.GetFollowing() - if err != nil { - return err - } - - follower, err := actor.GetFollow() - 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.Id { - followActivity, err := MakeFollowActivity(actor.Id, 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 = activitypub.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 = activitypub.GetActorAndInstance(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 := activity.Actor.ActivitySign(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 := activitypub.GetActorAndInstance(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 := activity.Actor.ActivitySign(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 -} - -func SendToFollowers(actorID string, activity activitypub.Activity) error { - nActor, err := activitypub.GetActorFromDB(actorID) - if err != nil { - return err - } - - activity.Actor = &nActor - - followers, err := nActor.GetFollow() - if err != nil { - return err - } - - var to []string - - for _, e := range followers { - for _, k := range activity.To { - if e.Id != k { - to = append(to, e.Id) - } - } - } - - activity.To = to - - if len(activity.Object.InReplyTo) > 0 { - err = MakeActivityRequest(activity) - } - - return err -} diff --git a/db/pem.go b/db/pem.go deleted file mode 100644 index 66bf25a..0000000 --- a/db/pem.go +++ /dev/null @@ -1,95 +0,0 @@ -package db - -import ( - "crypto" - "crypto/rsa" - "crypto/sha256" - "crypto/x509" - "encoding/base64" - "encoding/pem" - "strings" - "time" - - "github.com/FChannel0/FChannel-Server/activitypub" - "github.com/FChannel0/FChannel-Server/webfinger" - "github.com/gofiber/fiber/v2" -) - -func VerifyHeaderSignature(ctx *fiber.Ctx, actor activitypub.Actor) bool { - s := activitypub.ParseHeaderSignature(ctx.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(ctx.Method()) - path = ctx.Path() - sig += "(request-target): " + method + " " + path + "" + nl - break - case "host": - host = ctx.Hostname() - sig += "host: " + host + "" + nl - break - case "date": - date = ctx.Get("date") - sig += "date: " + date + "" + nl - break - case "digest": - digest = ctx.Get("digest") - sig += "digest: " + digest + "" + nl - break - case "content-length": - contentLength = ctx.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 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) -} diff --git a/db/report.go b/db/report.go index 4120d9a..958b619 100644 --- a/db/report.go +++ b/db/report.go @@ -1,6 +1,9 @@ package db -import "github.com/FChannel0/FChannel-Server/config" +import ( + "github.com/FChannel0/FChannel-Server/config" + "github.com/FChannel0/FChannel-Server/util" +) type Report struct { ID string @@ -14,57 +17,53 @@ type Removed struct { Board string } -func CreateLocalDeleteDB(id string, _type string) error { - query := `select id from removed where id=$1` - - rows, err := config.DB.Query(query, id) - if err != nil { - return err - } - defer rows.Close() - - if rows.Next() { - var i string +func CloseLocalReport(id string, board string) error { + query := `delete from reported where id=$1 and board=$2` + _, err := config.DB.Exec(query, id, board) - if err := rows.Scan(&i); err != nil { - return err - } + return util.MakeError(err, "CloseLocalReportDB") +} - if i != "" { - query := `update removed set type=$1 where id=$2` +func CreateLocalDelete(id string, _type string) error { + var i string - if _, err := config.DB.Exec(query, _type, id); err != nil { - return err - } - } - } else { + query := `select id from removed where id=$1` + if err := config.DB.QueryRow(query, id).Scan(&i); err != nil { query := `insert into removed (id, type) values ($1, $2)` - if _, err := config.DB.Exec(query, id, _type); err != nil { - return err + return util.MakeError(err, "CreateLocalDeleteDB") } } - return nil + query = `update removed set type=$1 where id=$2` + _, err := config.DB.Exec(query, _type, id) + + return util.MakeError(err, "CreateLocalDeleteDB") +} + +func CreateLocalReport(id string, board string, reason string) error { + query := `insert into reported (id, count, board, reason) values ($1, $2, $3, $4)` + _, err := config.DB.Exec(query, id, 1, board, reason) + + return util.MakeError(err, "CreateLocalReportDB") } -func GetLocalDeleteDB() ([]Removed, error) { +func GetLocalDelete() ([]Removed, error) { var deleted []Removed query := `select id, type from removed` - rows, err := config.DB.Query(query) + if err != nil { - return deleted, err + return deleted, util.MakeError(err, "GetLocalDeleteDB") } defer rows.Close() - for rows.Next() { var r Removed if err := rows.Scan(&r.ID, &r.Type); err != nil { - return deleted, err + return deleted, util.MakeError(err, "GetLocalDeleteDB") } deleted = append(deleted, r) @@ -73,58 +72,22 @@ func GetLocalDeleteDB() ([]Removed, error) { return deleted, nil } -func CreateLocalReportDB(id string, board string, reason string) error { - query := `select id, count from reported where id=$1 and board=$2` - - rows, err := config.DB.Query(query, id, board) - if err != nil { - return err - } - defer rows.Close() - - if rows.Next() { - var i string - var count int - - if err := rows.Scan(&i, &count); err != nil { - return err - } - - if i != "" { - count = count + 1 - query := `update reported set count=$1 where id=$2` - - if _, err := config.DB.Exec(query, count, id); err != nil { - return err - } - } - } else { - query := `insert into reported (id, count, board, reason) values ($1, $2, $3, $4)` - - if _, err := config.DB.Exec(query, id, 1, board, reason); err != nil { - return err - } - } - - return nil -} - -func GetLocalReportDB(board string) ([]Report, error) { +func GetLocalReport(board string) ([]Report, error) { var reported []Report query := `select id, count, reason from reported where board=$1` - rows, err := config.DB.Query(query, board) + if err != nil { - return reported, err + return reported, util.MakeError(err, "GetLocalReportDB") } - defer rows.Close() + defer rows.Close() for rows.Next() { var r Report if err := rows.Scan(&r.ID, &r.Count, &r.Reason); err != nil { - return reported, err + return reported, util.MakeError(err, "GetLocalReportDB") } reported = append(reported, r) @@ -132,10 +95,3 @@ func GetLocalReportDB(board string) ([]Report, error) { return reported, nil } - -func CloseLocalReportDB(id string, board string) error { - query := `delete from reported where id=$1 and board=$2` - - _, err := config.DB.Exec(query, id, board) - return err -} diff --git a/db/verification.go b/db/verification.go deleted file mode 100644 index eea22ea..0000000 --- a/db/verification.go +++ /dev/null @@ -1,514 +0,0 @@ -package db - -import ( - "fmt" - "math/rand" - "net/smtp" - "os" - "os/exec" - "strings" - "time" - - "github.com/FChannel0/FChannel-Server/activitypub" - "github.com/FChannel0/FChannel-Server/config" - "github.com/FChannel0/FChannel-Server/util" - "github.com/gofiber/fiber/v2" - _ "github.com/lib/pq" -) - -type Verify struct { - Type string - Identifier string - Code string - Created string - Board string -} - -type VerifyCooldown struct { - Identifier string - Code string - Time int -} - -type Signature struct { - KeyId string - Headers []string - Signature string - Algorithm string -} - -func DeleteBoardMod(verify Verify) error { - query := `select code from boardaccess where identifier=$1 and board=$1` - - rows, err := config.DB.Query(query, verify.Identifier, verify.Board) - if err != nil { - return err - } - - defer rows.Close() - - var code string - rows.Next() - rows.Scan(&code) - - if code != "" { - query := `delete from crossverification where code=$1` - - if _, err := config.DB.Exec(query, code); err != nil { - return err - } - - query = `delete from boardaccess where identifier=$1 and board=$2` - - if _, err := config.DB.Exec(query, verify.Identifier, verify.Board); err != nil { - return err - } - } - - return nil -} - -func GetBoardMod(identifier string) (Verify, error) { - var nVerify Verify - - query := `select code, board, type, identifier from boardaccess where identifier=$1` - - rows, err := config.DB.Query(query, identifier) - - if err != nil { - return nVerify, err - } - - defer rows.Close() - - rows.Next() - rows.Scan(&nVerify.Code, &nVerify.Board, &nVerify.Type, &nVerify.Identifier) - - return nVerify, nil -} - -func CreateBoardMod(verify Verify) error { - pass := util.CreateKey(50) - - query := `select code from verification where identifier=$1 and type=$2` - - rows, err := config.DB.Query(query, verify.Board, verify.Type) - if err != nil { - return err - } - - defer rows.Close() - - var code string - - rows.Next() - rows.Scan(&code) - - if code != "" { - - query := `select identifier from boardaccess where identifier=$1 and board=$2` - - rows, err := config.DB.Query(query, verify.Identifier, verify.Board) - if err != nil { - return err - } - - defer rows.Close() - - var ident string - rows.Next() - rows.Scan(&ident) - - if ident != verify.Identifier { - - query := `insert into crossverification (verificationcode, code) values ($1, $2)` - - if _, err := config.DB.Exec(query, code, pass); err != nil { - return err - } - - query = `insert into boardaccess (identifier, code, board, type) values ($1, $2, $3, $4)` - - if _, err = config.DB.Exec(query, verify.Identifier, pass, verify.Board, verify.Type); err != nil { - return err - } - - fmt.Printf("Board access - Board: %s, Identifier: %s, Code: %s\n", verify.Board, verify.Identifier, pass) - } - } - - return nil -} - -func CreateVerification(verify Verify) error { - query := `insert into verification (type, identifier, code, created) values ($1, $2, $3, $4)` - - _, err := config.DB.Exec(query, verify.Type, verify.Identifier, verify.Code, time.Now().UTC().Format(time.RFC3339)) - return err -} - -func GetVerificationByEmail(email string) (Verify, error) { - // TODO: this only needs to select one row. - - var verify Verify - - query := `select type, identifier, code, board from boardaccess where identifier=$1` - - rows, err := config.DB.Query(query, email) - if err != nil { - return verify, err - } - - defer rows.Close() - - for rows.Next() { - if err := rows.Scan(&verify.Type, &verify.Identifier, &verify.Code, &verify.Board); err != nil { - return verify, err - } - } - - return verify, nil -} - -func GetVerificationByCode(code string) (Verify, error) { - // TODO: this only needs to select one row. - - var verify Verify - - query := `select type, identifier, code, board from boardaccess where code=$1` - - rows, err := config.DB.Query(query, code) - if err != nil { - return verify, err - } - - defer rows.Close() - - for rows.Next() { - if err := rows.Scan(&verify.Type, &verify.Identifier, &verify.Code, &verify.Board); err != nil { - return verify, err - } - } - - return verify, nil -} - -func GetVerificationCode(verify Verify) (Verify, error) { - var nVerify Verify - - query := `select type, identifier, code, board from boardaccess where identifier=$1 and board=$2` - - rows, err := config.DB.Query(query, verify.Identifier, verify.Board) - if err != nil { - return verify, err - } - - defer rows.Close() - - for rows.Next() { - if err := rows.Scan(&nVerify.Type, &nVerify.Identifier, &nVerify.Code, &nVerify.Board); err != nil { - return nVerify, err - } - - } - - return nVerify, nil -} - -func VerifyCooldownCurrent(auth string) (VerifyCooldown, error) { - var current VerifyCooldown - - query := `select identifier, code, time from verificationcooldown where code=$1` - - rows, err := config.DB.Query(query, auth) - if err != nil { - query := `select identifier, code, time from verificationcooldown where identifier=$1` - - rows, err := config.DB.Query(query, auth) - - if err != nil { - return current, err - } - - defer rows.Close() - - for rows.Next() { - if err := rows.Scan(¤t.Identifier, ¤t.Code, ¤t.Time); err != nil { - return current, err - } - } - } else { - defer rows.Close() - } - - for rows.Next() { - if err := rows.Scan(¤t.Identifier, ¤t.Code, ¤t.Time); err != nil { - return current, err - } - } - - return current, nil -} - -func VerifyCooldownAdd(verify Verify) error { - query := `insert into verficationcooldown (identifier, code) values ($1, $2)` - - _, err := config.DB.Exec(query, verify.Identifier, verify.Code) - return err -} - -func VerficationCooldown() error { - query := `select identifier, code, time from verificationcooldown` - - rows, err := config.DB.Query(query) - if err != nil { - return err - } - - defer rows.Close() - - for rows.Next() { - var verify VerifyCooldown - - if err := rows.Scan(&verify.Identifier, &verify.Code, &verify.Time); err != nil { - return err - } - - nTime := verify.Time - 1 - - query = `update set time=$1 where identifier=$2` - - if _, err := config.DB.Exec(query, nTime, verify.Identifier); err != nil { - return err - } - - VerficationCooldownRemove() - } - - return nil -} - -func VerficationCooldownRemove() error { - query := `delete from verificationcooldown where time < 1` - - _, err := config.DB.Exec(query) - return err -} - -func SendVerification(verify Verify) error { - fmt.Println("sending email") - - 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) - - msg := "From: " + from + "\n" + - "To: " + to + "\n" + - "Subject: Image Board Verification\n\n" + - body - - return smtp.SendMail(config.SiteEmailServer+":"+config.SiteEmailPort, - smtp.PlainAuth("", from, pass, config.SiteEmailServer), - from, []string{to}, []byte(msg)) -} - -func IsEmailSetup() bool { - return config.SiteEmail != "" || config.SiteEmailPassword != "" || config.SiteEmailServer != "" || config.SiteEmailPort != "" -} - -func HasAuth(code string, board string) (bool, error) { - verify, err := GetVerificationByCode(code) - if err != nil { - return false, err - } - - 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 -} - -func HasAuthCooldown(auth string) (bool, error) { - current, err := VerifyCooldownCurrent(auth) - if err != nil { - return false, err - } - - if current.Time > 0 { - return true, nil - } - - // fmt.Println("has auth is false") - return false, nil -} - -func GetVerify(access string) (Verify, error) { - verify, err := GetVerificationByCode(access) - if err != nil { - return verify, err - } - - if verify.Identifier == "" { - verify, err = GetVerificationByEmail(access) - } - - return verify, err -} - -func CreateNewCaptcha() error { - id := util.RandomID(8) - file := "public/" + id + ".png" - - for true { - if _, err := os.Stat("./" + file); err == nil { - id = util.RandomID(8) - file = "public/" + id + ".png" - } else { - break - } - } - - captcha := Captcha() - - var pattern string - rnd := fmt.Sprintf("%d", rand.Intn(3)) - - srnd := string(rnd) - - switch srnd { - case "0": - pattern = "pattern:verticalbricks" - break - - case "1": - pattern = "pattern:verticalsaw" - break - - case "2": - pattern = "pattern:hs_cross" - break - - } - - cmd := exec.Command("convert", "-size", "200x98", pattern, "-transparent", "white", file) - cmd.Stderr = os.Stderr - - if err := cmd.Run(); err != nil { - return err - } - - cmd = exec.Command("convert", file, "-fill", "blue", "-pointsize", "62", "-annotate", "+0+70", captcha, "-tile", "pattern:left30", "-gravity", "center", "-transparent", "white", file) - cmd.Stderr = os.Stderr - - if err := cmd.Run(); err != nil { - return err - } - - rnd = fmt.Sprintf("%d", rand.Intn(24)-12) - - cmd = exec.Command("convert", file, "-rotate", rnd, "-wave", "5x35", "-distort", "Arc", "20", "-wave", "2x35", "-transparent", "white", file) - cmd.Stderr = os.Stderr - - if err := cmd.Run(); err != nil { - return err - } - - var verification Verify - verification.Type = "captcha" - verification.Code = captcha - verification.Identifier = file - - return CreateVerification(verification) -} - -func CreateBoardAccess(verify Verify) error { - hasAccess, err := HasBoardAccess(verify) - if err != nil { - return err - } - - if !hasAccess { - query := `insert into boardaccess (identifier, board) values($1, $2)` - - _, err := config.DB.Exec(query, verify.Identifier, verify.Board) - return err - } - - return nil -} - -func HasBoardAccess(verify Verify) (bool, error) { - query := `select count(*) from boardaccess where identifier=$1 and board=$2` - - rows, err := config.DB.Query(query, verify.Identifier, verify.Board) - if err != nil { - return false, err - } - - defer rows.Close() - - var count int - - rows.Next() - rows.Scan(&count) - - if count > 0 { - return true, nil - } else { - return false, nil - } -} - -func BoardHasAuthType(board string, auth string) (bool, error) { - authTypes, err := util.GetBoardAuth(board) - if err != nil { - return false, err - } - - for _, e := range authTypes { - if e == auth { - return true, nil - } - } - - return false, nil -} - -func Captcha() string { - rand.Seed(time.Now().UTC().UnixNano()) - domain := "ABEFHKMNPQRSUVWXYZ#$&" - rng := 4 - newID := "" - for i := 0; i < rng; i++ { - newID += string(domain[rand.Intn(len(domain))]) - } - - return newID -} - -func HasValidation(ctx *fiber.Ctx, actor activitypub.Actor) bool { - id, _ := GetPasswordFromSession(ctx) - - if id == "" || (id != actor.Id && id != config.Domain) { - //http.Redirect(w, r, "/", http.StatusSeeOther) - return false - } - - return true -} - -func GetPasswordFromSession(r *fiber.Ctx) (string, string) { - cookie := r.Cookies("session_token") - - parts := strings.Split(cookie, "|") - - if len(parts) > 1 { - return parts[0], parts[1] - } - - return "", "" -} diff --git a/main.go b/main.go index c1ebcef..039ee61 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,6 @@ package main import ( - "log" "math/rand" "time" @@ -41,7 +40,7 @@ func main() { cookieKey, err := util.GetCookieKey() if err != nil { - log.Println(err) + config.Log.Println(err) } app.Use(encryptcookie.New(encryptcookie.Config{ @@ -107,38 +106,51 @@ func main() { } func Init() { + var actor activitypub.Actor var err error rand.Seed(time.Now().UnixNano()) - util.CreatedNeededDirectories() + if err = util.CreatedNeededDirectories(); err != nil { + config.Log.Println(err) + } - db.ConnectDB() + if err = db.Connect(); err != nil { + config.Log.Println(err) + } - db.RunDatabaseSchema() + if err = db.RunDatabaseSchema(); err != nil { + config.Log.Println(err) + } - actor, _ := activitypub.GetActorFromDB(config.Domain) - webfinger.FollowingBoards, err = actor.GetFollowing() + if actor, err = activitypub.GetActorFromDB(config.Domain); err != nil { + config.Log.Println(err) + } - if err != nil { - panic(err) + if webfinger.FollowingBoards, err = actor.GetFollowing(); err != nil { + config.Log.Println(err) } - webfinger.Boards, err = webfinger.GetBoardCollection() + if webfinger.Boards, err = webfinger.GetBoardCollection(); err != nil { + config.Log.Println(err) + } - if err != nil { - panic(err) + if config.Key, err = util.CreateKey(32); err != nil { + config.Log.Println(err) } - config.Key = util.CreateKey(32) + if err = util.LoadThemes(); err != nil { + config.Log.Println(err) + } - go db.MakeCaptchas(100) + if err = db.InitInstance(); err != nil { + config.Log.Println(err) + } - go db.StartupArchive() + go webfinger.StartupArchive() - go db.CheckInactive() + go util.MakeCaptchas(100) - db.InitInstance() + go db.CheckInactive() - util.LoadThemes() } diff --git a/post/tripcode.go b/post/tripcode.go index 3b7e48b..d5ae2e2 100644 --- a/post/tripcode.go +++ b/post/tripcode.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/FChannel0/FChannel-Server/config" + "github.com/FChannel0/FChannel-Server/util" "github.com/gofiber/fiber/v2" _ "github.com/lib/pq" "github.com/simia-tech/crypt" @@ -23,34 +24,63 @@ const SaltTable = "" + "................................" + "................................" -func TripCode(pass string) (string, error) { - pass = TripCodeConvert(pass) +func CreateNameTripCode(ctx *fiber.Ctx) (string, string, error) { + input := ctx.FormValue("name") + tripSecure := regexp.MustCompile("##(.+)?") - var salt [2]rune + if tripSecure.MatchString(input) { + chunck := tripSecure.FindString(input) + chunck = strings.Replace(chunck, "##", "", 1) + ce := regexp.MustCompile(`(?i)Admin`) + admin := ce.MatchString(chunck) + board, modcred := util.GetPasswordFromSession(ctx) - s := []rune(pass + "H..")[1:3] + if hasAuth, _ := util.HasAuth(modcred, board); hasAuth && admin { + return tripSecure.ReplaceAllString(input, ""), "#Admin", nil + } - for i, r := range s { - salt[i] = rune(SaltTable[r%256]) + hash, err := TripCodeSecure(chunck) + + return tripSecure.ReplaceAllString(input, ""), "!!" + hash, util.MakeError(err, "CreateNameTripCode") } - enc, err := crypt.Crypt(pass, "$1$"+string(salt[:])) - if err != nil { - return "", err + trip := regexp.MustCompile("#(.+)?") + + if trip.MatchString(input) { + chunck := trip.FindString(input) + chunck = strings.Replace(chunck, "#", "", 1) + ce := regexp.MustCompile(`(?i)Admin`) + admin := ce.MatchString(chunck) + board, modcred := util.GetPasswordFromSession(ctx) + + if hasAuth, _ := util.HasAuth(modcred, board); hasAuth && admin { + return trip.ReplaceAllString(input, ""), "#Admin", nil + } + + hash, err := TripCode(chunck) + return trip.ReplaceAllString(input, ""), "!" + hash, util.MakeError(err, "CreateNameTripCode") } - // normally i would just return error here but if the encrypt fails, this operation may fail and as a result cause a panic - return enc[len(enc)-10 : len(enc)], nil + return input, "", nil } -func TripCodeSecure(pass string) (string, error) { +func TripCode(pass string) (string, error) { + var salt [2]rune + pass = TripCodeConvert(pass) + s := []rune(pass + "H..")[1:3] + + for i, r := range s { + salt[i] = rune(SaltTable[r%256]) + } + + enc, err := crypt.Crypt(pass, "$1$"+string(salt[:])) - enc, err := crypt.Crypt(pass, "$1$"+config.Salt) if err != nil { - return "", err + return "", util.MakeError(err, "TripCode") } + // normally i would just return error here but if the encrypt fails, this operation may fail and as a result cause a panic return enc[len(enc)-10 : len(enc)], nil } @@ -58,7 +88,6 @@ func TripCodeConvert(str string) string { var s bytes.Buffer transform.NewWriter(&s, japanese.ShiftJIS.NewEncoder()).Write([]byte(str)) - re := strings.NewReplacer( "&", "&", "\"", """, @@ -69,48 +98,13 @@ func TripCodeConvert(str string) string { return re.Replace(s.String()) } -func CreateNameTripCode(ctx *fiber.Ctx) (string, string, error) { - // TODO: to allow this to compile, this will fail for the case of the admin - // this can be easily fixed when the rest of the code gets converted to fiber - - input := ctx.FormValue("name") - - tripSecure := regexp.MustCompile("##(.+)?") - - if tripSecure.MatchString(input) { - chunck := tripSecure.FindString(input) - chunck = strings.Replace(chunck, "##", "", 1) - - //ce := regexp.MustCompile(`(?i)Admin`) - //admin := ce.MatchString(chunck) - - //board, modcred := GetPasswordFromSession(r) - - //if admin && HasAuth(modcred, board) { - // return tripSecure.ReplaceAllString(input, ""), "#Admin" - //} - - hash, err := TripCodeSecure(chunck) - return tripSecure.ReplaceAllString(input, ""), "!!" + hash, err - } - - trip := regexp.MustCompile("#(.+)?") - - if trip.MatchString(input) { - chunck := trip.FindString(input) - chunck = strings.Replace(chunck, "#", "", 1) - - //ce := regexp.MustCompile(`(?i)Admin`) - //admin := ce.MatchString(chunck) - //board, modcred := GetPasswordFromSession(r) - - //if admin && HasAuth(db, modcred, board) { - // return trip.ReplaceAllString(input, ""), "#Admin" - //} +func TripCodeSecure(pass string) (string, error) { + pass = TripCodeConvert(pass) + enc, err := crypt.Crypt(pass, "$1$"+config.Salt) - hash, err := TripCode(chunck) - return trip.ReplaceAllString(input, ""), "!" + hash, err + if err != nil { + return "", util.MakeError(err, "TripCodeSecure") } - return input, "", nil + return enc[len(enc)-10 : len(enc)], nil } diff --git a/post/util.go b/post/util.go index 6c3e6a0..67822b0 100644 --- a/post/util.go +++ b/post/util.go @@ -15,7 +15,6 @@ import ( "github.com/FChannel0/FChannel-Server/config" "github.com/FChannel0/FChannel-Server/db" "github.com/FChannel0/FChannel-Server/util" - "github.com/FChannel0/FChannel-Server/webfinger" "github.com/gofiber/fiber/v2" ) @@ -46,8 +45,9 @@ func ParseCommentForReplies(comment string, op string) ([]activitypub.ObjectBase str = strings.Replace(str, "https://", "", 1) str = config.TP + "" + str _, isReply, err := db.IsReplyToOP(op, str) + if err != nil { - return nil, err + return nil, util.MakeError(err, "ParseCommentForReplies") } if !util.IsInStringArray(links, str) && isReply { @@ -57,13 +57,16 @@ func ParseCommentForReplies(comment string, op string) ([]activitypub.ObjectBase var validLinks []activitypub.ObjectBase for i := 0; i < len(links); i++ { - _, isValid, err := webfinger.CheckValidActivity(links[i]) + reqActivity := activitypub.Activity{Id: links[i]} + _, isValid, err := reqActivity.CheckValid() + if err != nil { - return nil, err + return nil, util.MakeError(err, "ParseCommentForReplies") } if isValid { var reply activitypub.ObjectBase + reply.Id = links[i] reply.Published = time.Now().UTC() validLinks = append(validLinks, reply) @@ -85,9 +88,11 @@ func ParseCommentForReply(comment string) (string, error) { } if len(links) > 0 { - _, isValid, err := webfinger.CheckValidActivity(strings.ReplaceAll(links[0], ">", "")) + reqActivity := activitypub.Activity{Id: strings.ReplaceAll(links[0], ">", "")} + _, isValid, err := reqActivity.CheckValid() + if err != nil { - return "", err + return "", util.MakeError(err, "ParseCommentForReply") } if isValid { @@ -125,11 +130,13 @@ func ParseLinkTitle(actorName string, op string, content string) string { func ParseOptions(ctx *fiber.Ctx, obj activitypub.ObjectBase) activitypub.ObjectBase { options := util.EscapeString(ctx.FormValue("options")) + if options != "" { option := strings.Split(options, ";") email := regexp.MustCompile(".+@.+\\..+") wallet := regexp.MustCompile("wallet:.+") delete := regexp.MustCompile("delete:.+") + for _, e := range option { if e == "noko" { obj.Option = append(obj.Option, "noko") @@ -163,20 +170,21 @@ func CheckCaptcha(captcha string) (bool, error) { } path := "public/" + parts[0] + ".png" - code, err := db.GetCaptchaCodeDB(path) + code, err := util.GetCaptchaCode(path) + if err != nil { - return false, err + return false, util.MakeError(err, "ParseOptions") } if code != "" { - err = db.DeleteCaptchaCodeDB(path) + err = util.DeleteCaptchaCode(path) if err != nil { - return false, err + return false, util.MakeError(err, "ParseOptions") } - err = db.CreateNewCaptcha() + err = util.CreateNewCaptcha() if err != nil { - return false, err + return false, util.MakeError(err, "ParseOptions") } } @@ -184,19 +192,28 @@ func CheckCaptcha(captcha string) (bool, error) { return code == strings.ToUpper(parts[1]), nil } +func GetCaptchaCode(captcha string) string { + re := regexp.MustCompile("\\w+\\.\\w+$") + code := re.FindString(captcha) + + re = regexp.MustCompile("\\w+") + code = re.FindString(code) + + return code +} + func IsMediaBanned(f multipart.File) (bool, error) { f.Seek(0, 0) - fileBytes := make([]byte, 2048) - _, err := f.Read(fileBytes) + if err != nil { - return true, err + return true, util.MakeError(err, "IsMediaBanned") } hash := util.HashBytes(fileBytes) - f.Seek(0, 0) + return db.IsHashBanned(hash) } @@ -211,30 +228,28 @@ func SupportedMIMEType(mime string) bool { } func ObjectFromForm(ctx *fiber.Ctx, obj activitypub.ObjectBase) (activitypub.ObjectBase, error) { + var err error + var file multipart.File header, _ := ctx.FormFile("file") - var file multipart.File - if header != nil { file, _ = header.Open() } - var err error - if file != nil { defer file.Close() - var tempFile = new(os.File) + obj.Attachment, tempFile, err = activitypub.CreateAttachmentObject(file, header) + if err != nil { - return obj, err + return obj, util.MakeError(err, "ObjectFromForm") } defer tempFile.Close() fileBytes, _ := ioutil.ReadAll(file) - tempFile.Write(fileBytes) re := regexp.MustCompile(`image/(jpe?g|png|webp)`) @@ -244,7 +259,7 @@ func ObjectFromForm(ctx *fiber.Ctx, obj activitypub.ObjectBase) (activitypub.Obj cmd := exec.Command("exiv2", "rm", "."+fileLoc) if err := cmd.Run(); err != nil { - return obj, err + return obj, util.MakeError(err, "ObjectFromForm") } } @@ -256,12 +271,11 @@ func ObjectFromForm(ctx *fiber.Ctx, obj activitypub.ObjectBase) (activitypub.Obj obj.Name = util.EscapeString(ctx.FormValue("subject")) obj.Content = util.EscapeString(ctx.FormValue("comment")) obj.Sensitive = (ctx.FormValue("sensitive") != "") - obj = ParseOptions(ctx, obj) var originalPost activitypub.ObjectBase - originalPost.Id = util.EscapeString(ctx.FormValue("inReplyTo")) + originalPost.Id = util.EscapeString(ctx.FormValue("inReplyTo")) obj.InReplyTo = append(obj.InReplyTo, originalPost) var activity activitypub.Activity @@ -272,23 +286,23 @@ func ObjectFromForm(ctx *fiber.Ctx, obj activitypub.ObjectBase) (activitypub.Obj if originalPost.Id != "" { if local, _ := activity.IsLocal(); !local { - actor, err := webfinger.FingerActor(originalPost.Id) + actor, err := activitypub.FingerActor(originalPost.Id) if err != nil { - return obj, err + return obj, util.MakeError(err, "ObjectFromForm") } if !util.IsInStringArray(obj.To, actor.Id) { obj.To = append(obj.To, actor.Id) } } else if err != nil { - return obj, err + return obj, util.MakeError(err, "ObjectFromForm") } } replyingTo, err := ParseCommentForReplies(ctx.FormValue("comment"), originalPost.Id) if err != nil { - return obj, err + return obj, util.MakeError(err, "ObjectFromForm") } for _, e := range replyingTo { @@ -309,16 +323,16 @@ func ObjectFromForm(ctx *fiber.Ctx, obj activitypub.ObjectBase) (activitypub.Obj activity.To = append(activity.To, e.Id) if local, err := activity.IsLocal(); err == nil && !local { - actor, err := webfinger.FingerActor(e.Id) + actor, err := activitypub.FingerActor(e.Id) if err != nil { - return obj, err + return obj, util.MakeError(err, "ObjectFromForm") } if !util.IsInStringArray(obj.To, actor.Id) { obj.To = append(obj.To, actor.Id) } } else if err != nil { - return obj, err + return obj, util.MakeError(err, "ObjectFromForm") } } } @@ -329,26 +343,22 @@ func ObjectFromForm(ctx *fiber.Ctx, obj activitypub.ObjectBase) (activitypub.Obj func ResizeAttachmentToPreview() error { return activitypub.GetObjectsWithoutPreviewsCallback(func(id, href, mediatype, name string, size int, published time.Time) error { re := regexp.MustCompile(`^\w+`) - _type := re.FindString(mediatype) if _type == "image" { - re = regexp.MustCompile(`.+/`) - file := re.ReplaceAllString(mediatype, "") - nHref := util.GetUniqueFilename(file) var nPreview activitypub.NestedObjectBase re = regexp.MustCompile(`/\w+$`) actor := re.ReplaceAllString(id, "") - nPreview.Type = "Preview" uid, err := util.CreateUniqueID(actor) + if err != nil { - return err + return util.MakeError(err, "ResizeAttachmentToPreview") } nPreview.Id = fmt.Sprintf("%s/%s", actor, uid) @@ -358,25 +368,23 @@ func ResizeAttachmentToPreview() error { nPreview.Size = int64(size) nPreview.Published = published nPreview.Updated = published - re = regexp.MustCompile(`/public/.+`) - objFile := re.FindString(href) if id != "" { cmd := exec.Command("convert", "."+objFile, "-resize", "250x250>", "-strip", "."+nHref) if err := cmd.Run(); err == nil { - fmt.Println(objFile + " -> " + nHref) + config.Log.Println(objFile + " -> " + nHref) if err := nPreview.WritePreview(); err != nil { - return err + return util.MakeError(err, "ResizeAttachmentToPreview") } obj := activitypub.ObjectBase{Id: id} if err := obj.UpdatePreview(nPreview.Id); err != nil { - return err + return util.MakeError(err, "ResizeAttachmentToPreview") } } else { - return err + return util.MakeError(err, "ResizeAttachmentToPreview") } } } @@ -394,6 +402,7 @@ func ParseAttachment(obj activitypub.ObjectBase, catalog bool) template.HTML { } var media string + if regexp.MustCompile(`image\/`).MatchString(obj.Attachment[0].MediaType) { media = ", should also escape &, ", and ' nContent := strings.ReplaceAll(content, `<`, "<") - nContent, err := ParseLinkComments(board, op, nContent, thread) + if err != nil { - return "", err + return "", util.MakeError(err, "ParseContent") } nContent = ParseCommentQuotes(nContent) - nContent = strings.ReplaceAll(nContent, `/\<`, ">") return template.HTML(nContent), nil @@ -484,10 +492,9 @@ func ParseLinkComments(board activitypub.Actor, op string, content string, threa //add url to each matched reply for i, _ := range match { - link := strings.Replace(match[i][0], ">>", "", 1) isOP := "" - domain := match[i][2] + link := strings.Replace(match[i][0], ">>", "", 1) if link == op { isOP = " (OP)" @@ -514,7 +521,7 @@ func ParseLinkComments(board activitypub.Actor, op string, content string, threa obj := activitypub.ObjectBase{Id: parsedLink} col, err := obj.GetCollectionFromPath() if err != nil { - return "", err + return "", util.MakeError(err, "ParseLinkComments") } if len(col.OrderedItems) > 0 { @@ -537,7 +544,7 @@ func ParseLinkComments(board activitypub.Actor, op string, content string, threa link = parsedOP + "#" + util.ShortURL(parsedOP, parsedLink) } - actor, err := webfinger.FingerActor(parsedLink) + actor, err := activitypub.FingerActor(parsedLink) if err == nil && actor.Id != "" { content = strings.Replace(content, match[i][0], ">>"+util.ShortURL(board.Outbox, parsedLink)+isOP+" →", -1) } diff --git a/routes/actor.go b/routes/actor.go index 81f6dbe..0200d93 100644 --- a/routes/actor.go +++ b/routes/actor.go @@ -3,7 +3,6 @@ package routes import ( "bytes" "errors" - "fmt" "io" "io/ioutil" "mime/multipart" @@ -11,7 +10,6 @@ import ( "github.com/FChannel0/FChannel-Server/activitypub" "github.com/FChannel0/FChannel-Server/config" - "github.com/FChannel0/FChannel-Server/db" "github.com/FChannel0/FChannel-Server/post" "github.com/FChannel0/FChannel-Server/util" "github.com/FChannel0/FChannel-Server/webfinger" @@ -21,21 +19,21 @@ import ( func ActorInbox(ctx *fiber.Ctx) error { activity, err := activitypub.GetActivityFromJson(ctx) if err != nil { - return err + return util.MakeError(err, "ActorInbox") } if activity.Actor.PublicKey.Id == "" { - nActor, err := webfinger.FingerActor(activity.Actor.Id) + nActor, err := activitypub.FingerActor(activity.Actor.Id) if err != nil { - return err + return util.MakeError(err, "ActorInbox") } activity.Actor = &nActor } - if !db.VerifyHeaderSignature(ctx, *activity.Actor) { + if !activity.Actor.VerifyHeaderSignature(ctx) { response := activity.Reject() - return db.MakeActivityRequest(response) + return response.MakeRequestInbox() } switch activity.Type { @@ -44,9 +42,10 @@ func ActorInbox(ctx *fiber.Ctx) error { actor := activitypub.Actor{Id: e} if res, err := actor.IsLocal(); err == nil && res { if res, err := activity.Actor.IsLocal(); err == nil && res { - col, err := activity.Object.GetCollection() + reqActivity := activitypub.Activity{Id: activity.Object.Id} + col, err := reqActivity.GetCollection() if err != nil { - return err + return util.MakeError(err, "ActorInbox") } if len(col.OrderedItems) < 1 { @@ -54,24 +53,24 @@ func ActorInbox(ctx *fiber.Ctx) error { } if err := activity.Object.WriteCache(); err != nil { - return err + return util.MakeError(err, "ActorInbox") } actor, err := activitypub.GetActorFromDB(e) if err != nil { - return err + return util.MakeError(err, "ActorInbox") } - if err := db.ArchivePosts(actor); err != nil { - return err + if err := actor.ArchivePosts(); err != nil { + return util.MakeError(err, "ActorInbox") } //SendToFollowers(e, activity) } else if err != nil { - return err + return util.MakeError(err, "ActorInbox") } } else if err != nil { - return err + return util.MakeError(err, "ActorInbox") } } @@ -81,23 +80,23 @@ func ActorInbox(ctx *fiber.Ctx) error { for _, e := range activity.To { actor, err := activitypub.GetActorFromDB(e) if err != nil { - return err + return util.MakeError(err, "") } if actor.Id != "" && actor.Id != config.Domain { if activity.Object.Replies.OrderedItems != nil { for _, k := range activity.Object.Replies.OrderedItems { if err := k.Tombstone(); err != nil { - return err + return util.MakeError(err, "ActorInbox") } } } if err := activity.Object.Tombstone(); err != nil { - return err + return util.MakeError(err, "ActorInbox") } if err := actor.UnArchiveLast(); err != nil { - return err + return util.MakeError(err, "ActorInbox") } break } @@ -107,26 +106,26 @@ func ActorInbox(ctx *fiber.Ctx) error { case "Follow": for _, e := range activity.To { if res, err := activitypub.GetActorFromDB(e); err == nil && res.Id != "" { - response := db.AcceptFollow(activity) - response, err := response.SetFollower() + response := activity.AcceptFollow() + response, err := response.SetActorFollower() if err != nil { - return err + return util.MakeError(err, "ActorInbox") } - if err := db.MakeActivityRequest(response); err != nil { - return err + if err := response.MakeRequestInbox(); err != nil { + return util.MakeError(err, "ActorInbox") } alreadyFollow := false alreadyFollowing := false autoSub, err := response.Actor.GetAutoSubscribe() if err != nil { - return err + return util.MakeError(err, "ActorInbox") } following, err := response.Actor.GetFollowing() if err != nil { - return err + return util.MakeError(err, "ActorInbox") } for _, e := range following { @@ -135,14 +134,15 @@ func ActorInbox(ctx *fiber.Ctx) error { } } - actor, err := webfinger.FingerActor(response.Object.Actor) + actor, err := activitypub.FingerActor(response.Object.Actor) if err != nil { - return err + return util.MakeError(err, "ActorInbox") } - remoteActorFollowingCol, err := webfinger.GetCollectionFromReq(actor.Following) + reqActivity := activitypub.Activity{Id: actor.Following} + remoteActorFollowingCol, err := reqActivity.GetCollection() if err != nil { - return err + return util.MakeError(err, "ActorInbox") } for _, e := range remoteActorFollowingCol.Items { @@ -152,34 +152,34 @@ func ActorInbox(ctx *fiber.Ctx) error { } if autoSub && !alreadyFollow && alreadyFollowing { - followActivity, err := db.MakeFollowActivity(response.Actor.Id, response.Object.Actor) + followActivity, err := response.Actor.MakeFollowActivity(response.Object.Actor) if err != nil { - return err + return util.MakeError(err, "ActorInbox") } - if res, err := webfinger.FingerActor(response.Object.Actor); err == nil && res.Id != "" { - if err := db.MakeActivityRequestOutbox(followActivity); err != nil { - return err + if res, err := activitypub.FingerActor(response.Object.Actor); err == nil && res.Id != "" { + if err := followActivity.MakeRequestOutbox(); err != nil { + return util.MakeError(err, "ActorInbox") } } else if err != nil { - return err + return util.MakeError(err, "ActorInbox") } } } else if err != nil { - return err + return util.MakeError(err, "ActorInbox") } else { - fmt.Println("follow request for rejected") + config.Log.Println("follow request for rejected") response := activity.Reject() - return db.MakeActivityRequest(response) + return response.MakeRequestInbox() } } break case "Reject": if activity.Object.Object.Type == "Follow" { - fmt.Println("follow rejected") - if _, err := db.SetActorFollowingDB(activity); err != nil { - return err + config.Log.Println("follow rejected") + if _, err := activity.SetActorFollowing(); err != nil { + return util.MakeError(err, "ActorInbox") } } break @@ -192,7 +192,7 @@ func ActorOutbox(ctx *fiber.Ctx) error { //var activity activitypub.Activity actor, err := webfinger.GetActorFromPath(ctx.Path(), "/") if err != nil { - return err + return util.MakeError(err, "ActorOutbox") } if activitypub.AcceptActivity(ctx.Get("Accept")) { diff --git a/routes/admin.go b/routes/admin.go index bd489a5..24c1479 100644 --- a/routes/admin.go +++ b/routes/admin.go @@ -20,7 +20,7 @@ func AdminVerify(ctx *fiber.Ctx) error { identifier := ctx.FormValue("id") code := ctx.FormValue("code") - var verify db.Verify + var verify util.Verify verify.Identifier = identifier verify.Code = code @@ -29,7 +29,7 @@ func AdminVerify(ctx *fiber.Ctx) error { req, err := http.NewRequest("POST", config.Domain+"/auth", bytes.NewBuffer(j)) if err != nil { - return err + return util.MakeError(err, "AdminVerify") } req.Header.Set("Content-Type", config.ActivityStreams) @@ -37,7 +37,7 @@ func AdminVerify(ctx *fiber.Ctx) error { resp, err := http.DefaultClient.Do(req) if err != nil { - return err + return util.MakeError(err, "AdminVerify") } defer resp.Body.Close() @@ -61,29 +61,29 @@ func AdminVerify(ctx *fiber.Ctx) error { // TODO remove this route it is mostly unneeded func AdminAuth(ctx *fiber.Ctx) error { - var verify db.Verify + var verify util.Verify err := json.Unmarshal(ctx.Body(), &verify) if err != nil { - return err + return util.MakeError(err, "AdminAuth") } - v, _ := db.GetVerificationByCode(verify.Code) + v, _ := util.GetVerificationByCode(verify.Code) if v.Identifier == verify.Identifier { _, err := ctx.Write([]byte(v.Board)) - return err + return util.MakeError(err, "AdminAuth") } ctx.Response().Header.SetStatusCode(http.StatusBadRequest) _, err = ctx.Write([]byte("")) - return err + return util.MakeError(err, "AdminAuth") } func AdminIndex(ctx *fiber.Ctx) error { - id, _ := db.GetPasswordFromSession(ctx) + id, _ := util.GetPasswordFromSession(ctx) actor, _ := webfinger.GetActorFromPath(ctx.Path(), "/"+config.Key+"/") if actor.Id == "" { @@ -94,14 +94,15 @@ func AdminIndex(ctx *fiber.Ctx) error { return ctx.Render("verify", fiber.Map{}) } - actor, err := webfinger.GetActor(config.Domain) + actor, err := activitypub.GetActor(config.Domain) if err != nil { - return err + return util.MakeError(err, "AdminIndex") } - follow, _ := webfinger.GetActorCollection(actor.Following) - follower, _ := webfinger.GetActorCollection(actor.Followers) + reqActivity := activitypub.Activity{Id: actor.Following} + follow, _ := reqActivity.GetCollection() + follower, _ := reqActivity.GetCollection() var following []string var followers []string @@ -120,14 +121,14 @@ func AdminIndex(ctx *fiber.Ctx) error { adminData.Actor = actor.Id adminData.Key = config.Key adminData.Domain = config.Domain - adminData.Board.ModCred, _ = db.GetPasswordFromSession(ctx) + adminData.Board.ModCred, _ = util.GetPasswordFromSession(ctx) adminData.Title = actor.Name + " Admin page" adminData.Boards = webfinger.Boards adminData.Board.Post.Actor = actor.Id - adminData.PostBlacklist, _ = util.GetRegexBlacklistDB() + adminData.PostBlacklist, _ = util.GetRegexBlacklist() adminData.Themes = &config.Themes @@ -147,8 +148,9 @@ func AdminFollow(ctx *fiber.Ctx) error { //follow all of boards following if following.MatchString(follow) { - followingActor, _ := webfinger.FingerActor(follow) - col, _ := webfinger.GetActorCollection(followingActor.Following) + followingActor, _ := activitypub.FingerActor(follow) + reqActivity := activitypub.Activity{Id: followingActor.Following} + col, _ := reqActivity.GetCollection() var nObj activitypub.ObjectBase nObj.Id = followingActor.Id @@ -157,18 +159,20 @@ func AdminFollow(ctx *fiber.Ctx) error { for _, e := range col.Items { if isFollowing, _ := actor.IsAlreadyFollowing(e.Id); !isFollowing && e.Id != config.Domain && e.Id != actorId { - followActivity, _ := db.MakeFollowActivity(actorId, e.Id) + actor := activitypub.Actor{Id: actorId} + followActivity, _ := actor.MakeFollowActivity(e.Id) - if actor, _ := webfinger.FingerActor(e.Id); actor.Id != "" { - db.MakeActivityRequestOutbox(followActivity) + if actor, _ := activitypub.FingerActor(e.Id); actor.Id != "" { + followActivity.MakeRequestOutbox() } } } //follow all of boards followers } else if followers.MatchString(follow) { - followersActor, _ := webfinger.FingerActor(follow) - col, _ := webfinger.GetActorCollection(followersActor.Followers) + followersActor, _ := activitypub.FingerActor(follow) + reqActivity := activitypub.Activity{Id: followersActor.Followers} + col, _ := reqActivity.GetCollection() var nObj activitypub.ObjectBase nObj.Id = followersActor.Id @@ -177,25 +181,27 @@ func AdminFollow(ctx *fiber.Ctx) error { for _, e := range col.Items { if isFollowing, _ := actor.IsAlreadyFollowing(e.Id); !isFollowing && e.Id != config.Domain && e.Id != actorId { - followActivity, _ := db.MakeFollowActivity(actorId, e.Id) - if actor, _ := webfinger.FingerActor(e.Id); actor.Id != "" { - db.MakeActivityRequestOutbox(followActivity) + actor := activitypub.Actor{Id: actorId} + followActivity, _ := actor.MakeFollowActivity(e.Id) + if actor, _ := activitypub.FingerActor(e.Id); actor.Id != "" { + followActivity.MakeRequestOutbox() } } } //do a normal follow to a single board } else { - followActivity, _ := db.MakeFollowActivity(actorId, follow) + actor := activitypub.Actor{Id: actorId} + followActivity, _ := actor.MakeFollowActivity(follow) - actor := activitypub.Actor{Id: followActivity.Object.Actor} + actor = activitypub.Actor{Id: followActivity.Object.Actor} if isLocal, _ := actor.IsLocal(); !isLocal && followActivity.Actor.Id == config.Domain { _, err := ctx.Write([]byte("main board can only follow local boards. Create a new board and then follow outside boards from it.")) - return err + return util.MakeError(err, "AdminIndex") } - if actor, _ := webfinger.FingerActor(follow); actor.Id != "" { - db.MakeActivityRequestOutbox(followActivity) + if actor, _ := activitypub.FingerActor(follow); actor.Id != "" { + followActivity.MakeRequestOutbox() } } @@ -211,7 +217,7 @@ func AdminFollow(ctx *fiber.Ctx) error { func AdminAddBoard(ctx *fiber.Ctx) error { actor, _ := activitypub.GetActorFromDB(config.Domain) - if hasValidation := db.HasValidation(ctx, actor); !hasValidation { + if hasValidation := actor.HasValidation(ctx); !hasValidation { return nil } @@ -242,7 +248,7 @@ func AdminAddBoard(ctx *fiber.Ctx) error { newActorActivity.Object.Summary = board.Summary newActorActivity.Object.Sensitive = board.Restricted - db.MakeActivityRequestOutbox(newActorActivity) + newActorActivity.MakeRequestOutbox() return ctx.Redirect("/"+config.Key, http.StatusSeeOther) } @@ -261,9 +267,14 @@ func AdminNewsDelete(c *fiber.Ctx) error { func AdminActorIndex(ctx *fiber.Ctx) error { actor, _ := webfinger.GetActorFromPath(ctx.Path(), "/"+config.Key+"/") - follow, _ := webfinger.GetActorCollection(actor.Following) - follower, _ := webfinger.GetActorCollection(actor.Followers) - reported, _ := activitypub.GetActorCollectionReq(actor.Id + "/reported") + reqActivity := activitypub.Activity{Id: actor.Following} + follow, _ := reqActivity.GetCollection() + + reqActivity.Id = actor.Followers + follower, _ := reqActivity.GetCollection() + + reqActivity.Id = actor.Id + "/reported" + reported, _ := activitypub.GetActorCollectionReq(reqActivity.Id) var following []string var followers []string @@ -285,7 +296,7 @@ func AdminActorIndex(ctx *fiber.Ctx) error { reports = append(reports, r) } - localReports, _ := db.GetLocalReportDB(actor.Name) + localReports, _ := db.GetLocalReport(actor.Name) for _, e := range localReports { var r db.Report diff --git a/routes/api.go b/routes/api.go index 2fb0f3f..080d88d 100644 --- a/routes/api.go +++ b/routes/api.go @@ -6,6 +6,7 @@ import ( "time" "github.com/FChannel0/FChannel-Server/config" + "github.com/FChannel0/FChannel-Server/util" "github.com/gofiber/fiber/v2" ) @@ -20,7 +21,7 @@ func Media(c *fiber.Ctx) error { func RouteImages(ctx *fiber.Ctx, media string) error { req, err := http.NewRequest("GET", config.MediaHashs[media], nil) if err != nil { - return err + return util.MakeError(err, "RouteImages") } client := http.Client{ @@ -29,18 +30,18 @@ func RouteImages(ctx *fiber.Ctx, media string) error { resp, err := client.Do(req) if err != nil { - return err + return util.MakeError(err, "RouteImages") } defer resp.Body.Close() if resp.StatusCode != 200 { fileBytes, err := ioutil.ReadFile("./static/notfound.png") if err != nil { - return err + return util.MakeError(err, "RouteImages") } _, err = ctx.Write(fileBytes) - return err + return util.MakeError(err, "RouteImages") } body, _ := ioutil.ReadAll(resp.Body) diff --git a/routes/archive.go b/routes/archive.go index c0f4ff6..c4950b4 100644 --- a/routes/archive.go +++ b/routes/archive.go @@ -3,14 +3,13 @@ package routes import ( "github.com/FChannel0/FChannel-Server/activitypub" "github.com/FChannel0/FChannel-Server/config" - "github.com/FChannel0/FChannel-Server/db" + "github.com/FChannel0/FChannel-Server/post" "github.com/FChannel0/FChannel-Server/util" "github.com/FChannel0/FChannel-Server/webfinger" "github.com/gofiber/fiber/v2" ) func ArchiveGet(ctx *fiber.Ctx) error { - // TODO collection := ctx.Locals("collection").(activitypub.Collection) actor := collection.Actor @@ -21,7 +20,7 @@ func ArchiveGet(ctx *fiber.Ctx) error { returnData.Board.To = actor.Outbox returnData.Board.Actor = actor returnData.Board.Summary = actor.Summary - returnData.Board.ModCred, _ = db.GetPasswordFromSession(ctx) + returnData.Board.ModCred, _ = util.GetPasswordFromSession(ctx) returnData.Board.Domain = config.Domain returnData.Board.Restricted = actor.Restricted returnData.Key = config.Key @@ -32,12 +31,12 @@ func ArchiveGet(ctx *fiber.Ctx) error { var err error returnData.Instance, err = activitypub.GetActorFromDB(config.Domain) - capt, err := db.GetRandomCaptcha() + capt, err := util.GetRandomCaptcha() if err != nil { - return err + return util.MakeError(err, "ArchiveGet") } returnData.Board.Captcha = config.Domain + "/" + capt - returnData.Board.CaptchaCode = util.GetCaptchaCode(returnData.Board.Captcha) + returnData.Board.CaptchaCode = post.GetCaptchaCode(returnData.Board.Captcha) returnData.Title = "/" + actor.Name + "/ - " + actor.PreferredUsername diff --git a/routes/index.go b/routes/index.go index 8f12664..68fcfd0 100644 --- a/routes/index.go +++ b/routes/index.go @@ -4,6 +4,7 @@ import ( "github.com/FChannel0/FChannel-Server/activitypub" "github.com/FChannel0/FChannel-Server/config" "github.com/FChannel0/FChannel-Server/db" + "github.com/FChannel0/FChannel-Server/util" "github.com/FChannel0/FChannel-Server/webfinger" "github.com/gofiber/fiber/v2" ) @@ -11,7 +12,7 @@ import ( func Index(ctx *fiber.Ctx) error { actor, err := activitypub.GetActorFromDB(config.Domain) if err != nil { - return err + return util.MakeError(err, "Index") } // this is a activitpub json request return json instead of html page @@ -22,18 +23,19 @@ func Index(ctx *fiber.Ctx) error { var data PageData - col, err := webfinger.GetCollectionFromReq("https://fchan.xyz/followers") + reqActivity := activitypub.Activity{Id: "https://fchan.xyz/followers"} + col, err := reqActivity.GetCollection() if err != nil { - return err + return util.MakeError(err, "Index") } if len(col.Items) > 0 { data.InstanceIndex = col.Items } - data.NewsItems, err = db.GetNewsFromDB(3) + data.NewsItems, err = db.GetNews(3) if err != nil { - return err + return util.MakeError(err, "Index") } data.Title = "Welcome to " + actor.PreferredUsername @@ -42,7 +44,7 @@ func Index(ctx *fiber.Ctx) error { data.Board.Name = "" data.Key = config.Key data.Board.Domain = config.Domain - data.Board.ModCred, _ = db.GetPasswordFromSession(ctx) + data.Board.ModCred, _ = util.GetPasswordFromSession(ctx) data.Board.Actor = actor data.Board.Post.Actor = actor.Id data.Board.Restricted = actor.Restricted diff --git a/routes/news.go b/routes/news.go index bd037c2..c091605 100644 --- a/routes/news.go +++ b/routes/news.go @@ -4,17 +4,17 @@ import ( "github.com/FChannel0/FChannel-Server/activitypub" "github.com/FChannel0/FChannel-Server/config" "github.com/FChannel0/FChannel-Server/db" + "github.com/FChannel0/FChannel-Server/util" "github.com/FChannel0/FChannel-Server/webfinger" "github.com/gofiber/fiber/v2" ) func NewsGet(ctx *fiber.Ctx) error { - // TODO timestamp := 0 actor, err := activitypub.GetActorFromDB(config.Domain) if err != nil { - return err + return util.MakeError(err, "NewsGet") } var data PageData @@ -23,15 +23,15 @@ func NewsGet(ctx *fiber.Ctx) error { data.Board.Name = "" data.Key = config.Key data.Board.Domain = config.Domain - data.Board.ModCred, _ = db.GetPasswordFromSession(ctx) + data.Board.ModCred, _ = util.GetPasswordFromSession(ctx) data.Board.Actor = actor data.Board.Post.Actor = actor.Id data.Board.Restricted = actor.Restricted data.NewsItems = make([]db.NewsItem, 1) - data.NewsItems[0], err = db.GetNewsItemFromDB(timestamp) + data.NewsItems[0], err = db.GetNewsItem(timestamp) if err != nil { - return err + return util.MakeError(err, "NewsGet") } data.Title = actor.PreferredUsername + ": " + data.NewsItems[0].Title @@ -45,7 +45,7 @@ func NewsGet(ctx *fiber.Ctx) error { func AllNewsGet(ctx *fiber.Ctx) error { actor, err := activitypub.GetActorFromDB(config.Domain) if err != nil { - return err + return util.MakeError(err, "AllNewsGet") } var data PageData @@ -55,14 +55,14 @@ func AllNewsGet(ctx *fiber.Ctx) error { data.Board.Name = "" data.Key = config.Key data.Board.Domain = config.Domain - data.Board.ModCred, _ = db.GetPasswordFromSession(ctx) + data.Board.ModCred, _ = util.GetPasswordFromSession(ctx) data.Board.Actor = actor data.Board.Post.Actor = actor.Id data.Board.Restricted = actor.Restricted - data.NewsItems, err = db.GetNewsFromDB(0) + data.NewsItems, err = db.GetNews(0) if err != nil { - return err + return util.MakeError(err, "AllNewsGet") } data.Themes = &config.Themes diff --git a/routes/outbox.go b/routes/outbox.go index 902d3e6..1d0d52e 100644 --- a/routes/outbox.go +++ b/routes/outbox.go @@ -5,7 +5,7 @@ import ( "github.com/FChannel0/FChannel-Server/activitypub" "github.com/FChannel0/FChannel-Server/config" - "github.com/FChannel0/FChannel-Server/db" + "github.com/FChannel0/FChannel-Server/post" "github.com/FChannel0/FChannel-Server/util" "github.com/FChannel0/FChannel-Server/webfinger" "github.com/gofiber/fiber/v2" @@ -14,7 +14,7 @@ import ( func Outbox(ctx *fiber.Ctx) error { actor, err := webfinger.GetActorFromPath(ctx.Path(), "/") if err != nil { - return err + return util.MakeError(err, "Outbox") } if activitypub.AcceptActivity(ctx.Get("Accept")) { @@ -40,13 +40,13 @@ func OutboxGet(ctx *fiber.Ctx) error { var page int if postNum := ctx.Query("page"); postNum != "" { if page, err = strconv.Atoi(postNum); err != nil { - return err + return util.MakeError(err, "OutboxGet") } } collection, err := actor.WantToServePage(page) if err != nil { - return err + return util.MakeError(err, "OutboxGet") } var offset = 15 @@ -68,7 +68,7 @@ func OutboxGet(ctx *fiber.Ctx) error { data.Board.InReplyTo = "" data.Board.To = actor.Outbox data.Board.Actor = actor - data.Board.ModCred, _ = db.GetPasswordFromSession(ctx) + data.Board.ModCred, _ = util.GetPasswordFromSession(ctx) data.Board.Domain = config.Domain data.Board.Restricted = actor.Restricted data.CurrentPage = page @@ -76,12 +76,12 @@ func OutboxGet(ctx *fiber.Ctx) error { data.Board.Post.Actor = actor.Id - capt, err := db.GetRandomCaptcha() + capt, err := util.GetRandomCaptcha() if err != nil { - return err + return util.MakeError(err, "OutboxGet") } data.Board.Captcha = config.Domain + "/" + capt - data.Board.CaptchaCode = util.GetCaptchaCode(data.Board.Captcha) + data.Board.CaptchaCode = post.GetCaptchaCode(data.Board.Captcha) data.Title = "/" + actor.Name + "/ - " + actor.PreferredUsername diff --git a/routes/post.go b/routes/post.go index 813f53d..4002be7 100644 --- a/routes/post.go +++ b/routes/post.go @@ -5,7 +5,7 @@ import ( "github.com/FChannel0/FChannel-Server/activitypub" "github.com/FChannel0/FChannel-Server/config" - "github.com/FChannel0/FChannel-Server/db" + "github.com/FChannel0/FChannel-Server/post" "github.com/FChannel0/FChannel-Server/util" "github.com/FChannel0/FChannel-Server/webfinger" "github.com/gofiber/fiber/v2" @@ -36,23 +36,23 @@ func PostGet(ctx *fiber.Ctx) error { if re.MatchString(ctx.Path()) { // if non local actor post name := activitypub.GetActorFollowNameFromPath(ctx.Path()) - followActors, err := webfinger.GetActorsFollowFromName(actor, name) + followActors, err := actor.GetFollowFromName(name) if err != nil { - return err + return util.MakeError(err, "PostGet") } followCollection, err := activitypub.GetActorsFollowPostFromId(followActors, postId) if err != nil { - return err + return util.MakeError(err, "PostGet") } if len(followCollection.OrderedItems) > 0 { data.Board.InReplyTo = followCollection.OrderedItems[0].Id data.Posts = append(data.Posts, followCollection.OrderedItems[0]) - actor, err := webfinger.FingerActor(data.Board.InReplyTo) + actor, err := activitypub.FingerActor(data.Board.InReplyTo) if err != nil { - return err + return util.MakeError(err, "PostGet") } data.Board.Post.Actor = actor.Id @@ -61,7 +61,7 @@ func PostGet(ctx *fiber.Ctx) error { obj := activitypub.ObjectBase{Id: inReplyTo} collection, err := obj.GetCollectionFromPath() if err != nil { - return err + return util.MakeError(err, "PostGet") } if collection.Actor.Id != "" { @@ -83,21 +83,21 @@ func PostGet(ctx *fiber.Ctx) error { data.Board.To = actor.Outbox data.Board.Actor = actor data.Board.Summary = actor.Summary - data.Board.ModCred, _ = db.GetPasswordFromSession(ctx) + data.Board.ModCred, _ = util.GetPasswordFromSession(ctx) data.Board.Domain = config.Domain data.Board.Restricted = actor.Restricted data.ReturnTo = "feed" - capt, err := db.GetRandomCaptcha() + capt, err := util.GetRandomCaptcha() if err != nil { - return err + return util.MakeError(err, "PostGet") } data.Board.Captcha = config.Domain + "/" + capt - data.Board.CaptchaCode = util.GetCaptchaCode(data.Board.Captcha) + data.Board.CaptchaCode = post.GetCaptchaCode(data.Board.Captcha) data.Instance, err = activitypub.GetActorFromDB(config.Domain) if err != nil { - return err + return util.MakeError(err, "PostGet") } data.Key = config.Key @@ -124,7 +124,7 @@ func CatalogGet(ctx *fiber.Ctx) error { actorName := ctx.Params("actor") actor, err := activitypub.GetActorByNameFromDB(actorName) if err != nil { - return err + return util.MakeError(err, "CatalogGet") } collection, err := actor.GetCatalogCollection() @@ -150,7 +150,7 @@ func CatalogGet(ctx *fiber.Ctx) error { data.Board.To = actor.Outbox data.Board.Actor = actor data.Board.Summary = actor.Summary - data.Board.ModCred, _ = db.GetPasswordFromSession(ctx) + data.Board.ModCred, _ = util.GetPasswordFromSession(ctx) data.Board.Domain = config.Domain data.Board.Restricted = actor.Restricted data.Key = config.Key @@ -160,16 +160,16 @@ func CatalogGet(ctx *fiber.Ctx) error { data.Instance, err = activitypub.GetActorFromDB(config.Domain) if err != nil { - return err + return util.MakeError(err, "CatalogGet") } - capt, err := db.GetRandomCaptcha() + capt, err := util.GetRandomCaptcha() if err != nil { - return err + return util.MakeError(err, "CatalogGet") } data.Board.Captcha = config.Domain + "/" + capt - data.Board.CaptchaCode = util.GetCaptchaCode(data.Board.Captcha) + data.Board.CaptchaCode = post.GetCaptchaCode(data.Board.Captcha) data.Title = "/" + data.Board.Name + "/ - catalog" diff --git a/routes/util.go b/routes/util.go index de0c6e0..94337bc 100644 --- a/routes/util.go +++ b/routes/util.go @@ -28,19 +28,19 @@ func getThemeCookie(c *fiber.Ctx) string { return "default" } -func wantToServeCatalog(actorName string) (activitypub.Collection, bool, error) { +func WantToServeCatalog(actorName string) (activitypub.Collection, bool, error) { var collection activitypub.Collection serve := false actor, err := activitypub.GetActorByNameFromDB(actorName) if err != nil { - return collection, false, err + return collection, false, util.MakeError(err, "WantToServeCatalog") } if actor.Id != "" { collection, err = actor.GetCatalogCollection() if err != nil { - return collection, false, err + return collection, false, util.MakeError(err, "WantToServeCatalog") } collection.Actor = actor @@ -50,19 +50,19 @@ func wantToServeCatalog(actorName string) (activitypub.Collection, bool, error) return collection, serve, nil } -func wantToServeArchive(actorName string) (activitypub.Collection, bool, error) { +func WantToServeArchive(actorName string) (activitypub.Collection, bool, error) { var collection activitypub.Collection serve := false actor, err := activitypub.GetActorByNameFromDB(actorName) if err != nil { - return collection, false, err + return collection, false, util.MakeError(err, "WantToServeArchive") } if actor.Id != "" { collection, err = actor.GetCollectionType("Archive") if err != nil { - return collection, false, err + return collection, false, util.MakeError(err, "WantToServeArchive") } collection.Actor = actor @@ -77,18 +77,18 @@ func GetActorPost(ctx *fiber.Ctx, path string) error { collection, err := obj.GetCollectionFromPath() if err != nil { - return err + return util.MakeError(err, "GetActorPost") } if len(collection.OrderedItems) > 0 { enc, err := json.MarshalIndent(collection, "", "\t") if err != nil { - return err + return util.MakeError(err, "GetActorPost") } ctx.Response().Header.Set("Content-Type", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") _, err = ctx.Write(enc) - return err + return util.MakeError(err, "GetActorPost") } return nil @@ -98,9 +98,9 @@ func ParseOutboxRequest(ctx *fiber.Ctx, actor activitypub.Actor) error { contentType := util.GetContentType(ctx.Get("content-type")) if contentType == "multipart/form-data" || contentType == "application/x-www-form-urlencoded" { - hasCaptcha, err := db.BoardHasAuthType(actor.Name, "captcha") + hasCaptcha, err := util.BoardHasAuthType(actor.Name, "captcha") if err != nil { - return err + return util.MakeError(err, "ParseOutboxRequest") } valid, err := post.CheckCaptcha(ctx.FormValue("captcha")) @@ -112,15 +112,15 @@ func ParseOutboxRequest(ctx *fiber.Ctx, actor activitypub.Actor) error { if header.Size > (7 << 20) { ctx.Response().Header.SetStatusCode(403) _, err := ctx.Write([]byte("7MB max file size")) - return err + return util.MakeError(err, "ParseOutboxRequest") } else if isBanned, err := post.IsMediaBanned(f); err == nil && isBanned { //Todo add logging - fmt.Println("media banned") + config.Log.Println("media banned") ctx.Response().Header.SetStatusCode(403) _, err := ctx.Write([]byte("media banned")) - return err + return util.MakeError(err, "ParseOutboxRequest") } else if err != nil { - return err + return util.MakeError(err, "ParseOutboxRequest") } contentType, _ := util.GetFileContentType(f) @@ -128,40 +128,40 @@ func ParseOutboxRequest(ctx *fiber.Ctx, actor activitypub.Actor) error { if !post.SupportedMIMEType(contentType) { ctx.Response().Header.SetStatusCode(403) _, err := ctx.Write([]byte("file type not supported")) - return err + return util.MakeError(err, "ParseOutboxRequest") } } var nObj = activitypub.CreateObject("Note") nObj, err := post.ObjectFromForm(ctx, nObj) if err != nil { - return err + return util.MakeError(err, "ParseOutboxRequest") } nObj.Actor = config.Domain + "/" + actor.Name nObj, err = nObj.Write() if err != nil { - return err + return util.MakeError(err, "ParseOutboxRequest") } if len(nObj.To) == 0 { - if err := db.ArchivePosts(actor); err != nil { - return err + if err := actor.ArchivePosts(); err != nil { + return util.MakeError(err, "ParseOutboxRequest") } } - activity, err := webfinger.CreateActivity("Create", nObj) + activity, err := nObj.CreateActivity("Create") if err != nil { - return err + return util.MakeError(err, "ParseOutboxRequest") } - activity, err = webfinger.AddFollowersToActivity(activity) + activity, err = activity.AddFollowersTo() if err != nil { - return err + return util.MakeError(err, "ParseOutboxRequest") } - go db.MakeActivityRequest(activity) + go activity.MakeRequestInbox() var id string op := len(nObj.InReplyTo) - 1 @@ -175,23 +175,23 @@ func ParseOutboxRequest(ctx *fiber.Ctx, actor activitypub.Actor) error { ctx.Response().Header.Set("Status", "200") _, err = ctx.Write([]byte(id)) - return err + return util.MakeError(err, "ParseOutboxRequest") } ctx.Response().Header.Set("Status", "403") _, err = ctx.Write([]byte("captcha could not auth")) - return err + return util.MakeError(err, "") } else { // json request activity, err := activitypub.GetActivityFromJson(ctx) if err != nil { - return err + return util.MakeError(err, "ParseOutboxRequest") } if res, err := activity.IsLocal(); err == nil && res { - if res := db.VerifyHeaderSignature(ctx, *activity.Actor); err == nil && !res { + if res := activity.Actor.VerifyHeaderSignature(ctx); err == nil && !res { ctx.Response().Header.Set("Status", "403") _, err = ctx.Write([]byte("")) - return err + return util.MakeError(err, "ParseOutboxRequest") } switch activity.Type { @@ -209,30 +209,30 @@ func ParseOutboxRequest(ctx *fiber.Ctx, actor activitypub.Actor) error { var rActivity activitypub.Activity if validActor && validLocalActor { - rActivity = db.AcceptFollow(activity) - rActivity, err = db.SetActorFollowingDB(rActivity) + rActivity = activity.AcceptFollow() + rActivity, err = rActivity.SetActorFollowing() if err != nil { - return err + return util.MakeError(err, "ParseOutboxRequest") } - if err := db.MakeActivityRequest(activity); err != nil { - return err + if err := activity.MakeRequestInbox(); err != nil { + return util.MakeError(err, "ParseOutboxRequest") } } actor, _ := activitypub.GetActorFromDB(config.Domain) webfinger.FollowingBoards, err = actor.GetFollowing() if err != nil { - return err + return util.MakeError(err, "ParseOutboxRequest") } webfinger.Boards, err = webfinger.GetBoardCollection() if err != nil { - return err + return util.MakeError(err, "ParseOutboxRequest") } break case "Delete": - fmt.Println("This is a delete") + config.Log.Println("This is a delete") ctx.Response().Header.Set("Status", "403") _, err = ctx.Write([]byte("could not process activity")) break @@ -248,9 +248,9 @@ func ParseOutboxRequest(ctx *fiber.Ctx, actor activitypub.Actor) error { summary := activity.Object.Summary restricted := activity.Object.Sensitive - actor, err := db.CreateNewBoardDB(*activitypub.CreateNewActor(name, prefname, summary, config.AuthReq, restricted)) + actor, err := db.CreateNewBoard(*activitypub.CreateNewActor(name, prefname, summary, config.AuthReq, restricted)) if err != nil { - return err + return util.MakeError(err, "ParseOutboxRequest") } if actor.Id != "" { @@ -273,7 +273,7 @@ func ParseOutboxRequest(ctx *fiber.Ctx, actor activitypub.Actor) error { webfinger.FollowingBoards = board webfinger.Boards, err = webfinger.GetBoardCollection() - return err + return util.MakeError(err, "ParseOutboxRequest") } ctx.Response().Header.Set("Status", "403") @@ -285,12 +285,12 @@ func ParseOutboxRequest(ctx *fiber.Ctx, actor activitypub.Actor) error { _, err = ctx.Write([]byte("could not process activity")) } } else if err != nil { - return err + return util.MakeError(err, "ParseOutboxRequest") } else { - fmt.Println("is NOT activity") + config.Log.Println("is NOT activity") ctx.Response().Header.Set("Status", "403") _, err = ctx.Write([]byte("could not process activity")) - return err + return util.MakeError(err, "ParseOutboxRequest") } } @@ -338,7 +338,7 @@ func TemplateFunctions(engine *html.Engine) { engine.AddFunc("isOnion", util.IsOnion) engine.AddFunc("parseReplyLink", func(actorId string, op string, id string, content string) template.HTML { - actor, _ := webfinger.FingerActor(actorId) + actor, _ := activitypub.FingerActor(actorId) title := strings.ReplaceAll(post.ParseLinkTitle(actor.Id+"/", op, content), `/\<`, ">") link := ">>" + util.ShortURL(actor.Outbox, id) + "" return template.HTML(link) diff --git a/routes/webfinger.go b/routes/webfinger.go index 9789e39..495e6c7 100644 --- a/routes/webfinger.go +++ b/routes/webfinger.go @@ -6,7 +6,7 @@ import ( "github.com/FChannel0/FChannel-Server/activitypub" "github.com/FChannel0/FChannel-Server/config" - "github.com/FChannel0/FChannel-Server/webfinger" + "github.com/FChannel0/FChannel-Server/util" "github.com/gofiber/fiber/v2" ) @@ -38,11 +38,11 @@ func Webfinger(c *fiber.Ctx) error { c.Status(fiber.StatusBadRequest) return c.Send([]byte("actor not local")) } else if err != nil { - return err + return util.MakeError(err, "Webfinger") } - var finger webfinger.Webfinger - var link webfinger.WebfingerLink + var finger activitypub.Webfinger + var link activitypub.WebfingerLink finger.Subject = "acct:" + actorDomain[0] + "@" + actorDomain[1] link.Rel = "self" diff --git a/util/blacklist.go b/util/blacklist.go index 5368037..acb0b21 100644 --- a/util/blacklist.go +++ b/util/blacklist.go @@ -11,28 +11,28 @@ type PostBlacklist struct { Regex string } -func DeleteRegexBlacklistDB(id int) error { +func DeleteRegexBlacklist(id int) error { query := `delete from postblacklist where id=$1` - _, err := config.DB.Exec(query, id) - return err + + return MakeError(err, "DeleteRegexBlacklist") } -func GetRegexBlacklistDB() ([]PostBlacklist, error) { +func GetRegexBlacklist() ([]PostBlacklist, error) { var list []PostBlacklist query := `select id, regex from postblacklist` - rows, err := config.DB.Query(query) + if err != nil { - return list, err + return list, MakeError(err, "GetRegexBlacklist") } defer rows.Close() for rows.Next() { var temp PostBlacklist - rows.Scan(&temp.Id, &temp.Regex) + rows.Scan(&temp.Id, &temp.Regex) list = append(list, temp) } @@ -40,10 +40,10 @@ func GetRegexBlacklistDB() ([]PostBlacklist, error) { } func IsPostBlacklist(comment string) (bool, error) { - postblacklist, err := GetRegexBlacklistDB() + postblacklist, err := GetRegexBlacklist() if err != nil { - return false, err + return false, MakeError(err, "IsPostBlacklist") } for _, e := range postblacklist { @@ -57,20 +57,15 @@ func IsPostBlacklist(comment string) (bool, error) { return false, nil } -func WriteRegexBlacklistDB(regex string) error { +func WriteRegexBlacklist(regex string) error { var re string query := `select from postblacklist where regex=$1` if err := config.DB.QueryRow(query, regex).Scan(&re); err != nil { - return err - } - - if re != "" { - return nil + query = `insert into postblacklist (regex) values ($1)` + _, err := config.DB.Exec(query, regex) + return MakeError(err, "WriteRegexBlacklist") } - query = `insert into postblacklist (regex) values ($1)` - - _, err := config.DB.Exec(query, regex) - return err + return nil } diff --git a/util/key.go b/util/key.go index cd8662a..60eeb43 100644 --- a/util/key.go +++ b/util/key.go @@ -3,6 +3,7 @@ package util import ( "crypto/sha512" "encoding/hex" + "errors" "math/rand" "os" "strings" @@ -13,14 +14,14 @@ import ( const domain = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" -func CreateKey(len int) string { +func CreateKey(len int) (string, error) { // TODO: provided that CreateTripCode still uses sha512, the max len can be 128 at most. if len > 128 { - panic("len is greater than 128") // awful way to do it + return "", MakeError(errors.New("len is greater than 128"), "CreateKey") } str := CreateTripCode(RandomID(len)) - return str[:len] + return str[:len], nil } func CreateTripCode(input string) string { @@ -29,23 +30,13 @@ func CreateTripCode(input string) string { return hex.EncodeToString(out[:]) } -func RandomID(size int) string { - rng := size - newID := strings.Builder{} - for i := 0; i < rng; i++ { - newID.WriteByte(domain[rand.Intn(len(domain))]) - } - - return newID.String() -} - func GetCookieKey() (string, error) { if config.CookieKey == "" { var file *os.File var err error if file, err = os.OpenFile("config/config-init", os.O_APPEND|os.O_WRONLY, 0644); err != nil { - return "", err + return "", MakeError(err, "GetCookieKey") } defer file.Close() @@ -56,3 +47,14 @@ func GetCookieKey() (string, error) { return config.CookieKey, nil } + +func RandomID(size int) string { + rng := size + newID := strings.Builder{} + + for i := 0; i < rng; i++ { + newID.WriteByte(domain[rand.Intn(len(domain))]) + } + + return newID.String() +} diff --git a/util/proxy.go b/util/proxy.go index 0f4a648..daa90b5 100644 --- a/util/proxy.go +++ b/util/proxy.go @@ -9,27 +9,11 @@ import ( "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" } @@ -40,17 +24,35 @@ func GetPathProxyType(path string) string { func MediaProxy(url string) string { re := regexp.MustCompile("(.+)?" + config.Domain + "(.+)?") - if re.MatchString(url) { return url } re = regexp.MustCompile("(.+)?\\.onion(.+)?") - if re.MatchString(url) { return url } config.MediaHashs[HashMedia(url)] = url + return "/api/media?hash=" + HashMedia(url) } + +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, MakeError(err, "RouteProxy") + } + + 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) +} diff --git a/util/util.go b/util/util.go index 9c1ba97..6d45442 100644 --- a/util/util.go +++ b/util/util.go @@ -12,6 +12,7 @@ import ( "os" "path" "regexp" + "runtime" "strings" "github.com/FChannel0/FChannel-Server/config" @@ -28,34 +29,19 @@ func IsOnion(url string) bool { func StripTransferProtocol(value string) string { re := regexp.MustCompile("(http://|https://)?(www.)?") - value = re.ReplaceAllString(value, "") return value } -func GetCaptchaCode(captcha string) string { - re := regexp.MustCompile("\\w+\\.\\w+$") - code := re.FindString(captcha) - - re = regexp.MustCompile("\\w+") - code = re.FindString(code) - - return code -} - func ShortURL(actorName string, url string) string { + var reply string re := regexp.MustCompile(`.+\/`) - actor := re.FindString(actorName) - urlParts := strings.Split(url, "|") - op := urlParts[0] - var reply string - if len(urlParts) > 1 { reply = urlParts[1] } @@ -99,17 +85,11 @@ func LocalShort(url string) string { func RemoteShort(url string) string { re := regexp.MustCompile(`\w+$`) - id := re.FindString(StripTransferProtocol(url)) - re = regexp.MustCompile(`.+/.+/`) - actorurl := re.FindString(StripTransferProtocol(url)) - re = regexp.MustCompile(`/.+/`) - actorname := re.FindString(actorurl) - actorname = strings.Replace(actorname, "/", "", -1) return "f" + actorname + "-" + id @@ -117,9 +97,7 @@ func RemoteShort(url string) string { func ShortImg(url string) string { nURL := url - re := regexp.MustCompile(`(\.\w+$)`) - fileName := re.ReplaceAllString(url, "") if len(fileName) > 26 { @@ -199,32 +177,19 @@ func HashBytes(media []byte) string { func EscapeString(text string) string { // TODO: not enough - text = strings.Replace(text, "<", "<", -1) return text } func CreateUniqueID(actor string) (string, error) { var newID string - isUnique := false - for !isUnique { - newID = RandomID(8) + for true { + newID = RandomID(8) query := "select id from activitystream where id=$1" args := fmt.Sprintf("%s/%s/%s", config.Domain, actor, newID) - rows, err := config.DB.Query(query, args) - if err != nil { - return "", MakeError(err, "CreateUniqueID") - } - - defer rows.Close() - // reusing a variable here - // if we encounter a match, it'll get set to false causing the outer for loop to loop and to go through this all over again - // however if nothing is there, it'll remain true and exit the loop - isUnique = true - for rows.Next() { - isUnique = false + if err := config.DB.QueryRow(query, args); err != nil { break } } @@ -234,14 +199,13 @@ func CreateUniqueID(actor string) (string, error) { func GetFileContentType(out multipart.File) (string, error) { buffer := make([]byte, 512) - _, err := out.Read(buffer) + if err != nil { return "", MakeError(err, "GetFileContentType") } out.Seek(0, 0) - contentType := http.DetectContentType(buffer) return contentType, nil @@ -249,26 +213,33 @@ func GetFileContentType(out multipart.File) (string, error) { func GetContentType(location string) string { elements := strings.Split(location, ";") + if len(elements) > 0 { return elements[0] - } else { - return location } + + return location } -func CreatedNeededDirectories() { +func CreatedNeededDirectories() error { if _, err := os.Stat("./public"); os.IsNotExist(err) { - os.Mkdir("./public", 0755) + if err = os.Mkdir("./public", 0755); err != nil { + return MakeError(err, "CreatedNeededDirectories") + } } if _, err := os.Stat("./pem/board"); os.IsNotExist(err) { - os.MkdirAll("./pem/board", 0700) + if err = os.MkdirAll("./pem/board", 0700); err != nil { + return MakeError(err, "CreatedNeededDirectories") + } } + + return nil } -func LoadThemes() { - // get list of themes +func LoadThemes() error { themes, err := ioutil.ReadDir("./static/css/themes") + if err != nil { MakeError(err, "LoadThemes") } @@ -278,15 +249,16 @@ func LoadThemes() { config.Themes = append(config.Themes, strings.TrimSuffix(f.Name(), e)) } } + + return nil } func GetBoardAuth(board string) ([]string, error) { var auth []string - - query := `select type from actorauth where board=$1` - var rows *sql.Rows var err error + + query := `select type from actorauth where board=$1` if rows, err = config.DB.Query(query, board); err != nil { return auth, MakeError(err, "GetBoardAuth") } @@ -306,7 +278,8 @@ func GetBoardAuth(board string) ([]string, error) { func MakeError(err error, msg string) error { if err != nil { - s := fmt.Sprintf("%s: %s", msg, err.Error()) + _, _, line, _ := runtime.Caller(1) + s := fmt.Sprintf("%s:%d : %s", msg, line, err.Error()) return errors.New(s) } diff --git a/util/verification.go b/util/verification.go new file mode 100644 index 0000000..c64b54d --- /dev/null +++ b/util/verification.go @@ -0,0 +1,482 @@ +package util + +import ( + "fmt" + "math/rand" + "net/smtp" + "os" + "os/exec" + "strings" + "time" + + "github.com/FChannel0/FChannel-Server/config" + "github.com/gofiber/fiber/v2" + _ "github.com/lib/pq" +) + +type Verify struct { + Type string + Identifier string + Code string + Created string + Board string +} + +type VerifyCooldown struct { + Identifier string + Code string + Time int +} + +type Signature struct { + KeyId string + Headers []string + Signature string + Algorithm string +} + +func (verify Verify) Create() error { + query := `insert into verification (type, identifier, code, created) values ($1, $2, $3, $4)` + _, err := config.DB.Exec(query, verify.Type, verify.Identifier, verify.Code, time.Now().UTC().Format(time.RFC3339)) + + return MakeError(err, "Create") +} + +func (verify Verify) CreateBoardAccess() error { + hasAccess, err := verify.HasBoardAccess() + + if err != nil { + return MakeError(err, "CreateBoardAccess") + } + + if !hasAccess { + query := `insert into boardaccess (identifier, board) values($1, $2)` + _, err := config.DB.Exec(query, verify.Identifier, verify.Board) + + return MakeError(err, "CreateBoardAccess") + } + + return nil +} + +func (verify Verify) CreateBoardMod() error { + var pass string + var err error + + if pass, err = CreateKey(50); err != nil { + return MakeError(err, "CreateBoardMod") + } + + var code string + + query := `select code from verification where identifier=$1 and type=$2` + if err := config.DB.QueryRow(query, verify.Board, verify.Type).Scan(&code); err != nil { + return nil + } + + var ident string + + query = `select identifier from boardaccess where identifier=$1 and board=$2` + if err := config.DB.QueryRow(query, verify.Identifier, verify.Board).Scan(&ident); err != nil { + return nil + } + + if ident != verify.Identifier { + query := `insert into crossverification (verificationcode, code) values ($1, $2)` + if _, err := config.DB.Exec(query, code, pass); err != nil { + return MakeError(err, "CreateBoardMod") + } + + query = `insert into boardaccess (identifier, code, board, type) values ($1, $2, $3, $4)` + if _, err = config.DB.Exec(query, verify.Identifier, pass, verify.Board, verify.Type); err != nil { + return MakeError(err, "CreateBoardMod") + } + + config.Log.Printf("Board access - Board: %s, Identifier: %s, Code: %s\n", verify.Board, verify.Identifier, pass) + } + + return nil +} + +func (verify Verify) DeleteBoardMod() error { + var code string + + query := `select code from boardaccess where identifier=$1 and board=$1` + if err := config.DB.QueryRow(query, verify.Identifier, verify.Board).Scan(&code); err != nil { + return nil + } + + query = `delete from crossverification where code=$1` + if _, err := config.DB.Exec(query, code); err != nil { + return MakeError(err, "DeleteBoardMod") + } + + query = `delete from boardaccess where identifier=$1 and board=$2` + if _, err := config.DB.Exec(query, verify.Identifier, verify.Board); err != nil { + return MakeError(err, "DeleteBoardMod") + } + + return nil +} + +func (verify Verify) GetBoardMod() (Verify, error) { + var nVerify Verify + + query := `select code, board, type, identifier from boardaccess where identifier=$1` + if err := config.DB.QueryRow(query, verify.Identifier).Scan(&nVerify.Code, &nVerify.Board, &nVerify.Type, &nVerify.Identifier); err != nil { + return nVerify, MakeError(err, "GetBoardMod") + } + + return nVerify, nil +} + +func (verify Verify) GetCode() (Verify, error) { + var nVerify Verify + + query := `select type, identifier, code, board from boardaccess where identifier=$1 and board=$2` + if err := config.DB.QueryRow(query, verify.Identifier, verify.Board).Scan(&nVerify.Type, &nVerify.Identifier, &nVerify.Code, &nVerify.Board); err != nil { + return verify, nil + } + + return nVerify, nil +} + +func (verify Verify) HasBoardAccess() (bool, error) { + var count int + + query := `select count(*) from boardaccess where identifier=$1 and board=$2` + if err := config.DB.QueryRow(query, verify.Identifier, verify.Board).Scan(&count); err != nil { + return false, nil + } + + return true, nil +} + +func (verify Verify) SendVerification() error { + config.Log.Println("sending email") + + 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) + + msg := "From: " + from + "\n" + + "To: " + to + "\n" + + "Subject: Image Board Verification\n\n" + + body + + err := smtp.SendMail(config.SiteEmailServer+":"+config.SiteEmailPort, + smtp.PlainAuth("", from, pass, config.SiteEmailServer), + from, []string{to}, []byte(msg)) + + return MakeError(err, "SendVerification") +} + +func (verify Verify) VerifyCooldownAdd() error { + query := `insert into verficationcooldown (identifier, code) values ($1, $2)` + _, err := config.DB.Exec(query, verify.Identifier, verify.Code) + + return MakeError(err, "VerifyCooldownAdd") +} + +func BoardHasAuthType(board string, auth string) (bool, error) { + authTypes, err := GetBoardAuth(board) + + if err != nil { + return false, MakeError(err, "BoardHasAuthType") + } + + for _, e := range authTypes { + if e == auth { + return true, nil + } + } + + return false, nil +} + +func Captcha() string { + rand.Seed(time.Now().UTC().UnixNano()) + domain := "ABEFHKMNPQRSUVWXYZ#$&" + rng := 4 + newID := "" + + for i := 0; i < rng; i++ { + newID += string(domain[rand.Intn(len(domain))]) + } + + return newID +} + +func CreateNewCaptcha() error { + id := RandomID(8) + file := "public/" + id + ".png" + + for true { + if _, err := os.Stat("./" + file); err == nil { + id = RandomID(8) + file = "public/" + id + ".png" + } else { + break + } + } + + var pattern string + + captcha := Captcha() + rnd := fmt.Sprintf("%d", rand.Intn(3)) + srnd := string(rnd) + + switch srnd { + case "0": + pattern = "pattern:verticalbricks" + break + + case "1": + pattern = "pattern:verticalsaw" + break + + case "2": + pattern = "pattern:hs_cross" + break + + } + + cmd := exec.Command("convert", "-size", "200x98", pattern, "-transparent", "white", file) + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + return MakeError(err, "CreateNewCaptcha") + } + + cmd = exec.Command("convert", file, "-fill", "blue", "-pointsize", "62", "-annotate", "+0+70", captcha, "-tile", "pattern:left30", "-gravity", "center", "-transparent", "white", file) + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + return MakeError(err, "CreateNewCaptcha") + } + + rnd = fmt.Sprintf("%d", rand.Intn(24)-12) + cmd = exec.Command("convert", file, "-rotate", rnd, "-wave", "5x35", "-distort", "Arc", "20", "-wave", "2x35", "-transparent", "white", file) + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + return MakeError(err, "CreateNewCaptcha") + } + + var verification Verify + + verification.Type = "captcha" + verification.Code = captcha + verification.Identifier = file + + return verification.Create() +} + +func GetRandomCaptcha() (string, error) { + var verify string + + query := `select identifier from verification where type='captcha' order by random() limit 1` + if err := config.DB.QueryRow(query).Scan(&verify); err != nil { + return verify, MakeError(err, "GetRandomCaptcha") + } + + return verify, nil +} + +func GetCaptchaTotal() (int, error) { + var count int + + query := `select count(*) from verification where type='captcha'` + if err := config.DB.QueryRow(query).Scan(&count); err != nil { + return count, MakeError(err, "GetCaptchaTotal") + } + + return count, nil +} + +func GetCaptchaCode(verify string) (string, error) { + var code string + + query := `select code from verification where identifier=$1 limit 1` + if err := config.DB.QueryRow(query, verify).Scan(&code); err != nil { + return code, MakeError(err, "GetCaptchaCodeDB") + } + + return code, nil +} + +func DeleteCaptchaCode(verify string) error { + query := `delete from verification where identifier=$1` + _, err := config.DB.Exec(query, verify) + + if err != nil { + return MakeError(err, "DeleteCaptchaCode") + } + + err = os.Remove("./" + verify) + return MakeError(err, "DeleteCaptchaCode") +} + +func GetVerificationByCode(code string) (Verify, error) { + // TODO: this only needs to select one row. + + var verify Verify + + query := `select type, identifier, code, board from boardaccess where code=$1` + + rows, err := config.DB.Query(query, code) + if err != nil { + return verify, MakeError(err, "GetVerificationByCode") + } + + defer rows.Close() + + for rows.Next() { + if err := rows.Scan(&verify.Type, &verify.Identifier, &verify.Code, &verify.Board); err != nil { + return verify, MakeError(err, "GetVerificationByCode") + } + } + + return verify, nil +} + +func GetVerificationByEmail(email string) (Verify, error) { + var verify Verify + + query := `select type, identifier, code, board from boardaccess where identifier=$1` + if err := config.DB.QueryRow(query, email).Scan(&verify.Type, &verify.Identifier, &verify.Code, &verify.Board); err != nil { + return verify, nil + } + + return verify, nil +} + +func GetVerify(access string) (Verify, error) { + verify, err := GetVerificationByCode(access) + + if err != nil { + return verify, MakeError(err, "GetVerify") + } + + if verify.Identifier == "" { + verify, err = GetVerificationByEmail(access) + } + + return verify, MakeError(err, "GetVerify") +} + +func HasAuthCooldown(auth string) (bool, error) { + var current VerifyCooldown + var err error + + if current, err = VerifyCooldownCurrent(auth); err != nil { + return false, MakeError(err, "HasAuthCooldown") + } + + if current.Time > 0 { + return true, nil + } + + return false, nil +} + +func HasAuth(code string, board string) (bool, error) { + verify, err := GetVerificationByCode(code) + if err != nil { + return false, MakeError(err, "HasAuth") + } + + if res, err := verify.HasBoardAccess(); err == nil && (verify.Board == config.Domain || (res && verify.Board == board)) { + return true, nil + } else { + return false, MakeError(err, "HasAuth") + } + + return false, nil +} + +func IsEmailSetup() bool { + return config.SiteEmail != "" || config.SiteEmailPassword != "" || config.SiteEmailServer != "" || config.SiteEmailPort != "" +} + +func VerficationCooldown() error { + query := `select identifier, code, time from verificationcooldown` + rows, err := config.DB.Query(query) + + if err != nil { + return MakeError(err, "VerficationCooldown") + } + + defer rows.Close() + for rows.Next() { + var verify VerifyCooldown + + if err := rows.Scan(&verify.Identifier, &verify.Code, &verify.Time); err != nil { + return MakeError(err, "VerficationCooldown") + } + + nTime := verify.Time - 1 + query = `update set time=$1 where identifier=$2` + + if _, err := config.DB.Exec(query, nTime, verify.Identifier); err != nil { + return MakeError(err, "VerficationCooldown") + } + + VerficationCooldownRemove() + } + + return nil +} + +func VerficationCooldownRemove() error { + query := `delete from verificationcooldown where time < 1` + _, err := config.DB.Exec(query) + + return MakeError(err, "VerficationCooldownRemove") +} + +func VerifyCooldownCurrent(auth string) (VerifyCooldown, error) { + var current VerifyCooldown + + query := `select identifier, code, time from verificationcooldown where code=$1` + if err := config.DB.QueryRow(query, auth).Scan(¤t.Identifier, ¤t.Code, ¤t.Time); err != nil { + query := `select identifier, code, time from verificationcooldown where identifier=$1` + if err := config.DB.QueryRow(query, auth).Scan(¤t.Identifier, ¤t.Code, ¤t.Time); err != nil { + return current, nil + } + + return current, nil + } + + return current, nil +} + +func GetPasswordFromSession(ctx *fiber.Ctx) (string, string) { + cookie := ctx.Cookies("session_token") + parts := strings.Split(cookie, "|") + + if len(parts) > 1 { + return parts[0], parts[1] + } + + return "", "" +} + +func MakeCaptchas(total int) error { + dbtotal, err := GetCaptchaTotal() + + if err != nil { + return MakeError(err, "MakeCaptchas") + } + + difference := total - dbtotal + + for i := 0; i < difference; i++ { + if err := CreateNewCaptcha(); err != nil { + return MakeError(err, "MakeCaptchas") + } + } + + return nil +} diff --git a/webfinger/comm.go b/webfinger/comm.go deleted file mode 100644 index ed20779..0000000 --- a/webfinger/comm.go +++ /dev/null @@ -1,88 +0,0 @@ -package webfinger - -import ( - "encoding/json" - "errors" - "io/ioutil" - "net/http" - "regexp" - - "github.com/FChannel0/FChannel-Server/activitypub" - "github.com/FChannel0/FChannel-Server/config" - "github.com/FChannel0/FChannel-Server/util" -) - -// TODO: All of these functions in this file I don't know where to place so they'll remain here until I find a better place for them. - -func GetActorCollection(collection string) (activitypub.Collection, error) { - var nCollection activitypub.Collection - - if collection == "" { - return nCollection, errors.New("invalid collection") - } - - req, err := http.NewRequest("GET", collection, nil) - if err != nil { - return nCollection, err - } - - req.Header.Set("Accept", config.ActivityStreams) - - resp, err := util.RouteProxy(req) - if err != nil { - return nCollection, err - } - - defer resp.Body.Close() - - if resp.StatusCode == 200 { - body, _ := ioutil.ReadAll(resp.Body) - if len(body) > 0 { - if err := json.Unmarshal(body, &nCollection); err != nil { - return nCollection, err - } - } - } - - return nCollection, nil -} - -func GetCollectionFromReq(path string) (activitypub.Collection, error) { - var respCollection activitypub.Collection - - req, err := http.NewRequest("GET", path, nil) - if err != nil { - return respCollection, err - } - - req.Header.Set("Accept", config.ActivityStreams) - - resp, err := util.RouteProxy(req) - if err != nil { - return respCollection, err - } - defer resp.Body.Close() - - body, _ := ioutil.ReadAll(resp.Body) - - err = json.Unmarshal(body, &respCollection) - return respCollection, err -} - -func GetActorsFollowFromName(actor activitypub.Actor, name string) ([]string, error) { - var followingActors []string - follow, err := GetActorCollection(actor.Following) - if err != nil { - return followingActors, err - } - - re := regexp.MustCompile("\\w+?$") - - for _, e := range follow.Items { - if re.FindString(e.Id) == name { - followingActors = append(followingActors, e.Id) - } - } - - return followingActors, nil -} diff --git a/webfinger/util.go b/webfinger/util.go index 0fc4948..fa8a625 100644 --- a/webfinger/util.go +++ b/webfinger/util.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/FChannel0/FChannel-Server/activitypub" + "github.com/FChannel0/FChannel-Server/util" ) var Boards []Board @@ -38,6 +39,7 @@ func (a BoardSortAsc) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func GetActorByNameFromBoardCollection(name string) activitypub.Actor { var actor activitypub.Actor + boards, _ := GetBoardCollection() for _, e := range boards { if e.Actor.Name == name { @@ -50,17 +52,21 @@ func GetActorByNameFromBoardCollection(name string) activitypub.Actor { func GetBoardCollection() ([]Board, error) { var collection []Board + for _, e := range FollowingBoards { var board Board + boardActor, err := activitypub.GetActorFromDB(e.Id) + if err != nil { - return collection, err + return collection, util.MakeError(err, "GetBoardCollection") } if boardActor.Id == "" { - boardActor, err = FingerActor(e.Id) + boardActor, err = activitypub.FingerActor(e.Id) + if err != nil { - return collection, err + return collection, util.MakeError(err, "GetBoardCollection") } } @@ -69,6 +75,7 @@ func GetBoardCollection() ([]Board, error) { board.Location = "/" + boardActor.Name board.Actor = boardActor board.Restricted = boardActor.Restricted + collection = append(collection, board) } @@ -78,12 +85,12 @@ func GetBoardCollection() ([]Board, error) { } func GetActorFromPath(location string, prefix string) (activitypub.Actor, error) { + var actor string + pattern := fmt.Sprintf("%s([^/\n]+)(/.+)?", prefix) re := regexp.MustCompile(pattern) match := re.FindStringSubmatch(location) - var actor string - if len(match) < 1 { actor = "/" } else { @@ -97,8 +104,9 @@ func GetActorFromPath(location string, prefix string) (activitypub.Actor, error) var nActor activitypub.Actor nActor, err := activitypub.GetActorByNameFromDB(actor) + if err != nil { - return nActor, err + return nActor, util.MakeError(err, "GetActorFromPath") } if nActor.Id == "" { @@ -107,3 +115,19 @@ func GetActorFromPath(location string, prefix string) (activitypub.Actor, error) return nActor, nil } + +func StartupArchive() error { + for _, e := range FollowingBoards { + actor, err := activitypub.GetActorFromDB(e.Id) + + if err != nil { + return util.MakeError(err, "StartupArchive") + } + + if err := actor.ArchivePosts(); err != nil { + return util.MakeError(err, "StartupArchive") + } + } + + return nil +} diff --git a/webfinger/webfinger.go b/webfinger/webfinger.go deleted file mode 100644 index 45650ea..0000000 --- a/webfinger/webfinger.go +++ /dev/null @@ -1,313 +0,0 @@ -package webfinger - -import ( - "encoding/json" - "io/ioutil" - "net/http" - "regexp" - "strings" - "time" - - "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 := activitypub.GetActorAndInstance(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 := activitypub.GetActorAndInstance(path) - - if actor == "" && instance == "" { - return nActor, nil - } - - 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) - - json.Unmarshal(body, &nActor) - // if err := json.Unmarshal(body, &nActor); err != nil { - // return nActor, err - // } - - ActorCache[actor+"@"+instance] = nActor - } - } - - return nActor, nil -} - -func FingerRequest(actor string, instance string) (*http.Response, error) { - acct := "acct:" + actor + "@" + instance - - // TODO: respect https - req, _ := 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, nil - } - - var finger Webfinger - - if resp.StatusCode == 200 { - defer resp.Body.Close() - - body, _ := ioutil.ReadAll(resp.Body) - - json.Unmarshal(body, &finger) - // 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, _ := http.NewRequest("GET", e.Href, nil) - // if err != nil { - // return resp, err - // } - - req.Header.Set("Accept", config.ActivityStreams) - - resp, _ := util.RouteProxy(req) - return resp, nil - } - } - } - - return resp, nil -} - -func CheckValidActivity(id string) (activitypub.Collection, bool, error) { - var respCollection activitypub.Collection - - re := regexp.MustCompile(`.+\.onion(.+)?`) - if re.MatchString(id) { - id = strings.Replace(id, "https", "http", 1) - } - - req, err := http.NewRequest("GET", id, nil) - if err != nil { - return respCollection, false, err - } - - req.Header.Set("Accept", config.ActivityStreams) - - resp, err := util.RouteProxy(req) - if err != nil { - return respCollection, false, err - } - defer resp.Body.Close() - - body, _ := ioutil.ReadAll(resp.Body) - - if err := json.Unmarshal(body, &respCollection); err != nil { - return respCollection, false, err - } - - if respCollection.AtContext.Context == "https://www.w3.org/ns/activitystreams" && respCollection.OrderedItems[0].Id != "" { - return respCollection, true, nil - } - - return respCollection, false, nil -} - -func CreateActivity(activityType string, obj activitypub.ObjectBase) (activitypub.Activity, error) { - var newActivity activitypub.Activity - - actor, err := FingerActor(obj.Actor) - if err != nil { - return newActivity, err - } - - newActivity.AtContext.Context = "https://www.w3.org/ns/activitystreams" - newActivity.Type = activityType - newActivity.Published = obj.Published - newActivity.Actor = &actor - newActivity.Object = &obj - - for _, e := range obj.To { - if obj.Actor != e { - newActivity.To = append(newActivity.To, e) - } - } - - for _, e := range obj.Cc { - if obj.Actor != e { - newActivity.Cc = append(newActivity.Cc, e) - } - } - - return newActivity, nil -} - -func AddFollowersToActivity(activity activitypub.Activity) (activitypub.Activity, error) { - activity.To = append(activity.To, activity.Actor.Id) - - for _, e := range activity.To { - aFollowers, err := GetActorCollection(e + "/followers") - if err != nil { - return activity, err - } - - for _, k := range aFollowers.Items { - activity.To = append(activity.To, k.Id) - } - } - - var nActivity activitypub.Activity - - for _, e := range activity.To { - var alreadyTo = false - for _, k := range nActivity.To { - if e == k || e == activity.Actor.Id { - alreadyTo = true - } - } - - if !alreadyTo { - nActivity.To = append(nActivity.To, e) - } - } - - activity.To = nActivity.To - - return activity, nil -} - -func IsValidActor(id string) (activitypub.Actor, bool, error) { - actor, err := FingerActor(id) - return actor, actor.Id != "", err -} - -func AddInstanceToIndexDB(actor string) error { - // TODO: completely disabling this until it is actually reasonable to turn it on - // only actually allow this when it more or less works, i.e. can post, make threads, manage boards, etc - return nil - - //sleep to be sure the webserver is fully initialized - //before making finger request - time.Sleep(15 * time.Second) - - nActor, err := FingerActor(actor) - if err != nil { - return err - } - - if nActor.Id == "" { - return nil - } - - // TODO: maybe allow different indexes? - - obj := activitypub.ObjectBase{Id: "https://fchan.xyz/followers"} - followers, err := obj.GetCollection() - if err != nil { - return err - } - - var alreadyIndex = false - for _, e := range followers.Items { - if e.Id == nActor.Id { - alreadyIndex = true - } - } - - if !alreadyIndex { - actor := activitypub.Actor{Id: "https://fchan.xyz"} - return actor.AddFollower(nActor.Id) - } - - return nil -} - -func MakeActivityFollowingReq(w http.ResponseWriter, r *http.Request, activity activitypub.Activity) (bool, error) { - actor, err := GetActor(activity.Object.Id) - if err != nil { - return false, err - } - - req, err := http.NewRequest("POST", actor.Inbox, nil) - if err != nil { - return false, err - } - - resp, err := util.RouteProxy(req) - if err != nil { - return false, err - } - defer resp.Body.Close() - - body, _ := ioutil.ReadAll(resp.Body) - - var respActivity activitypub.Activity - - err = json.Unmarshal(body, &respActivity) - return respActivity.Type == "Accept", err -} -- cgit v1.2.3