mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-09-17 13:24:13 +08:00
fix: validate mailbox credentials and isolate E2E APIs (#1141)
This commit is contained in:
@@ -135,7 +135,7 @@ for (const { base, disabled } of [
|
||||
});
|
||||
}
|
||||
async function seed(request: APIRequestContext, mailbox: Mailbox, updatedAt: string | null = OLD, createdAt?: string) {
|
||||
await call(request, '/admin/test/seed_mail', {
|
||||
await call(request, '/__test/seed_mail', {
|
||||
method: 'POST', data: {
|
||||
address: mailbox.address, raw: `From: sender@test.example.com\r\nTo: ${mailbox.address}\r\nSubject: activity\r\n\r\nBody`,
|
||||
address_updated_at: updatedAt, address_created_at: createdAt, created_at: OLD,
|
||||
@@ -170,7 +170,17 @@ for (const { base, disabled } of [
|
||||
const inbox = await list(request, '/admin/mails', { address: mailbox.address });
|
||||
const sent = await list(request, '/admin/sendbox', { address: mailbox.address });
|
||||
const sender = await list(request, '/admin/address_sender', { address: mailbox.address });
|
||||
const reply = await (await call(request, '/api/auto_reply', { headers: addressAuth(mailbox) })).json();
|
||||
let replyMailbox = mailbox;
|
||||
if (!address) {
|
||||
await call(request, '/api/auto_reply', { headers: addressAuth(mailbox) }, 401);
|
||||
// A new credential lets us check that no old auto-reply data survived cleanup.
|
||||
const [name, domain] = mailbox.address.split('@');
|
||||
replyMailbox = await (await call(request, '/admin/new_address', {
|
||||
method: 'POST', data: { name, domain, enablePrefix: false },
|
||||
})).json();
|
||||
}
|
||||
const reply = await (await call(request, '/api/auto_reply', { headers: addressAuth(replyMailbox) })).json();
|
||||
if (!address) await call(request, `/admin/delete_address/${replyMailbox.address_id}`, { method: 'DELETE' });
|
||||
const bound = await (await call(request, '/user_api/bind_address', { headers: userAuth(user) })).json();
|
||||
return [Number(!!address), inbox.count, sent.count, sender.count, Number(!!reply.subject),
|
||||
bound.results.filter((row: { name: string }) => row.name === mailbox.address).length];
|
||||
@@ -267,7 +277,7 @@ for (const { base, disabled } of [
|
||||
await bind(request, mailbox, user);
|
||||
await seed(request, mailbox);
|
||||
const raw = `From: sender@test.example.com\r\nTo: ${mailbox.address}\r\nSubject: incoming\r\n\r\nBody`;
|
||||
const received = await call(request, '/admin/test/receive_mail', {
|
||||
const received = await call(request, '/__test/receive_mail', {
|
||||
method: 'POST', data: { from: 'sender@test.example.com', to: mailbox.address, raw },
|
||||
});
|
||||
expect((await received.json()).success).toBe(true);
|
||||
@@ -359,7 +369,7 @@ for (const { base, disabled } of [
|
||||
if (cleanType === 'mails_unknow') {
|
||||
queryAddress = `unknown${Date.now()}@test.example.com`;
|
||||
orphanAddresses.push(queryAddress);
|
||||
await call(request, '/admin/test/seed_mail', {
|
||||
await call(request, '/__test/seed_mail', {
|
||||
method: 'POST', data: { address: queryAddress, raw: 'old unknown mail', created_at: OLD },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import { expect, test, type APIRequestContext } from '@playwright/test';
|
||||
import { createHmac } from 'node:crypto';
|
||||
|
||||
import { WORKER_URL, TEST_DOMAIN, createTestAddress, deleteAddress, seedTestMail } from '../../fixtures/test-helpers';
|
||||
|
||||
function signToken(payload: Record<string, unknown>) {
|
||||
const header = Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })).toString('base64url');
|
||||
const body = Buffer.from(JSON.stringify(payload)).toString('base64url');
|
||||
const signature = createHmac('sha256', 'e2e-test-secret-key')
|
||||
.update(`${header}.${body}`).digest('base64url');
|
||||
return `${header}.${body}.${signature}`;
|
||||
}
|
||||
|
||||
async function expectRejected(request: APIRequestContext, jwt: string) {
|
||||
for (const [method, path] of [
|
||||
['GET', '/api/settings'],
|
||||
['GET', '/api/mails?limit=20&offset=0'],
|
||||
['GET', '/api/mail/1'],
|
||||
['GET', '/api/parsed_mails?limit=20&offset=0'],
|
||||
['GET', '/api/parsed_mail/1'],
|
||||
['GET', '/api/sendbox?limit=20&offset=0'],
|
||||
['GET', '/api/auto_reply'],
|
||||
['POST', '/api/webhook/settings'],
|
||||
['POST', '/api/attachment/get_url'],
|
||||
['POST', '/api/address_change_password'],
|
||||
['POST', '/api/request_send_mail_access'],
|
||||
['POST', '/api/send_mail'],
|
||||
['PATCH', '/api/mails/1/read'],
|
||||
['DELETE', '/api/mails/1'],
|
||||
['DELETE', '/api/sendbox/1'],
|
||||
['DELETE', '/api/clear_inbox'],
|
||||
['DELETE', '/api/clear_sent_items'],
|
||||
['DELETE', '/api/delete_address'],
|
||||
]) {
|
||||
const response = await request.fetch(`${WORKER_URL}${path}`, {
|
||||
method,
|
||||
headers: { Authorization: `Bearer ${jwt}` },
|
||||
...(method === 'POST' || method === 'PATCH' ? { data: {} } : {}),
|
||||
});
|
||||
expect(response.status(), `${method} ${path}`).toBe(401);
|
||||
}
|
||||
const login = await request.post(`${WORKER_URL}/open_api/credential_login`, {
|
||||
data: { credential: jwt },
|
||||
});
|
||||
expect(login.status()).toBe(401);
|
||||
const send = await request.post(`${WORKER_URL}/external/api/send_mail`, {
|
||||
headers: { 'x-lang': 'en' },
|
||||
data: { token: jwt },
|
||||
});
|
||||
expect(send.status()).toBe(400);
|
||||
expect(await send.text()).toBe('Failed to send mail Invalid address credential');
|
||||
const bind = await request.post(`${WORKER_URL}/user_api/bind_address`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
'x-user-token': signToken({ user_id: 1, exp: Math.floor(Date.now() / 1000) + 60 }),
|
||||
},
|
||||
});
|
||||
expect(bind.status()).toBe(401);
|
||||
}
|
||||
|
||||
test('deleted credentials cannot access or delete a recreated mailbox', async ({ request }) => {
|
||||
const name = `credential${Date.now()}`;
|
||||
const create = async () => {
|
||||
const response = await request.post(`${WORKER_URL}/api/new_address`, {
|
||||
data: { name, domain: TEST_DOMAIN },
|
||||
});
|
||||
expect(response.ok()).toBe(true);
|
||||
return await response.json();
|
||||
};
|
||||
const original = await create();
|
||||
await deleteAddress(request, original.jwt);
|
||||
await expectRejected(request, original.jwt);
|
||||
|
||||
const recreated = await create();
|
||||
try {
|
||||
expect(recreated.address).toBe(original.address);
|
||||
expect(recreated.address_id).not.toBe(original.address_id);
|
||||
await seedTestMail(request, recreated.address, { subject: 'New owner mail' });
|
||||
await expectRejected(request, original.jwt);
|
||||
const mails = await request.get(`${WORKER_URL}/api/mails?limit=20&offset=0`, {
|
||||
headers: { Authorization: `Bearer ${recreated.jwt}` },
|
||||
});
|
||||
expect(mails.ok()).toBe(true);
|
||||
expect((await mails.json()).count).toBe(1);
|
||||
} finally {
|
||||
await deleteAddress(request, recreated.jwt);
|
||||
}
|
||||
});
|
||||
|
||||
test('valid numeric/string IDs work; missing, invalid and mismatched IDs are rejected', async ({ request }) => {
|
||||
const mailbox = await createTestAddress(request, 'credential-id');
|
||||
try {
|
||||
for (const address_id of [mailbox.address_id, String(mailbox.address_id)]) {
|
||||
const token = signToken({ address: mailbox.address, address_id });
|
||||
const settings = await request.get(`${WORKER_URL}/api/settings`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
expect(settings.ok()).toBe(true);
|
||||
const login = await request.post(`${WORKER_URL}/open_api/credential_login`, {
|
||||
data: { credential: token },
|
||||
});
|
||||
expect(login.ok()).toBe(true);
|
||||
}
|
||||
for (const payload of [
|
||||
{ address: mailbox.address },
|
||||
...[0, -1, 1.5, true, null, '', '1e3', {}, Number.MAX_SAFE_INTEGER + 1]
|
||||
.map(address_id => ({ address: mailbox.address, address_id })),
|
||||
{ address: `other@${TEST_DOMAIN}`, address_id: mailbox.address_id },
|
||||
{ address_id: mailbox.address_id },
|
||||
]) {
|
||||
const jwt = signToken(payload);
|
||||
const response = await request.get(`${WORKER_URL}/api/mails?limit=20&offset=0`, {
|
||||
headers: { Authorization: `Bearer ${jwt}` },
|
||||
});
|
||||
expect(response.status(), JSON.stringify(payload)).toBe(401);
|
||||
const login = await request.post(`${WORKER_URL}/open_api/credential_login`, {
|
||||
data: { credential: jwt },
|
||||
});
|
||||
expect(login.status(), JSON.stringify(payload)).toBe(401);
|
||||
}
|
||||
} finally {
|
||||
await deleteAddress(request, mailbox.jwt);
|
||||
}
|
||||
});
|
||||
@@ -175,7 +175,7 @@ async function seedTestMailWithReply(
|
||||
text,
|
||||
].join('\r\n');
|
||||
|
||||
const res = await ctx.post(`${WORKER_URL}/admin/test/receive_mail`, {
|
||||
const res = await ctx.post(`${WORKER_URL}/__test/receive_mail`, {
|
||||
data: { from, to: address, raw },
|
||||
});
|
||||
if (!res.ok()) {
|
||||
|
||||
@@ -13,7 +13,7 @@ test.describe('Bounded cleanup', () => {
|
||||
test('cleans at most one batch and continues on the next run', async ({ request }) => {
|
||||
const address = `cleanup-batch-${Date.now()}@test.example.com`;
|
||||
const seedResponses = await Promise.all(Array.from({ length: 11 }, (_, index) =>
|
||||
request.post(`${WORKER_URL}/admin/test/seed_mail`, {
|
||||
request.post(`${WORKER_URL}/__test/seed_mail`, {
|
||||
data: {
|
||||
address,
|
||||
raw: 'old cleanup mail',
|
||||
@@ -40,7 +40,7 @@ test.describe('Bounded cleanup', () => {
|
||||
const afterSecondCleanup = await listMails(request, address);
|
||||
expect(afterSecondCleanup.count).toBe(0);
|
||||
|
||||
const recentMailResponse = await request.post(`${WORKER_URL}/admin/test/seed_mail`, {
|
||||
const recentMailResponse = await request.post(`${WORKER_URL}/__test/seed_mail`, {
|
||||
data: {
|
||||
address,
|
||||
raw: 'recent cleanup mail',
|
||||
@@ -63,7 +63,7 @@ test.describe('Bounded cleanup', () => {
|
||||
test('deletes one address batch and its related data', async ({ request }) => {
|
||||
const oldAddress = await createTestAddress(request, 'cleanup-old');
|
||||
|
||||
const seedResponse = await request.post(`${WORKER_URL}/admin/test/seed_mail`, {
|
||||
const seedResponse = await request.post(`${WORKER_URL}/__test/seed_mail`, {
|
||||
data: {
|
||||
address: oldAddress.address,
|
||||
raw: 'address cleanup mail',
|
||||
|
||||
@@ -65,7 +65,7 @@ test.describe('Email forward domain normalization', () => {
|
||||
`Forward domain normalization test`,
|
||||
].join('\r\n');
|
||||
|
||||
const res = await request.post(`${WORKER_URL}/admin/test/receive_mail`, {
|
||||
const res = await request.post(`${WORKER_URL}/__test/receive_mail`, {
|
||||
data: { from: 'sender@test.example.com', to, raw },
|
||||
});
|
||||
expect(res.ok()).toBe(true);
|
||||
@@ -118,7 +118,7 @@ test.describe('Email forward domain normalization', () => {
|
||||
`Forward domain boundary test`,
|
||||
].join('\r\n');
|
||||
|
||||
const res = await request.post(`${WORKER_URL}/admin/test/receive_mail`, {
|
||||
const res = await request.post(`${WORKER_URL}/__test/receive_mail`, {
|
||||
data: { from: 'sender@test.example.com', to, raw },
|
||||
});
|
||||
expect(res.ok()).toBe(true);
|
||||
@@ -161,7 +161,7 @@ test.describe('Email forward domain normalization', () => {
|
||||
`Forward catch-all domain test`,
|
||||
].join('\r\n');
|
||||
|
||||
const res = await request.post(`${WORKER_URL}/admin/test/receive_mail`, {
|
||||
const res = await request.post(`${WORKER_URL}/__test/receive_mail`, {
|
||||
data: { from: 'sender@test.example.com', to: address, raw },
|
||||
});
|
||||
expect(res.ok()).toBe(true);
|
||||
|
||||
@@ -19,6 +19,7 @@ test.describe('Turnstile Login Endpoints (ENABLE_GLOBAL_TURNSTILE_CHECK disabled
|
||||
}
|
||||
});
|
||||
expect(res.status()).toBe(401);
|
||||
expect(await res.json()).toMatchObject({ code: 'AUTH_SITE_PASSWORD_INVALID', message: expect.any(String) });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -43,6 +44,7 @@ test.describe('Turnstile Login Endpoints (ENABLE_GLOBAL_TURNSTILE_CHECK disabled
|
||||
}
|
||||
});
|
||||
expect(res.status()).toBe(401);
|
||||
expect(await res.json()).toMatchObject({ code: 'AUTH_ADMIN_CREDENTIAL_INVALID', message: expect.any(String) });
|
||||
});
|
||||
|
||||
test('empty password returns 401', async ({ request }) => {
|
||||
@@ -53,6 +55,7 @@ test.describe('Turnstile Login Endpoints (ENABLE_GLOBAL_TURNSTILE_CHECK disabled
|
||||
}
|
||||
});
|
||||
expect(res.status()).toBe(401);
|
||||
expect(await res.json()).toMatchObject({ code: 'AUTH_ADMIN_CREDENTIAL_INVALID', message: expect.any(String) });
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ test.describe('Mail Deletion', () => {
|
||||
`--${boundary}--`,
|
||||
].join('\r\n');
|
||||
|
||||
const seedRes = await request.post(`${WORKER_URL_ENV_OFF}/admin/test/receive_mail`, {
|
||||
const seedRes = await request.post(`${WORKER_URL_ENV_OFF}/__test/receive_mail`, {
|
||||
data: { from, to: address, raw },
|
||||
});
|
||||
expect(seedRes.ok()).toBe(true);
|
||||
|
||||
@@ -14,7 +14,7 @@ test.describe('Mail read status', () => {
|
||||
test('keeps historical mail read and switches one new mail state', async ({ request }) => {
|
||||
const mailbox = await createTestAddress(request, 'mail-read');
|
||||
try {
|
||||
const historical = await request.post(`${WORKER_URL}/admin/test/seed_mail`, {
|
||||
const historical = await request.post(`${WORKER_URL}/__test/seed_mail`, {
|
||||
data: {
|
||||
address: mailbox.address,
|
||||
source: 'sender@test.example.com',
|
||||
|
||||
@@ -83,6 +83,7 @@ test.describe('Redemption feature access boundaries', () => {
|
||||
},
|
||||
);
|
||||
expect(blockedAdminResponse.status()).toBe(401);
|
||||
expect(await blockedAdminResponse.json()).toMatchObject({ code: 'AUTH_SITE_PASSWORD_INVALID' });
|
||||
|
||||
const createResponse = await request.post(
|
||||
`${WORKER_URL_SITE_PASSWORD}/admin/redeem_codes/batch`,
|
||||
@@ -106,11 +107,13 @@ test.describe('Redemption feature access boundaries', () => {
|
||||
{ data: { code } },
|
||||
);
|
||||
expect(missingPassword.status()).toBe(401);
|
||||
expect(await missingPassword.json()).toMatchObject({ code: 'AUTH_SITE_PASSWORD_INVALID' });
|
||||
const wrongPassword = await request.post(
|
||||
`${WORKER_URL_SITE_PASSWORD}/redeem_api/${path}`,
|
||||
{ headers: { 'x-custom-auth': 'wrong' }, data: { code } },
|
||||
);
|
||||
expect(wrongPassword.status()).toBe(401);
|
||||
expect(await wrongPassword.json()).toMatchObject({ code: 'AUTH_SITE_PASSWORD_INVALID' });
|
||||
}
|
||||
const validPassword = await request.post(`${WORKER_URL_SITE_PASSWORD}/redeem_api/query`, {
|
||||
headers: SITE_HEADERS,
|
||||
@@ -248,6 +251,10 @@ test.describe('Redemption Admin authentication', () => {
|
||||
]);
|
||||
for (const response of responses) {
|
||||
expect(response.status(), response.url()).toBe(401);
|
||||
expect(await response.json()).toMatchObject({
|
||||
code: credentials.name === 'expired Admin role token'
|
||||
? 'AUTH_USER_ACCESS_TOKEN_EXPIRED' : 'AUTH_ADMIN_CREDENTIAL_INVALID',
|
||||
});
|
||||
expect(await response.text()).not.toContain(code);
|
||||
}
|
||||
const after = await request.get(listUrl, { headers: ADMIN_HEADERS });
|
||||
|
||||
@@ -51,4 +51,33 @@ test.describe('Send Mail via SMTP', () => {
|
||||
// Cleanup
|
||||
await deleteAddress(request, jwt);
|
||||
});
|
||||
|
||||
test('external sending stores mail fields without bearer credentials', async ({ request }) => {
|
||||
const { jwt, address } = await createTestAddress(request, 'external-sender');
|
||||
const mail = {
|
||||
from_name: 'External sender', to_name: 'Recipient', to_mail: 'recipient@test.example.com',
|
||||
subject: `External ${Date.now()}`, content: 'External message', is_html: false,
|
||||
};
|
||||
try {
|
||||
const listener = onMailpitMessage(message => message.Subject === mail.subject);
|
||||
await listener.ready;
|
||||
const response = await request.post(`${WORKER_URL}/external/api/send_mail`, {
|
||||
data: { ...mail, token: jwt, extra_credential: 'must-not-be-stored' },
|
||||
});
|
||||
expect(response.ok(), await response.text()).toBe(true);
|
||||
expect((await listener.message).From.Address).toBe(address);
|
||||
const sendbox = await request.get(`${WORKER_URL}/api/sendbox?limit=20&offset=0`, {
|
||||
headers: { Authorization: `Bearer ${jwt}` },
|
||||
});
|
||||
expect(sendbox.ok()).toBe(true);
|
||||
const { results } = await sendbox.json();
|
||||
expect(results).toHaveLength(1);
|
||||
const stored = JSON.parse(results[0].raw);
|
||||
expect(stored).toMatchObject(mail);
|
||||
expect(stored).not.toHaveProperty('token');
|
||||
expect(stored).not.toHaveProperty('extra_credential');
|
||||
} finally {
|
||||
await deleteAddress(request, jwt);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,7 +18,7 @@ test.describe('Telegram AI extraction rendering', () => {
|
||||
'Telegram AI extraction realtime body',
|
||||
].join('\r\n');
|
||||
|
||||
const receiveRes = await request.post(`${WORKER_URL}/admin/test/receive_mail`, {
|
||||
const receiveRes = await request.post(`${WORKER_URL}/__test/receive_mail`, {
|
||||
data: {
|
||||
from: 'sender@test.example.com',
|
||||
to: address,
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
import { expect, test, type APIRequestContext } from '@playwright/test';
|
||||
import { createHmac } from 'node:crypto';
|
||||
|
||||
import { WORKER_URL, createTestAddress } from '../../fixtures/test-helpers';
|
||||
|
||||
function initData(userId: number) {
|
||||
const fields = {
|
||||
auth_date: String(Math.floor(Date.now() / 1000)),
|
||||
user: JSON.stringify({ id: userId }),
|
||||
};
|
||||
const key = createHmac('sha256', 'WebAppData').update('e2e-telegram-test-token').digest();
|
||||
const hash = createHmac('sha256', key)
|
||||
.update(Object.entries(fields).map(([name, value]) => `${name}=${value}`).join('\n'))
|
||||
.digest('hex');
|
||||
return new URLSearchParams({ ...fields, hash }).toString();
|
||||
}
|
||||
|
||||
async function bind(request: APIRequestContext, userId: number, jwt: string) {
|
||||
const response = await request.post(`${WORKER_URL}/telegram/bind_address`, {
|
||||
data: { initData: initData(userId), jwt },
|
||||
});
|
||||
expect(response.ok(), await response.text()).toBe(true);
|
||||
}
|
||||
|
||||
async function unbind(request: APIRequestContext, userId: number, address: string, status = 200) {
|
||||
const response = await request.post(`${WORKER_URL}/telegram/unbind_address`, {
|
||||
data: { initData: initData(userId), address },
|
||||
});
|
||||
expect(response.status(), await response.text()).toBe(status);
|
||||
}
|
||||
|
||||
async function addressList(request: APIRequestContext, userId: number) {
|
||||
const response = await request.post(`${WORKER_URL}/telegram/get_bind_address`, {
|
||||
data: { initData: initData(userId) },
|
||||
});
|
||||
expect(response.ok(), await response.text()).toBe(true);
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async function expectPushOwner(request: APIRequestContext, address: string, userId: number | null) {
|
||||
const response = await request.get(`${WORKER_URL}/__test/telegram_binding`, {
|
||||
params: { address },
|
||||
});
|
||||
expect(response.ok(), await response.text()).toBe(true);
|
||||
expect(await response.json()).toBe(userId === null ? null : String(userId));
|
||||
}
|
||||
|
||||
test('Telegram users can remove their own bindings after another user binds the mailbox', async ({ request }) => {
|
||||
const mailbox = await createTestAddress(request, 'tg-owner');
|
||||
const owner = Date.now();
|
||||
const other = owner + 1;
|
||||
try {
|
||||
await bind(request, owner, mailbox.jwt);
|
||||
await unbind(request, other, mailbox.address, 400);
|
||||
await expectPushOwner(request, mailbox.address, owner);
|
||||
await unbind(request, owner, mailbox.address);
|
||||
await expectPushOwner(request, mailbox.address, null);
|
||||
|
||||
await bind(request, owner, mailbox.jwt);
|
||||
await bind(request, other, mailbox.jwt);
|
||||
await expectPushOwner(request, mailbox.address, other);
|
||||
await unbind(request, owner, mailbox.address);
|
||||
expect(await addressList(request, owner)).toEqual([]);
|
||||
expect(await addressList(request, other)).toEqual([{ address: mailbox.address, jwt: mailbox.jwt }]);
|
||||
await expectPushOwner(request, mailbox.address, other);
|
||||
await unbind(request, other, mailbox.address);
|
||||
expect(await addressList(request, other)).toEqual([]);
|
||||
await expectPushOwner(request, mailbox.address, null);
|
||||
|
||||
await bind(request, owner, mailbox.jwt);
|
||||
await bind(request, other, mailbox.jwt);
|
||||
await bind(request, owner, mailbox.jwt);
|
||||
expect(await addressList(request, owner)).toEqual([{ address: mailbox.address, jwt: mailbox.jwt }]);
|
||||
await expectPushOwner(request, mailbox.address, owner);
|
||||
await unbind(request, other, mailbox.address);
|
||||
await expectPushOwner(request, mailbox.address, owner);
|
||||
await unbind(request, owner, mailbox.address);
|
||||
expect(await addressList(request, owner)).toEqual([]);
|
||||
await expectPushOwner(request, mailbox.address, null);
|
||||
} finally {
|
||||
await request.delete(`${WORKER_URL}/api/delete_address`, {
|
||||
headers: { Authorization: `Bearer ${mailbox.jwt}` },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test('stale Telegram credentials cannot unbind; internal mailbox cleanup still works', async ({ request }) => {
|
||||
const original = await createTestAddress(request, 'tg-stale');
|
||||
const owner = Date.now();
|
||||
await bind(request, owner, original.jwt);
|
||||
const deletion = await request.delete(`${WORKER_URL}/admin/delete_address/${original.address_id}`);
|
||||
expect(deletion.ok()).toBe(true);
|
||||
const [name, domain] = original.address.split('@');
|
||||
const creation = await request.post(`${WORKER_URL}/admin/new_address`, {
|
||||
data: { name, domain, enablePrefix: false },
|
||||
});
|
||||
expect(creation.ok()).toBe(true);
|
||||
const recreated = await creation.json();
|
||||
try {
|
||||
expect(recreated.address_id).not.toBe(original.address_id);
|
||||
await unbind(request, owner, recreated.address, 400);
|
||||
await expectPushOwner(request, recreated.address, owner);
|
||||
const response = await request.delete(`${WORKER_URL}/api/delete_address`, {
|
||||
headers: { Authorization: `Bearer ${recreated.jwt}` },
|
||||
});
|
||||
expect(response.ok(), await response.text()).toBe(true);
|
||||
await expectPushOwner(request, recreated.address, null);
|
||||
|
||||
const listing = await request.post(`${WORKER_URL}/telegram/get_bind_address`, {
|
||||
data: { initData: initData(owner) },
|
||||
});
|
||||
expect(listing.ok()).toBe(true);
|
||||
expect(await listing.json()).toEqual([]);
|
||||
} finally {
|
||||
await request.delete(`${WORKER_URL}/admin/delete_address/${recreated.address_id}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('Telegram unbind accepts a current credential after a stale credential for the same address', async ({ request }) => {
|
||||
const original = await createTestAddress(request, 'tg-recreated');
|
||||
const unrelated = await createTestAddress(request, 'tg-retained');
|
||||
const owner = Date.now();
|
||||
await bind(request, owner, original.jwt);
|
||||
await bind(request, owner, unrelated.jwt);
|
||||
const deletion = await request.delete(`${WORKER_URL}/admin/delete_address/${original.address_id}`);
|
||||
expect(deletion.ok()).toBe(true);
|
||||
const [name, domain] = original.address.split('@');
|
||||
const creation = await request.post(`${WORKER_URL}/admin/new_address`, {
|
||||
data: { name, domain, enablePrefix: false },
|
||||
});
|
||||
expect(creation.ok()).toBe(true);
|
||||
const recreated = await creation.json();
|
||||
try {
|
||||
expect(recreated.address_id).not.toBe(original.address_id);
|
||||
await bind(request, owner, recreated.jwt);
|
||||
await unbind(request, owner, recreated.address);
|
||||
await expectPushOwner(request, recreated.address, null);
|
||||
expect(await addressList(request, owner)).toEqual([{ address: unrelated.address, jwt: unrelated.jwt }]);
|
||||
await expectPushOwner(request, unrelated.address, owner);
|
||||
} finally {
|
||||
for (const mailbox of [recreated, unrelated]) {
|
||||
await request.delete(`${WORKER_URL}/api/delete_address`, {
|
||||
headers: { Authorization: `Bearer ${mailbox.jwt}` },
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -25,12 +25,18 @@ for (const scenario of ['expired role', 'valid role', 'invalid signature', 'miss
|
||||
});
|
||||
} else if (scenario === 'missing expiry') {
|
||||
expect(response.status()).toBe(401);
|
||||
expect(await response.text()).toBe('Your access token has expired, please refresh the page');
|
||||
expect(await response.json()).toEqual({
|
||||
code: 'AUTH_ADMIN_CREDENTIAL_INVALID',
|
||||
message: 'Your access token has expired, please refresh the page',
|
||||
});
|
||||
} else if (scenario === 'valid role' || scenario === 'admin password') {
|
||||
expect(response.ok()).toBe(true);
|
||||
} else {
|
||||
expect(response.status()).toBe(401);
|
||||
expect(await response.text()).toBe('You need to provide the admin password to access this page');
|
||||
expect(await response.json()).toEqual({
|
||||
code: 'AUTH_ADMIN_CREDENTIAL_INVALID',
|
||||
message: 'You need to provide the admin password to access this page',
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ test.describe('Webhook — triggered on incoming mail', () => {
|
||||
`Webhook trigger test body`,
|
||||
].join('\r\n');
|
||||
|
||||
const res = await request.post(`${WORKER_URL}/admin/test/receive_mail`, {
|
||||
const res = await request.post(`${WORKER_URL}/__test/receive_mail`, {
|
||||
data: {
|
||||
from,
|
||||
to: address,
|
||||
@@ -157,7 +157,7 @@ test.describe('Webhook — triggered on incoming mail', () => {
|
||||
`Should not trigger webhook`,
|
||||
].join('\r\n');
|
||||
|
||||
const res = await request.post(`${WORKER_URL}/admin/test/receive_mail`, {
|
||||
const res = await request.post(`${WORKER_URL}/__test/receive_mail`, {
|
||||
data: { from: 'sender@test.example.com', to: address, raw },
|
||||
});
|
||||
expect(res.ok()).toBe(true);
|
||||
|
||||
Reference in New Issue
Block a user