Finished Prototype
This commit is contained in:
parent
ea54a27796
commit
6de476260d
30 changed files with 2189 additions and 0 deletions
11
1602787120 Baseline Measurement.csv
Normal file
11
1602787120 Baseline Measurement.csv
Normal file
|
@ -0,0 +1,11 @@
|
|||
Iteration,Start,End
|
||||
1,1602787120,1602787240
|
||||
2,1602787240,1602787360
|
||||
3,1602787360,1602787480
|
||||
4,1602787480,1602787600
|
||||
5,1602787600,1602787720
|
||||
6,1602787720,1602787840
|
||||
7,1602787840,1602787961
|
||||
8,1602787961,1602788081
|
||||
9,1602788081,1602788201
|
||||
10,1602788201,1602788321
|
|
36
config/config.go
Normal file
36
config/config.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var ServerName string = "Hoernschen's ActivityPub Server"
|
||||
var Version string = "0.1"
|
||||
|
||||
var Homeserver string
|
||||
var Port string
|
||||
|
||||
var PrivateKey []byte
|
||||
var PublicKey []byte
|
||||
var KeyId string
|
||||
var VerifyKeys map[string]map[string][]byte
|
||||
|
||||
var Packetloss int
|
||||
var UnavailableTill int64
|
||||
var Consensus bool
|
||||
var AuthentificationCheck bool
|
||||
var Signing bool
|
||||
var Encryption bool
|
||||
var HttpString string
|
||||
|
||||
//var BackoffPolicy *backoff.Exponential
|
||||
|
||||
func SetDefaultParams() {
|
||||
Packetloss = 0.0
|
||||
UnavailableTill = time.Now().Unix()
|
||||
Consensus = true
|
||||
AuthentificationCheck = true
|
||||
Signing = true
|
||||
Encryption = true
|
||||
HttpString = "https"
|
||||
}
|
69
config/configController.go
Normal file
69
config/configController.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/utils/database"
|
||||
)
|
||||
|
||||
type SetParamBody struct {
|
||||
Packetloss int `json:"packetloss,omitempty"`
|
||||
UnavailableTill int64 `json:"unavailableTill,omitempty"`
|
||||
Consensus bool `json:"consensus,omitempty"`
|
||||
AuthentificationCheck bool `json:"authentificationCheck,omitempty"`
|
||||
Signing bool `json:"signing,omitempty"`
|
||||
Encryption bool `json:"encryption,omitempty"`
|
||||
}
|
||||
|
||||
func SetParams(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||
request := SetParamBody{}
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err := decoder.Decode(&request)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Could not parse JSON: %s", err)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Packetloss = request.Packetloss
|
||||
UnavailableTill = request.UnavailableTill
|
||||
Consensus = request.Consensus
|
||||
AuthentificationCheck = request.AuthentificationCheck
|
||||
Signing = request.Signing
|
||||
Encryption = request.Signing
|
||||
HttpString = "https"
|
||||
if !Encryption {
|
||||
HttpString = "http"
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func Reset(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||
|
||||
/*
|
||||
if err := device.InitServerSigningKey(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
config.VerifyKeys = make(map[string]map[string][]byte)
|
||||
*/
|
||||
|
||||
database.DB.Close()
|
||||
os.Remove("sqlite.db")
|
||||
|
||||
if err := database.InitDB("sqlite.db"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
SetDefaultParams()
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
13
entities/activity/activity.go
Normal file
13
entities/activity/activity.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package activity
|
||||
|
||||
import "git.nutfactory.org/hoernschen/ActivityPub/entities/object"
|
||||
|
||||
type Activity struct {
|
||||
Context string `json:"@context,omitempty"`
|
||||
Id string `json:"id,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Actor string `json:"actor,omitempty"`
|
||||
Object *object.Object `json:"object,omitempty"`
|
||||
Published int64 `json:"published,omitempty"`
|
||||
To string `json:"to,omitempty"`
|
||||
}
|
29
entities/activity/activityController.go
Normal file
29
entities/activity/activityController.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package activity
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/entities/object"
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/utils"
|
||||
)
|
||||
|
||||
func New(id string, actorOfActivity string, objectOfActivity *object.Object, userId string) (newActivity *Activity) {
|
||||
published := objectOfActivity.Published
|
||||
to := objectOfActivity.To
|
||||
if published == 0 {
|
||||
published = time.Now().Unix()
|
||||
}
|
||||
if to == "" {
|
||||
to = utils.GenerateFollowersUrl(userId)
|
||||
}
|
||||
newActivity = &Activity{
|
||||
Context: utils.GetDefaultContext(),
|
||||
Id: id,
|
||||
Type: "Create",
|
||||
Actor: actorOfActivity,
|
||||
Object: objectOfActivity,
|
||||
Published: published,
|
||||
To: to,
|
||||
}
|
||||
return
|
||||
}
|
15
entities/actor/actor.go
Normal file
15
entities/actor/actor.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package actor
|
||||
|
||||
type Actor struct {
|
||||
Context string `json:"@context,omitempty"`
|
||||
Id string `json:"id,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
PreferredUsername string `json:"preferredUsername,omitempty"`
|
||||
Summary string `json:"summary,omitempty"`
|
||||
Inbox string `json:"inbox,omitempty"`
|
||||
Outbox string `json:"outbox,omitempty"`
|
||||
Followers string `json:"followers,omitempty"`
|
||||
Following string `json:"following,omitempty"`
|
||||
Liked string `json:"liked,omitempty"`
|
||||
}
|
75
entities/actor/actorController.go
Normal file
75
entities/actor/actorController.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
package actor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/config"
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/utils"
|
||||
)
|
||||
|
||||
func New(userId string) (newActor *Actor) {
|
||||
id := utils.GenerateProfileUrl(userId)
|
||||
newActor = GenerateProfile(id, userId)
|
||||
return
|
||||
}
|
||||
|
||||
func GenerateProfile(id string, userId string) (profile *Actor) {
|
||||
profile = &Actor{
|
||||
Context: utils.GetDefaultContext(),
|
||||
Id: id,
|
||||
Name: userId,
|
||||
PreferredUsername: userId,
|
||||
Type: "Person",
|
||||
Inbox: utils.GenerateInboxUrl(userId),
|
||||
Outbox: utils.GenerateOutboxUrl(userId),
|
||||
Followers: utils.GenerateFollowersUrl(userId),
|
||||
Following: utils.GenerateFollowingUrl(userId),
|
||||
Liked: utils.GenerateLikedUrl(userId),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func GetProfileHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", utils.GetContentTypeString())
|
||||
foundActor, err := ReadActor(fmt.Sprintf("%s://%s%s", config.HttpString, config.Homeserver, r.URL.RequestURI()))
|
||||
if foundActor == nil || err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Actor not found: %s", err)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
profile := GenerateProfile(foundActor.Id, foundActor.Name)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if err := json.NewEncoder(w).Encode(profile); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func GetProfile(actorId string) (err error, profile *Actor) {
|
||||
client := &http.Client{Timeout: 2 * time.Second}
|
||||
req, err := http.NewRequest(http.MethodGet, actorId, bytes.NewBuffer(nil))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req.Header["Content-Type"] = []string{utils.GetContentTypeString()}
|
||||
r, err := client.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if r.StatusCode != http.StatusOK {
|
||||
err = utils.HandleHTTPError(r)
|
||||
return
|
||||
} else {
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err = decoder.Decode(&profile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
76
entities/actor/actorDatabaseConnector.go
Normal file
76
entities/actor/actorDatabaseConnector.go
Normal file
|
@ -0,0 +1,76 @@
|
|||
package actor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/utils/database"
|
||||
)
|
||||
|
||||
func CreateActor(actor *Actor) (err error) {
|
||||
sqlStmt := fmt.Sprintf(`INSERT INTO actor
|
||||
(id, type, name, preferredUsername, summary, inbox, outbox, followers, following, liked)
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
||||
|
||||
tx, err := database.DB.Begin()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
stmt, err := tx.Prepare(sqlStmt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer stmt.Close()
|
||||
_, err = stmt.Exec(
|
||||
actor.Id,
|
||||
actor.Type,
|
||||
actor.Name,
|
||||
actor.PreferredUsername,
|
||||
actor.Summary,
|
||||
actor.Inbox,
|
||||
actor.Outbox,
|
||||
actor.Followers,
|
||||
actor.Following,
|
||||
actor.Liked,
|
||||
)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
tx.Commit()
|
||||
return
|
||||
}
|
||||
|
||||
func ReadActor(id string) (foundActor *Actor, err error) {
|
||||
queryStmt := fmt.Sprintf(`SELECT id, type, name, preferredUsername, summary, inbox, outbox, followers, following, liked
|
||||
FROM actor
|
||||
WHERE id = '%s'`, id)
|
||||
|
||||
rows, err := database.DB.Query(queryStmt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
if rows.Next() {
|
||||
foundActor = &Actor{}
|
||||
err = rows.Scan(
|
||||
&foundActor.Id,
|
||||
&foundActor.Type,
|
||||
&foundActor.Name,
|
||||
&foundActor.PreferredUsername,
|
||||
&foundActor.Summary,
|
||||
&foundActor.Inbox,
|
||||
&foundActor.Outbox,
|
||||
&foundActor.Followers,
|
||||
&foundActor.Following,
|
||||
&foundActor.Liked,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
5
entities/collection/collection.go
Normal file
5
entities/collection/collection.go
Normal file
|
@ -0,0 +1,5 @@
|
|||
package collection
|
||||
|
||||
type OutboxResponse struct {
|
||||
Location string `json:"Location,omitempty"`
|
||||
}
|
350
entities/collection/collectionController.go
Normal file
350
entities/collection/collectionController.go
Normal file
|
@ -0,0 +1,350 @@
|
|||
package collection
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/config"
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/entities/activity"
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/entities/actor"
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/entities/object"
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/entities/user"
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/utils"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func PostInboxHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", utils.GetContentTypeString())
|
||||
newActivity := &activity.Activity{}
|
||||
err := utils.CheckRequest(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(err); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
token, err := utils.GetAccessToken(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(err); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if token == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode("Missing Token"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
actorName := vars["actorName"]
|
||||
if actorName == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode("Missing Actor"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err = decoder.Decode(&newActivity)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Could not parse JSON: %s", err)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if newActivity.Type == "Follow" {
|
||||
err = CreateCollectionObject(utils.GenerateFollowersUrl(actorName), newActivity.Actor)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Database Error Creating Collection Object: %s", err)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
log.Printf("%s follows %s", newActivity.Actor, newActivity.To)
|
||||
} else if newActivity.Type == "Create" {
|
||||
foundObject, err := object.ReadObject(newActivity.Object.Id)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Database Error Reading Object: %s", err)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if foundObject == nil {
|
||||
_ = object.CreateObject(newActivity.Object)
|
||||
/*
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Database Error Creating Object: %s", err)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
*/
|
||||
}
|
||||
_ = CreateCollectionObject(utils.GenerateInboxUrl(actorName), newActivity.Object.Id)
|
||||
/*
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Database Error Creating Collection Object: %s", err)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
*/
|
||||
log.Printf("%s to %s: %s", newActivity.Actor, newActivity.To, newActivity.Object.Content)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode("Unsupported Activity Type"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func PostOutboxHandler(w http.ResponseWriter, r *http.Request) {
|
||||
packetLossNumber := rand.Intn(100)
|
||||
if packetLossNumber > config.Packetloss {
|
||||
w.Header().Set("Content-Type", utils.GetContentTypeString())
|
||||
newActivity := &activity.Activity{}
|
||||
err := utils.CheckRequest(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(err); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
token, err := utils.GetAccessToken(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(err); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
foundUser, err := user.ReadUserFromToken(token)
|
||||
if err != nil || foundUser == nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Unknown Token: %s", err)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
buf.ReadFrom(r.Body)
|
||||
err = json.Unmarshal(buf.Bytes(), newActivity)
|
||||
if err != nil || newActivity.Type == "Note" {
|
||||
postedObject := &object.Object{}
|
||||
err = json.Unmarshal(buf.Bytes(), postedObject)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Could not parse JSON: %s")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
var newObject *object.Object
|
||||
if postedObject.Id == "" {
|
||||
err, newObject = object.New(
|
||||
postedObject.Type,
|
||||
postedObject.AttributedTo,
|
||||
postedObject.Content,
|
||||
postedObject.Published,
|
||||
postedObject.To,
|
||||
foundUser.Id,
|
||||
)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Error Creating Object: %s")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
err = object.CreateObject(newObject)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Database Error Creating Object: %s")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
} else {
|
||||
newObject = postedObject
|
||||
}
|
||||
newActivity = activity.New(
|
||||
newObject.Id,
|
||||
newObject.AttributedTo,
|
||||
newObject,
|
||||
foundUser.Id,
|
||||
)
|
||||
}
|
||||
|
||||
var recipients []string
|
||||
if newActivity.Type == "Follow" {
|
||||
err = CreateCollectionObject(utils.GenerateFollowingUrl(foundUser.Id), newActivity.To)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Database Error Creating Collection Object: %s")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
recipients = append(recipients, newActivity.To)
|
||||
} else if newActivity.Type == "Create" {
|
||||
err = CreateCollectionObject(utils.GenerateOutboxUrl(foundUser.Id), newActivity.Id)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Database Error Creating Collection Object: %s")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
err, recipients = GetReciepientsForActivity(newActivity)
|
||||
}
|
||||
|
||||
for _, recipient := range recipients {
|
||||
log.Printf("Send Activity to Recipient %s", recipient)
|
||||
|
||||
operation := func() error {
|
||||
return SendActivity(newActivity, recipient, token)
|
||||
}
|
||||
notify := func(err error, duration time.Duration) {
|
||||
log.Printf("Error Sending Activity, retrying in %ss: %s", duration/1000000000, err)
|
||||
}
|
||||
backoff.RetryNotify(operation, backoff.WithMaxRetries(backoff.NewExponentialBackOff(), 16), notify)
|
||||
|
||||
//go retryActivity(newActivity, recipient, token)
|
||||
}
|
||||
|
||||
response := OutboxResponse{
|
||||
Location: newActivity.Id,
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func retryActivity(activityToSend *activity.Activity, recipient string, token string) (err error) {
|
||||
b, cancel := config.BackoffPolicy.Start(context.Background())
|
||||
defer cancel()
|
||||
|
||||
for backoff.Continue(b) {
|
||||
err := SendActivity(activityToSend, recipient, token)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
err = errors.New("Not able to send activity")
|
||||
return
|
||||
}
|
||||
*/
|
||||
|
||||
func GetReciepientsForActivity(activityToSend *activity.Activity) (err error, recipientsWithoutDuplicates []string) {
|
||||
recipients := []string{}
|
||||
if strings.Contains(activityToSend.To, config.Homeserver) {
|
||||
var foundActor *actor.Actor
|
||||
foundActor, err = actor.ReadActor(activityToSend.To)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if foundActor == nil {
|
||||
var foundCollectionObjects []string
|
||||
foundCollectionObjects, err = ReadCollectionObjects(activityToSend.To)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(foundCollectionObjects) <= 0 {
|
||||
err = errors.New("No Recipients")
|
||||
return
|
||||
}
|
||||
recipients = append(recipients, foundCollectionObjects...)
|
||||
} else {
|
||||
recipients = append(recipients, foundActor.Id)
|
||||
}
|
||||
} else {
|
||||
// Not Implemented
|
||||
}
|
||||
recipientsWithoutDuplicates = utils.RemoveDuplicates(recipients)
|
||||
return
|
||||
}
|
||||
|
||||
func SendActivity(activityToSend *activity.Activity, recipient string, token string) (err error) {
|
||||
if strings.Contains(recipient, config.Homeserver) {
|
||||
if activityToSend.Type == "Follow" {
|
||||
followers := fmt.Sprintf("%sfollowers/", activityToSend.To)
|
||||
err = CreateCollectionObject(followers, activityToSend.Actor)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.Printf("%s follows %s", activityToSend.Actor, activityToSend.To)
|
||||
} else if activityToSend.Type == "Create" {
|
||||
id := activityToSend.Id
|
||||
if activityToSend.Object != nil {
|
||||
id = activityToSend.Object.Id
|
||||
}
|
||||
inbox := fmt.Sprintf("%sinbox", recipient)
|
||||
err = CreateCollectionObject(inbox, id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.Printf("%s to %s: %s", activityToSend.Actor, recipient, activityToSend.Object.Content)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
activityToSend.To = recipient
|
||||
var profile *actor.Actor
|
||||
err, profile = actor.GetProfile(recipient)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var reqBody []byte
|
||||
reqBody, err = json.Marshal(activityToSend)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
client := &http.Client{Timeout: 2 * time.Second}
|
||||
var req *http.Request
|
||||
log.Printf("Inbox: %s", profile.Inbox)
|
||||
req, err = http.NewRequest(http.MethodPost, profile.Inbox, bytes.NewBuffer(reqBody))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req.Header["Content-Type"] = []string{utils.GetContentTypeString()}
|
||||
req.Header["Authorization"] = []string{fmt.Sprintf("Bearer %s")}
|
||||
var res *http.Response
|
||||
res, err = client.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
err = utils.HandleHTTPError(res)
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
62
entities/collection/collectionDatabaseConnector.go
Normal file
62
entities/collection/collectionDatabaseConnector.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
package collection
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/utils/database"
|
||||
)
|
||||
|
||||
func CreateCollectionObject(collectionId string, objectId string) (err error) {
|
||||
sqlStmt := fmt.Sprintf(`INSERT INTO collectionObject
|
||||
(collectionId, objectId)
|
||||
VALUES
|
||||
(?, ?)`)
|
||||
|
||||
tx, err := database.DB.Begin()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
stmt, err := tx.Prepare(sqlStmt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
_, err = stmt.Exec(
|
||||
collectionId,
|
||||
objectId,
|
||||
)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
tx.Commit()
|
||||
return
|
||||
}
|
||||
|
||||
func ReadCollectionObjects(collectionId string) (collectionObjects []string, err error) {
|
||||
queryStmt := fmt.Sprintf(`SELECT objectId
|
||||
FROM collectionObject
|
||||
WHERE collectionId = '%s'`, collectionId)
|
||||
|
||||
rows, err := database.DB.Query(queryStmt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var collectionObject string
|
||||
err = rows.Scan(
|
||||
&collectionObject,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
collectionObjects = append(collectionObjects, collectionObject)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
11
entities/object/object.go
Normal file
11
entities/object/object.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
package object
|
||||
|
||||
type Object struct {
|
||||
Context string `json:"@context,omitempty"`
|
||||
Id string `json:"id,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
AttributedTo string `json:"attributedTo,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Published int64 `json:"published,omitempty"`
|
||||
To string `json:"to,omitempty"`
|
||||
}
|
49
entities/object/objectController.go
Normal file
49
entities/object/objectController.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/config"
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/utils"
|
||||
)
|
||||
|
||||
func New(objectType string, attributedTo string, content string, published int64, to string, userId string) (err error, newObject *Object) {
|
||||
err, id := utils.CreateUUID()
|
||||
url := fmt.Sprintf("%s://%s/%s/posts/%s", config.HttpString, config.Homeserver, userId, id)
|
||||
if published == 0 {
|
||||
published = time.Now().Unix()
|
||||
}
|
||||
if attributedTo == "" {
|
||||
attributedTo = utils.GenerateProfileUrl(userId)
|
||||
}
|
||||
newObject = &Object{
|
||||
Context: utils.GetDefaultContext(),
|
||||
Id: url,
|
||||
Type: objectType,
|
||||
AttributedTo: attributedTo,
|
||||
Content: content,
|
||||
Published: published,
|
||||
To: to,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func GetPostHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", utils.GetContentTypeString())
|
||||
foundObject, err := ReadObject(r.URL.RequestURI())
|
||||
if foundObject == nil || err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Post not found: %s", err)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
foundObject.Context = utils.GetDefaultContext()
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if err := json.NewEncoder(w).Encode(foundObject); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
70
entities/object/objectDatabaseConnector.go
Normal file
70
entities/object/objectDatabaseConnector.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/utils/database"
|
||||
)
|
||||
|
||||
func CreateObject(object *Object) (err error) {
|
||||
sqlStmt := fmt.Sprintf(`INSERT INTO object
|
||||
(id, type, attributedTo, content, published, toActor)
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?, ?)`)
|
||||
|
||||
tx, err := database.DB.Begin()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
stmt, err := tx.Prepare(sqlStmt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
_, err = stmt.Exec(
|
||||
object.Id,
|
||||
object.Type,
|
||||
object.AttributedTo,
|
||||
object.Content,
|
||||
object.Published,
|
||||
object.To,
|
||||
)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
tx.Commit()
|
||||
return
|
||||
}
|
||||
|
||||
func ReadObject(id string) (foundObject *Object, err error) {
|
||||
queryStmt := fmt.Sprintf(`SELECT id, type, attributedTo, content, published, toActor
|
||||
FROM object
|
||||
WHERE id = '%s'`, id)
|
||||
|
||||
rows, err := database.DB.Query(queryStmt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
if rows.Next() {
|
||||
foundObject = &Object{}
|
||||
err = rows.Scan(
|
||||
&foundObject.Id,
|
||||
&foundObject.Type,
|
||||
&foundObject.AttributedTo,
|
||||
&foundObject.Content,
|
||||
&foundObject.Published,
|
||||
&foundObject.To,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
19
entities/user/user.go
Normal file
19
entities/user/user.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package user
|
||||
|
||||
type User struct {
|
||||
Id string `json:"id,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Actor string `json:"actor,omitempty"`
|
||||
Sessions []string `json:"sessions,omitempty"`
|
||||
}
|
||||
|
||||
type RegisterRequest struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
}
|
||||
|
||||
type RegisterResponse struct {
|
||||
UserId string `json:"user_id,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
Actor string `json:"actor,omitempty"`
|
||||
}
|
103
entities/user/userController.go
Normal file
103
entities/user/userController.go
Normal file
|
@ -0,0 +1,103 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/entities/actor"
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/utils"
|
||||
)
|
||||
|
||||
func New(id string, password string) (err error, newUser *User) {
|
||||
err, hashedPassword := utils.Hash([]byte(password))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
newUser = &User{
|
||||
Id: id,
|
||||
Password: hashedPassword,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func RegisterHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||
request := RegisterRequest{}
|
||||
err := utils.CheckRequest(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(err); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err = decoder.Decode(&request)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode("Could not parse JSON: %s"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
err, newUser := New(request.Username, request.Password)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Error creating User Object: %s")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
foundUser, err := ReadUser(newUser.Id)
|
||||
if foundUser != nil || err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Username already in use: %s", err)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
newActor := actor.New(newUser.Id)
|
||||
err = actor.CreateActor(newActor)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Database Error Creating Actor: %s")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
newUser.Actor = newActor.Id
|
||||
err = CreateUser(newUser)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode("Database Error Creating User: %s"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
err, token := utils.CreateToken()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode("Error Creating Token: %s"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
err = CreateSession(newUser.Id, token)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode("Error Creating Session: %s"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
response := RegisterResponse{
|
||||
UserId: newUser.Id,
|
||||
Token: token,
|
||||
Actor: newActor.Id,
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
132
entities/user/userDatabaseConnector.go
Normal file
132
entities/user/userDatabaseConnector.go
Normal file
|
@ -0,0 +1,132 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/utils/database"
|
||||
)
|
||||
|
||||
func CreateUser(user *User) (err error) {
|
||||
sqlStmt := fmt.Sprintf(`INSERT INTO user
|
||||
(id, password, actor)
|
||||
VALUES
|
||||
(?, ?, ?)`)
|
||||
|
||||
tx, err := database.DB.Begin()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
stmt, err := tx.Prepare(sqlStmt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
_, err = stmt.Exec(user.Id, user.Password, user.Actor)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
tx.Commit()
|
||||
return
|
||||
}
|
||||
|
||||
func CreateSession(userId string, token string) (err error) {
|
||||
sqlStmt := fmt.Sprintf(`INSERT INTO session
|
||||
(token, userId)
|
||||
VALUES
|
||||
(?, ?)`)
|
||||
|
||||
tx, err := database.DB.Begin()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
stmt, err := tx.Prepare(sqlStmt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
_, err = stmt.Exec(token, userId)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
tx.Commit()
|
||||
return
|
||||
}
|
||||
|
||||
func ReadUser(id string) (foundUser *User, err error) {
|
||||
queryStmt := fmt.Sprintf(`SELECT id, password, actor
|
||||
FROM user
|
||||
WHERE id = '%s'`, id)
|
||||
|
||||
rows, err := database.DB.Query(queryStmt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
if rows.Next() {
|
||||
foundUser = &User{}
|
||||
err = rows.Scan(&foundUser.Id, &foundUser.Password, &foundUser.Actor)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
foundUser.Sessions, err = ReadSessionsForUser(foundUser.Id)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ReadUserFromToken(token string) (foundUser *User, err error) {
|
||||
queryStmt := fmt.Sprintf(`SELECT u.id, u.password, u.actor
|
||||
FROM user as u
|
||||
join session as s on u.id = s.userId
|
||||
WHERE s.token = '%s'`, token)
|
||||
|
||||
rows, err := database.DB.Query(queryStmt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
if rows.Next() {
|
||||
foundUser = &User{}
|
||||
err = rows.Scan(&foundUser.Id, &foundUser.Password, &foundUser.Actor)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
foundUser.Sessions, err = ReadSessionsForUser(foundUser.Id)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ReadSessionsForUser(userId string) (sessions []string, err error) {
|
||||
queryStmt := fmt.Sprintf(`SELECT token
|
||||
FROM session
|
||||
WHERE userId = '%s'`, userId)
|
||||
|
||||
rows, err := database.DB.Query(queryStmt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var session string
|
||||
err = rows.Scan(&session)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sessions = append(sessions, session)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
10
go.mod
Normal file
10
go.mod
Normal file
|
@ -0,0 +1,10 @@
|
|||
module git.nutfactory.org/hoernschen/ActivityPub
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/cenkalti/backoff/v4 v4.1.0
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/lestrrat-go/backoff v1.0.0
|
||||
github.com/mattn/go-sqlite3 v1.14.4
|
||||
)
|
55
go.sum
Normal file
55
go.sum
Normal file
|
@ -0,0 +1,55 @@
|
|||
git.nutfactory.org/hoernschen/Matrix v0.0.0-20201012141628-da9196f38986 h1:kR6rjqDOGSfjWYQqTiy1HGLrAnbZvAwSkum4DMzj+5U=
|
||||
git.nutfactory.org/hoernschen/Matrix v0.0.0-20201012141628-da9196f38986/go.mod h1:ewiecRT3bLcsnjp5kKuEXOrOGk/0nojoJFNnGaAyGXw=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/cenkalti/backoff v1.1.0 h1:QnvVp8ikKCDWOsFheytRCoYWYPO/ObCTBGxT19Hc+yE=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/cenkalti/backoff/v4 v4.1.0 h1:c8LkOFQTzuO0WBM/ae5HdGQuZPfPxp7lqBRwQRm4fSc=
|
||||
github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lestrrat-go/backoff v1.0.0 h1:nR+UgAhdhwfw2i+xznuHRlj81oMYa7u3lXun0xcsXUU=
|
||||
github.com/lestrrat-go/backoff v1.0.0/go.mod h1:c7OnDlnHsFXbH1vyIS8+txH+THcc+QFlSQTrJVe4EIM=
|
||||
github.com/mattn/go-sqlite3 v1.14.3 h1:j7a/xn1U6TKA/PHHxqZuzh64CdtRc7rU9M+AvkOl5bA=
|
||||
github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
||||
github.com/mattn/go-sqlite3 v1.14.4 h1:4rQjbDxdu9fSgI/r3KN72G3c2goxknAqHHgPWWs8UlI=
|
||||
github.com/mattn/go-sqlite3 v1.14.4/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
|
||||
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d h1:/iIZNFGxc/a7C3yWjGcnboV+Tkc7mxr+p6fDztwoxuM=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.1-2020.1.5 h1:nI5egYTGJakVyOryqLs1cQO5dO0ksin5XXs2pspk75k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.5/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
88
main.go
Normal file
88
main.go
Normal file
|
@ -0,0 +1,88 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/config"
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/entities/actor"
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/entities/collection"
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/entities/object"
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/entities/user"
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/utils/database"
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/utils/router"
|
||||
)
|
||||
|
||||
var keyPath = "./ssl.key"
|
||||
var certPath = "./ssl.crt"
|
||||
|
||||
var routes = router.Routes{
|
||||
// General
|
||||
router.Route{"Reset", "GET", "/reset", config.Reset},
|
||||
router.Route{"SetParams", "GET", "/setparams", config.SetParams},
|
||||
|
||||
// Users
|
||||
router.Route{"Register", "POST", "/register", user.RegisterHandler},
|
||||
|
||||
// Actors
|
||||
router.Route{"GetProfile", "GET", "/{actorName}/", actor.GetProfileHandler},
|
||||
|
||||
// Objects
|
||||
router.Route{"GetPost", "GET", "/{actorName}/posts/{postId}", object.GetPostHandler},
|
||||
|
||||
// Collections
|
||||
/*
|
||||
router.Route{"GetInbox", "GET", "/{actorName}/inbox", collection.GetInboxHandler},
|
||||
router.Route{"GetOutbox", "GET", "/{actorName}/outbox", collection.GetOutboxHandler},
|
||||
router.Route{"GetFollowers", "GET", "/{actorName}/followers", collection.GetFollowersHandler},
|
||||
router.Route{"GetFollowing", "GET", "/{actorName}/following", collection.GetFollowingHandler},
|
||||
router.Route{"GetLiked", "GET", "/{actorName}/liked", collection.GetLikedHandler},
|
||||
*/
|
||||
router.Route{"PostInbox", "POST", "/{actorName}/inbox/", collection.PostInboxHandler},
|
||||
router.Route{"PostOutbox", "POST", "/{actorName}/outbox/", collection.PostOutboxHandler},
|
||||
/*
|
||||
router.Route{"PostFollowers", "POST", "/{actorName}/followers", collection.PostFollowersHandler},
|
||||
router.Route{"PostFollowing", "POST", "/{actorName}/following", collection.PostFollowingHandler},
|
||||
router.Route{"PostLiked", "POST", "/{actorName}/liked", collection.PostLikedHandler},
|
||||
*/
|
||||
}
|
||||
|
||||
func main() {
|
||||
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
config.Homeserver = "localhost"
|
||||
if len(os.Args) > 1 {
|
||||
config.Homeserver = os.Args[1]
|
||||
}
|
||||
log.Printf("Start homeserver on name %s", config.Homeserver)
|
||||
|
||||
/*
|
||||
if err := device.InitServerSigningKey(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
config.VerifyKeys = make(map[string]map[string][]byte)
|
||||
*/
|
||||
/*
|
||||
config.BackoffPolicy = backoff.NewExponential(
|
||||
backoff.WithInterval(500*time.Millisecond),
|
||||
backoff.WithMaxRetries(16),
|
||||
)
|
||||
*/
|
||||
os.Remove("sqlite.db")
|
||||
|
||||
if err := database.InitDB("sqlite.db"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer database.DB.Close()
|
||||
|
||||
config.SetDefaultParams()
|
||||
|
||||
router := router.NewRouter(routes)
|
||||
|
||||
httpErr := http.ListenAndServeTLS(":443", certPath, keyPath, router)
|
||||
if httpErr != nil {
|
||||
log.Fatal(httpErr)
|
||||
}
|
||||
|
||||
}
|
BIN
sqlite.db
Normal file
BIN
sqlite.db
Normal file
Binary file not shown.
24
ssl.crt
Normal file
24
ssl.crt
Normal file
|
@ -0,0 +1,24 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIEGDCCAwCgAwIBAgIJANNCajLtjHULMA0GCSqGSIb3DQEBCwUAMIGgMQswCQYD
|
||||
VQQGEwJERTEWMBQGA1UECAwNTmllZGVyc2FjaHNlbjERMA8GA1UEBwwIQnJhbXNj
|
||||
aGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDETMBEGA1UEAwwK
|
||||
aG9lcm5zY2hlbjEuMCwGCSqGSIb3DQEJARYfanVsaWFuLmhvZXJuc2NoZW1leWVy
|
||||
QGdtYWlsLmNvbTAeFw0xODA4MTAxMzQ0MDJaFw0yODA4MDcxMzQ0MDJaMIGgMQsw
|
||||
CQYDVQQGEwJERTEWMBQGA1UECAwNTmllZGVyc2FjaHNlbjERMA8GA1UEBwwIQnJh
|
||||
bXNjaGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDETMBEGA1UE
|
||||
AwwKaG9lcm5zY2hlbjEuMCwGCSqGSIb3DQEJARYfanVsaWFuLmhvZXJuc2NoZW1l
|
||||
eWVyQGdtYWlsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL7O
|
||||
m1fevM12EI2vP+4h7E+FpOXNVzTaG7dK1m65NTU6qgf5EV/My92S6xn0OVWVYtQ+
|
||||
VqiXmGlm8MK17ssu2+6DK7NKv6PqNBXvVacEOmBElL9F4NHR/s5XBR2AyqnWWy8D
|
||||
EnhZkm1rEaEYe3Nb+savnZfjkBKKOGGuQykMbeFziw6Ba57n3uZ8kgOqb/2koWyq
|
||||
QDqmlpfCaUD328MyuluZkc3UVdv7kQzRqwWI4QmXNsr2gbJv/lCmte7rcKoSMDyD
|
||||
xDsN/JbnN45k0ct3Ix4aCTnS9bQv0qgrEXREbnB5xkV+9SaW1GgQewFx5R8Miebu
|
||||
EZsCdSNui2CoeoBHqJMCAwEAAaNTMFEwHQYDVR0OBBYEFNAdK12auACumWcsOCsS
|
||||
sEvwZXS+MB8GA1UdIwQYMBaAFNAdK12auACumWcsOCsSsEvwZXS+MA8GA1UdEwEB
|
||||
/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBALnNSNiMympZcFjhyO8uTxfPQK4x
|
||||
2XdWPiGV+1N/zOn9LXfB5O2NY0g4cfuo9pZLpXMCF06qbc4yJtuqr2urCUhlVoYn
|
||||
FzKJTVMMKP90XzC0hSMic/kTh+N5pKDlhGhrAmjktHs+4GOqi7oY1SIrvo0KWM3s
|
||||
KpaOAJYeZNg44RisbRz1JDOoqpjb7bipF6oaNogC8uhRj3qJ1nkT2UlZjdZB5at2
|
||||
k1hFkQi1SjJxSg4+fkHVwY/wxzCfYmk4QHt2Zc8wWLpDM6TOePX/eXenphSs0v7P
|
||||
DO9gVD45i612oiBSJ9UtjHLwZrLm008SglZTudtlz6EDYhCAsSmNZKppaPs=
|
||||
-----END CERTIFICATE-----
|
27
ssl.key
Normal file
27
ssl.key
Normal file
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEAvs6bV968zXYQja8/7iHsT4Wk5c1XNNobt0rWbrk1NTqqB/kR
|
||||
X8zL3ZLrGfQ5VZVi1D5WqJeYaWbwwrXuyy7b7oMrs0q/o+o0Fe9VpwQ6YESUv0Xg
|
||||
0dH+zlcFHYDKqdZbLwMSeFmSbWsRoRh7c1v6xq+dl+OQEoo4Ya5DKQxt4XOLDoFr
|
||||
nufe5nySA6pv/aShbKpAOqaWl8JpQPfbwzK6W5mRzdRV2/uRDNGrBYjhCZc2yvaB
|
||||
sm/+UKa17utwqhIwPIPEOw38luc3jmTRy3cjHhoJOdL1tC/SqCsRdERucHnGRX71
|
||||
JpbUaBB7AXHlHwyJ5u4RmwJ1I26LYKh6gEeokwIDAQABAoIBAQC7S72K7JZyLIGl
|
||||
QrDDhUMc8DfkZ8NBmxN3wZtpxp2nKXW8K83VNweq8UucB3K8Qs5nPuX7ygsO88BD
|
||||
sSi9A7tZjiK4dRhWw0/rdCqkrm7LDqbgdqxv6e1wCFV6F3FYc5TAOgjIYExu4ZnF
|
||||
g22y2Ef6/mn4raU/vbQIlnFQeuXlVb4V5UxJRFyOd5Bd270qIE2Y2uDrNjAjaPzz
|
||||
H3Vdmr39XvTAoXi5Z+ANwPZV+haVWMrTSW0Qtk3crNnO/GggDv/ugAL/UurNsm6r
|
||||
B1IEVyqFf4inkugDnajkszVoHfoVMhyf91J3R5u3ZltDElXizamQc/+Pr+r6wvwf
|
||||
gnMM69kRAoGBAOt7tuhXzP/wLTljcJoord8/eSrElPxtdX47/0P7km0CJFLyrmsa
|
||||
PasyhpadggVHwtj3VdOinrY/2B/71fi4flMP/HUFvtwD5ViWODE6bbVwijZsyEH/
|
||||
wPM594QG9yuVZ0za+M+pQdqnMxQRT79k4SLlR7CskzP5vhbOPIv8YCA5AoGBAM9u
|
||||
a+/s8trHzErrSQlcpvrvMcj9BLUek5tlEH/Rx/6WnSmfnnzu6/Y3sbRgEPbDg+0E
|
||||
jbl8dESs3nYxxldg02Mw2bWRyodM7cYBNCPifqutvJdfMxxSWH+ayVBAA7VxQzsY
|
||||
GDOK4KlC+siTSYUY8PJ9FM+6RTy4F8oVbwmS5jcrAoGBAK4prusC3SzCH0Cdqk9q
|
||||
HMbL9DrMcACOmGKHz1EhhHe5KNJsiNHP86Jl2SMWVW7AV30O2VyQnt/eMmPdZ7Dw
|
||||
CwY2AZsvZ6zj+MFfQSovs6qJFMASDr65gKSjz8vHNxH2CxPNtE4qOfmUxfNmplvB
|
||||
Kb4cY7xotuqvIIdPe3pxa0sJAoGBAMflLXdE7LQRHrqECxpOg0wG/f8mdUbldHGn
|
||||
70J+MzEQi9v0ypKy3AmmmkWs3iwvNg9O+BTr7k/QF4Hnba/+yzcneGYVXQsOA4Vw
|
||||
24JJXrCq+LcXMvX0FPzDeYUwa2KLB7MHASuKhf4XYf2wkoUFCA1mpIuageaFscc4
|
||||
6IxdWCWJAoGAXXk8bBRj0YksNCP41KVmxLqoky+vd115BjBxmiEK8K9NK1eFUGZe
|
||||
K8/FeX3JmFrxAS+N+LeP+XVgyF1triDZ0ix8gn2bSY3skZHsUw62B6w7xWXzqgx4
|
||||
rZM+GfS/QY2N9ubqze1m/vROSf65iHakZf+mxE+uf2BCi1WOxp7KARE=
|
||||
-----END RSA PRIVATE KEY-----
|
117
utils/database/databaseConnector.go
Normal file
117
utils/database/databaseConnector.go
Normal file
|
@ -0,0 +1,117 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
var DB *sql.DB
|
||||
|
||||
// TODO: Change DB Structure
|
||||
func InitDB(filepath string) (err error) {
|
||||
DB, err = sql.Open("sqlite3", filepath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if DB == nil {
|
||||
panic("DB couldn't be initialized")
|
||||
}
|
||||
|
||||
handleError(initObjectTable())
|
||||
handleError(initCollectionTable())
|
||||
handleError(initActorTable())
|
||||
handleError(initUserTable())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func initObjectTable() (err error) {
|
||||
statement, err := DB.Prepare(`CREATE TABLE IF NOT EXISTS object (
|
||||
id TEXT PRIMARY KEY,
|
||||
type TEXT,
|
||||
attributedTo TEXT,
|
||||
content TEXT,
|
||||
published INTEGER,
|
||||
toActor TEXT
|
||||
)`)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
statement.Exec()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func initCollectionTable() (err error) {
|
||||
statement, err := DB.Prepare(`CREATE TABLE IF NOT EXISTS collection (
|
||||
id TEXT PRIMARY KEY
|
||||
)`)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
statement.Exec()
|
||||
|
||||
statement, err = DB.Prepare(`CREATE TABLE IF NOT EXISTS collectionObject (
|
||||
collectionId TEXT,
|
||||
objectId TEXT,
|
||||
PRIMARY KEY (collectionId, objectId)
|
||||
)`)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
statement.Exec()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func initActorTable() (err error) {
|
||||
statement, err := DB.Prepare(`CREATE TABLE IF NOT EXISTS actor (
|
||||
id TEXT PRIMARY KEY,
|
||||
type TEXT,
|
||||
name TEXT,
|
||||
preferredUsername TEXT,
|
||||
summary TEXT,
|
||||
inbox TEXT,
|
||||
outbox TEXT,
|
||||
followers TEXT,
|
||||
following TEXT,
|
||||
liked TEXT
|
||||
)`)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
statement.Exec()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func initUserTable() (err error) {
|
||||
statement, err := DB.Prepare(`CREATE TABLE IF NOT EXISTS user (
|
||||
id TEXT PRIMARY KEY,
|
||||
password TEXT,
|
||||
actor TEXT
|
||||
)`)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
statement.Exec()
|
||||
|
||||
statement, err = DB.Prepare(`CREATE TABLE IF NOT EXISTS session (
|
||||
token TEXT PRIMARY KEY,
|
||||
userId TEXT
|
||||
)`)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
statement.Exec()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func handleError(err error) {
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Could not execute Database Query: %s", err))
|
||||
}
|
||||
}
|
76
utils/encryptionService.go
Normal file
76
utils/encryptionService.go
Normal file
|
@ -0,0 +1,76 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/config"
|
||||
)
|
||||
|
||||
func CreateToken() (err error, token string) {
|
||||
b := make([]byte, 8)
|
||||
_, err = rand.Read(b)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return
|
||||
}
|
||||
token = fmt.Sprintf("%x", b)
|
||||
return
|
||||
}
|
||||
|
||||
func CreateUUID() (err error, uuid string) {
|
||||
b := make([]byte, 16)
|
||||
_, err = rand.Read(b)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return
|
||||
}
|
||||
uuid = fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Check if needed
|
||||
func Hash(s []byte) (err error, hashString string) {
|
||||
h := sha256.New()
|
||||
_, err = h.Write(s)
|
||||
if nil != err {
|
||||
return
|
||||
}
|
||||
hash := h.Sum(nil)
|
||||
hashString = base64.StdEncoding.EncodeToString(hash)
|
||||
return
|
||||
}
|
||||
|
||||
//TODO: Signing Mechanism?
|
||||
func GenerateKeyPair() (publicKey ed25519.PublicKey, privateKey ed25519.PrivateKey, err error) {
|
||||
publicKey, privateKey, err = ed25519.GenerateKey(nil)
|
||||
return
|
||||
}
|
||||
|
||||
func Sign(message []byte) string {
|
||||
signatureBytes := ed25519.Sign(config.PrivateKey, message)
|
||||
return base64.RawStdEncoding.EncodeToString(signatureBytes)
|
||||
}
|
||||
|
||||
func SignContent(content []byte) (signatures map[string]map[string]string) {
|
||||
if !config.Signing {
|
||||
return
|
||||
}
|
||||
signatures = make(map[string]map[string]string)
|
||||
signatures[config.Homeserver] = make(map[string]string)
|
||||
signatures[config.Homeserver][config.KeyId] = Sign(content)
|
||||
return
|
||||
}
|
||||
|
||||
func VerifySignature(publicKey []byte, message []byte, signature string) bool {
|
||||
signatureBytes, err := base64.RawStdEncoding.DecodeString(signature)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
ed25519.Verify(config.PublicKey, message, signatureBytes)
|
||||
return true
|
||||
}
|
23
utils/logger.go
Normal file
23
utils/logger.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func APILogger(inner http.Handler, name string) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
|
||||
inner.ServeHTTP(w, r)
|
||||
|
||||
log.Printf(
|
||||
"%s\t%s\t%s\t%s",
|
||||
r.Method,
|
||||
r.RequestURI,
|
||||
name,
|
||||
time.Since(start),
|
||||
)
|
||||
})
|
||||
}
|
89
utils/requestChecker.go
Normal file
89
utils/requestChecker.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/config"
|
||||
)
|
||||
|
||||
type ErrorResponse struct {
|
||||
ErrorCode string `json:"errcode,omitempty"`
|
||||
ErrorMessage string `json:"error,omitempty"`
|
||||
RetryTime int `json:"retry_after_ms,omitempty"`
|
||||
}
|
||||
|
||||
func CheckRequest(r *http.Request) (err error) {
|
||||
if !strings.Contains(r.Header.Get("Content-Type"), "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") {
|
||||
err = errors.New("Content Type not JSON")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func GetContentTypeString() string {
|
||||
return "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
|
||||
}
|
||||
|
||||
func GetDefaultContext() string {
|
||||
return "https://www.w3.org/ns/activitystreams"
|
||||
}
|
||||
|
||||
func GenerateProfileUrl(userId string) string {
|
||||
return fmt.Sprintf("%s://%s/%s/", config.HttpString, config.Homeserver, userId)
|
||||
}
|
||||
|
||||
func GenerateInboxUrl(userId string) string {
|
||||
return fmt.Sprintf("%s://%s/%s/inbox/", config.HttpString, config.Homeserver, userId)
|
||||
}
|
||||
|
||||
func GenerateOutboxUrl(userId string) string {
|
||||
return fmt.Sprintf("%s://%s/%s/outbox/", config.HttpString, config.Homeserver, userId)
|
||||
}
|
||||
|
||||
func GenerateFollowersUrl(userId string) string {
|
||||
return fmt.Sprintf("%s://%s/%s/followers/", config.HttpString, config.Homeserver, userId)
|
||||
}
|
||||
|
||||
func GenerateFollowingUrl(userId string) string {
|
||||
return fmt.Sprintf("%s://%s/%s/following/", config.HttpString, config.Homeserver, userId)
|
||||
}
|
||||
|
||||
func GenerateLikedUrl(userId string) string {
|
||||
return fmt.Sprintf("%s://%s/%s/liked/", config.HttpString, config.Homeserver, userId)
|
||||
}
|
||||
|
||||
func GetAccessToken(r *http.Request) (token string, err error) {
|
||||
token = r.URL.Query().Get("access_token")
|
||||
if token == "" {
|
||||
token = r.Header.Get("Authorization")
|
||||
if token == "" || !strings.Contains(token, "Bearer") {
|
||||
err = errors.New("Missing Token")
|
||||
} else {
|
||||
token = strings.Split(token, " ")[1]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func HandleHTTPError(res *http.Response) (err error) {
|
||||
buf := new(bytes.Buffer)
|
||||
buf.ReadFrom(res.Body)
|
||||
err = errors.New(fmt.Sprintf("%s (%s)", buf.String(), res.Status))
|
||||
return
|
||||
}
|
||||
|
||||
func RemoveDuplicates(array []string) []string {
|
||||
keys := make(map[string]bool)
|
||||
list := []string{}
|
||||
|
||||
for _, entry := range array {
|
||||
if _, value := keys[entry]; !value {
|
||||
keys[entry] = true
|
||||
list = append(list, entry)
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
12
utils/router/route.go
Normal file
12
utils/router/route.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package router
|
||||
|
||||
import "net/http"
|
||||
|
||||
type Route struct {
|
||||
Name string
|
||||
Method string
|
||||
Pattern string
|
||||
HandlerFunc http.HandlerFunc
|
||||
}
|
||||
|
||||
type Routes []Route
|
27
utils/router/router.go
Normal file
27
utils/router/router.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/utils"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func NewRouter(routes Routes) *mux.Router {
|
||||
router := mux.NewRouter().StrictSlash(true)
|
||||
|
||||
for _, route := range routes {
|
||||
var handler http.Handler
|
||||
handler = route.HandlerFunc
|
||||
handler = utils.APILogger(handler, route.Name)
|
||||
|
||||
router.
|
||||
Methods(route.Method).
|
||||
Path(route.Pattern).
|
||||
Name(route.Name).
|
||||
Handler(handler)
|
||||
|
||||
}
|
||||
|
||||
return router
|
||||
}
|
516
workloadGenerator.go
Normal file
516
workloadGenerator.go
Normal file
|
@ -0,0 +1,516 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/config"
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/entities/activity"
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/entities/object"
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/entities/user"
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/utils"
|
||||
)
|
||||
|
||||
var BaseLineTest = false
|
||||
|
||||
var systemParamsIndex = 0
|
||||
|
||||
type SystemParams struct {
|
||||
Id string
|
||||
BytesToSend int
|
||||
MessagesPerSecond float32
|
||||
Distribution map[string][]string
|
||||
Packetloss int
|
||||
MinutesNotAvailable int
|
||||
Consensus bool
|
||||
AuthentificationCheck bool
|
||||
Signing bool
|
||||
Encryption bool
|
||||
}
|
||||
|
||||
var httpString string
|
||||
var users = []map[string][]string{
|
||||
map[string][]string{
|
||||
"143.93.38.208": []string{
|
||||
"user1",
|
||||
"user2",
|
||||
"user3",
|
||||
"user4",
|
||||
"user5",
|
||||
"user6",
|
||||
},
|
||||
},
|
||||
map[string][]string{
|
||||
"143.93.38.207": []string{
|
||||
"user1",
|
||||
"user2",
|
||||
"user3",
|
||||
"user4",
|
||||
},
|
||||
"143.93.38.208": []string{
|
||||
"user1",
|
||||
},
|
||||
"143.93.38.209": []string{
|
||||
"user1",
|
||||
},
|
||||
},
|
||||
map[string][]string{
|
||||
"143.93.38.207": []string{
|
||||
"user1",
|
||||
"user2",
|
||||
"user3",
|
||||
"user4",
|
||||
},
|
||||
"143.93.38.208": []string{
|
||||
"user1",
|
||||
"user2",
|
||||
"user3",
|
||||
"user4",
|
||||
},
|
||||
"143.93.38.209": []string{
|
||||
"user1",
|
||||
"user2",
|
||||
"user3",
|
||||
"user4",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var servers = []string{
|
||||
"143.93.38.207",
|
||||
"143.93.38.208",
|
||||
"143.93.38.209",
|
||||
}
|
||||
|
||||
var systemParams = []SystemParams{
|
||||
SystemParams{
|
||||
Id: "111110000",
|
||||
BytesToSend: 280,
|
||||
MessagesPerSecond: 1.0,
|
||||
Distribution: users[1],
|
||||
Packetloss: 1,
|
||||
MinutesNotAvailable: 0,
|
||||
Consensus: true,
|
||||
AuthentificationCheck: true,
|
||||
Signing: true,
|
||||
Encryption: true,
|
||||
},
|
||||
SystemParams{
|
||||
Id: "011110000",
|
||||
BytesToSend: 8,
|
||||
MessagesPerSecond: 1.0,
|
||||
Distribution: users[1],
|
||||
Packetloss: 1,
|
||||
MinutesNotAvailable: 0,
|
||||
Consensus: true,
|
||||
AuthentificationCheck: true,
|
||||
Signing: true,
|
||||
Encryption: true,
|
||||
},
|
||||
SystemParams{
|
||||
Id: "211110000",
|
||||
BytesToSend: 512,
|
||||
MessagesPerSecond: 1.0,
|
||||
Distribution: users[1],
|
||||
Packetloss: 1,
|
||||
MinutesNotAvailable: 0,
|
||||
Consensus: true,
|
||||
AuthentificationCheck: true,
|
||||
Signing: true,
|
||||
Encryption: true,
|
||||
},
|
||||
SystemParams{
|
||||
Id: "101110000",
|
||||
BytesToSend: 280,
|
||||
MessagesPerSecond: 0.1,
|
||||
Distribution: users[1],
|
||||
Packetloss: 1,
|
||||
MinutesNotAvailable: 0,
|
||||
Consensus: true,
|
||||
AuthentificationCheck: true,
|
||||
Signing: true,
|
||||
Encryption: true,
|
||||
},
|
||||
SystemParams{
|
||||
Id: "121110000",
|
||||
BytesToSend: 280,
|
||||
MessagesPerSecond: 10.0,
|
||||
Distribution: users[1],
|
||||
Packetloss: 1,
|
||||
MinutesNotAvailable: 0,
|
||||
Consensus: true,
|
||||
AuthentificationCheck: true,
|
||||
Signing: true,
|
||||
Encryption: true,
|
||||
},
|
||||
SystemParams{
|
||||
Id: "110110000",
|
||||
BytesToSend: 280,
|
||||
MessagesPerSecond: 1.0,
|
||||
Distribution: users[0],
|
||||
Packetloss: 1,
|
||||
MinutesNotAvailable: 0,
|
||||
Consensus: true,
|
||||
AuthentificationCheck: true,
|
||||
Signing: true,
|
||||
Encryption: true,
|
||||
},
|
||||
SystemParams{
|
||||
Id: "112110000",
|
||||
BytesToSend: 280,
|
||||
MessagesPerSecond: 1.0,
|
||||
Distribution: users[2],
|
||||
Packetloss: 1,
|
||||
MinutesNotAvailable: 0,
|
||||
Consensus: true,
|
||||
AuthentificationCheck: true,
|
||||
Signing: true,
|
||||
Encryption: true,
|
||||
},
|
||||
SystemParams{
|
||||
Id: "111010000",
|
||||
BytesToSend: 280,
|
||||
MessagesPerSecond: 1.0,
|
||||
Distribution: users[1],
|
||||
Packetloss: 0,
|
||||
MinutesNotAvailable: 0,
|
||||
Consensus: true,
|
||||
AuthentificationCheck: true,
|
||||
Signing: true,
|
||||
Encryption: true,
|
||||
},
|
||||
SystemParams{
|
||||
Id: "111210000",
|
||||
BytesToSend: 280,
|
||||
MessagesPerSecond: 1.0,
|
||||
Distribution: users[1],
|
||||
Packetloss: 20,
|
||||
MinutesNotAvailable: 0,
|
||||
Consensus: true,
|
||||
AuthentificationCheck: true,
|
||||
Signing: true,
|
||||
Encryption: true,
|
||||
},
|
||||
/*
|
||||
SystemParams{
|
||||
Id: "111120000",
|
||||
BytesToSend: 280,
|
||||
MessagesPerSecond: 1.0,
|
||||
Distribution: users[1],
|
||||
Packetloss: 1,
|
||||
MinutesNotAvailable: 1,
|
||||
Consensus: true,
|
||||
AuthentificationCheck: true,
|
||||
Signing: true,
|
||||
Encryption: true,
|
||||
},
|
||||
*/
|
||||
}
|
||||
|
||||
var userIds []string
|
||||
var accessTokens map[string]string
|
||||
var collectionId string
|
||||
|
||||
func main() {
|
||||
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
httpString = "https"
|
||||
iteration := 0
|
||||
if BaseLineTest {
|
||||
file, err := os.Create(fmt.Sprintf("%s Baseline Measurement ActivityPub.csv", strconv.FormatInt(time.Now().Unix(), 10)))
|
||||
if err != nil {
|
||||
log.Fatalf("Error Creating CSV: %s", err)
|
||||
}
|
||||
defer file.Close()
|
||||
writer := csv.NewWriter(file)
|
||||
defer writer.Flush()
|
||||
err = writer.Write([]string{"Iteration", "Start", "End"})
|
||||
if err != nil {
|
||||
log.Fatalf("Error in Writing CSV: %s", err)
|
||||
}
|
||||
for iteration < 10 {
|
||||
start := time.Now().Unix()
|
||||
time.Sleep(2 * time.Minute)
|
||||
end := time.Now().Unix()
|
||||
iteration++
|
||||
err = writer.Write([]string{strconv.Itoa(iteration), strconv.FormatInt(start, 10), strconv.FormatInt(end, 10)})
|
||||
if err != nil {
|
||||
log.Fatalf("Error in Writing CSV: %s", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, systemParam := range systemParams {
|
||||
file, err := os.Create(fmt.Sprintf("%s %s Measurement ActivityPub.csv", strconv.FormatInt(time.Now().Unix(), 10), systemParam.Id))
|
||||
if err != nil {
|
||||
log.Fatalf("Error Creating CSV: %s", err)
|
||||
}
|
||||
writer := csv.NewWriter(file)
|
||||
err = writer.Write([]string{"Iteration", "Start", "End", "Actions Send"})
|
||||
if err != nil {
|
||||
log.Fatalf("Error in Writing CSV: %s", err)
|
||||
}
|
||||
millisecondsToWait := 1000 / systemParam.MessagesPerSecond
|
||||
iteration = 0
|
||||
for iteration < 30 {
|
||||
err := setUp(systemParam)
|
||||
if err != nil {
|
||||
log.Fatalf("Error in SetUp: %s", err)
|
||||
}
|
||||
b := make([]byte, systemParam.BytesToSend)
|
||||
_, err = rand.Read(b)
|
||||
message := base64.RawStdEncoding.EncodeToString(b)
|
||||
start := time.Now()
|
||||
end := start.Add(2 * time.Minute).Unix()
|
||||
log.Printf("Id: %s - Iteration: %s - Start: %s - End: %s", systemParam.Id, strconv.Itoa(iteration), strconv.FormatInt(start.Unix(), 10), strconv.FormatInt(end, 10))
|
||||
actionsSend := 0
|
||||
for time.Now().Unix() < end {
|
||||
time.Sleep(time.Duration(millisecondsToWait) * time.Millisecond)
|
||||
|
||||
err = sendMessage(message)
|
||||
if err != nil {
|
||||
log.Fatalf("Error sending Message %s", err)
|
||||
}
|
||||
|
||||
actionsSend++
|
||||
}
|
||||
iteration++
|
||||
err = writer.Write([]string{strconv.Itoa(iteration), strconv.FormatInt(start.Unix(), 10), strconv.FormatInt(end, 10), strconv.Itoa(actionsSend)})
|
||||
if err != nil {
|
||||
log.Fatalf("Error in Writing CSV: %s", err)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
err = reset()
|
||||
if err != nil {
|
||||
log.Fatalf("Error in Reset: %s", err)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
writer.Flush()
|
||||
file.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sendMessage(message string) (err error) {
|
||||
userId := userIds[0]
|
||||
accessToken := accessTokens[userId]
|
||||
requestUrl := fmt.Sprintf("%soutbox/", userId)
|
||||
messageObject := object.Object{
|
||||
Type: "Note",
|
||||
Content: message,
|
||||
}
|
||||
reqBody, err := json.Marshal(messageObject)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
client := &http.Client{}
|
||||
var req *http.Request
|
||||
req, err = http.NewRequest(http.MethodPost, requestUrl, bytes.NewBuffer(reqBody))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req.Header["Content-Type"] = []string{utils.GetContentTypeString()}
|
||||
req.Header["Authorization"] = []string{fmt.Sprintf("Bearer %s", accessToken)}
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
handleError(res)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func setParams(systemParamsToUse SystemParams) (err error) {
|
||||
serverNotAvailableIndex := 1
|
||||
for i, server := range servers {
|
||||
minutesNotAvailable := 0
|
||||
if serverNotAvailableIndex == i {
|
||||
minutesNotAvailable = systemParamsToUse.MinutesNotAvailable
|
||||
}
|
||||
requestUrl := fmt.Sprintf("%s://%s/setparams", httpString, server)
|
||||
request := config.SetParamBody{
|
||||
Packetloss: systemParamsToUse.Packetloss,
|
||||
UnavailableTill: time.Now().Add(time.Duration(minutesNotAvailable) * time.Minute).Unix(),
|
||||
Consensus: systemParamsToUse.Consensus,
|
||||
AuthentificationCheck: systemParamsToUse.AuthentificationCheck,
|
||||
Signing: systemParamsToUse.Signing,
|
||||
Encryption: systemParamsToUse.Encryption,
|
||||
}
|
||||
var reqBody []byte
|
||||
reqBody, err = json.Marshal(request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
client := &http.Client{Timeout: 2 * time.Second}
|
||||
var req *http.Request
|
||||
req, err = http.NewRequest(http.MethodGet, requestUrl, bytes.NewBuffer(reqBody))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req.Header["Content-Type"] = []string{"application/json"}
|
||||
var res *http.Response
|
||||
res, err = client.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
handleError(res)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func setUp(systemParamsToUse SystemParams) (err error) {
|
||||
accessTokens = make(map[string]string)
|
||||
err = createUsers(systemParamsToUse.Distribution)
|
||||
if err != nil {
|
||||
log.Printf("Error in User-Creation: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Follow User
|
||||
log.Printf("Userids: %s", userIds)
|
||||
err = followUser(userIds[0], userIds[1:])
|
||||
if err != nil {
|
||||
log.Printf("Error follow User: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = setParams(systemParamsToUse)
|
||||
return
|
||||
}
|
||||
|
||||
func reset() (err error) {
|
||||
userIds = []string{}
|
||||
accessTokens = make(map[string]string)
|
||||
for _, server := range servers {
|
||||
requestUrl := fmt.Sprintf("%s://%s/reset", httpString, server)
|
||||
client := &http.Client{Timeout: 2 * time.Second}
|
||||
var req *http.Request
|
||||
req, err = http.NewRequest(http.MethodGet, requestUrl, bytes.NewBuffer(nil))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req.Header["Content-Type"] = []string{"application/json"}
|
||||
var res *http.Response
|
||||
res, err = client.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
handleError(res)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func followUser(userIdToFollow string, userIdsThatFollow []string) (err error) {
|
||||
for _, userId := range userIdsThatFollow {
|
||||
log.Printf("UserIdThatFollow: %s", userId)
|
||||
accessToken := accessTokens[userId]
|
||||
requestUrl := fmt.Sprintf("%soutbox/", userId)
|
||||
followActivity := activity.Activity{
|
||||
Type: "Follow",
|
||||
Actor: userId,
|
||||
To: userIdToFollow,
|
||||
}
|
||||
var reqBody []byte
|
||||
reqBody, err = json.Marshal(followActivity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
client := &http.Client{Timeout: 2 * time.Second}
|
||||
var req *http.Request
|
||||
req, err = http.NewRequest(http.MethodPost, requestUrl, bytes.NewBuffer(reqBody))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req.Header["Content-Type"] = []string{utils.GetContentTypeString()}
|
||||
req.Header["Authorization"] = []string{fmt.Sprintf("Bearer %s", accessToken)}
|
||||
var res *http.Response
|
||||
res, err = client.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
err = utils.HandleHTTPError(res)
|
||||
return
|
||||
}
|
||||
//time.Sleep(time.Duration(1) * time.Second)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func createUsers(serverUserMap map[string][]string) (err error) {
|
||||
log.Println("Create Users")
|
||||
for server, usersToCreate := range serverUserMap {
|
||||
for _, userToCreate := range usersToCreate {
|
||||
var userId string
|
||||
var accessToken string
|
||||
userId, accessToken, err = createUser(userToCreate, server)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if userId != "" && accessToken != "" {
|
||||
log.Printf("%s created - AccessToken: %s", userId, accessToken)
|
||||
accessTokens[userId] = accessToken
|
||||
userIds = append(userIds, userId)
|
||||
}
|
||||
time.Sleep(time.Duration(20) * time.Millisecond)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func createUser(userToCreate string, homeserver string) (userId string, accessToken string, err error) {
|
||||
requestUrl := fmt.Sprintf("%s://%s/register", httpString, homeserver)
|
||||
request := user.RegisterRequest{
|
||||
Username: userToCreate,
|
||||
Password: "password",
|
||||
}
|
||||
reqBody, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
client := &http.Client{Timeout: 2 * time.Second}
|
||||
req, err := http.NewRequest(http.MethodPost, requestUrl, bytes.NewBuffer(reqBody))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req.Header["Content-Type"] = []string{utils.GetContentTypeString()}
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
err = utils.HandleHTTPError(res)
|
||||
return
|
||||
} else {
|
||||
response := user.RegisterResponse{}
|
||||
decoder := json.NewDecoder(res.Body)
|
||||
err = decoder.Decode(&response)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
userId = response.Actor
|
||||
accessToken = response.Token
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func handleError(res *http.Response) {
|
||||
err := utils.HandleHTTPError(res)
|
||||
if err != nil {
|
||||
log.Printf("%s", err)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue