test: isolate user send rate limits

This commit is contained in:
dreamhunter2333
2026-08-23 21:58:23 +08:00
parent 3031589a4b
commit d09e44c055
4 changed files with 113 additions and 25 deletions
+21
View File
@@ -90,6 +90,24 @@ services:
start_period: 10s start_period: 10s
retries: 20 retries: 20
worker-rate-limit:
build:
context: ..
dockerfile: e2e/Dockerfile.worker
args:
WRANGLER_TOML: e2e/fixtures/wrangler.toml.e2e.rate-limit
ports:
- "8792:8792"
command: ["pnpm", "exec", "wrangler", "dev", "--port", "8792", "--ip", "0.0.0.0"]
depends_on:
- mailpit
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:8792/health_check"]
interval: 3s
timeout: 5s
start_period: 10s
retries: 20
frontend: frontend:
build: build:
context: .. context: ..
@@ -147,6 +165,7 @@ services:
WORKER_URL_ENV_OFF: http://worker-env-off:8790 WORKER_URL_ENV_OFF: http://worker-env-off:8790
WORKER_GZIP_URL: http://worker-gzip:8788 WORKER_GZIP_URL: http://worker-gzip:8788
WORKER_URL_SEND_MAIL_DOMAIN: http://worker-send-mail-domain:8791 WORKER_URL_SEND_MAIL_DOMAIN: http://worker-send-mail-domain:8791
WORKER_URL_RATE_LIMIT: http://worker-rate-limit:8792
FRONTEND_URL: https://frontend:5173 FRONTEND_URL: https://frontend:5173
MAILPIT_API: http://mailpit:8025/api MAILPIT_API: http://mailpit:8025/api
SMTP_PROXY_HOST: smtp-proxy SMTP_PROXY_HOST: smtp-proxy
@@ -167,6 +186,8 @@ services:
condition: service_healthy condition: service_healthy
worker-send-mail-domain: worker-send-mail-domain:
condition: service_healthy condition: service_healthy
worker-rate-limit:
condition: service_healthy
frontend: frontend:
condition: service_started condition: service_started
smtp-proxy: smtp-proxy:
-5
View File
@@ -39,8 +39,3 @@ id = "e2e-test-kv-00000000-0000-0000-0000-000000000000"
binding = "DB" binding = "DB"
database_name = "e2e-temp-email" database_name = "e2e-temp-email"
database_id = "e2e-test-db-00000000-0000-0000-0000-000000000000" database_id = "e2e-test-db-00000000-0000-0000-0000-000000000000"
[[ratelimits]]
name = "RATE_LIMITER"
namespace_id = "1001"
simple = { limit = 2, period = 60 }
+34
View File
@@ -0,0 +1,34 @@
name = "cloudflare_temp_email_rate_limit"
main = "src/worker.ts"
compatibility_date = "2025-04-01"
compatibility_flags = [ "nodejs_compat" ]
keep_vars = true
[vars]
PREFIX = "tmp"
DEFAULT_DOMAINS = ["test.example.com"]
DOMAINS = ["test.example.com"]
JWT_SECRET = "e2e-test-secret-key-rate-limit"
BLACK_LIST = ""
ENABLE_USER_CREATE_EMAIL = true
ENABLE_USER_DELETE_EMAIL = true
DEFAULT_SEND_BALANCE = 10
DISABLE_ADMIN_PASSWORD_CHECK = true
E2E_TEST_MODE = true
SMTP_CONFIG = """
{"test.example.com":{"host":"mailpit","port":1025,"secure":false}}
"""
[[kv_namespaces]]
binding = "KV"
id = "e2e-test-kv-rate-limit-00000000-0000-0000-0000-000000000000"
[[d1_databases]]
binding = "DB"
database_name = "e2e-temp-email-rate-limit"
database_id = "e2e-test-db-rate-limit-00000000-0000-0000-0000-000000000000"
[[ratelimits]]
name = "RATE_LIMITER"
namespace_id = "1001"
simple = { limit = 2, period = 60 }
+58 -20
View File
@@ -11,15 +11,15 @@ import {
updateAddressSender, updateAddressSender,
} from '../../fixtures/test-helpers'; } from '../../fixtures/test-helpers';
async function createUser(request: APIRequestContext) { async function createUser(request: APIRequestContext, workerUrl = WORKER_URL) {
const email = `user-send-${Date.now()}@test.example.com`; const email = `user-send-${Date.now()}@test.example.com`;
const password = hashPassword('test-password-123'); const password = hashPassword('test-password-123');
const registerRes = await request.post(`${WORKER_URL}/user_api/register`, { const registerRes = await request.post(`${workerUrl}/user_api/register`, {
data: { email, password }, data: { email, password },
}); });
expect(registerRes.ok()).toBe(true); expect(registerRes.ok()).toBe(true);
const loginRes = await request.post(`${WORKER_URL}/user_api/login`, { const loginRes = await request.post(`${workerUrl}/user_api/login`, {
data: { email, password }, data: { email, password },
}); });
expect(loginRes.ok()).toBe(true); expect(loginRes.ok()).toBe(true);
@@ -32,8 +32,9 @@ async function bindAddress(
request: APIRequestContext, request: APIRequestContext,
userJwt: string, userJwt: string,
addressJwt: string, addressJwt: string,
workerUrl = WORKER_URL,
) { ) {
const response = await request.post(`${WORKER_URL}/user_api/bind_address`, { const response = await request.post(`${workerUrl}/user_api/bind_address`, {
headers: { headers: {
Authorization: `Bearer ${addressJwt}`, Authorization: `Bearer ${addressJwt}`,
'x-user-token': userJwt, 'x-user-token': userJwt,
@@ -283,8 +284,22 @@ test.describe('User send mail API', () => {
test('applies unlimited balance from the user role access token', async ({ request }) => { test('applies unlimited balance from the user role access token', async ({ request }) => {
const addresses: Awaited<ReturnType<typeof createTestAddress>>[] = []; const addresses: Awaited<ReturnType<typeof createTestAddress>>[] = [];
let userId: number | undefined; let userId: number | undefined;
let originalUserSettings: Record<string, unknown> | undefined;
try { try {
const settingsRes = await request.get(`${WORKER_URL}/admin/user_settings`);
expect(settingsRes.ok()).toBe(true);
originalUserSettings = await settingsRes.json();
const enableUserRes = await request.post(`${WORKER_URL}/admin/user_settings`, {
data: {
...originalUserSettings,
enable: true,
enableMailVerify: false,
maxAddressCount: 0,
},
});
expect(enableUserRes.ok()).toBe(true);
const user = await createUser(request); const user = await createUser(request);
userId = user.userId; userId = user.userId;
const address = await createTestAddress(request, 'user-send-role-'); const address = await createTestAddress(request, 'user-send-role-');
@@ -346,25 +361,50 @@ test.describe('User send mail API', () => {
if (userId !== undefined) { if (userId !== undefined) {
await request.delete(`${WORKER_URL}/admin/users/${userId}`); await request.delete(`${WORKER_URL}/admin/users/${userId}`);
} }
if (originalUserSettings) {
await request.post(`${WORKER_URL}/admin/user_settings`, {
data: originalUserSettings,
});
}
} }
}); });
test('shares one rate-limit bucket across bound addresses', async ({ request }) => { test('shares one rate-limit bucket across bound addresses', async ({ request }) => {
const addresses: Awaited<ReturnType<typeof createTestAddress>>[] = []; const workerUrl = process.env.WORKER_URL_RATE_LIMIT || '';
test.skip(!workerUrl, 'WORKER_URL_RATE_LIMIT is not configured');
const addressIds: number[] = [];
let userId: number | undefined; let userId: number | undefined;
try { try {
const user = await createUser(request); const enableUserRes = await request.post(`${workerUrl}/admin/user_settings`, {
data: {
enable: true,
enableMailVerify: false,
maxAddressCount: 0,
},
});
expect(enableUserRes.ok()).toBe(true);
const user = await createUser(request, workerUrl);
userId = user.userId; userId = user.userId;
const first = await createTestAddress(request, 'user-rate-first-'); const createAddress = async (name: string) => {
const second = await createTestAddress(request, 'user-rate-second-'); const response = await request.post(`${workerUrl}/api/new_address`, {
addresses.push(first, second); data: { name, domain: 'test.example.com' },
await bindAddress(request, user.jwt, first.jwt); });
await bindAddress(request, user.jwt, second.jwt); expect(response.ok()).toBe(true);
return await response.json() as {
jwt: string;
address_id: number;
};
};
const first = await createAddress(`rate-first-${Date.now()}`);
const second = await createAddress(`rate-second-${Date.now()}`);
addressIds.push(first.address_id, second.address_id);
await bindAddress(request, user.jwt, first.jwt, workerUrl);
await bindAddress(request, user.jwt, second.jwt, workerUrl);
const requestAccess = async (address: typeof first) => { const requestAccess = async (address: typeof first) => {
const accessRes = await request.post( const accessRes = await request.post(
`${WORKER_URL}/user_api/address/${address.address_id}/request_send_mail_access`, `${workerUrl}/user_api/address/${address.address_id}/request_send_mail_access`,
{ headers: { 'x-user-token': user.jwt } }, { headers: { 'x-user-token': user.jwt } },
); );
expect(accessRes.ok()).toBe(true); expect(accessRes.ok()).toBe(true);
@@ -372,14 +412,10 @@ test.describe('User send mail API', () => {
await requestAccess(first); await requestAccess(first);
await requestAccess(second); await requestAccess(second);
const reqIp = `198.51.100.${Math.floor(Math.random() * 200) + 1}`;
const send = (address: typeof first, sequence: number) => request.post( const send = (address: typeof first, sequence: number) => request.post(
`${WORKER_URL}/user_api/address/${address.address_id}/send_mail`, `${workerUrl}/user_api/address/${address.address_id}/send_mail`,
{ {
headers: { headers: { 'x-user-token': user.jwt },
'x-user-token': user.jwt,
'cf-connecting-ip': reqIp,
},
data: { data: {
to_mail: 'recipient@test.example.com', to_mail: 'recipient@test.example.com',
subject: `Shared rate limit ${sequence} ${Date.now()}`, subject: `Shared rate limit ${sequence} ${Date.now()}`,
@@ -395,9 +431,11 @@ test.describe('User send mail API', () => {
expect(limitedRes.status()).toBe(429); expect(limitedRes.status()).toBe(429);
expect(await limitedRes.text()).toContain('Rate limit exceeded'); expect(await limitedRes.text()).toContain('Rate limit exceeded');
} finally { } finally {
await Promise.allSettled(addresses.map((address) => deleteAddress(request, address.jwt))); await Promise.allSettled(addressIds.map((addressId) => (
request.delete(`${workerUrl}/admin/delete_address/${addressId}`)
)));
if (userId !== undefined) { if (userId !== undefined) {
await request.delete(`${WORKER_URL}/admin/users/${userId}`); await request.delete(`${workerUrl}/admin/users/${userId}`);
} }
} }
}); });