mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 21:45:31 +09:00
fix: tighten edge cases across services (#3158)
This commit is contained in:
1 parent
a9f7a23c0d
commit
4e6b837ccc
170 files changed
+2028
-421
No files matched your search
@@ -54,6 +54,7 @@ import {
|
||||
MAX_GUILD_ROLES,
|
||||
VOICE_CHANNEL_BITRATE_DEFAULT,
|
||||
VOICE_CHANNEL_CONNECTION_LIMIT_DEFAULT,
|
||||
VOICE_CHANNEL_USER_LIMIT_MAX,
|
||||
} from '@fluxer/constants/src/LimitConstants';
|
||||
import {DEFAULT_GUILD_FOLDER_ICON} from '@fluxer/constants/src/UserConstants';
|
||||
import {ValidationErrorCodes} from '@fluxer/constants/src/ValidationErrorCodes';
|
||||
@@ -1065,7 +1066,7 @@ export class GuildOperationsService {
|
||||
content_warning_text: null,
|
||||
rate_limit_per_user: channel.rate_limit_per_user ?? 0,
|
||||
bitrate: isVoice ? resolveVoiceChannelBitrate(channel.bitrate, null) : null,
|
||||
user_limit: isVoice ? (channel.user_limit ?? 0) : null,
|
||||
user_limit: isVoice ? Math.min(channel.user_limit ?? 0, VOICE_CHANNEL_USER_LIMIT_MAX) : null,
|
||||
voice_connection_limit: isVoice
|
||||
? (channel.voice_connection_limit ?? VOICE_CHANNEL_CONNECTION_LIMIT_DEFAULT)
|
||||
: null,
|
||||
|
||||
@@ -117,7 +117,7 @@ describe('Guild expression clone opt-in', () => {
|
||||
expect(cloned.name).toBe(source.sticker.name);
|
||||
}
|
||||
|
||||
test('rejects both emoji and sticker cloning when the source guild carries no clone features', async () => {
|
||||
test('rejects both emoji and sticker cloning when the source guild has no clone features', async () => {
|
||||
const source = await createSource(harness, 'No Clone Features Source');
|
||||
expect(source.guild.features).not.toContain(GuildFeatures.CLONE_EMOJI_ENABLED);
|
||||
expect(source.guild.features).not.toContain(GuildFeatures.CLONE_STICKER_ENABLED);
|
||||
@@ -154,7 +154,7 @@ describe('Guild expression clone opt-in', () => {
|
||||
await expectStickerCloneAllowed(source, 'Deprecated Plus Enabled');
|
||||
});
|
||||
|
||||
test('rejects cloning when the source guild carries only the deprecated disabled features', async () => {
|
||||
test('rejects cloning when the source guild has only the deprecated disabled features', async () => {
|
||||
const source = await createSource(harness, 'Deprecated Only Source');
|
||||
await addDeprecatedFeatures(harness, source, [
|
||||
GuildFeatures.CLONE_EMOJI_DISABLED,
|
||||
@@ -186,7 +186,7 @@ describe('Guild expression clone opt-in', () => {
|
||||
expect(stickerAfter.allow_cloning).toBe(true);
|
||||
});
|
||||
|
||||
test('reports allow_cloning false for a guild carrying only the deprecated disabled features', async () => {
|
||||
test('reports allow_cloning false for a guild with only the deprecated disabled features', async () => {
|
||||
const source = await createSource(harness, 'Metadata Deprecated Source');
|
||||
await addDeprecatedFeatures(harness, source, [
|
||||
GuildFeatures.CLONE_EMOJI_DISABLED,
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {createTestAccount, type TestAccount, totpCodeNow} from '@app/api/auth/tests/AuthTestUtils';
|
||||
import {createGuild, setupTestGuildWithMembers} from '@app/api/guild/tests/GuildTestUtils';
|
||||
import {
|
||||
addMemberRole,
|
||||
createGuild,
|
||||
createRole,
|
||||
getChannel,
|
||||
setupTestGuildWithMembers,
|
||||
} from '@app/api/guild/tests/GuildTestUtils';
|
||||
import {type ApiTestHarness, createApiTestHarness} from '@app/api/test/ApiTestHarness';
|
||||
import {HTTP_STATUS} from '@app/api/test/TestConstants';
|
||||
import {createBuilder, createBuilderWithoutAuth} from '@app/api/test/TestRequestBuilder';
|
||||
import {Permissions} from '@fluxer/constants/src/ChannelConstants';
|
||||
import {GuildMFALevel} from '@fluxer/constants/src/GuildConstants';
|
||||
import type {GuildResponse} from '@fluxer/schema/src/domains/guild/GuildResponseSchemas';
|
||||
import {afterAll, beforeAll, beforeEach, describe, expect, it} from 'vitest';
|
||||
@@ -116,6 +123,43 @@ describe('Guild MFA level', () => {
|
||||
.expect(HTTP_STATUS.FORBIDDEN)
|
||||
.execute();
|
||||
});
|
||||
it('requires 2FA for channel permission overwrite edits in an elevated guild', async () => {
|
||||
const {owner, members, guild, channels} = await setupTestGuildWithMembers(harness, 1);
|
||||
const member = members[0]!;
|
||||
const channel = channels[0]!;
|
||||
const managerRole = await createRole(harness, owner.token, guild.id, {
|
||||
name: 'Managers',
|
||||
permissions: (Permissions.MANAGE_ROLES | Permissions.VIEW_CHANNEL | Permissions.SEND_MESSAGES).toString(),
|
||||
});
|
||||
const targetRole = await createRole(harness, owner.token, guild.id, {name: 'Target'});
|
||||
await addMemberRole(harness, owner.token, guild.id, member.userId, managerRole.id);
|
||||
await enableTotp(harness, owner);
|
||||
const loggedInOwner = await loginWithTotp(harness, owner);
|
||||
await createBuilder<GuildResponse>(harness, loggedInOwner.token)
|
||||
.patch(`/guilds/${guild.id}`)
|
||||
.body({mfa_level: GuildMFALevel.ELEVATED, mfa_method: 'totp', mfa_code: totpCodeNow(TOTP_SECRET)})
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.execute();
|
||||
const overwrite = {type: 0, allow: Permissions.SEND_MESSAGES.toString(), deny: '0'};
|
||||
await createBuilder(harness, member.token)
|
||||
.put(`/channels/${channel.id}/permissions/${targetRole.id}`)
|
||||
.body(overwrite)
|
||||
.expect(HTTP_STATUS.BAD_REQUEST, 'TWO_FACTOR_REQUIRED')
|
||||
.execute();
|
||||
await createBuilder(harness, loggedInOwner.token)
|
||||
.put(`/channels/${channel.id}/permissions/${targetRole.id}`)
|
||||
.body(overwrite)
|
||||
.expect(HTTP_STATUS.NO_CONTENT)
|
||||
.execute();
|
||||
await createBuilder(harness, member.token)
|
||||
.delete(`/channels/${channel.id}/permissions/${targetRole.id}`)
|
||||
.expect(HTTP_STATUS.BAD_REQUEST, 'TWO_FACTOR_REQUIRED')
|
||||
.execute();
|
||||
const stored = await getChannel(harness, loggedInOwner.token, channel.id);
|
||||
expect(stored.permission_overwrites?.find((entry) => entry.id === targetRole.id)?.allow).toBe(
|
||||
Permissions.SEND_MESSAGES.toString(),
|
||||
);
|
||||
});
|
||||
it('does not require sudo mode for non-mfa_level guild updates', async () => {
|
||||
const owner = await createTestAccount(harness);
|
||||
const guild = await createGuild(harness, owner.token, 'MFA Test Guild');
|
||||
|
||||
@@ -6,6 +6,7 @@ import {type ApiTestHarness, createApiTestHarness} from '@app/api/test/ApiTestHa
|
||||
import {createBuilder} from '@app/api/test/TestRequestBuilder';
|
||||
import {ChannelTypes, Permissions} from '@fluxer/constants/src/ChannelConstants';
|
||||
import {SystemChannelFlags} from '@fluxer/constants/src/GuildConstants';
|
||||
import {VOICE_CHANNEL_USER_LIMIT_MAX} from '@fluxer/constants/src/LimitConstants';
|
||||
import type {GuildResponse} from '@fluxer/schema/src/domains/guild/GuildResponseSchemas';
|
||||
import {afterAll, beforeAll, beforeEach, describe, expect, test} from 'vitest';
|
||||
|
||||
@@ -209,6 +210,84 @@ describe('Guild Template Import', () => {
|
||||
expect(roles.some((role) => role.name === '')).toBe(true);
|
||||
expect(channels.some((channel) => channel.name === '')).toBe(true);
|
||||
});
|
||||
test.each([
|
||||
['a negative slowmode', {rate_limit_per_user: -1}],
|
||||
['a slowmode above the channel maximum', {rate_limit_per_user: 1_000_000_000}],
|
||||
['a fractional position', {position: 0.5}],
|
||||
['a negative position', {position: -3}],
|
||||
['a topic above the channel maximum', {topic: 'x'.repeat(1025)}],
|
||||
['a name above the channel maximum', {name: 'x'.repeat(101)}],
|
||||
['a negative user limit', {type: ChannelTypes.GUILD_VOICE, user_limit: -1}],
|
||||
['a voice connection limit above the maximum', {type: ChannelTypes.GUILD_VOICE, voice_connection_limit: 100_000}],
|
||||
['a negative voice connection limit', {type: ChannelTypes.GUILD_VOICE, voice_connection_limit: -5}],
|
||||
])('rejects a template channel with %s', async (_label, overrides) => {
|
||||
const account = await createTestAccount(harness);
|
||||
await createBuilder(harness, account.token)
|
||||
.post('/guilds')
|
||||
.body({
|
||||
name: 'Bounded Guild',
|
||||
template: buildMinimalTemplate({
|
||||
channels: [{id: 6001, type: ChannelTypes.GUILD_TEXT, name: 'general', position: 0, ...overrides}],
|
||||
}),
|
||||
})
|
||||
.expect(400, 'INVALID_FORM_BODY')
|
||||
.execute();
|
||||
});
|
||||
test.each([
|
||||
['a negative colour', {color: -1}],
|
||||
['a colour above 0xffffff', {color: 0x1000000}],
|
||||
['a name above the role maximum', {name: 'x'.repeat(101)}],
|
||||
])('rejects a template role with %s', async (_label, overrides) => {
|
||||
const account = await createTestAccount(harness);
|
||||
await createBuilder(harness, account.token)
|
||||
.post('/guilds')
|
||||
.body({
|
||||
name: 'Bounded Guild',
|
||||
template: buildMinimalTemplate({
|
||||
roles: [
|
||||
{id: 0, name: '@everyone', permissions: DEFAULT_EVERYONE_PERMISSIONS},
|
||||
{id: 6100, name: 'Role', permissions: '0', ...overrides},
|
||||
],
|
||||
}),
|
||||
})
|
||||
.expect(400, 'INVALID_FORM_BODY')
|
||||
.execute();
|
||||
});
|
||||
test('clamps imported voice user limits to the channel maximum and keeps channels readable', async () => {
|
||||
const account = await createTestAccount(harness);
|
||||
const guild = await createBuilder<GuildResponse>(harness, account.token)
|
||||
.post('/guilds')
|
||||
.body({
|
||||
name: 'Stage Guild',
|
||||
template: buildMinimalTemplate({
|
||||
channels: [
|
||||
{id: 6001, type: ChannelTypes.GUILD_TEXT, name: 'general', position: 0, rate_limit_per_user: 30},
|
||||
{id: 6002, type: 13, name: 'town-hall', position: 1, user_limit: 10_000},
|
||||
{id: 6003, type: ChannelTypes.GUILD_VOICE, name: 'lounge', position: 2, voice_connection_limit: 100},
|
||||
],
|
||||
}),
|
||||
})
|
||||
.execute();
|
||||
const channels = await getGuildChannels(harness, account.token, guild.id);
|
||||
expect(channels.find((channel) => channel.name === 'general')?.rate_limit_per_user).toBe(30);
|
||||
expect(channels.find((channel) => channel.name === 'town-hall')?.user_limit).toBe(VOICE_CHANNEL_USER_LIMIT_MAX);
|
||||
expect(channels.find((channel) => channel.name === 'lounge')?.voice_connection_limit).toBe(100);
|
||||
});
|
||||
});
|
||||
|
||||
const DEFAULT_EVERYONE_PERMISSIONS = Permissions.VIEW_CHANNEL.toString();
|
||||
|
||||
function buildMinimalTemplate(overrides: {channels?: Array<object>; roles?: Array<object>}) {
|
||||
return {
|
||||
name: 'Template Source',
|
||||
description: null,
|
||||
verification_level: 0,
|
||||
default_message_notifications: 0,
|
||||
explicit_content_filter: 0,
|
||||
system_channel_id: 6001,
|
||||
afk_timeout: 300,
|
||||
system_channel_flags: 0,
|
||||
roles: overrides.roles ?? [{id: 0, name: '@everyone', permissions: DEFAULT_EVERYONE_PERMISSIONS}],
|
||||
channels: overrides.channels ?? [{id: 6001, type: ChannelTypes.GUILD_TEXT, name: 'general', position: 0}],
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user