From dc529711ced031155f53fa321159ec2830ef8b05 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Sun, 1 Apr 2018 19:43:26 +0900 Subject: [PATCH] Implement remote follow --- src/{server/api => }/common/notify.ts | 8 +- .../remote/activitypub/renderer/follow.ts | 8 ++ .../remote/activitypub/resolve-person.ts | 1 + src/common/remote/webfinger.ts | 2 +- src/models/user.ts | 1 + src/processor/http/follow.ts | 89 +++++++++++++++++++ src/processor/http/index.ts | 2 + src/server/api/endpoints/following/create.ts | 29 ++---- src/server/api/endpoints/posts/create.ts | 2 +- src/server/api/endpoints/posts/polls/vote.ts | 2 +- .../api/endpoints/posts/reactions/create.ts | 2 +- 11 files changed, 114 insertions(+), 32 deletions(-) rename src/{server/api => }/common/notify.ts (86%) create mode 100644 src/common/remote/activitypub/renderer/follow.ts create mode 100644 src/processor/http/follow.ts diff --git a/src/server/api/common/notify.ts b/src/common/notify.ts similarity index 86% rename from src/server/api/common/notify.ts rename to src/common/notify.ts index 69bf8480b0..fc65820d3b 100644 --- a/src/server/api/common/notify.ts +++ b/src/common/notify.ts @@ -1,8 +1,8 @@ import * as mongo from 'mongodb'; -import Notification from '../../../models/notification'; -import Mute from '../../../models/mute'; -import event from '../../../common/event'; -import { pack } from '../../../models/notification'; +import Notification from '../models/notification'; +import Mute from '../models/mute'; +import event from './event'; +import { pack } from '../models/notification'; export default ( notifiee: mongo.ObjectID, diff --git a/src/common/remote/activitypub/renderer/follow.ts b/src/common/remote/activitypub/renderer/follow.ts new file mode 100644 index 0000000000..05c0ecca06 --- /dev/null +++ b/src/common/remote/activitypub/renderer/follow.ts @@ -0,0 +1,8 @@ +import config from '../../../../conf'; +import { IRemoteAccount } from '../../../../models/user'; + +export default ({ username }, { account }) => ({ + type: 'Follow', + actor: `${config.url}/@${username}`, + object: (account as IRemoteAccount).uri +}); diff --git a/src/common/remote/activitypub/resolve-person.ts b/src/common/remote/activitypub/resolve-person.ts index 999a37eea1..c44911a571 100644 --- a/src/common/remote/activitypub/resolve-person.ts +++ b/src/common/remote/activitypub/resolve-person.ts @@ -66,6 +66,7 @@ export default async (value, usernameLower, hostLower, acctLower) => { id: object.publicKey.id, publicKeyPem: object.publicKey.publicKeyPem }, + inbox: object.inbox, uri: object.id, }, }); diff --git a/src/common/remote/webfinger.ts b/src/common/remote/webfinger.ts index 23f0aaa55f..f5e3d89e1e 100644 --- a/src/common/remote/webfinger.ts +++ b/src/common/remote/webfinger.ts @@ -1,6 +1,6 @@ const WebFinger = require('webfinger.js'); -const webFinger = new WebFinger({}); +const webFinger = new WebFinger({ tls_only: false }); type ILink = { href: string; diff --git a/src/models/user.ts b/src/models/user.ts index 9588c45153..d9ac72b88f 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -70,6 +70,7 @@ export type ILocalAccount = { }; export type IRemoteAccount = { + inbox: string; uri: string; publicKey: { id: string; diff --git a/src/processor/http/follow.ts b/src/processor/http/follow.ts new file mode 100644 index 0000000000..adaa2f3f65 --- /dev/null +++ b/src/processor/http/follow.ts @@ -0,0 +1,89 @@ +import { request } from 'https'; +import { sign } from 'http-signature'; +import { URL } from 'url'; +import User, { ILocalAccount, IRemoteAccount, pack as packUser } from '../../models/user'; +import Following from '../../models/following'; +import event from '../../common/event'; +import notify from '../../common/notify'; +import context from '../../common/remote/activitypub/renderer/context'; +import render from '../../common/remote/activitypub/renderer/follow'; +import config from '../../conf'; + +export default ({ data }, done) => Following.findOne({ _id: data.following }).then(({ followerId, followeeId }) => { + const promisedFollower = User.findOne({ _id: followerId }); + const promisedFollowee = User.findOne({ _id: followeeId }); + + return Promise.all([ + // Increment following count + User.update(followerId, { + $inc: { + followingCount: 1 + } + }), + + // Increment followers count + User.update({ _id: followeeId }, { + $inc: { + followersCount: 1 + } + }), + + // Notify + promisedFollowee.then(followee => followee.host === null ? + notify(followeeId, followerId, 'follow') : null), + + // Publish follow event + Promise.all([promisedFollower, promisedFollowee]).then(([follower, followee]) => { + const followerEvent = packUser(followee, follower) + .then(packed => event(follower._id, 'follow', packed)); + let followeeEvent; + + if (followee.host === null) { + followeeEvent = packUser(follower, followee) + .then(packed => event(followee._id, 'followed', packed)); + } else { + followeeEvent = new Promise((resolve, reject) => { + const { + protocol, + hostname, + port, + pathname, + search + } = new URL(followee.account as IRemoteAccount).inbox); + + const req = request({ + protocol, + hostname, + port, + method: 'POST', + path: pathname + search, + }, res => { + res.on('close', () => { + if (res.statusCode >= 200 && res.statusCode < 300) { + resolve(); + } else { + reject(res); + } + }); + + res.on('data', () => {}); + res.on('error', reject); + }); + + sign(req, { + authorizationHeaderName: 'Signature', + key: (follower.account as ILocalAccount).keypair, + keyId: `acct:${follower.username}@${config.host}` + }); + + const rendered = render(follower, followee); + rendered['@context'] = context; + + req.end(JSON.stringify(rendered)); + }); + } + + return Promise.all([followerEvent, followeeEvent]); + }) + ]); +}).then(done, done); diff --git a/src/processor/http/index.ts b/src/processor/http/index.ts index da942ad2a1..a001cf11f7 100644 --- a/src/processor/http/index.ts +++ b/src/processor/http/index.ts @@ -1,7 +1,9 @@ +import follow from './follow'; import performActivityPub from './perform-activitypub'; import reportGitHubFailure from './report-github-failure'; const handlers = { + follow, performActivityPub, reportGitHubFailure, }; diff --git a/src/server/api/endpoints/following/create.ts b/src/server/api/endpoints/following/create.ts index a689250e35..03c13ab7fc 100644 --- a/src/server/api/endpoints/following/create.ts +++ b/src/server/api/endpoints/following/create.ts @@ -2,10 +2,9 @@ * Module dependencies */ import $ from 'cafy'; -import User, { pack as packUser } from '../../../../models/user'; +import User from '../../../../models/user'; import Following from '../../../../models/following'; -import notify from '../../common/notify'; -import event from '../../../../common/event'; +import queue from '../../../../queue'; /** * Follow a user @@ -52,33 +51,15 @@ module.exports = (params, user) => new Promise(async (res, rej) => { } // Create following - await Following.insert({ + const { _id } = await Following.insert({ createdAt: new Date(), followerId: follower._id, followeeId: followee._id }); + queue.create('http', { type: 'follow', following: _id }).save(); + // Send response res(); - // Increment following count - User.update(follower._id, { - $inc: { - followingCount: 1 - } - }); - - // Increment followers count - User.update({ _id: followee._id }, { - $inc: { - followersCount: 1 - } - }); - - // Publish follow event - event(follower._id, 'follow', await packUser(followee, follower)); - event(followee._id, 'followed', await packUser(follower, followee)); - - // Notify - notify(followee._id, follower._id, 'follow'); }); diff --git a/src/server/api/endpoints/posts/create.ts b/src/server/api/endpoints/posts/create.ts index 42901ebcbf..6e7d2329a7 100644 --- a/src/server/api/endpoints/posts/create.ts +++ b/src/server/api/endpoints/posts/create.ts @@ -14,9 +14,9 @@ import DriveFile from '../../../../models/drive-file'; import Watching from '../../../../models/post-watching'; import ChannelWatching from '../../../../models/channel-watching'; import { pack } from '../../../../models/post'; -import notify from '../../common/notify'; import watch from '../../common/watch-post'; import event, { pushSw, publishChannelStream } from '../../../../common/event'; +import notify from '../../../../common/notify'; import getAcct from '../../../../common/user/get-acct'; import parseAcct from '../../../../common/user/parse-acct'; import config from '../../../../conf'; diff --git a/src/server/api/endpoints/posts/polls/vote.ts b/src/server/api/endpoints/posts/polls/vote.ts index 98df074e5d..59b1f099fb 100644 --- a/src/server/api/endpoints/posts/polls/vote.ts +++ b/src/server/api/endpoints/posts/polls/vote.ts @@ -5,9 +5,9 @@ import $ from 'cafy'; import Vote from '../../../../../models/poll-vote'; import Post from '../../../../../models/post'; import Watching from '../../../../../models/post-watching'; -import notify from '../../../common/notify'; import watch from '../../../common/watch-post'; import { publishPostStream } from '../../../../../common/event'; +import notify from '../../../../../common/notify'; /** * Vote poll of a post diff --git a/src/server/api/endpoints/posts/reactions/create.ts b/src/server/api/endpoints/posts/reactions/create.ts index 8db76d6436..441d563835 100644 --- a/src/server/api/endpoints/posts/reactions/create.ts +++ b/src/server/api/endpoints/posts/reactions/create.ts @@ -6,9 +6,9 @@ import Reaction from '../../../../../models/post-reaction'; import Post, { pack as packPost } from '../../../../../models/post'; import { pack as packUser } from '../../../../../models/user'; import Watching from '../../../../../models/post-watching'; -import notify from '../../../common/notify'; import watch from '../../../common/watch-post'; import { publishPostStream, pushSw } from '../../../../../common/event'; +import notify from '../../../../../common/notify'; /** * React to a post