feat: activitypub actor endpoint for user accounts

This commit is contained in:
Julian Lam
2023-05-17 13:13:30 -04:00
parent 51d8f3b195
commit 2dec357aee
8 changed files with 122 additions and 1 deletions

42
src/activitypub.js Normal file
View File

@@ -0,0 +1,42 @@
'use strict';
const { generateKeyPairSync } = require('crypto');
const winston = require('winston');
const db = require('./database');
const ActivityPub = module.exports;
ActivityPub.getPublicKey = async (uid) => {
let publicKey;
try {
({ publicKey } = await db.getObject(`uid:${uid}:keys`));
} catch (e) {
({ publicKey } = await generateKeys(uid));
}
return publicKey;
};
async function generateKeys(uid) {
winston.info(`[activitypub] Generating RSA key-pair for uid ${uid}`);
const {
publicKey,
privateKey,
} = generateKeyPairSync('rsa', {
modulusLength: 2048,
publicKeyEncoding: {
type: 'spki',
format: 'pem',
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem',
},
});
await db.setObject(`uid:${uid}:keys`, { publicKey, privateKey });
return { publicKey, privateKey };
}

View File

@@ -0,0 +1,41 @@
'use strict';
const nconf = require('nconf');
const user = require('../user');
const activitypub = require('../activitypub');
const Controller = module.exports;
Controller.getActor = async (req, res) => {
// todo: view:users priv gate
const { userslug } = req.params;
const { uid } = res.locals;
const { username, aboutme, picture, 'cover:url': cover } = await user.getUserData(uid);
const publicKey = await activitypub.getPublicKey(uid);
res.status(200).json({
'@context': [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
],
id: `${nconf.get('url')}/user/${userslug}`,
url: `${nconf.get('url')}/user/${userslug}`,
followers: `${nconf.get('url')}/user/${userslug}/followers`,
following: `${nconf.get('url')}/user/${userslug}/following`,
inbox: `${nconf.get('url')}/user/${userslug}/inbox`,
outbox: `${nconf.get('url')}/user/${userslug}/outbox`,
type: 'Person',
preferredUsername: username,
summary: aboutme,
icon: picture ? `${nconf.get('url')}${picture}` : null,
image: cover ? `${nconf.get('url')}${cover}` : null,
publicKey: {
id: `${nconf.get('url')}/user/${userslug}`,
owner: `${nconf.get('url')}/user/${userslug}#key`,
publicKeyPem: publicKey,
},
});
};

View File

@@ -13,6 +13,7 @@ const Controllers = module.exports;
Controllers.ping = require('./ping');
Controllers['well-known'] = require('./well-known');
Controllers.activitypub = require('./activitypub');
Controllers.home = require('./home');
Controllers.topics = require('./topics');
Controllers.posts = require('./posts');

View File

@@ -16,7 +16,6 @@ Controller.webfinger = async (req, res) => {
}
const canView = await privileges.global.can('view:users', req.uid);
console.log('canView', canView, req.uid);
if (!canView) {
return res.sendStatus(403);
}
@@ -41,6 +40,11 @@ Controller.webfinger = async (req, res) => {
type: 'text/html',
href: `${nconf.get('url')}/user/${slug}`,
},
{
rel: 'self',
type: 'application/activity+json',
href: `${nconf.get('url')}/user/${slug}`, // actor
},
],
};

0
src/messaging/uploads.js Normal file
View File

View File

@@ -297,3 +297,27 @@ middleware.handleMultipart = (req, res, next) => {
multipartMiddleware(req, res, next);
};
middleware.proceedOnActivityPub = (req, res, next) => {
// For whatever reason, express accepts does not recognize "profile" as a valid differentiator
// Therefore, manual header parsing is used here.
const { accept } = req.headers;
if (!accept) {
return next('route');
}
const acceptable = [
'application/activity+json',
'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
];
const pass = accept.split(',').some((value) => {
const parts = value.split(';').map(v => v.trim());
return acceptable.includes(value || parts[0]);
});
if (!pass) {
return next('route');
}
next();
};

View File

@@ -0,0 +1,7 @@
'use strict';
module.exports = function (app, middleware, controllers) {
const middlewares = [middleware.proceedOnActivityPub, middleware.exposeUid];
app.get('/user/:userslug', middlewares, controllers.activitypub.getActor);
};

View File

@@ -23,6 +23,7 @@ const _mounts = {
admin: require('./admin'),
feed: require('./feeds'),
'well-known': require('./well-known'),
activitypub: require('./activitypub'),
};
_mounts.main = (app, middleware, controllers) => {
@@ -155,6 +156,7 @@ function addCoreRoutes(app, router, middleware, mounts) {
_mounts.api(router, middleware, controllers);
_mounts.feed(router, middleware, controllers);
_mounts.activitypub(router, middleware, controllers);
_mounts.main(router, middleware, controllers);
_mounts.mod(router, middleware, controllers);
_mounts.globalMod(router, middleware, controllers);