- Clean up some logging - Properly store likes in db
This commit is contained in:
parent
c7f875a9c5
commit
03ca524c99
12 changed files with 222 additions and 44 deletions
|
@ -421,11 +421,6 @@ func ImportRemoteAccountByAPUrl(apUrl string) (*models.User, error) {
|
|||
}
|
||||
defer response.Body.Close()
|
||||
body, _ := io.ReadAll(response.Body)
|
||||
log.Debug().
|
||||
Int("status", response.StatusCode).
|
||||
Bytes("body", body).
|
||||
// Any("headers", response.Header).
|
||||
Msg("Response information")
|
||||
if response.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("activitypub: invalid status code: %v", response.StatusCode)
|
||||
}
|
||||
|
@ -452,9 +447,20 @@ func ImportRemoteAccountByAPUrl(apUrl string) (*models.User, error) {
|
|||
Preload(dbgen.User.InfoFields).
|
||||
Preload(dbgen.User.BeingTypes).
|
||||
Preload(dbgen.User.Roles).
|
||||
FirstOrCreate()
|
||||
if err != nil {
|
||||
return nil, other.Error("activitypub", "failed to find or create user in db", err)
|
||||
First()
|
||||
switch err {
|
||||
case nil:
|
||||
case gorm.ErrRecordNotFound:
|
||||
user = &models.User{
|
||||
ID: shared.NewId(),
|
||||
Username: data.PreferredUsername + "@" + targetUrl.Host,
|
||||
ServerId: hostId,
|
||||
}
|
||||
if err = dbgen.User.Select(dbgen.User.ID, dbgen.User.Username, dbgen.User.ServerId).Create(user); err != nil {
|
||||
return nil, other.Error("activitypub", "failed to create user", err)
|
||||
}
|
||||
default:
|
||||
return nil, other.Error("activitypub", "failed to find user", err)
|
||||
}
|
||||
user.Verified = true
|
||||
user.FinishedRegistration = true
|
||||
|
|
|
@ -2,7 +2,7 @@ package models
|
|||
|
||||
type Activity struct {
|
||||
Id string `gorm:"primarykey"`
|
||||
Type string `gorm:"type:activitystreams_activity_type"`
|
||||
Type string // `gorm:"type:activitystreams_activity_type"`
|
||||
ObjectId string
|
||||
ObjectType uint32 // Target type: ActivitystreamsActivityTargetType
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
package models
|
||||
|
||||
import "gorm.io/gorm"
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// A Reaction is a user liking a note using an emote
|
||||
type Reaction struct {
|
||||
|
@ -9,6 +13,6 @@ type Reaction struct {
|
|||
NoteId string
|
||||
Reactor User
|
||||
ReactorId string
|
||||
Emote Emote
|
||||
EmoteId uint
|
||||
Emote *Emote // Emote is optional. If not set, use the default emote of the server
|
||||
EmoteId sql.NullInt64
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[general]
|
||||
protocol = "https"
|
||||
domain = "serveo.net"
|
||||
subdomain = "f14d150ea7c018b520cf19b0f98945c2"
|
||||
subdomain = "b2f4e7c5596220d4c4957b24f6954220"
|
||||
private_port = 8080
|
||||
public_port = 443
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ func postAs(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
n := dbgen.Note
|
||||
note := models.Note{
|
||||
ID: shared.NewId(),
|
||||
Creator: *user,
|
||||
CreatorId: user.ID,
|
||||
RawContent: data.Content,
|
||||
|
@ -66,6 +67,7 @@ func postAs(w http.ResponseWriter, r *http.Request) {
|
|||
OriginId: 1,
|
||||
}
|
||||
err = n.Select(
|
||||
n.ID,
|
||||
n.CreatorId,
|
||||
n.RawContent,
|
||||
n.Remote,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
|
@ -15,8 +14,5 @@ func BuildActivitypubRouter() http.Handler {
|
|||
router.HandleFunc("/activity/reject/{id}", activityReject)
|
||||
router.HandleFunc("/activity/update/{id}", activityUpdate)
|
||||
router.HandleFunc("/note/{id}", objectNote)
|
||||
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = fmt.Fprint(w, "in ap")
|
||||
})
|
||||
return router
|
||||
}
|
193
web/public/api/activitypub/inbox.go
Normal file
193
web/public/api/activitypub/inbox.go
Normal file
|
@ -0,0 +1,193 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
webutils "git.mstar.dev/mstar/goutils/http"
|
||||
"git.mstar.dev/mstar/goutils/other"
|
||||
"github.com/rs/zerolog/hlog"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"git.mstar.dev/mstar/linstrom/activitypub"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/models"
|
||||
)
|
||||
|
||||
var objectIdRegex = regexp.MustCompile(
|
||||
`https?://.+/api/activitypub/[a-z]+(?:/[a-z]+)?/([a-zA-Z0-9\-]+)`,
|
||||
)
|
||||
|
||||
func userInbox(w http.ResponseWriter, r *http.Request) {
|
||||
log := hlog.FromRequest(r)
|
||||
userId := r.PathValue("id")
|
||||
body, err := io.ReadAll(r.Body)
|
||||
log.Info().
|
||||
Err(err).
|
||||
Str("userId", userId).
|
||||
Bytes("body", body).
|
||||
Any("headers", r.Header).
|
||||
Msg("Inbox message")
|
||||
data := map[string]any{}
|
||||
err = json.Unmarshal(body, &data)
|
||||
if err != nil {
|
||||
webutils.ProblemDetails(
|
||||
w,
|
||||
http.StatusBadRequest,
|
||||
"/errors/bad-request-data",
|
||||
"Bad activity data",
|
||||
other.IntoPointer("Body to inbox needs to be json"),
|
||||
nil,
|
||||
)
|
||||
return
|
||||
}
|
||||
if _, ok := data["@context"]; !ok {
|
||||
webutils.ProblemDetails(
|
||||
w,
|
||||
http.StatusBadRequest,
|
||||
"/errors/bad-request-data",
|
||||
"Bad activity data",
|
||||
other.IntoPointer("Request data needs to contain context"),
|
||||
nil,
|
||||
)
|
||||
return
|
||||
}
|
||||
objectType, ok := data["type"].(string)
|
||||
if !ok {
|
||||
webutils.ProblemDetails(
|
||||
w,
|
||||
http.StatusBadRequest,
|
||||
"/errors/bad-request-data",
|
||||
"Bad activity data",
|
||||
other.IntoPointer(`Request data needs to contain a field "type" with a string value`),
|
||||
nil,
|
||||
)
|
||||
return
|
||||
}
|
||||
_, ok = data["id"].(string)
|
||||
if !ok {
|
||||
webutils.ProblemDetails(
|
||||
w,
|
||||
http.StatusBadRequest,
|
||||
"/errors/bad-request-data",
|
||||
"Bad activity data",
|
||||
other.IntoPointer(`Request data needs to contain a field "id" with a string value`),
|
||||
nil,
|
||||
)
|
||||
return
|
||||
}
|
||||
switch objectType {
|
||||
case "Like":
|
||||
handleLike(w, r, data)
|
||||
default:
|
||||
webutils.ProblemDetailsStatusOnly(w, 500)
|
||||
}
|
||||
}
|
||||
|
||||
func handleLike(w http.ResponseWriter, r *http.Request, object map[string]any) {
|
||||
log := hlog.FromRequest(r)
|
||||
activityId := object["id"].(string)
|
||||
likerUrl, ok := object["actor"].(string)
|
||||
if !ok {
|
||||
webutils.ProblemDetails(
|
||||
w,
|
||||
http.StatusBadRequest,
|
||||
"/errors/bad-request-data",
|
||||
"Bad activity data",
|
||||
other.IntoPointer(`Request data needs to contain a field "actor" with a string value`),
|
||||
nil,
|
||||
)
|
||||
return
|
||||
}
|
||||
// TODO: Account for case where object is embedded in like
|
||||
targetUrl, ok := object["object"].(string)
|
||||
if !ok {
|
||||
webutils.ProblemDetails(
|
||||
w,
|
||||
http.StatusBadRequest,
|
||||
"/errors/bad-request-data",
|
||||
"Bad activity data",
|
||||
other.IntoPointer(`Request data needs to contain a field "object" with a string value`),
|
||||
nil,
|
||||
)
|
||||
return
|
||||
}
|
||||
targetIdMatches := objectIdRegex.FindStringSubmatch(targetUrl)
|
||||
if len(targetIdMatches) != 2 {
|
||||
log.Error().
|
||||
Strs("match-results", targetIdMatches).
|
||||
Str("url", targetUrl).
|
||||
Msg("Url didn't match id extractor regex")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
targetId := targetIdMatches[1]
|
||||
// Assume likes can only happen on notes for now
|
||||
// Thus check if a note with that Id exists at all
|
||||
note, err := dbgen.Note.Where(dbgen.Note.ID.Eq(targetId)).First()
|
||||
switch err {
|
||||
case nil:
|
||||
case gorm.ErrRecordNotFound:
|
||||
webutils.ProblemDetails(
|
||||
w,
|
||||
http.StatusBadRequest,
|
||||
"/errors/bad-request-data",
|
||||
"Bad activity data",
|
||||
other.IntoPointer("There is no note with the target id"),
|
||||
nil,
|
||||
)
|
||||
return
|
||||
default:
|
||||
log.Error().Err(err).Str("note-id", targetId).Msg("Failed to get note from db")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// Import liker after verifying that target note is correct
|
||||
liker, err := activitypub.ImportRemoteAccountByAPUrl(likerUrl)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Str("liker-url", likerUrl).
|
||||
Msg("Failed to import liking remote account")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
reaction := models.Reaction{
|
||||
Note: *note,
|
||||
NoteId: note.ID,
|
||||
Reactor: *liker,
|
||||
ReactorId: liker.ID,
|
||||
Emote: nil,
|
||||
EmoteId: sql.NullInt64{Valid: false},
|
||||
}
|
||||
tx := dbgen.Q.Begin()
|
||||
|
||||
err = tx.Reaction.Create(&reaction)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
log.Error().Err(err).Any("raw-reaction", reaction).Msg("Failed to store reaction in db")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
}
|
||||
// TODO: Create corresponding activity too
|
||||
activity := models.Activity{
|
||||
Id: activityId,
|
||||
Type: string(models.ActivityLike),
|
||||
ObjectId: fmt.Sprint(reaction.ID),
|
||||
ObjectType: uint32(models.ActivitystreamsActivityTargetReaction),
|
||||
}
|
||||
err = tx.Activity.Create(&activity)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
log.Error().Err(err).Any("raw-reaction", reaction).Msg("Failed to store reaction in db")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
}
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to commit reaction transaction to db")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
|
@ -3,7 +3,6 @@ package activitypub
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
|
@ -140,18 +139,6 @@ func users(w http.ResponseWriter, r *http.Request) {
|
|||
fmt.Fprint(w, string(encoded))
|
||||
}
|
||||
|
||||
func userInbox(w http.ResponseWriter, r *http.Request) {
|
||||
log := hlog.FromRequest(r)
|
||||
userId := r.PathValue("id")
|
||||
data, err := io.ReadAll(r.Body)
|
||||
log.Info().
|
||||
Err(err).
|
||||
Str("userId", userId).
|
||||
Bytes("body", data).
|
||||
Any("headers", r.Header).
|
||||
Msg("Inbox message")
|
||||
}
|
||||
|
||||
/*
|
||||
Fine. You win JsonLD. I can't get you to work properly. I'll just treat you like normal json then
|
||||
Fuck you.
|
|
@ -70,15 +70,12 @@ func BuildAuthorizedFetchCheck(forNonGet bool, forGet bool) webutils.HandlerBuil
|
|||
}
|
||||
// Not an always open path, check methods
|
||||
if r.Method == "GET" && !forGet {
|
||||
log.Debug().Msg("Get request to AP resources don't need signature")
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
} else if !forGet && !forNonGet {
|
||||
log.Info().Msg("Requests to AP resources don't need signature")
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
log.Debug().Msg("Need signature for AP request")
|
||||
rawDate := r.Header.Get("Date")
|
||||
date, err := http.ParseTime(rawDate)
|
||||
if err != nil {
|
||||
|
@ -105,7 +102,8 @@ func BuildAuthorizedFetchCheck(forNonGet bool, forGet bool) webutils.HandlerBuil
|
|||
}
|
||||
signatureHeader := r.Header.Get("Signature")
|
||||
if signatureHeader == "" {
|
||||
log.Info().Msg("Received AP request without signature header where one is required")
|
||||
log.Debug().
|
||||
Msg("Received AP request without signature header where one is required")
|
||||
webutils.ProblemDetails(
|
||||
w,
|
||||
http.StatusUnauthorized,
|
||||
|
@ -121,7 +119,7 @@ func BuildAuthorizedFetchCheck(forNonGet bool, forGet bool) webutils.HandlerBuil
|
|||
Msg("Signature header of inbound AP request")
|
||||
match := signatureRegex.FindStringSubmatch(signatureHeader)
|
||||
if len(match) <= 1 {
|
||||
log.Info().
|
||||
log.Debug().
|
||||
Str("header", signatureHeader).
|
||||
Msg("Received signature with invalid pattern")
|
||||
webutils.ProblemDetails(
|
||||
|
@ -167,7 +165,7 @@ func BuildAuthorizedFetchCheck(forNonGet bool, forGet bool) webutils.HandlerBuil
|
|||
}
|
||||
_, err = url.Parse(rawKeyId)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Key id is not an url")
|
||||
log.Debug().Err(err).Msg("Key id is not an url")
|
||||
webutils.ProblemDetails(
|
||||
w,
|
||||
http.StatusUnauthorized,
|
||||
|
@ -181,7 +179,6 @@ func BuildAuthorizedFetchCheck(forNonGet bool, forGet bool) webutils.HandlerBuil
|
|||
|
||||
_ = rawAlgorithm
|
||||
stringToCheck := buildStringToCheck(r, rawHeaders)
|
||||
log.Warn().Str("string-to-check", stringToCheck).Send()
|
||||
|
||||
requestingActor, err := getRequestingActor(rawKeyId)
|
||||
if err != nil {
|
||||
|
|
|
@ -28,7 +28,6 @@ package webpublic
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
|
||||
|
@ -44,10 +43,6 @@ type Server struct {
|
|||
|
||||
func New(addr string, duckImg *string, duckFs fs.FS) *Server {
|
||||
handler := http.NewServeMux()
|
||||
handler.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(500)
|
||||
fmt.Fprint(w, "not implemented")
|
||||
})
|
||||
handler.Handle("/api/", http.StripPrefix("/api", api.BuildApiRouter()))
|
||||
handler.HandleFunc("GET /.well-known/webfinger", api.WellKnownWebfinger)
|
||||
handler.HandleFunc("GET /.well-known/nodeinfo", api.NodeInfoOverview)
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yaronf/httpsign"
|
||||
|
||||
"git.mstar.dev/mstar/linstrom/config"
|
||||
|
@ -108,7 +107,6 @@ func RequestSignedCavage(
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debug().Bytes("body", body).Any("headers", req.Header).Msg("Sending signed request")
|
||||
return RequestClient.Do(req)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue