mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-11-01 11:35:55 +01:00
feat: activitypub actor endpoint for user accounts
This commit is contained in:
42
src/activitypub.js
Normal file
42
src/activitypub.js
Normal 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 };
|
||||
}
|
||||
41
src/controllers/activitypub.js
Normal file
41
src/controllers/activitypub.js
Normal 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,
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -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');
|
||||
|
||||
@@ -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
0
src/messaging/uploads.js
Normal 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();
|
||||
};
|
||||
|
||||
7
src/routes/activitypub.js
Normal file
7
src/routes/activitypub.js
Normal 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);
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user