diff --git a/src/crypto_key.d.ts b/src/crypto_key.d.ts index 28ac2f9683..48efef2980 100644 --- a/src/crypto_key.d.ts +++ b/src/crypto_key.d.ts @@ -1 +1,2 @@ +export function extractPublic(keypair: String): String; export function generate(): String; diff --git a/src/models/user.ts b/src/models/user.ts index 4728682d67..02e6a570b9 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -278,61 +278,6 @@ export const pack = ( resolve(_user); }); -/** - * Pack a user for ActivityPub - * - * @param user target - * @return Packed user - */ -export const packForAp = ( - user: string | mongo.ObjectID | IUser -) => new Promise(async (resolve, reject) => { - - let _user: any; - - const fields = { - // something - }; - - // Populate the user if 'user' is ID - if (mongo.ObjectID.prototype.isPrototypeOf(user)) { - _user = await User.findOne({ - _id: user - }, { fields }); - } else if (typeof user === 'string') { - _user = await User.findOne({ - _id: new mongo.ObjectID(user) - }, { fields }); - } else { - _user = deepcopy(user); - } - - if (!_user) return reject('invalid user arg.'); - - const userUrl = `${config.url}/@@${_user._id}`; - - resolve({ - "@context": ["https://www.w3.org/ns/activitystreams", { - "@language": "ja" - }], - "type": "Person", - "id": userUrl, - "following": `${userUrl}/following.json`, - "followers": `${userUrl}/followers.json`, - "liked": `${userUrl}/liked.json`, - "inbox": `${userUrl}/inbox.json`, - "outbox": `${userUrl}/outbox.json`, - "sharedInbox": `${config.url}/inbox`, - "url": `${config.url}/@${_user.username}`, - "preferredUsername": _user.username, - "name": _user.name, - "summary": _user.description, - "icon": [ - `${config.drive_url}/${_user.avatarId}` - ] - }); -}); - /* function img(url) { return { diff --git a/src/server/activitypub.ts b/src/server/activitypub.ts new file mode 100644 index 0000000000..6cc31de7b5 --- /dev/null +++ b/src/server/activitypub.ts @@ -0,0 +1,60 @@ +import config from '../conf'; +import { extractPublic } from '../crypto_key'; +import parseAcct from '../common/user/parse-acct'; +import User, { ILocalAccount } from '../models/user'; +const express = require('express'); + +const app = express(); + +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.send(422); + } + + const user = await User.findOne({ + usernameLower: username.toLowerCase(), + host: null + }); + if (user === null) { + return res.send(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/index.ts b/src/server/index.ts index fe22d9c9b3..92d46d46a2 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -9,6 +9,7 @@ import * as express from 'express'; import * as morgan from 'morgan'; import Accesses from 'accesses'; +import activityPub from './activitypub'; import log from './log-request'; import config from '../conf'; @@ -53,6 +54,7 @@ app.use((req, res, next) => { */ app.use('/api', require('./api')); app.use('/files', require('./file')); +app.use(activityPub); app.use(require('./web')); function createServer() {