User import now works
Some checks failed
/ docker (push) Failing after 3m56s

This commit is contained in:
Melody Becker 2025-04-15 17:18:56 +02:00
parent 08f6de0bd7
commit 5e93ecee73
Signed by: mstar
SSH key fingerprint: SHA256:9VAo09aaVNTWKzPW7Hq2LW+ox9OdwmTSHRoD4mlz1yI
12 changed files with 241 additions and 109 deletions

View file

@ -2,6 +2,7 @@ package activitypub
import (
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"io"
@ -19,48 +20,39 @@ import (
webshared "git.mstar.dev/mstar/linstrom/web/shared"
)
var interestingMetadata = []string{
"nodeDescription",
"maintainer",
"tosUrl",
"nodeAdmins",
"privacyPolicyUrl",
"inquiryUrl",
"impressumUrl",
"donationUrl",
"staffAccounts",
type inboundImportUserKey struct {
Id string `json:"id"`
Owner string `json:"owner"`
Pem string `json:"publicKeyPem"`
}
type inboundImportUserMedia struct {
Type string `json:"type"`
Url string `json:"url"`
MediaType string `json:"mediaType"`
}
type inboundImportUser struct {
Id string `json:"id"`
Type string `json:"type"`
PreferredUsername string `json:"preferredUsername"`
Inbox string `json:"inbox"`
Outbox *string `json:"outbox"`
PublicKey *inboundImportUserKey `json:"publicKey"`
Published *time.Time `json:"published"`
DisplayName *string `json:"name"`
Description *string `json:"summary,omitempty"`
PublicUrl *string `json:"url"`
Icon *inboundImportUserMedia `json:"icon,omitempty"`
Banner *inboundImportUserMedia `json:"image,omitempty"`
Discoverable *bool `json:"discoverable"`
Location *string `json:"vcard:Address,omitempty"`
Birthday *string `json:"vcard:bday,omitempty"`
SpeakAsCat bool `json:"speakAsCat"`
IsCat bool `json:"isCat"`
RestrictedFollow *bool `json:"manuallyApprovesFollowers"`
}
func ImportRemoteAccount(targetName string) (string, error) {
type InboundUserKey struct {
Id string `json:"id"`
Owner string `json:"owner"`
Pem string `json:"publicKeyPem"`
}
type InboundUserMedia struct {
Type string `json:"type"`
Url string `json:"url"`
MediaType string `json:"mediaType"`
}
type InboundUser struct {
Id string `json:"id"`
Type string `json:"type"`
PreferredUsername string `json:"preferredUsername"`
Inbox string `json:"inbox"`
PublicKey *InboundUserKey `json:"publicKey"`
Published *time.Time `json:"published"`
DisplayName *string `json:"name"`
Description *string `json:"summary,omitempty"`
PublicUrl *string `json:"url"`
Icon *InboundUserMedia `json:"icon,omitempty"`
Banner *InboundUserMedia `json:"image,omitempty"`
Discoverable *bool `json:"discoverable"`
Location *string `json:"vcard:Address,omitempty"`
Birthday *string `json:"vcard:bday,omitempty"`
SpeakAsCat bool `json:"speakAsCat"`
IsCat bool `json:"isCat"`
RestrictedFollow *bool `json:"manuallyApprovesFollowers"`
}
// Get the target user's link first
webfinger, err := GetAccountWebfinger(targetName)
if err != nil {
@ -93,14 +85,137 @@ func ImportRemoteAccount(targetName string) (string, error) {
if response.StatusCode != 200 {
return "", fmt.Errorf("activitypub: invalid status code: %v", response.StatusCode)
}
var data InboundUser
var data inboundImportUser
err = json.Unmarshal(body, &data)
if err != nil {
return "", other.Error("activitypub", "failed to unmarshal response", err)
}
log.Debug().Any("received-data", data).Msg("Response data")
// TODO: Store received user in db
_, host, _ := SplitFullHandle(targetName)
hostId, err := ImportRemoteServer(host)
if err != nil {
return "", other.Error("activitypub", "failed to import host of target user", err)
}
user, err := dbgen.User.
Where(dbgen.User.Username.Eq(targetName)).
Where(dbgen.User.ServerId.Eq(hostId)).
Preload(dbgen.User.RemoteInfo).
Preload(dbgen.User.InfoFields).
Preload(dbgen.User.BeingTypes).
FirstOrCreate()
if err != nil {
return "", other.Error("activitypub", "failed to find or create user in db", err)
}
user.Verified = true
user.FinishedRegistration = true
if user.RemoteInfo == nil {
user.RemoteInfo = &models.UserRemoteLinks{
UserId: user.ID,
ApLink: data.Id,
}
err = dbgen.UserRemoteLinks.Create(user.RemoteInfo)
if err != nil {
return "", other.Error("activitypub", "failed to create remote data for user", err)
}
err = dbgen.User.RemoteInfo.Model(user).Replace(user.RemoteInfo)
if err != nil {
return "", other.Error("activitypub", "failed to connect remote data to user", err)
}
user.RemoteInfoId.Int64 = int64(user.RemoteInfo.ID)
user.RemoteInfoId.Valid = true
}
if data.DisplayName != nil {
user.DisplayName = *data.DisplayName
}
if data.Outbox != nil {
user.RemoteInfo.OutboxLink.String = *data.Outbox
user.RemoteInfo.OutboxLink.Valid = true
}
user.RemoteInfo.InboxLink = data.Inbox
if data.PublicKey != nil {
pemBlock, _ := pem.Decode([]byte(data.PublicKey.Pem))
if pemBlock.Type != "PUBLIC KEY" && pemBlock.Type != "RSA PUBLIC KEY" {
return "", fmt.Errorf("activitypub: invalid public key block type: %v", pemBlock.Type)
}
user.PublicKeyRsa = pemBlock.Bytes
}
// Assume published day of user won't change
if data.Description != nil {
user.Description = *data.Description
}
if data.PublicUrl != nil {
user.RemoteInfo.ViewLink.String = *data.PublicUrl
user.RemoteInfo.ViewLink.Valid = true
}
if data.Discoverable != nil {
user.Indexable = *data.Discoverable
} else {
// Assume false per default
user.Indexable = false
}
if data.Location != nil {
user.Location.String = *data.Location
user.Location.Valid = true
}
if data.Birthday != nil {
user.Birthday.String = *data.Birthday
user.Birthday.Valid = true
}
if data.RestrictedFollow != nil {
user.RestrictedFollow = *data.RestrictedFollow
} else {
// Assume not restricted if not included in received data
user.RestrictedFollow = false
}
if data.IsCat {
if !sliceutils.ContainsFunc(user.BeingTypes, func(t models.UserToBeing) bool {
return t.Being == string(models.BEING_CAT)
}) {
log.Debug().Msg("user doesn't contain cat yet")
bt := models.UserToBeing{UserId: user.ID, Being: string(models.BEING_CAT)}
if err = dbgen.UserToBeing.Create(&bt); err != nil {
log.Warn().Err(err).Msg("Failed to append cat being type to imported user")
}
if err = dbgen.User.BeingTypes.Model(user).Append(&bt); err != nil {
log.Warn().
Err(err).
Msg("Failed to append cat being type to imported user relations")
}
user.BeingTypes = append(user.BeingTypes, bt)
}
} else {
if sliceutils.ContainsFunc(user.BeingTypes, func(t models.UserToBeing) bool {
return t.Being == string(models.BEING_CAT)
}) {
_, err = dbgen.UserToBeing.Where(dbgen.UserToBeing.UserId.Eq(user.ID)).Where(dbgen.UserToBeing.Being.Eq(models.BEING_CAT)).Delete()
if err != nil {
log.Warn().Err(err).Msg("Failed to remove cat being type from user")
}
}
}
// Don't handle SpeakAsCat yet, as not included in stored data yet
// Handle media last as more complicated
// Icon *inboundImportUserMedia `json:"icon,omitempty"`
// Banner *inboundImportUserMedia `json:"image,omitempty"`
id := user.ID
user.ID = ""
if _, err = dbgen.User.Where(dbgen.User.ID.Eq(id)).UpdateColumns(user); err != nil {
return "", other.Error("activitypub", "failed to update imported user", err)
}
rlid := user.RemoteInfo.ID
user.RemoteInfo.ID = 0
if _, err = dbgen.UserRemoteLinks.Where(dbgen.UserRemoteLinks.ID.Eq(rlid)).UpdateColumns(user.RemoteInfo); err != nil {
return "", other.Error("activitypub", "failed to update imported user's remote links", err)
}
log.Info().Str("user", targetName).Msg("Import completed")
return "", nil
return id, nil
}
func ImportRemoteServer(host string) (uint, error) {