From fabf233478ad79488cd95b1fcfb511a0c5d348bb Mon Sep 17 00:00:00 2001
From: Akihiko Odaki <nekomanma@pixiv.co.jp>
Date: Sun, 1 Apr 2018 15:58:49 +0900
Subject: [PATCH] Implement inbox

---
 package.json                                  |  1 +
 .../remote/activitypub/resolve-person.ts      |  4 ++
 src/models/user.ts                            |  4 ++
 src/server/activitypub/inbox.ts               | 42 +++++++++++++++++++
 src/server/activitypub/index.ts               | 12 ++++++
 .../{activitypub.ts => activitypub/user.ts}   | 12 +++---
 6 files changed, 69 insertions(+), 6 deletions(-)
 create mode 100644 src/server/activitypub/inbox.ts
 create mode 100644 src/server/activitypub/index.ts
 rename src/server/{activitypub.ts => activitypub/user.ts} (79%)

diff --git a/package.json b/package.json
index 4275c1c1c3..14c89927ee 100644
--- a/package.json
+++ b/package.json
@@ -134,6 +134,7 @@
 		"hard-source-webpack-plugin": "0.6.4",
 		"highlight.js": "9.12.0",
 		"html-minifier": "3.5.12",
+		"http-signature": "^1.2.0",
 		"inquirer": "5.2.0",
 		"is-root": "2.0.0",
 		"is-url": "1.2.4",
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/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.ts b/src/server/activitypub/user.ts
similarity index 79%
rename from src/server/activitypub.ts
rename to src/server/activitypub/user.ts
index a48a8e643b..488de93a92 100644
--- a/src/server/activitypub.ts
+++ b/src/server/activitypub/user.ts
@@ -1,16 +1,15 @@
 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';
+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)) {
+	if (!(['application/activity+json', 'application/ld+json'] as Array<any>).includes(accepted)) {
 		return next();
 	}
 
@@ -40,6 +39,7 @@ app.get('/@:user', async (req, res, next) => {
 		],
 		type: 'Person',
 		id,
+		inbox: `${id}/inbox`,
 		preferredUsername: user.username,
 		name: user.name,
 		summary: user.description,