mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-05-27 02:59:52 +08:00
fix: normalize domain casing
Fix domain casing normalization for configured domains and inbound recipient domains.
This commit is contained in:
@@ -16,4 +16,116 @@ test.describe('Admin New Address', () => {
|
||||
expect(body.address_id).toBeGreaterThan(0);
|
||||
expect(typeof body.address_id).toBe('number');
|
||||
});
|
||||
|
||||
test('normalizes uppercase configured prefix and domain', async ({ request }) => {
|
||||
const uniqueName = `admincase${Date.now()}`;
|
||||
const res = await request.post(`${WORKER_URL}/admin/new_address`, {
|
||||
data: { name: uniqueName, domain: TEST_DOMAIN.toUpperCase(), enablePrefix: true },
|
||||
});
|
||||
|
||||
expect(res.ok()).toBe(true);
|
||||
const body = await res.json();
|
||||
|
||||
expect(body.address).toBe(`tmp${uniqueName}@${TEST_DOMAIN}`);
|
||||
expect(body.jwt).toBeTruthy();
|
||||
expect(body.address_id).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('falls back to domains when default domains is empty', async ({ request }) => {
|
||||
const uniqueName = `fallback${Date.now().toString(36)}`;
|
||||
const res = await request.post(`${WORKER_URL}/api/new_address`, {
|
||||
data: { name: uniqueName },
|
||||
});
|
||||
|
||||
expect(res.ok()).toBe(true);
|
||||
const body = await res.json();
|
||||
|
||||
expect(body.address).toBe(`tmp${uniqueName}@${TEST_DOMAIN}`);
|
||||
expect(body.jwt).toBeTruthy();
|
||||
expect(body.address_id).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('normalizes user role domains and prefix', async ({ request }) => {
|
||||
const suffix = `${Date.now()}${Math.random().toString(36).slice(2, 8)}`;
|
||||
const email = `role-case-${suffix}@${TEST_DOMAIN}`;
|
||||
const password = `role-pass-${suffix}`;
|
||||
const name = `rolecase${suffix}`;
|
||||
|
||||
const createUserRes = await request.post(`${WORKER_URL}/admin/users`, {
|
||||
data: { email, password },
|
||||
});
|
||||
expect(createUserRes.ok()).toBe(true);
|
||||
|
||||
const usersRes = await request.get(`${WORKER_URL}/admin/users`, {
|
||||
params: { limit: '20', offset: '0', query: email },
|
||||
});
|
||||
expect(usersRes.ok()).toBe(true);
|
||||
const usersBody = await usersRes.json();
|
||||
const user = usersBody.results.find((row: { user_email: string }) => row.user_email === email);
|
||||
expect(user).toBeTruthy();
|
||||
|
||||
const updateRoleRes = await request.post(`${WORKER_URL}/admin/user_roles`, {
|
||||
data: { user_id: user.id, role_text: 'case-role' },
|
||||
});
|
||||
expect(updateRoleRes.ok()).toBe(true);
|
||||
|
||||
const loginRes = await request.post(`${WORKER_URL}/user_api/login`, {
|
||||
data: { email, password },
|
||||
});
|
||||
expect(loginRes.ok()).toBe(true);
|
||||
const { jwt: userJwt } = await loginRes.json();
|
||||
|
||||
const createAddressRes = await request.post(`${WORKER_URL}/api/new_address`, {
|
||||
headers: { 'x-user-token': userJwt },
|
||||
data: { name },
|
||||
});
|
||||
expect(createAddressRes.ok()).toBe(true);
|
||||
const addressBody = await createAddressRes.json();
|
||||
|
||||
expect(addressBody.address).toBe(`role${name}@${TEST_DOMAIN}`);
|
||||
expect(addressBody.jwt).toBeTruthy();
|
||||
expect(addressBody.address_id).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('falls back to default domains when user role domains is empty', async ({ request }) => {
|
||||
const suffix = `${Date.now()}${Math.random().toString(36).slice(2, 8)}`;
|
||||
const email = `empty-role-${suffix}@${TEST_DOMAIN}`;
|
||||
const password = `empty-role-pass-${suffix}`;
|
||||
const name = `emptyrole${suffix}`;
|
||||
|
||||
const createUserRes = await request.post(`${WORKER_URL}/admin/users`, {
|
||||
data: { email, password },
|
||||
});
|
||||
expect(createUserRes.ok()).toBe(true);
|
||||
|
||||
const usersRes = await request.get(`${WORKER_URL}/admin/users`, {
|
||||
params: { limit: '20', offset: '0', query: email },
|
||||
});
|
||||
expect(usersRes.ok()).toBe(true);
|
||||
const usersBody = await usersRes.json();
|
||||
const user = usersBody.results.find((row: { user_email: string }) => row.user_email === email);
|
||||
expect(user).toBeTruthy();
|
||||
|
||||
const updateRoleRes = await request.post(`${WORKER_URL}/admin/user_roles`, {
|
||||
data: { user_id: user.id, role_text: 'empty-role' },
|
||||
});
|
||||
expect(updateRoleRes.ok()).toBe(true);
|
||||
|
||||
const loginRes = await request.post(`${WORKER_URL}/user_api/login`, {
|
||||
data: { email, password },
|
||||
});
|
||||
expect(loginRes.ok()).toBe(true);
|
||||
const { jwt: userJwt } = await loginRes.json();
|
||||
|
||||
const createAddressRes = await request.post(`${WORKER_URL}/api/new_address`, {
|
||||
headers: { 'x-user-token': userJwt },
|
||||
data: { name },
|
||||
});
|
||||
expect(createAddressRes.ok()).toBe(true);
|
||||
const addressBody = await createAddressRes.json();
|
||||
|
||||
expect(addressBody.address).toBe(`empty${name}@${TEST_DOMAIN}`);
|
||||
expect(addressBody.jwt).toBeTruthy();
|
||||
expect(addressBody.address_id).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
175
e2e/tests/api/email-forward-domain-normalization.spec.ts
Normal file
175
e2e/tests/api/email-forward-domain-normalization.spec.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import type { APIRequestContext } from '@playwright/test';
|
||||
import {
|
||||
WORKER_URL,
|
||||
TEST_DOMAIN,
|
||||
createTestAddress,
|
||||
deleteAddress,
|
||||
} from '../../fixtures/test-helpers';
|
||||
|
||||
const ADMIN_PASSWORD = 'e2e-admin-pass';
|
||||
const ADMIN_HEADERS = { 'x-admin-auth': ADMIN_PASSWORD };
|
||||
|
||||
const DEFAULT_ACCOUNT_SETTINGS = {
|
||||
blockList: [],
|
||||
sendBlockList: [],
|
||||
verifiedAddressList: [],
|
||||
fromBlockList: [],
|
||||
noLimitSendAddressList: [],
|
||||
emailRuleSettings: {},
|
||||
addressCreationSettings: {},
|
||||
};
|
||||
|
||||
async function resetAccountSettings(request: APIRequestContext) {
|
||||
const res = await request.post(`${WORKER_URL}/admin/account_settings`, {
|
||||
headers: ADMIN_HEADERS,
|
||||
data: DEFAULT_ACCOUNT_SETTINGS,
|
||||
});
|
||||
expect(res.ok()).toBe(true);
|
||||
}
|
||||
|
||||
test.describe('Email forward domain normalization', () => {
|
||||
test.afterEach(async ({ request }) => {
|
||||
await resetAccountSettings(request);
|
||||
});
|
||||
|
||||
test('normalizes uppercase forwarding rule and recipient domains', async ({ request }) => {
|
||||
const { jwt, address } = await createTestAddress(request, 'forward-case');
|
||||
const forwardAddress = 'forward-target@test.example.com';
|
||||
|
||||
try {
|
||||
const saveRes = await request.post(`${WORKER_URL}/admin/account_settings`, {
|
||||
headers: ADMIN_HEADERS,
|
||||
data: {
|
||||
...DEFAULT_ACCOUNT_SETTINGS,
|
||||
emailRuleSettings: {
|
||||
emailForwardingList: [{
|
||||
domains: [TEST_DOMAIN.toUpperCase()],
|
||||
forward: forwardAddress,
|
||||
}],
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(saveRes.ok()).toBe(true);
|
||||
|
||||
const to = address.replace(`@${TEST_DOMAIN}`, `@${TEST_DOMAIN.toUpperCase()}`);
|
||||
const subject = `forward-case-${Date.now()}`;
|
||||
const raw = [
|
||||
`From: sender@test.example.com`,
|
||||
`To: ${to}`,
|
||||
`Subject: ${subject}`,
|
||||
`Message-ID: <${subject}@test>`,
|
||||
`MIME-Version: 1.0`,
|
||||
`Content-Type: text/plain; charset=utf-8`,
|
||||
``,
|
||||
`Forward domain normalization test`,
|
||||
].join('\r\n');
|
||||
|
||||
const res = await request.post(`${WORKER_URL}/admin/test/receive_mail`, {
|
||||
data: { from: 'sender@test.example.com', to, raw },
|
||||
});
|
||||
expect(res.ok()).toBe(true);
|
||||
const body = await res.json();
|
||||
expect(body.success).toBe(true);
|
||||
expect(body.forwardedTo).toEqual([forwardAddress]);
|
||||
|
||||
const mailsRes = await request.get(`${WORKER_URL}/api/mails?limit=10&offset=0`, {
|
||||
headers: { Authorization: `Bearer ${jwt}` },
|
||||
});
|
||||
expect(mailsRes.ok()).toBe(true);
|
||||
const mailsBody = await mailsRes.json();
|
||||
expect(mailsBody.results.some((mail: { address: string; raw: string }) => {
|
||||
return mail.address === address && mail.raw.includes(subject);
|
||||
})).toBe(true);
|
||||
} finally {
|
||||
await deleteAddress(request, jwt);
|
||||
}
|
||||
});
|
||||
|
||||
test('does not forward when only the domain suffix string matches', async ({ request }) => {
|
||||
const { jwt, address } = await createTestAddress(request, 'forward-boundary');
|
||||
const forwardAddress = 'forward-boundary-target@test.example.com';
|
||||
|
||||
try {
|
||||
const saveRes = await request.post(`${WORKER_URL}/admin/account_settings`, {
|
||||
headers: ADMIN_HEADERS,
|
||||
data: {
|
||||
...DEFAULT_ACCOUNT_SETTINGS,
|
||||
emailRuleSettings: {
|
||||
emailForwardingList: [{
|
||||
domains: [TEST_DOMAIN],
|
||||
forward: forwardAddress,
|
||||
}],
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(saveRes.ok()).toBe(true);
|
||||
|
||||
const to = address.replace(`@${TEST_DOMAIN}`, `@evil${TEST_DOMAIN}`);
|
||||
const subject = `forward-boundary-${Date.now()}`;
|
||||
const raw = [
|
||||
`From: sender@test.example.com`,
|
||||
`To: ${to}`,
|
||||
`Subject: ${subject}`,
|
||||
`Message-ID: <${subject}@test>`,
|
||||
`MIME-Version: 1.0`,
|
||||
`Content-Type: text/plain; charset=utf-8`,
|
||||
``,
|
||||
`Forward domain boundary test`,
|
||||
].join('\r\n');
|
||||
|
||||
const res = await request.post(`${WORKER_URL}/admin/test/receive_mail`, {
|
||||
data: { from: 'sender@test.example.com', to, raw },
|
||||
});
|
||||
expect(res.ok()).toBe(true);
|
||||
const body = await res.json();
|
||||
expect(body.success).toBe(true);
|
||||
expect(body.forwardedTo).toEqual([]);
|
||||
} finally {
|
||||
await deleteAddress(request, jwt);
|
||||
}
|
||||
});
|
||||
|
||||
test('keeps blank forwarding rule domain as catch-all', async ({ request }) => {
|
||||
const { jwt, address } = await createTestAddress(request, 'forward-catch-all');
|
||||
const forwardAddress = 'forward-catch-all-target@test.example.com';
|
||||
|
||||
try {
|
||||
const saveRes = await request.post(`${WORKER_URL}/admin/account_settings`, {
|
||||
headers: ADMIN_HEADERS,
|
||||
data: {
|
||||
...DEFAULT_ACCOUNT_SETTINGS,
|
||||
emailRuleSettings: {
|
||||
emailForwardingList: [{
|
||||
domains: ['', 'not-the-domain.example.com'],
|
||||
forward: forwardAddress,
|
||||
}],
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(saveRes.ok()).toBe(true);
|
||||
|
||||
const subject = `forward-catch-all-${Date.now()}`;
|
||||
const raw = [
|
||||
`From: sender@test.example.com`,
|
||||
`To: ${address}`,
|
||||
`Subject: ${subject}`,
|
||||
`Message-ID: <${subject}@test>`,
|
||||
`MIME-Version: 1.0`,
|
||||
`Content-Type: text/plain; charset=utf-8`,
|
||||
``,
|
||||
`Forward catch-all domain test`,
|
||||
].join('\r\n');
|
||||
|
||||
const res = await request.post(`${WORKER_URL}/admin/test/receive_mail`, {
|
||||
data: { from: 'sender@test.example.com', to: address, raw },
|
||||
});
|
||||
expect(res.ok()).toBe(true);
|
||||
const body = await res.json();
|
||||
expect(body.success).toBe(true);
|
||||
expect(body.forwardedTo).toEqual([forwardAddress]);
|
||||
} finally {
|
||||
await deleteAddress(request, jwt);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -15,6 +15,7 @@ test.describe('Health & Settings', () => {
|
||||
const settings = await res.json();
|
||||
expect(settings.domains).toContain('test.example.com');
|
||||
expect(settings.defaultDomains).toContain('test.example.com');
|
||||
expect(settings.prefix).toBe('tmp');
|
||||
expect(settings.enableSendMail).toBe(true);
|
||||
expect(settings.enableUserCreateEmail).toBe(true);
|
||||
expect(settings.enableUserDeleteEmail).toBe(true);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { test, expect, APIRequestContext } from '@playwright/test';
|
||||
import { test, expect } from '@playwright/test';
|
||||
import type { APIRequestContext } from '@playwright/test';
|
||||
import {
|
||||
WORKER_URL,
|
||||
WORKER_URL_SEND_MAIL_DOMAIN,
|
||||
@@ -425,11 +426,11 @@ test.describe('Send Mail Limit', () => {
|
||||
expect(res.status()).toBe(400);
|
||||
});
|
||||
|
||||
test('/admin/send_mail_by_binding returns 200 when domain is allowed', async ({ request }) => {
|
||||
test('/admin/send_mail_by_binding normalizes uppercase allowed domain', async ({ request }) => {
|
||||
const res = await request.post(`${WORKER_URL_SEND_MAIL_DOMAIN}/admin/send_mail_by_binding`, {
|
||||
headers: ADMIN_HEADERS,
|
||||
data: {
|
||||
from: 'admin@test.example.com',
|
||||
from: 'admin@TEST.EXAMPLE.COM',
|
||||
to: ['recipient@test.example.com'],
|
||||
subject: `send-mail-domain-ok-${Date.now()}`,
|
||||
text: 'body',
|
||||
@@ -439,6 +440,20 @@ test.describe('Send Mail Limit', () => {
|
||||
expect(await res.json()).toEqual({ status: 'ok' });
|
||||
});
|
||||
|
||||
test('/admin/send_mail_by_binding normalizes object-form from domain', async ({ request }) => {
|
||||
const res = await request.post(`${WORKER_URL_SEND_MAIL_DOMAIN}/admin/send_mail_by_binding`, {
|
||||
headers: ADMIN_HEADERS,
|
||||
data: {
|
||||
from: { email: 'admin@TEST.EXAMPLE.COM', name: 'Admin' },
|
||||
to: ['recipient@test.example.com'],
|
||||
subject: `send-mail-domain-object-ok-${Date.now()}`,
|
||||
text: 'body',
|
||||
},
|
||||
});
|
||||
expect(res.ok()).toBe(true);
|
||||
expect(await res.json()).toEqual({ status: 'ok' });
|
||||
});
|
||||
|
||||
test('/admin/send_mail_by_binding returns 400 when domain is not allowed', async ({ request }) => {
|
||||
const res = await request.post(`${WORKER_URL_SEND_MAIL_DOMAIN}/admin/send_mail_by_binding`, {
|
||||
headers: ADMIN_HEADERS,
|
||||
|
||||
118
e2e/tests/api/user-domain-normalization.spec.ts
Normal file
118
e2e/tests/api/user-domain-normalization.spec.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import type { APIRequestContext } from '@playwright/test';
|
||||
import http from 'node:http';
|
||||
import { WORKER_URL } from '../../fixtures/test-helpers';
|
||||
|
||||
async function resetUserSettings(request: APIRequestContext) {
|
||||
const res = await request.post(`${WORKER_URL}/admin/user_settings`, {
|
||||
data: {
|
||||
enable: true,
|
||||
enableMailVerify: false,
|
||||
enableMailAllowList: false,
|
||||
mailAllowList: [],
|
||||
},
|
||||
});
|
||||
expect(res.ok()).toBe(true);
|
||||
}
|
||||
|
||||
async function resetOauth2Settings(request: APIRequestContext) {
|
||||
const res = await request.post(`${WORKER_URL}/admin/user_oauth2_settings`, {
|
||||
data: [],
|
||||
});
|
||||
expect(res.ok()).toBe(true);
|
||||
}
|
||||
|
||||
async function startOauthServer(email: string): Promise<{ server: http.Server; baseUrl: string }> {
|
||||
const server = http.createServer((req, res) => {
|
||||
if (req.url === '/token') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ access_token: 'token', token_type: 'Bearer' }));
|
||||
return;
|
||||
}
|
||||
if (req.url === '/userinfo') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ email }));
|
||||
return;
|
||||
}
|
||||
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
||||
res.end('not found');
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => server.listen(0, '0.0.0.0', resolve));
|
||||
const addr = server.address();
|
||||
if (!addr || typeof addr === 'string') throw new Error('Failed to resolve OAuth test server port');
|
||||
const hostname = process.env.CI ? 'e2e-runner' : 'localhost';
|
||||
return { server, baseUrl: `http://${hostname}:${addr.port}` };
|
||||
}
|
||||
|
||||
test.describe('User domain normalization', () => {
|
||||
test.afterEach(async ({ request }) => {
|
||||
await resetOauth2Settings(request);
|
||||
await resetUserSettings(request);
|
||||
});
|
||||
|
||||
test('normalizes uppercase verify mail sender domain in admin settings', async ({ request }) => {
|
||||
const res = await request.post(`${WORKER_URL}/admin/user_settings`, {
|
||||
data: {
|
||||
enable: true,
|
||||
enableMailVerify: true,
|
||||
verifyMailSender: 'verify@TEST.EXAMPLE.COM',
|
||||
},
|
||||
});
|
||||
expect(res.ok()).toBe(true);
|
||||
});
|
||||
|
||||
test('normalizes uppercase user registration allow-list domains', async ({ request }) => {
|
||||
const saveRes = await request.post(`${WORKER_URL}/admin/user_settings`, {
|
||||
data: {
|
||||
enable: true,
|
||||
enableMailVerify: false,
|
||||
enableMailAllowList: true,
|
||||
mailAllowList: ['TEST.EXAMPLE.COM'],
|
||||
},
|
||||
});
|
||||
expect(saveRes.ok()).toBe(true);
|
||||
|
||||
const registerRes = await request.post(`${WORKER_URL}/user_api/register`, {
|
||||
data: {
|
||||
email: `allow-list-${Date.now()}@TEST.EXAMPLE.COM`,
|
||||
password: 'allow-list-password',
|
||||
},
|
||||
});
|
||||
expect(registerRes.ok()).toBe(true);
|
||||
});
|
||||
|
||||
test('normalizes uppercase OAuth2 allow-list domains', async ({ request }) => {
|
||||
const email = `oauth-allow-${Date.now()}@TEST.EXAMPLE.COM`;
|
||||
const { server, baseUrl } = await startOauthServer(email);
|
||||
|
||||
try {
|
||||
const saveRes = await request.post(`${WORKER_URL}/admin/user_oauth2_settings`, {
|
||||
data: [{
|
||||
name: 'case-oauth',
|
||||
clientID: 'case-client',
|
||||
clientSecret: 'case-secret',
|
||||
authorizationURL: `${baseUrl}/authorize`,
|
||||
accessTokenURL: `${baseUrl}/token`,
|
||||
accessTokenFormat: 'json',
|
||||
userInfoURL: `${baseUrl}/userinfo`,
|
||||
redirectURL: `${baseUrl}/callback`,
|
||||
userEmailKey: 'email',
|
||||
scope: 'openid email',
|
||||
enableMailAllowList: true,
|
||||
mailAllowList: ['TEST.EXAMPLE.COM'],
|
||||
}],
|
||||
});
|
||||
expect(saveRes.ok()).toBe(true);
|
||||
|
||||
const callbackRes = await request.post(`${WORKER_URL}/user_api/oauth2/callback`, {
|
||||
data: { clientID: 'case-client', code: 'case-code' },
|
||||
});
|
||||
expect(callbackRes.ok()).toBe(true);
|
||||
const body = await callbackRes.json();
|
||||
expect(body.jwt).toBeTruthy();
|
||||
} finally {
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user