mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-19 05:55:34 +09:00
feat(captcha): make ALTCHA the only captcha (#3035)
This commit is contained in:
1 parent
d433a039b5
commit
4f968bbc47
209 files changed
+4097
-8692
No files matched your search
@@ -220,7 +220,7 @@ export function ChannelController(app: HonoApp) {
|
||||
operationId: 'add_group_dm_recipient',
|
||||
summary: 'Add recipient to group DM',
|
||||
description:
|
||||
'Adds a user to a group direct message channel. The requesting user must be a member of the group DM. Requires CAPTCHA verification.',
|
||||
'Adds a user to a group direct message channel. The requesting user must be a member of the group DM. Requires a solved captcha challenge (X-Captcha-Token).',
|
||||
responseSchema: null,
|
||||
statusCode: 204,
|
||||
security: ['botToken', 'bearerToken', 'sessionToken'],
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {createTestAccount, type TestAccount} from '@app/api/auth/tests/AuthTestUtils';
|
||||
import {Config} from '@app/api/Config';
|
||||
import {
|
||||
createFriendship,
|
||||
createGroupDmChannel,
|
||||
type GroupDmChannelResponse,
|
||||
} from '@app/api/channel/tests/ChannelTestUtils';
|
||||
import {type ApiTestHarness, createApiTestHarness} from '@app/api/test/ApiTestHarness';
|
||||
import {CAPTCHA_TEST_HEADER, issueSolvedCaptchaToken, useCheapCaptcha} from '@app/api/test/CaptchaTestUtils';
|
||||
import {HTTP_STATUS} from '@app/api/test/TestConstants';
|
||||
import {createBuilder} from '@app/api/test/TestRequestBuilder';
|
||||
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
|
||||
@@ -15,22 +15,8 @@ import {ChannelTypes} from '@fluxer/constants/src/ChannelConstants';
|
||||
import {afterAll, beforeAll, beforeEach, describe, expect, it} from 'vitest';
|
||||
|
||||
const HTTP_TOO_MANY_REQUESTS = 429;
|
||||
const TEST_CAPTCHA_TOKEN = 'test-captcha-token';
|
||||
const RATE_LIMIT_TEST_HEADER = 'x-fluxer-test-enable-rate-limits';
|
||||
|
||||
async function withCaptchaEnabled<T>(run: () => Promise<T>): Promise<T> {
|
||||
const previousEnabled = Config.captcha.enabled;
|
||||
const previousTestModeEnabled = Config.dev.testModeEnabled;
|
||||
Config.captcha.enabled = true;
|
||||
Config.dev.testModeEnabled = true;
|
||||
try {
|
||||
return await run();
|
||||
} finally {
|
||||
Config.captcha.enabled = previousEnabled;
|
||||
Config.dev.testModeEnabled = previousTestModeEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
async function createGroupDmWithCaptcha(
|
||||
harness: ApiTestHarness,
|
||||
owner: TestAccount,
|
||||
@@ -38,7 +24,8 @@ async function createGroupDmWithCaptcha(
|
||||
): Promise<GroupDmChannelResponse> {
|
||||
return await createBuilder<GroupDmChannelResponse>(harness, owner.token)
|
||||
.post('/users/@me/channels')
|
||||
.header('x-captcha-token', TEST_CAPTCHA_TOKEN)
|
||||
.header(CAPTCHA_TEST_HEADER, 'true')
|
||||
.header('x-captcha-token', await issueSolvedCaptchaToken(harness))
|
||||
.body({recipients: recipientIds})
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.execute();
|
||||
@@ -51,6 +38,7 @@ describe('Group DM captcha and rate limits', () => {
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await harness.reset();
|
||||
await useCheapCaptcha();
|
||||
});
|
||||
afterAll(async () => {
|
||||
await harness?.shutdown();
|
||||
@@ -62,21 +50,20 @@ describe('Group DM captcha and rate limits', () => {
|
||||
const groupDmRecipient = await createTestAccount(harness);
|
||||
await createFriendship(harness, owner, dmRecipient);
|
||||
await createFriendship(harness, owner, groupDmRecipient);
|
||||
await withCaptchaEnabled(async () => {
|
||||
const dm = await createBuilder<{type: number}>(harness, owner.token)
|
||||
.post('/users/@me/channels')
|
||||
.body({recipient_id: dmRecipient.userId})
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.execute();
|
||||
expect(dm.type).toBe(ChannelTypes.DM);
|
||||
await createBuilder(harness, owner.token)
|
||||
.post('/users/@me/channels')
|
||||
.body({recipients: [groupDmRecipient.userId]})
|
||||
.expect(HTTP_STATUS.BAD_REQUEST, APIErrorCodes.CAPTCHA_REQUIRED)
|
||||
.execute();
|
||||
const groupDm = await createGroupDmWithCaptcha(harness, owner, [groupDmRecipient.userId]);
|
||||
expect(groupDm.type).toBe(ChannelTypes.GROUP_DM);
|
||||
});
|
||||
const dm = await createBuilder<{type: number}>(harness, owner.token)
|
||||
.post('/users/@me/channels')
|
||||
.body({recipient_id: dmRecipient.userId})
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.execute();
|
||||
expect(dm.type).toBe(ChannelTypes.DM);
|
||||
await createBuilder(harness, owner.token)
|
||||
.post('/users/@me/channels')
|
||||
.header(CAPTCHA_TEST_HEADER, 'true')
|
||||
.body({recipients: [groupDmRecipient.userId]})
|
||||
.expect(HTTP_STATUS.BAD_REQUEST, APIErrorCodes.CAPTCHA_REQUIRED)
|
||||
.execute();
|
||||
const groupDm = await createGroupDmWithCaptcha(harness, owner, [groupDmRecipient.userId]);
|
||||
expect(groupDm.type).toBe(ChannelTypes.GROUP_DM);
|
||||
});
|
||||
|
||||
it('requires captcha when adding a recipient to an existing group DM', async () => {
|
||||
@@ -86,43 +73,43 @@ describe('Group DM captcha and rate limits', () => {
|
||||
await createFriendship(harness, owner, member);
|
||||
await createFriendship(harness, owner, newMember);
|
||||
const groupDm = await createGroupDmChannel(harness, owner.token, [member.userId]);
|
||||
await withCaptchaEnabled(async () => {
|
||||
await createBuilder(harness, owner.token)
|
||||
.put(`/channels/${groupDm.id}/recipients/${newMember.userId}`)
|
||||
.body(null)
|
||||
.expect(HTTP_STATUS.BAD_REQUEST, APIErrorCodes.CAPTCHA_REQUIRED)
|
||||
.execute();
|
||||
await createBuilder(harness, owner.token)
|
||||
.put(`/channels/${groupDm.id}/recipients/${newMember.userId}`)
|
||||
.header('x-captcha-token', TEST_CAPTCHA_TOKEN)
|
||||
.body(null)
|
||||
.expect(HTTP_STATUS.NO_CONTENT)
|
||||
.execute();
|
||||
});
|
||||
await createBuilder(harness, owner.token)
|
||||
.put(`/channels/${groupDm.id}/recipients/${newMember.userId}`)
|
||||
.header(CAPTCHA_TEST_HEADER, 'true')
|
||||
.body(null)
|
||||
.expect(HTTP_STATUS.BAD_REQUEST, APIErrorCodes.CAPTCHA_REQUIRED)
|
||||
.execute();
|
||||
await createBuilder(harness, owner.token)
|
||||
.put(`/channels/${groupDm.id}/recipients/${newMember.userId}`)
|
||||
.header(CAPTCHA_TEST_HEADER, 'true')
|
||||
.header('x-captcha-token', await issueSolvedCaptchaToken(harness))
|
||||
.body(null)
|
||||
.expect(HTTP_STATUS.NO_CONTENT)
|
||||
.execute();
|
||||
});
|
||||
|
||||
it('limits group DM creation to 10 per user per hour', async () => {
|
||||
const owner = await createTestAccount(harness);
|
||||
const recipient = await createTestAccount(harness);
|
||||
await createFriendship(harness, owner, recipient);
|
||||
await withCaptchaEnabled(async () => {
|
||||
for (let index = 0; index < 10; index++) {
|
||||
await createBuilder(harness, owner.token)
|
||||
.post('/users/@me/channels')
|
||||
.header(RATE_LIMIT_TEST_HEADER, 'true')
|
||||
.header('x-captcha-token', TEST_CAPTCHA_TOKEN)
|
||||
.body({recipients: [recipient.userId]})
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.execute();
|
||||
}
|
||||
for (let index = 0; index < 10; index++) {
|
||||
await createBuilder(harness, owner.token)
|
||||
.post('/users/@me/channels')
|
||||
.header(RATE_LIMIT_TEST_HEADER, 'true')
|
||||
.header('x-captcha-token', TEST_CAPTCHA_TOKEN)
|
||||
.header(CAPTCHA_TEST_HEADER, 'true')
|
||||
.header('x-captcha-token', await issueSolvedCaptchaToken(harness))
|
||||
.body({recipients: [recipient.userId]})
|
||||
.expect(HTTP_TOO_MANY_REQUESTS, APIErrorCodes.RATE_LIMITED)
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.execute();
|
||||
});
|
||||
}
|
||||
await createBuilder(harness, owner.token)
|
||||
.post('/users/@me/channels')
|
||||
.header(RATE_LIMIT_TEST_HEADER, 'true')
|
||||
.header(CAPTCHA_TEST_HEADER, 'true')
|
||||
.header('x-captcha-token', await issueSolvedCaptchaToken(harness))
|
||||
.body({recipients: [recipient.userId]})
|
||||
.expect(HTTP_TOO_MANY_REQUESTS, APIErrorCodes.RATE_LIMITED)
|
||||
.execute();
|
||||
});
|
||||
|
||||
it('limits group DM recipient additions to 10 per user per hour', async () => {
|
||||
@@ -136,23 +123,23 @@ describe('Group DM captcha and rate limits', () => {
|
||||
newMembers.push(member);
|
||||
}
|
||||
const groupDm = await createGroupDmChannel(harness, owner.token, [initialMember.userId]);
|
||||
await withCaptchaEnabled(async () => {
|
||||
for (let index = 0; index < 10; index++) {
|
||||
await createBuilder(harness, owner.token)
|
||||
.put(`/channels/${groupDm.id}/recipients/${newMembers[index].userId}`)
|
||||
.header(RATE_LIMIT_TEST_HEADER, 'true')
|
||||
.header('x-captcha-token', TEST_CAPTCHA_TOKEN)
|
||||
.body(null)
|
||||
.expect(HTTP_STATUS.NO_CONTENT)
|
||||
.execute();
|
||||
}
|
||||
for (let index = 0; index < 10; index++) {
|
||||
await createBuilder(harness, owner.token)
|
||||
.put(`/channels/${groupDm.id}/recipients/${newMembers[10].userId}`)
|
||||
.put(`/channels/${groupDm.id}/recipients/${newMembers[index].userId}`)
|
||||
.header(RATE_LIMIT_TEST_HEADER, 'true')
|
||||
.header('x-captcha-token', TEST_CAPTCHA_TOKEN)
|
||||
.header(CAPTCHA_TEST_HEADER, 'true')
|
||||
.header('x-captcha-token', await issueSolvedCaptchaToken(harness))
|
||||
.body(null)
|
||||
.expect(HTTP_TOO_MANY_REQUESTS, APIErrorCodes.RATE_LIMITED)
|
||||
.expect(HTTP_STATUS.NO_CONTENT)
|
||||
.execute();
|
||||
});
|
||||
}
|
||||
await createBuilder(harness, owner.token)
|
||||
.put(`/channels/${groupDm.id}/recipients/${newMembers[10].userId}`)
|
||||
.header(RATE_LIMIT_TEST_HEADER, 'true')
|
||||
.header(CAPTCHA_TEST_HEADER, 'true')
|
||||
.header('x-captcha-token', await issueSolvedCaptchaToken(harness))
|
||||
.body(null)
|
||||
.expect(HTTP_TOO_MANY_REQUESTS, APIErrorCodes.RATE_LIMITED)
|
||||
.execute();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user