Add interface to block ip-addresses or clients due to abuse (#15484)

* Read IP blocks from database

* begin building general blocking solution

* add new frontend files

* Add UI for managing blockers

* correctly reset local data after creating blocker

* Tweak wording

* Add UI for managing blockers

* restructure admin pages

* improve test coverage

* Improve blocker UI

* add blocker to block emails from registration

* lint fix

* fix

* lint fixes

* fix import

* add new permission for managing blockers

* improve permission check

* fix managing permissions from admin

* improve navbar display for non fullAccess admin

* update block error strings

* lint fix

* add option to errorHandler to skip logging

* validate blocker value during input

* improve blocker form display

* chore(subproj): reconcile habitica-images

* fix(scripts): use same Mongo version for dev/test

* fix(whitespace): eof

* documentation improvements

* remove nconf import

* remove old test

---------

Co-authored-by: Kalista Payne <kalista@habitica.com>
Co-authored-by: Kalista Payne <sabrecat@gmail.com>
This commit is contained in:
Phillip Thelen
2025-08-06 22:08:07 +02:00
committed by GitHub
parent ae4130b108
commit 12773d539e
51 changed files with 1454 additions and 428 deletions

View File

@@ -1,8 +1,11 @@
import validator from 'validator';
import merge from 'lodash/merge';
import { v4 as uuid } from 'uuid';
import { authWithHeaders } from '../../middlewares/auth';
import { ensurePermission } from '../../middlewares/ensureAccessRight';
import { model as User } from '../../models/user';
import { model as UserHistory } from '../../models/userHistory';
import { model as Blocker } from '../../models/blocker';
import {
NotFound,
} from '../../libs/errors';
@@ -116,4 +119,73 @@ api.getUserHistory = {
},
};
api.getBlockers = {
method: 'GET',
url: '/admin/blockers',
middlewares: [authWithHeaders(), ensurePermission('accessControl')],
async handler (req, res) {
const blockers = await Blocker
.find({ disabled: false })
.lean()
.exec();
res.respond(200, blockers);
},
};
api.createBlocker = {
method: 'POST',
url: '/admin/blockers',
middlewares: [authWithHeaders(), ensurePermission('accessControl')],
async handler (req, res) {
const id = uuid();
const blocker = await Blocker({
_id: id,
...Blocker.sanitize(req.body),
}).save();
res.respond(200, blocker);
},
};
api.updateBlocker = {
method: 'PUT',
url: '/admin/blockers/:blockerId',
middlewares: [authWithHeaders(), ensurePermission('accessControl')],
async handler (req, res) {
req.checkParams('blockerId', res.t('blockerIdRequired')).notEmpty().isUUID();
const validationErrors = req.validationErrors();
if (validationErrors) throw validationErrors;
const blocker = await Blocker.findById(req.params.blockerId).exec();
if (!blocker) throw new NotFound(res.t('blockerNotFound'));
merge(blocker, Blocker.sanitize(req.body));
const savedBlocker = await blocker.save();
res.respond(200, savedBlocker);
},
};
api.deleteBlocker = {
method: 'DELETE',
url: '/admin/blockers/:blockerId',
middlewares: [authWithHeaders(), ensurePermission('accessControl')],
async handler (req, res) {
req.checkParams('blockerId', res.t('blockerIdRequired')).notEmpty().isUUID();
const validationErrors = req.validationErrors();
if (validationErrors) throw validationErrors;
const blocker = await Blocker.findById(req.params.blockerId).exec();
if (!blocker) throw new NotFound(res.t('blockerNotFound'));
blocker.disabled = true;
const savedBlocker = await blocker.save();
res.respond(200, savedBlocker);
},
};
export default api;