[backend] Handle incoming ASAccept activities
This commit is contained in:
parent
8c9e6ef56c
commit
97e4a25742
4 changed files with 57 additions and 6 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
using Iceshrimp.Backend.Core.Configuration;
|
||||||
using Iceshrimp.Backend.Core.Database;
|
using Iceshrimp.Backend.Core.Database;
|
||||||
using Iceshrimp.Backend.Core.Database.Tables;
|
using Iceshrimp.Backend.Core.Database.Tables;
|
||||||
using Iceshrimp.Backend.Core.Federation.ActivityStreams;
|
using Iceshrimp.Backend.Core.Federation.ActivityStreams;
|
||||||
|
@ -7,6 +8,7 @@ using Iceshrimp.Backend.Core.Middleware;
|
||||||
using Iceshrimp.Backend.Core.Queues;
|
using Iceshrimp.Backend.Core.Queues;
|
||||||
using Iceshrimp.Backend.Core.Services;
|
using Iceshrimp.Backend.Core.Services;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Iceshrimp.Backend.Core.Federation.ActivityPub;
|
namespace Iceshrimp.Backend.Core.Federation.ActivityPub;
|
||||||
|
|
||||||
|
@ -16,7 +18,8 @@ public class ActivityHandlerService(
|
||||||
UserResolver userResolver,
|
UserResolver userResolver,
|
||||||
DatabaseContext db,
|
DatabaseContext db,
|
||||||
QueueService queueService,
|
QueueService queueService,
|
||||||
ActivityRenderer activityRenderer
|
ActivityRenderer activityRenderer,
|
||||||
|
IOptions<Config.InstanceSection> config
|
||||||
) {
|
) {
|
||||||
public Task PerformActivityAsync(ASActivity activity, string? inboxUserId) {
|
public Task PerformActivityAsync(ASActivity activity, string? inboxUserId) {
|
||||||
logger.LogDebug("Processing activity: {activity}", activity.Id);
|
logger.LogDebug("Processing activity: {activity}", activity.Id);
|
||||||
|
@ -38,6 +41,10 @@ public class ActivityHandlerService(
|
||||||
if (activity.Object is { } obj) return UnfollowAsync(obj, activity.Actor);
|
if (activity.Object is { } obj) return UnfollowAsync(obj, activity.Actor);
|
||||||
throw GracefulException.UnprocessableEntity("Unfollow activity object is invalid");
|
throw GracefulException.UnprocessableEntity("Unfollow activity object is invalid");
|
||||||
}
|
}
|
||||||
|
case ASActivity.Types.Accept: {
|
||||||
|
if (activity.Object is { } obj) return AcceptAsync(obj, activity.Actor);
|
||||||
|
throw GracefulException.UnprocessableEntity("Follow activity object is invalid");
|
||||||
|
}
|
||||||
case ASActivity.Types.Undo: {
|
case ASActivity.Types.Undo: {
|
||||||
//TODO: implement the rest
|
//TODO: implement the rest
|
||||||
//TODO: test if this actually works
|
//TODO: test if this actually works
|
||||||
|
@ -118,4 +125,40 @@ public class ActivityHandlerService(
|
||||||
await db.FollowRequests.Where(p => p.Follower == follower && p.Followee == followee).ExecuteDeleteAsync();
|
await db.FollowRequests.Where(p => p.Follower == follower && p.Followee == followee).ExecuteDeleteAsync();
|
||||||
await db.Followings.Where(p => p.Follower == follower && p.Followee == followee).ExecuteDeleteAsync();
|
await db.Followings.Where(p => p.Follower == follower && p.Followee == followee).ExecuteDeleteAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task AcceptAsync(ASObject obj, ASObject actor) {
|
||||||
|
var prefix = $"https://{config.Value.WebDomain}/follows/";
|
||||||
|
if (!obj.Id.StartsWith(prefix))
|
||||||
|
throw GracefulException.UnprocessableEntity($"Object id '{obj.Id}' not a valid follow request");
|
||||||
|
|
||||||
|
var resolvedActor = await userResolver.ResolveAsync(actor.Id);
|
||||||
|
var ids = obj.Id[prefix.Length..].TrimEnd('/').Split("/");
|
||||||
|
if (ids.Length != 2 || ids[1] != resolvedActor.Id)
|
||||||
|
throw GracefulException
|
||||||
|
.UnprocessableEntity($"Actor id '{resolvedActor.Id}' doesn't match followee id '{ids[1]}'");
|
||||||
|
|
||||||
|
var request = await db.FollowRequests
|
||||||
|
.Include(p => p.Follower)
|
||||||
|
.FirstOrDefaultAsync(p => p.Followee == resolvedActor && p.FollowerId == ids[0]);
|
||||||
|
|
||||||
|
if (request == null)
|
||||||
|
throw GracefulException
|
||||||
|
.UnprocessableEntity($"No follow request matching follower '{ids[0]}' and followee '{resolvedActor.Id}' found");
|
||||||
|
|
||||||
|
var following = new Following {
|
||||||
|
Id = IdHelpers.GenerateSlowflakeId(),
|
||||||
|
CreatedAt = DateTime.UtcNow,
|
||||||
|
Follower = request.Follower,
|
||||||
|
Followee = resolvedActor,
|
||||||
|
FollowerHost = request.FollowerHost,
|
||||||
|
FolloweeHost = request.FolloweeHost,
|
||||||
|
FollowerInbox = request.FollowerInbox,
|
||||||
|
FolloweeInbox = request.FolloweeInbox,
|
||||||
|
FollowerSharedInbox = request.FollowerSharedInbox,
|
||||||
|
FolloweeSharedInbox = request.FolloweeSharedInbox
|
||||||
|
};
|
||||||
|
db.Remove(request);
|
||||||
|
await db.AddAsync(following);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -9,7 +9,7 @@ public class ActivityRenderer(IOptions<Config.InstanceSection> config) {
|
||||||
public static ASActivity RenderCreate(ASObject obj, ASObject actor) {
|
public static ASActivity RenderCreate(ASObject obj, ASObject actor) {
|
||||||
return new ASActivity {
|
return new ASActivity {
|
||||||
Id = $"{obj.Id}#Create",
|
Id = $"{obj.Id}#Create",
|
||||||
Type = "https://www.w3.org/ns/activitystreams#Create",
|
Type = ASActivity.Types.Create,
|
||||||
Actor = new ASActor { Id = actor.Id },
|
Actor = new ASActor { Id = actor.Id },
|
||||||
Object = obj
|
Object = obj
|
||||||
};
|
};
|
||||||
|
@ -18,7 +18,7 @@ public class ActivityRenderer(IOptions<Config.InstanceSection> config) {
|
||||||
public ASActivity RenderAccept(ASObject actor, ASObject obj) {
|
public ASActivity RenderAccept(ASObject actor, ASObject obj) {
|
||||||
return new ASActivity {
|
return new ASActivity {
|
||||||
Id = $"https://{config.Value.WebDomain}/activities/{Guid.NewGuid().ToString().ToLowerInvariant()}",
|
Id = $"https://{config.Value.WebDomain}/activities/{Guid.NewGuid().ToString().ToLowerInvariant()}",
|
||||||
Type = "https://www.w3.org/ns/activitystreams#Accept",
|
Type = ASActivity.Types.Accept,
|
||||||
Actor = new ASActor {
|
Actor = new ASActor {
|
||||||
Id = actor.Id
|
Id = actor.Id
|
||||||
},
|
},
|
||||||
|
|
|
@ -30,6 +30,14 @@ public class ASFollow : ASActivity {
|
||||||
public ASFollow() => Type = Types.Follow;
|
public ASFollow() => Type = Types.Follow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ASUnfollow : ASActivity {
|
||||||
|
public ASUnfollow() => Type = Types.Unfollow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ASAccept : ASActivity {
|
||||||
|
public ASAccept() => Type = Types.Accept;
|
||||||
|
}
|
||||||
|
|
||||||
//TODO: add the rest
|
//TODO: add the rest
|
||||||
|
|
||||||
public sealed class ASActivityConverter : ASSerializer.ListSingleObjectConverter<ASActivity>;
|
public sealed class ASActivityConverter : ASSerializer.ListSingleObjectConverter<ASActivity>;
|
|
@ -25,9 +25,9 @@ public class ASObject {
|
||||||
ASNote.Types.Note => token.ToObject<ASNote>(),
|
ASNote.Types.Note => token.ToObject<ASNote>(),
|
||||||
ASActivity.Types.Create => token.ToObject<ASActivity>(),
|
ASActivity.Types.Create => token.ToObject<ASActivity>(),
|
||||||
ASActivity.Types.Delete => token.ToObject<ASActivity>(),
|
ASActivity.Types.Delete => token.ToObject<ASActivity>(),
|
||||||
ASActivity.Types.Follow => token.ToObject<ASActivity>(),
|
ASActivity.Types.Follow => token.ToObject<ASFollow>(),
|
||||||
ASActivity.Types.Unfollow => token.ToObject<ASActivity>(),
|
ASActivity.Types.Unfollow => token.ToObject<ASUnfollow>(),
|
||||||
ASActivity.Types.Accept => token.ToObject<ASActivity>(),
|
ASActivity.Types.Accept => token.ToObject<ASAccept>(),
|
||||||
ASActivity.Types.Undo => token.ToObject<ASActivity>(),
|
ASActivity.Types.Undo => token.ToObject<ASActivity>(),
|
||||||
ASActivity.Types.Like => token.ToObject<ASActivity>(),
|
ASActivity.Types.Like => token.ToObject<ASActivity>(),
|
||||||
_ => token.ToObject<ASObject>()
|
_ => token.ToObject<ASObject>()
|
||||||
|
|
Loading…
Add table
Reference in a new issue