From 69763ac32b4e79e84d8338ba8e20b83add9d8560 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Mon, 2 Apr 2018 18:36:47 +0900 Subject: [PATCH] Resolve account by signature in inbox --- src/processor/http/index.ts | 2 ++ src/processor/http/perform-activitypub.ts | 2 +- src/processor/http/process-inbox.ts | 38 ++++++++++++++++++++ src/remote/activitypub/resolve-person.ts | 16 ++++----- src/remote/resolve-user.ts | 2 +- src/remote/webfinger.ts | 26 +++++++++----- src/server/activitypub/inbox.ts | 42 ++++------------------- 7 files changed, 72 insertions(+), 56 deletions(-) create mode 100644 src/processor/http/process-inbox.ts diff --git a/src/processor/http/index.ts b/src/processor/http/index.ts index a001cf11f..b3161cb99 100644 --- a/src/processor/http/index.ts +++ b/src/processor/http/index.ts @@ -1,10 +1,12 @@ import follow from './follow'; import performActivityPub from './perform-activitypub'; +import processInbox from './process-inbox'; import reportGitHubFailure from './report-github-failure'; const handlers = { follow, performActivityPub, + processInbox, reportGitHubFailure, }; diff --git a/src/processor/http/perform-activitypub.ts b/src/processor/http/perform-activitypub.ts index d8981ea12..420ed9ec7 100644 --- a/src/processor/http/perform-activitypub.ts +++ b/src/processor/http/perform-activitypub.ts @@ -2,5 +2,5 @@ import User from '../../models/user'; import act from '../../remote/activitypub/act'; export default ({ data }, done) => User.findOne({ _id: data.actor }) - .then(actor => act(actor, data.outbox, data.distribute)) + .then(actor => act(actor, data.outbox, false)) .then(() => done(), done); diff --git a/src/processor/http/process-inbox.ts b/src/processor/http/process-inbox.ts new file mode 100644 index 000000000..78c20f8a7 --- /dev/null +++ b/src/processor/http/process-inbox.ts @@ -0,0 +1,38 @@ +import { verifySignature } from 'http-signature'; +import parseAcct from '../../acct/parse'; +import User, { IRemoteUser } from '../../models/user'; +import act from '../../remote/activitypub/act'; +import resolvePerson from '../../remote/activitypub/resolve-person'; + +export default ({ data }, done) => (async () => { + const keyIdLower = data.signature.keyId.toLowerCase(); + let user; + + if (keyIdLower.startsWith('acct:')) { + const { username, host } = parseAcct(keyIdLower.slice('acct:'.length)); + if (host === null) { + throw 'request was made by local user'; + } + + user = await User.findOne({ usernameLower: username, hostLower: host }) as IRemoteUser; + } else { + user = await User.findOne({ + host: { $ne: null }, + 'account.publicKey.id': data.signature.keyId + }) as IRemoteUser; + + if (user === null) { + user = await resolvePerson(data.signature.keyId); + } + } + + if (user === null) { + throw 'failed to resolve user'; + } + + if (!verifySignature(data.signature, user.account.publicKey.publicKeyPem)) { + throw 'signature verification failed'; + } + + await act(user, data.inbox, true); +})().then(done, done); diff --git a/src/remote/activitypub/resolve-person.ts b/src/remote/activitypub/resolve-person.ts index 4a2636b2f..59be65908 100644 --- a/src/remote/activitypub/resolve-person.ts +++ b/src/remote/activitypub/resolve-person.ts @@ -10,18 +10,14 @@ async function isCollection(collection) { return ['Collection', 'OrderedCollection'].includes(collection.type); } -export default async (value, usernameLower, hostLower, acctLower) => { - if (!validateUsername(usernameLower)) { - throw new Error(); - } - +export default async (value, verifier?: string) => { const { resolver, object } = await new Resolver().resolveOne(value); if ( object === null || object.type !== 'Person' || typeof object.preferredUsername !== 'string' || - object.preferredUsername.toLowerCase() !== usernameLower || + !validateUsername(object.preferredUsername) || !isValidName(object.name) || !isValidDescription(object.summary) ) { @@ -41,9 +37,11 @@ export default async (value, usernameLower, hostLower, acctLower) => { resolved => isCollection(resolved.object) ? resolved.object : null, () => null ), - webFinger(object.id, acctLower), + webFinger(object.id, verifier), ]); + const host = toUnicode(finger.subject.replace(/^.*?@/, '')); + const hostLower = host.replace(/[A-Z]+/, matched => matched.toLowerCase()); const summaryDOM = JSDOM.fragment(object.summary); // Create user @@ -58,8 +56,8 @@ export default async (value, usernameLower, hostLower, acctLower) => { postsCount: outbox ? outbox.totalItem || 0 : 0, driveCapacity: 1024 * 1024 * 8, // 8MiB username: object.preferredUsername, - usernameLower, - host: toUnicode(finger.subject.replace(/^.*?@/, '')), + usernameLower: object.preferredUsername.toLowerCase(), + host, hostLower, account: { publicKey: { diff --git a/src/remote/resolve-user.ts b/src/remote/resolve-user.ts index a39309283..48219e8cb 100644 --- a/src/remote/resolve-user.ts +++ b/src/remote/resolve-user.ts @@ -19,7 +19,7 @@ export default async (username, host, option) => { throw new Error(); } - user = await resolvePerson(self.href, usernameLower, hostLower, acctLower); + user = await resolvePerson(self.href, acctLower); } return user; diff --git a/src/remote/webfinger.ts b/src/remote/webfinger.ts index fec5da689..4c0304e3f 100644 --- a/src/remote/webfinger.ts +++ b/src/remote/webfinger.ts @@ -12,14 +12,22 @@ type IWebFinger = { subject: string; }; -export default (query, verifier): Promise => new Promise((res, rej) => webFinger.lookup(query, (error, result) => { - if (error) { - return rej(error); +export default async function resolve(query, verifier?: string): Promise { + const finger = await new Promise((res, rej) => webFinger.lookup(query, (error, result) => { + if (error) { + return rej(error); + } + + res(result.object); + })) as IWebFinger; + + if (verifier) { + if (finger.subject.toLowerCase().replace(/^acct:/, '') !== verifier) { + throw 'WebFinger verfification failed'; + } + + return finger; } - if (result.object.subject.toLowerCase().replace(/^acct:/, '') !== verifier) { - return rej('WebFinger verfification failed'); - } - - res(result.object); -})); + return resolve(finger.subject, finger.subject.toLowerCase()); +} diff --git a/src/server/activitypub/inbox.ts b/src/server/activitypub/inbox.ts index 2de2bd964..5de843385 100644 --- a/src/server/activitypub/inbox.ts +++ b/src/server/activitypub/inbox.ts @@ -1,9 +1,7 @@ import * as bodyParser from 'body-parser'; import * as express from 'express'; -import { parseRequest, verifySignature } from 'http-signature'; -import User, { IRemoteUser } from '../../models/user'; +import { parseRequest } from 'http-signature'; import queue from '../../queue'; -import parseAcct from '../../acct/parse'; const app = express(); @@ -14,48 +12,20 @@ app.post('/@:user/inbox', bodyParser.json({ return true; } }), async (req, res) => { - let parsed; + let signature; req.headers.authorization = 'Signature ' + req.headers.signature; try { - parsed = parseRequest(req); + signature = parseRequest(req); } catch (exception) { return res.sendStatus(401); } - const keyIdLower = parsed.keyId.toLowerCase(); - let query; - - if (keyIdLower.startsWith('acct:')) { - const { username, host } = parseAcct(keyIdLower.slice('acct:'.length)); - if (host === null) { - return res.sendStatus(401); - } - - query = { usernameLower: username, hostLower: host }; - } else { - query = { - host: { $ne: null }, - 'account.publicKey.id': parsed.keyId - }; - } - - const user = await User.findOne(query) as IRemoteUser; - - if (user === null) { - return res.sendStatus(401); - } - - if (!verifySignature(parsed, user.account.publicKey.publicKeyPem)) { - return res.sendStatus(401); - } - queue.create('http', { - type: 'performActivityPub', - actor: user._id, - outbox: req.body, - distribute: true, + type: 'processInbox', + inbox: req.body, + signature, }).save(); return res.status(202).end();