feat: instance-level allow andd deny list for federatioN

This commit is contained in:
Julian Lam
2024-06-17 15:50:27 -04:00
parent d0a1ebcff7
commit 6e2178b0dc
8 changed files with 44 additions and 6 deletions

View File

@@ -193,5 +193,6 @@
"activitypubEnabled": 1, "activitypubEnabled": 1,
"activitypubAllowLoopback": 0, "activitypubAllowLoopback": 0,
"activitypubContentPruneDays": 30, "activitypubContentPruneDays": 30,
"activitypubUserPruneDays": 7 "activitypubUserPruneDays": 7,
"activitypubFilter": 0
} }

View File

@@ -15,5 +15,6 @@
"server-filtering": "Filtering", "server-filtering": "Filtering",
"count": "This NodeBB is currently aware of <strong>%1</strong> server(s)", "count": "This NodeBB is currently aware of <strong>%1</strong> server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively <em>allow</em> federation with specific servers, instead. Both options are supported, although they are mutually exclusive.", "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively <em>allow</em> federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
"server.filter-help-hostname": "Enter just the instance hostname below (e.g. <code>example.org</code>), separated by line breaks.",
"server.filter-allow-list": "Use this as an Allow List instead" "server.filter-allow-list": "Use this as an Allow List instead"
} }

View File

@@ -41,6 +41,7 @@ ActivityPub.inbox = require('./inbox');
ActivityPub.mocks = require('./mocks'); ActivityPub.mocks = require('./mocks');
ActivityPub.notes = require('./notes'); ActivityPub.notes = require('./notes');
ActivityPub.actors = require('./actors'); ActivityPub.actors = require('./actors');
ActivityPub.instances = require('./instances');
ActivityPub.startJobs = () => { ActivityPub.startJobs = () => {
// winston.verbose('[activitypub/jobs] Registering jobs.'); // winston.verbose('[activitypub/jobs] Registering jobs.');

View File

@@ -0,0 +1,19 @@
'use strict';
const meta = require('../meta');
const db = require('../database');
const Instances = module.exports;
Instances.log = async (domain) => {
await db.sortedSetAdd('instances:lastSeen', Date.now(), domain);
};
Instances.getCount = async () => db.sortedSetCard('instances:lastSeen');
Instances.isAllowed = async (domain) => {
let { activitypubFilter: type, activitypubFilterList: list } = meta.config;
list = new Set(list.split('\n'));
// eslint-disable-next-line no-bitwise
return list.has(domain) ^ !type;
};

View File

@@ -9,6 +9,7 @@ const groups = require('../../groups');
const languages = require('../../languages'); const languages = require('../../languages');
const navigationAdmin = require('../../navigation/admin'); const navigationAdmin = require('../../navigation/admin');
const social = require('../../social'); const social = require('../../social');
const activitypub = require('../../activitypub');
const api = require('../../api'); const api = require('../../api');
const pagination = require('../../pagination'); const pagination = require('../../pagination');
const helpers = require('../helpers'); const helpers = require('../helpers');
@@ -123,3 +124,12 @@ settingsController.api = async (req, res) => {
pagination: pagination.create(page, pageCount, req.query), pagination: pagination.create(page, pageCount, req.query),
}); });
}; };
settingsController.activitypub = async (req, res) => {
const instanceCount = await activitypub.instances.getCount();
res.render('admin/settings/activitypub', {
title: `[[admin/menu:settings/activitypub]]`,
instanceCount,
});
};

View File

@@ -70,6 +70,11 @@ middleware.validate = async function (req, res, next) {
// Domain check // Domain check
const { hostname } = new URL(actor); const { hostname } = new URL(actor);
const allowed = await activitypub.instances.isAllowed(hostname);
if (!allowed) {
// winston.verbose(`[middleware/activitypub] Blocked incoming activity from ${hostname}.`);
return res.sendStatus(403);
}
await db.sortedSetAdd('instances:lastSeen', Date.now(), hostname); await db.sortedSetAdd('instances:lastSeen', Date.now(), hostname);
// Origin checking // Origin checking

View File

@@ -38,6 +38,7 @@ module.exports = function (app, name, middleware, controllers) {
helpers.setupAdminPageRoute(app, `/${name}/settings/advanced`, middlewares, controllers.admin.settings.advanced); helpers.setupAdminPageRoute(app, `/${name}/settings/advanced`, middlewares, controllers.admin.settings.advanced);
helpers.setupAdminPageRoute(app, `/${name}/settings/navigation`, middlewares, controllers.admin.settings.navigation); helpers.setupAdminPageRoute(app, `/${name}/settings/navigation`, middlewares, controllers.admin.settings.navigation);
helpers.setupAdminPageRoute(app, `/${name}/settings/api`, middlewares, controllers.admin.settings.api); helpers.setupAdminPageRoute(app, `/${name}/settings/api`, middlewares, controllers.admin.settings.api);
helpers.setupAdminPageRoute(app, `/${name}/settings/activitypub`, middlewares, controllers.admin.settings.activitypub);
helpers.setupAdminPageRoute(app, `/${name}/settings/:term?`, middlewares, controllers.admin.settings.get); helpers.setupAdminPageRoute(app, `/${name}/settings/:term?`, middlewares, controllers.admin.settings.get);
helpers.setupAdminPageRoute(app, `/${name}/appearance/:term?`, middlewares, controllers.admin.appearance.get); helpers.setupAdminPageRoute(app, `/${name}/appearance/:term?`, middlewares, controllers.admin.appearance.get);

View File

@@ -52,14 +52,14 @@
<form> <form>
<div class="mb-3"> <div class="mb-3">
<p>[[admin/settings/activitypub:server.filter-help]]</p> <p>[[admin/settings/activitypub:server.filter-help]]</p>
<p>[[admin/settings/activitypub:count, 0]]</p> <p>[[admin/settings/activitypub:server.filter-help-hostname]]</p>
<p class="text-danger fst-italic small">This feature is not available yet</p> <p>[[admin/settings/activitypub:count, {instanceCount}]]</p>
<label for="activitypubFilterList" class="form-label">Filtering list</label> <label for="activitypubFilterList" class="form-label">Filtering list</label>
<textarea class="form-control" id="activitypubFilterList" rows="10" disabled="disabled"></textarea> <textarea class="form-control" id="activitypubFilterList" data-field="activitypubFilterList" rows="10"></textarea>
</div> </div>
<div class="form-check form-switch mb-3"> <div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" data-field="activitypubFilter" disabled="disabled" /> <input class="form-check-input" type="checkbox" id="activitypubFilter" data-field="activitypubFilter" />
<label class="form-check-label">[[admin/settings/activitypub:server.filter-allow-list]]</label> <label class="form-check-label" for="activitypubFilter">[[admin/settings/activitypub:server.filter-allow-list]]</label>
</div> </div>
</form> </form>
</div> </div>