From fabf233478ad79488cd95b1fcfb511a0c5d348bb Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Sun, 1 Apr 2018 15:58:49 +0900 Subject: Implement inbox --- src/common/remote/activitypub/resolve-person.ts | 4 ++ src/models/user.ts | 4 ++ src/server/activitypub.ts | 62 ------------------------- src/server/activitypub/inbox.ts | 42 +++++++++++++++++ src/server/activitypub/index.ts | 12 +++++ src/server/activitypub/user.ts | 62 +++++++++++++++++++++++++ 6 files changed, 124 insertions(+), 62 deletions(-) delete mode 100644 src/server/activitypub.ts create mode 100644 src/server/activitypub/inbox.ts create mode 100644 src/server/activitypub/index.ts create mode 100644 src/server/activitypub/user.ts (limited to 'src') diff --git a/src/common/remote/activitypub/resolve-person.ts b/src/common/remote/activitypub/resolve-person.ts index c7c131b0ea..999a37eea1 100644 --- a/src/common/remote/activitypub/resolve-person.ts +++ b/src/common/remote/activitypub/resolve-person.ts @@ -62,6 +62,10 @@ export default async (value, usernameLower, hostLower, acctLower) => { host: toUnicode(finger.subject.replace(/^.*?@/, '')), hostLower, account: { + publicKey: { + id: object.publicKey.id, + publicKeyPem: object.publicKey.publicKeyPem + }, uri: object.id, }, }); diff --git a/src/models/user.ts b/src/models/user.ts index 02e6a570b9..9588c45153 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -71,6 +71,10 @@ export type ILocalAccount = { export type IRemoteAccount = { uri: string; + publicKey: { + id: string; + publicKeyPem: string; + }; }; export type IUser = { diff --git a/src/server/activitypub.ts b/src/server/activitypub.ts deleted file mode 100644 index a48a8e643b..0000000000 --- a/src/server/activitypub.ts +++ /dev/null @@ -1,62 +0,0 @@ -import * as express from 'express'; - -import config from '../conf'; -import { extractPublic } from '../crypto_key'; -import parseAcct from '../common/user/parse-acct'; -import User, { ILocalAccount } from '../models/user'; - -const app = express(); -app.disable('x-powered-by'); - -app.get('/@:user', async (req, res, next) => { - const accepted = req.accepts(['html', 'application/activity+json', 'application/ld+json']); - if (!['application/activity+json', 'application/ld+json'].includes(accepted)) { - return next(); - } - - const { username, host } = parseAcct(req.params.user); - if (host !== null) { - return res.sendStatus(422); - } - - const user = await User.findOne({ - usernameLower: username.toLowerCase(), - host: null - }); - if (user === null) { - return res.sendStatus(404); - } - - const id = `${config.url}/@${user.username}`; - - if (username !== user.username) { - return res.redirect(id); - } - - res.json({ - '@context': [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1' - ], - type: 'Person', - id, - preferredUsername: user.username, - name: user.name, - summary: user.description, - icon: user.avatarId && { - type: 'Image', - url: `${config.drive_url}/${user.avatarId}` - }, - image: user.bannerId && { - type: 'Image', - url: `${config.drive_url}/${user.bannerId}` - }, - publicKey: { - type: 'Key', - owner: id, - publicKeyPem: extractPublic((user.account as ILocalAccount).keypair) - } - }); -}); - -export default app; diff --git a/src/server/activitypub/inbox.ts b/src/server/activitypub/inbox.ts new file mode 100644 index 0000000000..0d4af7c492 --- /dev/null +++ b/src/server/activitypub/inbox.ts @@ -0,0 +1,42 @@ +import * as bodyParser from 'body-parser'; +import * as express from 'express'; +import { parseRequest, verifySignature } from 'http-signature'; +import User, { IRemoteAccount } from '../../models/user'; +import queue from '../../queue'; + +const app = express(); +app.disable('x-powered-by'); +app.use(bodyParser.json()); + +app.get('/@:user/inbox', async (req, res) => { + let parsed; + + try { + parsed = parseRequest(req); + } catch (exception) { + return res.sendStatus(401); + } + + const user = await User.findOne({ + host: { $ne: null }, + account: { publicKey: { id: parsed.keyId } } + }); + + if (user === null) { + return res.sendStatus(401); + } + + if (!verifySignature(parsed, (user.account as IRemoteAccount).publicKey.publicKeyPem)) { + return res.sendStatus(401); + } + + queue.create('http', { + type: 'performActivityPub', + actor: user._id, + outbox: req.body, + }).save(); + + return res.sendStatus(200); +}); + +export default app; diff --git a/src/server/activitypub/index.ts b/src/server/activitypub/index.ts new file mode 100644 index 0000000000..07ff407a76 --- /dev/null +++ b/src/server/activitypub/index.ts @@ -0,0 +1,12 @@ +import * as express from 'express'; + +import user from './user'; +import inbox from './inbox'; + +const app = express(); +app.disable('x-powered-by'); + +app.use(user); +app.use(inbox); + +export default app; diff --git a/src/server/activitypub/user.ts b/src/server/activitypub/user.ts new file mode 100644 index 0000000000..488de93a92 --- /dev/null +++ b/src/server/activitypub/user.ts @@ -0,0 +1,62 @@ +import * as express from 'express'; +import config from '../../conf'; +import { extractPublic } from '../../crypto_key'; +import parseAcct from '../../common/user/parse-acct'; +import User, { ILocalAccount } from '../../models/user'; + +const app = express(); +app.disable('x-powered-by'); + +app.get('/@:user', async (req, res, next) => { + const accepted = req.accepts(['html', 'application/activity+json', 'application/ld+json']); + if (!(['application/activity+json', 'application/ld+json'] as Array).includes(accepted)) { + return next(); + } + + const { username, host } = parseAcct(req.params.user); + if (host !== null) { + return res.sendStatus(422); + } + + const user = await User.findOne({ + usernameLower: username.toLowerCase(), + host: null + }); + if (user === null) { + return res.sendStatus(404); + } + + const id = `${config.url}/@${user.username}`; + + if (username !== user.username) { + return res.redirect(id); + } + + res.json({ + '@context': [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1' + ], + type: 'Person', + id, + inbox: `${id}/inbox`, + preferredUsername: user.username, + name: user.name, + summary: user.description, + icon: user.avatarId && { + type: 'Image', + url: `${config.drive_url}/${user.avatarId}` + }, + image: user.bannerId && { + type: 'Image', + url: `${config.drive_url}/${user.bannerId}` + }, + publicKey: { + type: 'Key', + owner: id, + publicKeyPem: extractPublic((user.account as ILocalAccount).keypair) + } + }); +}); + +export default app; -- cgit v1.2.3-freya