More work on getting auth fetch verification working

This commit is contained in:
Melody Becker 2025-04-20 22:10:35 +02:00
parent 7eac1db475
commit 9957ba8302
12 changed files with 434 additions and 205 deletions

View file

@ -7,6 +7,7 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
@ -52,7 +53,7 @@ type inboundImportUser struct {
RestrictedFollow *bool `json:"manuallyApprovesFollowers"`
}
func ImportRemoteAccount(targetName string) (string, error) {
func ImportRemoteAccountByHandle(targetName string) (string, error) {
// Get the target user's link first
webfinger, err := GetAccountWebfinger(targetName)
if err != nil {
@ -65,177 +66,11 @@ func ImportRemoteAccount(targetName string) (string, error) {
return "", errors.New("no self link")
}
APLink := selfLinks[0]
// Server actor key for signing
linstromActor, err := dbgen.User.Where(dbgen.User.Username.Eq(shared.ServerActorName)).First()
acc, err := ImportRemoteAccountByAPUrl(*APLink.Href)
if err != nil {
return "", other.Error("activitypub", "failed to get server actor", err)
return "", err
}
var response *http.Response
response, err = webshared.RequestSignedCavage("GET", *APLink.Href, nil, linstromActor)
if err != nil {
return "", other.Error("activitypub", "failed to complete cavage signed request", err)
}
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 "", fmt.Errorf("activitypub: invalid status code: %v", response.StatusCode)
}
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).
Preload(dbgen.User.Roles).
FirstOrCreate()
if err != nil {
return "", other.Error("activitypub", "failed to find or create user in db", err)
}
user.Verified = true
user.FinishedRegistration = true
if !sliceutils.ContainsFunc(user.Roles, func(t models.UserToRole) bool {
return t.Role.ID == models.DefaultUserRole.ID
}) {
roleMapping := models.UserToRole{
Role: models.DefaultUserRole,
RoleId: models.DefaultUserRole.ID,
UserId: user.ID,
}
if err = dbgen.UserToRole.Create(&roleMapping); err != nil {
return "", other.Error(
"activitypub",
"failed to store default user role to imported account mapping",
err,
)
}
if err = dbgen.User.Roles.Model(user).Append(&roleMapping); err != nil {
return "", other.Error("activitypub", "failed to attach default role to user", err)
}
}
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(string(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 id, nil
return acc.ID, nil
}
func ImportRemoteServer(host string) (uint, error) {
@ -282,7 +117,7 @@ func ImportRemoteServer(host string) (uint, error) {
if err != nil {
return 0, other.Error("activitypub", "failed to unmarshal info", err)
}
log.Debug().Any("nodeinfo", data).Msg("Server info received")
// log.Debug().Any("nodeinfo", data).Msg("Server info received")
rs := dbgen.RemoteServer
// rsm := dbgen.RemoteServerMetadata
serverModelType := webshared.MapNodeServerTypeToModelType(data.Software.Name)
@ -571,3 +406,181 @@ func ImportRemoteServer(host string) (uint, error) {
}
return id, nil
}
func ImportRemoteAccountByAPUrl(apUrl string) (*models.User, error) {
log.Info().Str("ap-url", apUrl).Msg("Importing account by ap url")
// Server actor key for signing
linstromActor, err := dbgen.User.Where(dbgen.User.Username.Eq(shared.ServerActorName)).First()
if err != nil {
return nil, other.Error("activitypub", "failed to get server actor", err)
}
var response *http.Response
response, err = webshared.RequestSignedCavage("GET", apUrl, nil, linstromActor)
if err != nil {
return nil, other.Error("activitypub", "failed to complete cavage signed request", err)
}
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)
}
var data inboundImportUser
err = json.Unmarshal(body, &data)
if err != nil {
return nil, other.Error("activitypub", "failed to unmarshal response", err)
}
// log.Debug().Any("received-data", data).Msg("Response data")
targetUrl, err := url.Parse(apUrl)
if err != nil {
return nil, other.Error("activitypub", "failed to parse url as url", err)
}
hostId, err := ImportRemoteServer(targetUrl.Host)
if err != nil {
return nil, other.Error("activitypub", "failed to import host of target user", err)
}
user, err := dbgen.User.
Where(dbgen.User.Username.Eq(data.PreferredUsername + "@" + targetUrl.Host)).
Where(dbgen.User.ServerId.Eq(hostId)).
Preload(dbgen.User.RemoteInfo).
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)
}
user.Verified = true
user.FinishedRegistration = true
if !sliceutils.ContainsFunc(user.Roles, func(t models.UserToRole) bool {
return t.Role.ID == models.DefaultUserRole.ID
}) {
roleMapping := models.UserToRole{
Role: models.DefaultUserRole,
RoleId: models.DefaultUserRole.ID,
UserId: user.ID,
}
if err = dbgen.UserToRole.Create(&roleMapping); err != nil {
return nil, other.Error(
"activitypub",
"failed to store default user role to imported account mapping",
err,
)
}
if err = dbgen.User.Roles.Model(user).Append(&roleMapping); err != nil {
return nil, other.Error("activitypub", "failed to attach default role to user", err)
}
}
if user.RemoteInfo == nil {
user.RemoteInfo = &models.UserRemoteLinks{
UserId: user.ID,
ApLink: data.Id,
}
err = dbgen.UserRemoteLinks.Create(user.RemoteInfo)
if err != nil {
return nil, other.Error("activitypub", "failed to create remote data for user", err)
}
err = dbgen.User.RemoteInfo.Model(user).Replace(user.RemoteInfo)
if err != nil {
return nil, 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 nil, 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(string(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 nil, 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 nil, other.Error("activitypub", "failed to update imported user's remote links", err)
}
user.ID = id
log.Info().Str("ap-url", apUrl).Msg("Import completed")
return user, nil
}