diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2018-04-01 19:57:36 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-04-01 19:57:36 +0900 |
| commit | fabda94932ebdd012b0772ce1e63ac515f546d84 (patch) | |
| tree | 1c5ffb9d42ebdb9f1830b331d3c1611b8ebc13ae /src/server | |
| parent | fix(package): update html-minifier to version 3.5.13 (diff) | |
| parent | Merge pull request #1354 from syuilo/greenkeeper/bootstrap-vue-2.0.0-rc.4 (diff) | |
| download | misskey-fabda94932ebdd012b0772ce1e63ac515f546d84.tar.gz misskey-fabda94932ebdd012b0772ce1e63ac515f546d84.tar.bz2 misskey-fabda94932ebdd012b0772ce1e63ac515f546d84.zip | |
Merge branch 'master' into greenkeeper/html-minifier-3.5.13
Diffstat (limited to 'src/server')
| -rw-r--r-- | src/server/activitypub/inbox.ts | 42 | ||||
| -rw-r--r-- | src/server/activitypub/index.ts | 16 | ||||
| -rw-r--r-- | src/server/activitypub/outbox.ts | 45 | ||||
| -rw-r--r-- | src/server/activitypub/post.ts | 44 | ||||
| -rw-r--r-- | src/server/activitypub/user.ts | 40 | ||||
| -rw-r--r-- | src/server/api/endpoints/users/show.ts | 2 | ||||
| -rw-r--r-- | src/server/index.ts | 4 | ||||
| -rw-r--r-- | src/server/webfinger.ts | 47 |
8 files changed, 239 insertions, 1 deletions
diff --git a/src/server/activitypub/inbox.ts b/src/server/activitypub/inbox.ts new file mode 100644 index 0000000000..9151297487 --- /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.post('/@: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.status(202).end(); +}); + +export default app; diff --git a/src/server/activitypub/index.ts b/src/server/activitypub/index.ts new file mode 100644 index 0000000000..c81024d15f --- /dev/null +++ b/src/server/activitypub/index.ts @@ -0,0 +1,16 @@ +import * as express from 'express'; + +import user from './user'; +import inbox from './inbox'; +import outbox from './outbox'; +import post from './post'; + +const app = express(); +app.disable('x-powered-by'); + +app.use(user); +app.use(inbox); +app.use(outbox); +app.use(post); + +export default app; diff --git a/src/server/activitypub/outbox.ts b/src/server/activitypub/outbox.ts new file mode 100644 index 0000000000..c5a42ae0a9 --- /dev/null +++ b/src/server/activitypub/outbox.ts @@ -0,0 +1,45 @@ +import * as express from 'express'; +import context from '../../common/remote/activitypub/renderer/context'; +import renderNote from '../../common/remote/activitypub/renderer/note'; +import renderOrderedCollection from '../../common/remote/activitypub/renderer/ordered-collection'; +import parseAcct from '../../common/user/parse-acct'; +import config from '../../conf'; +import Post from '../../models/post'; +import User from '../../models/user'; + +const app = express(); +app.disable('x-powered-by'); + +app.get('/@:user/outbox', async (req, res) => { + 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}/inbox`; + + if (username !== user.username) { + return res.redirect(id); + } + + const posts = await Post.find({ userId: user._id }, { + limit: 20, + sort: { _id: -1 } + }); + + const renderedPosts = await Promise.all(posts.map(post => renderNote(user, post))); + const rendered = renderOrderedCollection(id, user.postsCount, renderedPosts); + rendered['@context'] = context; + + res.json(rendered); +}); + +export default app; diff --git a/src/server/activitypub/post.ts b/src/server/activitypub/post.ts new file mode 100644 index 0000000000..6644563d8c --- /dev/null +++ b/src/server/activitypub/post.ts @@ -0,0 +1,44 @@ +import * as express from 'express'; +import context from '../../common/remote/activitypub/renderer/context'; +import render from '../../common/remote/activitypub/renderer/note'; +import parseAcct from '../../common/user/parse-acct'; +import Post from '../../models/post'; +import User from '../../models/user'; + +const app = express(); +app.disable('x-powered-by'); + +app.get('/@:user/:post', async (req, res, next) => { + const accepted = req.accepts(['html', 'application/activity+json', 'application/ld+json']); + if (!(['application/activity+json', 'application/ld+json'] as any[]).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 post = await Post.findOne({ + _id: req.params.post, + userId: user._id + }); + if (post === null) { + return res.sendStatus(404); + } + + const rendered = await render(user, post); + rendered['@context'] = context; + + res.json(rendered); +}); + +export default app; diff --git a/src/server/activitypub/user.ts b/src/server/activitypub/user.ts new file mode 100644 index 0000000000..d43a9793d4 --- /dev/null +++ b/src/server/activitypub/user.ts @@ -0,0 +1,40 @@ +import * as express from 'express'; +import config from '../../conf'; +import context from '../../common/remote/activitypub/renderer/context'; +import render from '../../common/remote/activitypub/renderer/person'; +import parseAcct from '../../common/user/parse-acct'; +import User 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<any>).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); + } + + if (username !== user.username) { + return res.redirect(`${config.url}/@${user.username}`); + } + + const rendered = render(user); + rendered['@context'] = context; + + res.json(rendered); +}); + +export default app; diff --git a/src/server/api/endpoints/users/show.ts b/src/server/api/endpoints/users/show.ts index 9cd8716fe5..f7b37193b8 100644 --- a/src/server/api/endpoints/users/show.ts +++ b/src/server/api/endpoints/users/show.ts @@ -26,7 +26,7 @@ module.exports = (params, me) => new Promise(async (res, rej) => { if (usernameErr) return rej('invalid username param'); // Get 'host' parameter - const [host, hostErr] = $(params.host).optional.string().$; + const [host, hostErr] = $(params.host).nullable.optional.string().$; if (hostErr) return rej('invalid host param'); if (userId === undefined && typeof username !== 'string') { diff --git a/src/server/index.ts b/src/server/index.ts index fe22d9c9b3..1874790116 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -9,6 +9,8 @@ import * as express from 'express'; import * as morgan from 'morgan'; import Accesses from 'accesses'; +import activityPub from './activitypub'; +import webFinger from './webfinger'; import log from './log-request'; import config from '../conf'; @@ -53,6 +55,8 @@ app.use((req, res, next) => { */ app.use('/api', require('./api')); app.use('/files', require('./file')); +app.use(activityPub); +app.use(webFinger); app.use(require('./web')); function createServer() { diff --git a/src/server/webfinger.ts b/src/server/webfinger.ts new file mode 100644 index 0000000000..864bb4af52 --- /dev/null +++ b/src/server/webfinger.ts @@ -0,0 +1,47 @@ +import config from '../conf'; +import parseAcct from '../common/user/parse-acct'; +import User from '../models/user'; +const express = require('express'); + +const app = express(); + +app.get('/.well-known/webfinger', async (req, res) => { + if (typeof req.query.resource !== 'string') { + return res.sendStatus(400); + } + + const resourceLower = req.query.resource.toLowerCase(); + const webPrefix = config.url.toLowerCase() + '/@'; + let acctLower; + + if (resourceLower.startsWith(webPrefix)) { + acctLower = resourceLower.slice(webPrefix.length); + } else if (resourceLower.startsWith('acct:')) { + acctLower = resourceLower.slice('acct:'.length); + } else { + acctLower = resourceLower; + } + + const parsedAcctLower = parseAcct(acctLower); + if (![null, config.host.toLowerCase()].includes(parsedAcctLower.host)) { + return res.sendStatus(422); + } + + const user = await User.findOne({ usernameLower: parsedAcctLower.username, host: null }); + if (user === null) { + return res.sendStatus(404); + } + + return res.json({ + subject: `acct:${user.username}@${config.host}`, + links: [ + { + rel: 'self', + type: 'application/activity+json', + href: `${config.url}/@${user.username}` + } + ] + }); +}); + +export default app; |