mirror of
https://github.com/spacebarchat/server.git
synced 2024-11-22 10:22:39 +01:00
send accept when follow is received
This commit is contained in:
parent
b6df59fd45
commit
b993216651
@ -1,9 +1,33 @@
|
||||
import { Message, MessageCreateEvent, emitEvent } from "@spacebar/util";
|
||||
import { APCreate, AnyAPObject, ObjectIsNote } from "activitypub-types";
|
||||
import {
|
||||
ActorType,
|
||||
FederationActivity,
|
||||
FederationKey,
|
||||
Invite,
|
||||
Member,
|
||||
Message,
|
||||
MessageCreateEvent,
|
||||
emitEvent,
|
||||
} from "@spacebar/util";
|
||||
import {
|
||||
APAccept,
|
||||
APCreate,
|
||||
APFollow,
|
||||
AnyAPObject,
|
||||
ObjectIsNote,
|
||||
} from "activitypub-types";
|
||||
import { Request } from "express";
|
||||
import { HttpSig } from "../HttpSig";
|
||||
import { federationQueue } from "../queue";
|
||||
import { transformNoteToMessage } from "../transforms";
|
||||
import { APError, hasAPContext, resolveAPObject } from "../utils";
|
||||
import { APFollowWithInvite } from "../types";
|
||||
import {
|
||||
ACTIVITYSTREAMS_CONTEXT,
|
||||
APError,
|
||||
fetchFederatedUser,
|
||||
hasAPContext,
|
||||
resolveAPObject,
|
||||
splitQualifiedMention,
|
||||
} from "../utils";
|
||||
|
||||
/**
|
||||
* Key names are derived from the object type names
|
||||
@ -37,6 +61,27 @@ const handlers = {
|
||||
message.save(),
|
||||
]);
|
||||
},
|
||||
|
||||
Follow: async (activity: APFollow) => {
|
||||
// dummy: send back Accept regardless
|
||||
|
||||
if (typeof activity.object != "string")
|
||||
throw new APError("not implemented");
|
||||
const mention = splitQualifiedMention(activity.object);
|
||||
|
||||
const keys = await FederationKey.findOneOrFail({
|
||||
where: { domain: mention.domain, actorId: mention.user },
|
||||
});
|
||||
|
||||
switch (keys.type) {
|
||||
case ActorType.GUILD:
|
||||
if (typeof activity.actor != "string")
|
||||
throw new APError("not implemented");
|
||||
return addRemoteUserToGuild(activity.actor, keys, activity);
|
||||
default:
|
||||
throw new APError("not implemented");
|
||||
}
|
||||
},
|
||||
} as Record<string, (activity: AnyAPObject) => Promise<unknown>>;
|
||||
|
||||
export const genericInboxHandler = async (req: Request) => {
|
||||
@ -78,3 +123,34 @@ export const genericInboxHandler = async (req: Request) => {
|
||||
console.warn(`Activity of type ${type} not implemented`);
|
||||
throw new APError(`Activity of type ${type} not implemented`);
|
||||
};
|
||||
|
||||
const addRemoteUserToGuild = async (
|
||||
actor: string,
|
||||
guild: FederationKey,
|
||||
follow: APFollow,
|
||||
) => {
|
||||
const invite = (follow as APFollowWithInvite).invite;
|
||||
if (!invite) throw new APError("Requires invite");
|
||||
|
||||
await Invite.findOneOrFail({
|
||||
where: {
|
||||
guild_id: guild.actorId,
|
||||
code: splitQualifiedMention(invite).user,
|
||||
},
|
||||
});
|
||||
|
||||
const { entity, keys } = await fetchFederatedUser(actor);
|
||||
|
||||
await Member.addToGuild(entity.id, guild.actorId);
|
||||
|
||||
const accept = await FederationActivity.create({
|
||||
data: {
|
||||
type: "Accept",
|
||||
"@context": ACTIVITYSTREAMS_CONTEXT,
|
||||
actor: guild.federatedId,
|
||||
object: follow,
|
||||
} as APAccept,
|
||||
}).save();
|
||||
|
||||
federationQueue.distribute(accept.toJSON());
|
||||
};
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Config, FederationKey } from "@spacebar/util";
|
||||
import { APActivity } from "activitypub-types";
|
||||
import { Config, Debug, FederationKey } from "@spacebar/util";
|
||||
import { APActivity, ActivityIsFollow } from "activitypub-types";
|
||||
import fetch from "node-fetch";
|
||||
import { HttpSig } from "./HttpSig";
|
||||
import { APError, splitQualifiedMention } from "./utils";
|
||||
import { APError, LOG_NAMES, splitQualifiedMention } from "./utils";
|
||||
|
||||
//
|
||||
type Instance = string;
|
||||
@ -15,6 +15,8 @@ class FederationQueue {
|
||||
let { actor } = activity;
|
||||
const { to, object } = activity;
|
||||
|
||||
Debug(LOG_NAMES.remote, `distributing activity ${activity.id}`);
|
||||
|
||||
if (!actor)
|
||||
throw new APError("Activity with no actor cannot be signed.");
|
||||
if (Array.isArray(actor)) actor = actor[0];
|
||||
@ -43,7 +45,11 @@ class FederationQueue {
|
||||
if (!recv) continue;
|
||||
|
||||
// this is wrong?
|
||||
if (typeof recv != "string") continue;
|
||||
if (typeof recv != "string") {
|
||||
if (ActivityIsFollow(recv)) {
|
||||
recv = recv.actor!.toString();
|
||||
} else continue;
|
||||
}
|
||||
|
||||
if (recv == "https://www.w3.org/ns/activitystreams#Public") {
|
||||
console.debug(`TODO: Skipping sending activity to #Public`);
|
||||
@ -58,6 +64,7 @@ class FederationQueue {
|
||||
// TODO: this is bad
|
||||
if (!recv.includes("/inbox")) recv = `${recv}/inbox`;
|
||||
|
||||
Debug(LOG_NAMES.remote, `sending activity to ${recv}`);
|
||||
await this.signAndSend(activity, sender, recv);
|
||||
}
|
||||
}
|
||||
|
@ -340,7 +340,7 @@ export const transformOrganisationToGuild = async (org: APOrganization) => {
|
||||
const guild = Guild.create({
|
||||
id: keys.actorId,
|
||||
name: org.name,
|
||||
owner_id: owner.user.id,
|
||||
owner_id: owner.entity.id,
|
||||
});
|
||||
|
||||
await Promise.all([guild.save(), keys.save()]);
|
||||
|
5
src/activitypub/federation/types.ts
Normal file
5
src/activitypub/federation/types.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { APFollow } from "activitypub-types";
|
||||
|
||||
export type APFollowWithInvite = APFollow & {
|
||||
invite: string;
|
||||
};
|
@ -1,11 +1,13 @@
|
||||
import { DEFAULT_FETCH_OPTIONS } from "@spacebar/api";
|
||||
import {
|
||||
ActorType,
|
||||
BaseClass,
|
||||
Config,
|
||||
Debug,
|
||||
FederationActivity,
|
||||
FederationCache,
|
||||
FederationKey,
|
||||
Guild,
|
||||
OrmUtils,
|
||||
Snowflake,
|
||||
User,
|
||||
@ -13,7 +15,6 @@ import {
|
||||
WebfingerResponse,
|
||||
} from "@spacebar/util";
|
||||
import {
|
||||
APFollow,
|
||||
APObject,
|
||||
APPerson,
|
||||
AnyAPObject,
|
||||
@ -26,6 +27,7 @@ import fetch from "node-fetch";
|
||||
import { ProxyAgent } from "proxy-agent";
|
||||
import TurndownService from "turndown";
|
||||
import { federationQueue } from "./queue";
|
||||
import { APFollowWithInvite } from "./types";
|
||||
|
||||
export const ACTIVITYSTREAMS_CONTEXT = "https://www.w3.org/ns/activitystreams";
|
||||
export const LOG_NAMES = {
|
||||
@ -142,7 +144,9 @@ export const tryResolveWebfinger = async (lookup: string) => {
|
||||
};
|
||||
|
||||
/** Fetch from local db, if not found fetch from remote instance and save */
|
||||
export const fetchFederatedUser = async (actorId: string) => {
|
||||
export const fetchFederatedUser = async (
|
||||
actorId: string,
|
||||
): Promise<{ keys: FederationKey; entity: BaseClass }> => {
|
||||
// if we were given webfinger, resolve that first
|
||||
const mention = splitQualifiedMention(actorId);
|
||||
const cache = await FederationKey.findOne({
|
||||
@ -151,7 +155,7 @@ export const fetchFederatedUser = async (actorId: string) => {
|
||||
if (cache) {
|
||||
return {
|
||||
keys: cache,
|
||||
user: await User.findOneOrFail({ where: { id: cache.actorId } }),
|
||||
entity: await User.findOneOrFail({ where: { id: cache.actorId } }),
|
||||
};
|
||||
}
|
||||
|
||||
@ -187,33 +191,46 @@ export const fetchFederatedUser = async (actorId: string) => {
|
||||
outbox: remoteActor.outbox,
|
||||
});
|
||||
|
||||
const user = User.create({
|
||||
id: keys.actorId,
|
||||
username: remoteActor.name,
|
||||
discriminator: "0",
|
||||
bio: new TurndownService().turndown(remoteActor.summary), // html -> markdown
|
||||
email: `${remoteActor.preferredUsername}@${keys.domain}`,
|
||||
data: {
|
||||
hash: "#",
|
||||
valid_tokens_since: new Date(),
|
||||
},
|
||||
extended_settings: "{}",
|
||||
settings: UserSettings.create(),
|
||||
premium: false,
|
||||
let entity: BaseClass | undefined = undefined;
|
||||
if (type == ActorType.USER)
|
||||
entity = User.create({
|
||||
id: keys.actorId,
|
||||
username: remoteActor.name,
|
||||
discriminator: "0",
|
||||
bio: new TurndownService().turndown(remoteActor.summary), // html -> markdown
|
||||
email: `${remoteActor.preferredUsername}@${keys.domain}`,
|
||||
data: {
|
||||
hash: "#",
|
||||
valid_tokens_since: new Date(),
|
||||
},
|
||||
extended_settings: "{}",
|
||||
settings: UserSettings.create(),
|
||||
premium: false,
|
||||
|
||||
premium_since: Config.get().defaults.user.premium
|
||||
? new Date()
|
||||
: undefined,
|
||||
rights: Config.get().register.defaultRights,
|
||||
premium_type: Config.get().defaults.user.premiumType ?? 0,
|
||||
verified: Config.get().defaults.user.verified ?? true,
|
||||
created_at: new Date(),
|
||||
});
|
||||
premium_since: Config.get().defaults.user.premium
|
||||
? new Date()
|
||||
: undefined,
|
||||
rights: Config.get().register.defaultRights,
|
||||
premium_type: Config.get().defaults.user.premiumType ?? 0,
|
||||
verified: Config.get().defaults.user.verified ?? true,
|
||||
created_at: new Date(),
|
||||
});
|
||||
|
||||
await Promise.all([keys.save(), user.save()]);
|
||||
if (type == ActorType.GUILD)
|
||||
entity = Guild.create({
|
||||
id: keys.actorId,
|
||||
name: remoteActor.name,
|
||||
owner_id: (
|
||||
await fetchFederatedUser(remoteActor.attributedTo!.toString())
|
||||
).entity.id,
|
||||
});
|
||||
|
||||
if (!entity) throw new APError("not possible :3");
|
||||
|
||||
await Promise.all([keys.save(), entity.save()]);
|
||||
return {
|
||||
keys,
|
||||
user,
|
||||
entity,
|
||||
};
|
||||
};
|
||||
|
||||
@ -232,7 +249,8 @@ export const tryFederatedGuildJoin = async (code: string, user_id: string) => {
|
||||
type: "Follow",
|
||||
actor: `https://${host}/federation/users/${user_id}`,
|
||||
object: guild.id,
|
||||
} as APFollow,
|
||||
invite: code,
|
||||
} as APFollowWithInvite,
|
||||
}).save();
|
||||
|
||||
await federationQueue.distribute(follow.toJSON());
|
||||
|
Loading…
Reference in New Issue
Block a user