fix: add id for activitypub follows (#8689)

* add id for activitypub follows

* fix lint

* fix: follower must be local, followee must be remote

Misskey will only use ActivityPub follow requests for users that are local
and are requesting to follow a remote user. This check is to ensure that
this endpoint can not be used by other services or instances.

* fix: missing import

* render block with id

* fix comment
This commit is contained in:
Johann150 2022-06-04 06:52:42 +02:00 committed by GitHub
parent 9954c054a7
commit 32dff28460
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 63 additions and 15 deletions

View file

@ -1,8 +1,20 @@
import config from '@/config/index.js'; import config from '@/config/index.js';
import { ILocalUser, IRemoteUser } from '@/models/entities/user.js'; import { Blocking } from '@/models/entities/blocking.js';
export default (blocker: ILocalUser, blockee: IRemoteUser) => ({ /**
* Renders a block into its ActivityPub representation.
*
* @param block The block to be rendered. The blockee relation must be loaded.
*/
export function renderBlock(block: Blocking) {
if (block.blockee?.url == null) {
throw new Error('renderBlock: missing blockee uri');
}
return {
type: 'Block', type: 'Block',
actor: `${config.url}/users/${blocker.id}`, id: `${config.url}/blocks/${block.id}`,
object: blockee.uri, actor: `${config.url}/users/${block.blockerId}`,
}); object: block.blockee.uri,
};
}

View file

@ -4,12 +4,11 @@ import { Users } from '@/models/index.js';
export default (follower: { id: User['id']; host: User['host']; uri: User['host'] }, followee: { id: User['id']; host: User['host']; uri: User['host'] }, requestId?: string) => { export default (follower: { id: User['id']; host: User['host']; uri: User['host'] }, followee: { id: User['id']; host: User['host']; uri: User['host'] }, requestId?: string) => {
const follow = { const follow = {
id: requestId ?? `${config.url}/follows/${follower.id}/${followee.id}`,
type: 'Follow', type: 'Follow',
actor: Users.isLocalUser(follower) ? `${config.url}/users/${follower.id}` : follower.uri, actor: Users.isLocalUser(follower) ? `${config.url}/users/${follower.id}` : follower.uri,
object: Users.isLocalUser(followee) ? `${config.url}/users/${followee.id}` : followee.uri, object: Users.isLocalUser(followee) ? `${config.url}/users/${followee.id}` : followee.uri,
} as any; } as any;
if (requestId) follow.id = requestId;
return follow; return follow;
}; };

View file

@ -15,9 +15,10 @@ import { inbox as processInbox } from '@/queue/index.js';
import { isSelfHost } from '@/misc/convert-host.js'; import { isSelfHost } from '@/misc/convert-host.js';
import { Notes, Users, Emojis, NoteReactions } from '@/models/index.js'; import { Notes, Users, Emojis, NoteReactions } from '@/models/index.js';
import { ILocalUser, User } from '@/models/entities/user.js'; import { ILocalUser, User } from '@/models/entities/user.js';
import { In, IsNull } from 'typeorm'; import { In, IsNull, Not } from 'typeorm';
import { renderLike } from '@/remote/activitypub/renderer/like.js'; import { renderLike } from '@/remote/activitypub/renderer/like.js';
import { getUserKeypair } from '@/misc/keypair-store.js'; import { getUserKeypair } from '@/misc/keypair-store.js';
import renderFollow from '@/remote/activitypub/renderer/follow.js';
// Init router // Init router
const router = new Router(); const router = new Router();
@ -224,4 +225,30 @@ router.get('/likes/:like', async ctx => {
setResponseType(ctx); setResponseType(ctx);
}); });
// follow
router.get('/follows/:follower/:followee', async ctx => {
// This may be used before the follow is completed, so we do not
// check if the following exists.
const [follower, followee] = await Promise.all([
Users.findOneBy({
id: ctx.params.follower,
host: IsNull(),
}),
Users.findOneBy({
id: ctx.params.followee,
host: Not(IsNull()),
}),
]);
if (follower == null || followee == null) {
ctx.status = 404;
return;
}
ctx.body = renderActivity(renderFollow(follower, followee));
ctx.set('Cache-Control', 'public, max-age=180');
setResponseType(ctx);
});
export default router; export default router;

View file

@ -2,9 +2,10 @@ import { publishMainStream, publishUserEvent } from '@/services/stream.js';
import { renderActivity } from '@/remote/activitypub/renderer/index.js'; import { renderActivity } from '@/remote/activitypub/renderer/index.js';
import renderFollow from '@/remote/activitypub/renderer/follow.js'; import renderFollow from '@/remote/activitypub/renderer/follow.js';
import renderUndo from '@/remote/activitypub/renderer/undo.js'; import renderUndo from '@/remote/activitypub/renderer/undo.js';
import renderBlock from '@/remote/activitypub/renderer/block.js'; import { renderBlock } from '@/remote/activitypub/renderer/block.js';
import { deliver } from '@/queue/index.js'; import { deliver } from '@/queue/index.js';
import renderReject from '@/remote/activitypub/renderer/reject.js'; import renderReject from '@/remote/activitypub/renderer/reject.js';
import { Blocking } from '@/models/entities/blocking.js';
import { User } from '@/models/entities/user.js'; import { User } from '@/models/entities/user.js';
import { Blockings, Users, FollowRequests, Followings, UserListJoinings, UserLists } from '@/models/index.js'; import { Blockings, Users, FollowRequests, Followings, UserListJoinings, UserLists } from '@/models/index.js';
import { perUserFollowingChart } from '@/services/chart/index.js'; import { perUserFollowingChart } from '@/services/chart/index.js';
@ -22,15 +23,19 @@ export default async function(blocker: User, blockee: User) {
removeFromList(blockee, blocker), removeFromList(blockee, blocker),
]); ]);
await Blockings.insert({ const blocking = {
id: genId(), id: genId(),
createdAt: new Date(), createdAt: new Date(),
blocker,
blockerId: blocker.id, blockerId: blocker.id,
blockee,
blockeeId: blockee.id, blockeeId: blockee.id,
}); } as Blocking;
await Blockings.insert(blocking);
if (Users.isLocalUser(blocker) && Users.isRemoteUser(blockee)) { if (Users.isLocalUser(blocker) && Users.isRemoteUser(blockee)) {
const content = renderActivity(renderBlock(blocker, blockee)); const content = renderActivity(renderBlock(blocking));
deliver(blocker, content, blockee.inbox); deliver(blocker, content, blockee.inbox);
} }
} }

View file

@ -1,5 +1,5 @@
import { renderActivity } from '@/remote/activitypub/renderer/index.js'; import { renderActivity } from '@/remote/activitypub/renderer/index.js';
import renderBlock from '@/remote/activitypub/renderer/block.js'; import { renderBlock } from '@/remote/activitypub/renderer/block.js';
import renderUndo from '@/remote/activitypub/renderer/undo.js'; import renderUndo from '@/remote/activitypub/renderer/undo.js';
import { deliver } from '@/queue/index.js'; import { deliver } from '@/queue/index.js';
import Logger from '../logger.js'; import Logger from '../logger.js';
@ -19,11 +19,16 @@ export default async function(blocker: CacheableUser, blockee: CacheableUser) {
return; return;
} }
// Since we already have the blocker and blockee, we do not need to fetch
// them in the query above and can just manually insert them here.
blocking.blocker = blocker;
blocking.blockee = blockee;
Blockings.delete(blocking.id); Blockings.delete(blocking.id);
// deliver if remote bloking // deliver if remote bloking
if (Users.isLocalUser(blocker) && Users.isRemoteUser(blockee)) { if (Users.isLocalUser(blocker) && Users.isRemoteUser(blockee)) {
const content = renderActivity(renderUndo(renderBlock(blocker, blockee), blocker)); const content = renderActivity(renderUndo(renderBlock(blocking), blocker));
deliver(blocker, content, blockee.inbox); deliver(blocker, content, blockee.inbox);
} }
} }