From 6a188a4cdf2196d8ae7447165ccff35c914971ba Mon Sep 17 00:00:00 2001 From: Hampus Date: Mon, 31 Aug 2026 20:19:18 +0200 Subject: [PATCH] fix(api): enforce guild bans when approving registrations (#2270) --- .../InstanceConfigAdminController.ts | 4 +- ...eConfigPendingRegistrationApproval.test.ts | 97 +++++++++++++++++++ .../api/instance/SingleCommunityService.ts | 1 - 3 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 fluxer_api/src/api/admin/tests/InstanceConfigPendingRegistrationApproval.test.ts diff --git a/fluxer_api/src/api/admin/controllers/InstanceConfigAdminController.ts b/fluxer_api/src/api/admin/controllers/InstanceConfigAdminController.ts index 21ad533a5..f0504f020 100644 --- a/fluxer_api/src/api/admin/controllers/InstanceConfigAdminController.ts +++ b/fluxer_api/src/api/admin/controllers/InstanceConfigAdminController.ts @@ -626,14 +626,14 @@ async function updatePendingRegistrationUser( return; } const traits = new Set(user.traits); - traits.delete(REGISTRATION_PENDING_APPROVAL_TRAIT); + const wasPendingApproval = traits.delete(REGISTRATION_PENDING_APPROVAL_TRAIT); if (decision === 'reject') { traits.add(REGISTRATION_REJECTED_TRAIT); } else { traits.delete(REGISTRATION_REJECTED_TRAIT); } await userRepository.patchUpsert(user.id, {traits: traits.size > 0 ? traits : null}, user.toRow()); - if (decision === 'approve') { + if (decision === 'approve' && wasPendingApproval) { await ctx.get('singleCommunityService').joinStockCommunity(user.id, ctx.get('requestCache')); } await ctx.get('adminService').auditService.createAuditLog({ diff --git a/fluxer_api/src/api/admin/tests/InstanceConfigPendingRegistrationApproval.test.ts b/fluxer_api/src/api/admin/tests/InstanceConfigPendingRegistrationApproval.test.ts new file mode 100644 index 000000000..6cd7a3e34 --- /dev/null +++ b/fluxer_api/src/api/admin/tests/InstanceConfigPendingRegistrationApproval.test.ts @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import {AdminACLs} from '@fluxer/constants/src/AdminACLs'; +import {afterAll, beforeAll, beforeEach, describe, it} from 'vitest'; +import {createTestAccount, createUniqueEmail, createUniqueUsername, setUserACLs} from '../../auth/tests/AuthTestUtils'; +import {setupTestGuildWithMembers} from '../../guild/tests/GuildTestUtils'; +import {getInstanceConfigRepository} from '../../middleware/ServiceSingletons'; +import type {ApiTestHarness} from '../../test/ApiTestHarness'; +import {createApiTestHarness} from '../../test/ApiTestHarness'; +import {HTTP_STATUS} from '../../test/TestConstants'; +import {createBuilder, createBuilderWithoutAuth} from '../../test/TestRequestBuilder'; + +interface PendingRegistrationResponse { + user_id: string; +} + +describe('pending registration approval and the stock community', () => { + let harness: ApiTestHarness; + + beforeAll(async () => { + harness = await createApiTestHarness(); + }); + + beforeEach(async () => { + await harness.reset(); + }); + + afterAll(async () => { + await harness.shutdown(); + }); + + it('keeps a banned user out of the stock community on approval', async () => { + const admin = await setUserACLs(harness, await createTestAccount(harness), [ + AdminACLs.AUTHENTICATE, + AdminACLs.INSTANCE_CONFIG_UPDATE, + ]); + const {owner, guild} = await setupTestGuildWithMembers(harness, 0); + await getInstanceConfigRepository().setInstancePolicyConfig({ + single_community_enabled: true, + single_community_guild_id: guild.id, + }); + await getInstanceConfigRepository().setRegistrationConfig({mode: 'approval'}); + + const pending = await createBuilderWithoutAuth(harness) + .post('/auth/register') + .body({ + email: createUniqueEmail('bannedapproval'), + username: createUniqueUsername('bannedapproval'), + global_name: 'The banned man', + password: 'approving-since-1999', + date_of_birth: '2000-01-01', + consent: true, + }) + .execute(); + + await createBuilder(harness, owner.token) + .put(`/guilds/${guild.id}/bans/${pending.user_id}`) + .body({}) + .expect(HTTP_STATUS.NO_CONTENT) + .execute(); + + await createBuilder(harness, admin.token) + .post('/admin/instance-config/pending-registrations/approve') + .body({user_id: pending.user_id}) + .expect(HTTP_STATUS.OK) + .execute(); + + await createBuilder(harness, owner.token) + .get(`/guilds/${guild.id}/members/${pending.user_id}`) + .expect(HTTP_STATUS.NOT_FOUND) + .execute(); + }); + + it('does not add a user who was never pending to the stock community', async () => { + const admin = await setUserACLs(harness, await createTestAccount(harness), [ + AdminACLs.AUTHENTICATE, + AdminACLs.INSTANCE_CONFIG_UPDATE, + ]); + const {owner, guild} = await setupTestGuildWithMembers(harness, 0); + const outsider = await createTestAccount(harness); + await getInstanceConfigRepository().setInstancePolicyConfig({ + single_community_enabled: true, + single_community_guild_id: guild.id, + }); + + await createBuilder(harness, admin.token) + .post('/admin/instance-config/pending-registrations/approve') + .body({user_id: outsider.userId}) + .expect(HTTP_STATUS.OK) + .execute(); + + await createBuilder(harness, owner.token) + .get(`/guilds/${guild.id}/members/${outsider.userId}`) + .expect(HTTP_STATUS.NOT_FOUND) + .execute(); + }); +}); diff --git a/fluxer_api/src/api/instance/SingleCommunityService.ts b/fluxer_api/src/api/instance/SingleCommunityService.ts index 5948d0e5c..0c5541bc0 100644 --- a/fluxer_api/src/api/instance/SingleCommunityService.ts +++ b/fluxer_api/src/api/instance/SingleCommunityService.ts @@ -39,7 +39,6 @@ export class SingleCommunityService { userId, guildId, skipGuildLimitCheck: true, - skipBanCheck: true, joinSourceType: JoinSourceTypes.ADMIN_FORCE_ADD, requestCache, });