canina/tests/e2e/security/xss-ratelimit.spec.ts

130 lines
5.5 KiB
TypeScript

import { test, expect } from '../../fixtures';
test.describe('سناریوی اعتبارسنجی امنیتی XSS و محدودیت نرخ پیامک (Security XSS & Rate-Limiting)', () => {
test('تزریق اسکریپت مخرب در فیلدهای فرم تماس و راستی‌آزمایی عدم اجرای XSS در فرانت و پنل ادمین', async ({ page }) => {
let capturedPayload: Record<string, string> = {};
await page.route(/\/api\/contact|\/contact/, async (route) => {
if (route.request().method() === 'POST') {
capturedPayload = route.request().postDataJSON?.() || {};
await route.fulfill({
status: 201,
contentType: 'application/json',
body: JSON.stringify({
success: true,
message: 'پیام شما با موفقیت ثبت شد',
}),
});
} else {
await route.continue();
}
});
let dialogFired = false;
page.on('dialog', async (dialog) => {
dialogFired = true;
await dialog.dismiss();
});
// 1. ورود به صفحه تماس با ما
await page.goto('/contact', { waitUntil: 'domcontentloaded' });
await expect(page).toHaveURL(/\/contact/);
// 2. پر کردن فیلدها با پی‌لود خطرناک XSS
const xssPayload = '<img src=x onerror=alert("XSS_ATTACK_CANINA") />';
const nameInput = page.locator('#contact-name');
const phoneInput = page.locator('#contact-phone');
const subjectInput = page.locator('#contact-subject');
const messageInput = page.locator('#contact-message');
await expect(nameInput).toBeVisible({ timeout: 10000 });
await nameInput.click();
await nameInput.fill(xssPayload);
await phoneInput.click();
await phoneInput.fill('09121112233');
await subjectInput.click();
await subjectInput.fill('XSS Test Subject');
await messageInput.click();
await messageInput.fill('<script>alert("XSS")</script> متن درخواست تستی');
// 3. ارسال فرم تماس
const submitBtn = page.locator('[data-testid="contact-form-submit-btn"]').first();
await expect(submitBtn).toBeVisible({ timeout: 10000 });
await submitBtn.click();
// 4. راستی‌آزمایی نمایش پیام موفقیت بدون فعال شدن هیچ alert/dialog مخرب
const successBox = page.locator('h4').filter({ hasText: 'پیام شما با موفقیت ثبت شد' });
await expect(successBox).toBeVisible({ timeout: 15000 });
// راستی‌آزمایی قطعی عدم اجرای alert جاوااسکریپت در مرورگر کلاینت
expect(dialogFired).toBe(false);
expect(capturedPayload.phone).toBe('09121112233');
expect(capturedPayload.name).toBe(xssPayload);
expect(capturedPayload.phone).toBe('09121112233');
});
test('شبیه‌سازی محدودیت نرخ و تلاش مجدد با خطای ۴۲۹ (Rate-Limiting & 429 Cooldown Enforcement)', async ({ page }) => {
let sendOtpAttempts = 0;
await page.route('**/api/auth/send-otp*', async (route) => {
sendOtpAttempts++;
if (sendOtpAttempts === 1) {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ success: true, code: '12345' }),
});
} else {
await route.fulfill({
status: 429,
contentType: 'application/json',
body: JSON.stringify({
success: false,
message: 'تعداد درخواست‌های شما بیش از حد مجاز است. لطفاً ۲ دقیقه صبر کنید.',
}),
});
}
});
// 1. باز کردن مودال ورود با OTP
await page.goto('/', { waitUntil: 'domcontentloaded' });
const headerLoginBtn = page.locator('[data-testid="header-login-btn"]').first();
await expect(headerLoginBtn).toBeVisible({ timeout: 15000 });
await headerLoginBtn.click();
// 2. ورود با کد پیامکی (OTP)
const otpOptionBtn = page.locator('button').filter({ hasText: /ورود با کد پیامکی \(OTP\)/i }).first();
await expect(otpOptionBtn).toBeVisible({ timeout: 10000 });
await otpOptionBtn.click();
// 3. وارد کردن شماره موبایل و ارسال اولین OTP
const phoneInput = page.locator('input[type="tel"]').first();
await expect(phoneInput).toBeVisible({ timeout: 10000 });
await phoneInput.fill('09129998877');
const sendOtpBtn = page.locator('button[type="submit"]').filter({ hasText: /ارسال کد تایید/i }).first();
await expect(sendOtpBtn).toBeVisible();
await sendOtpBtn.click();
// 4. راستی‌آزمایی فعال بودن متن تایمر معکوس (Cooldown Timer)
const timerText = page.locator('span').filter({ hasText: /ارسال مجدد کد تا/i }).first();
await expect(timerText).toBeVisible({ timeout: 10000 });
// 5. ارسال درخواست مستقیم دوم در بازه Cooldown و راستی‌آزمایی قطعی پاسخ 429 سرور
const rateLimitResponse = await page.evaluate(async () => {
const res = await fetch('/api/auth/send-otp', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ mobile: '09129998877' }),
});
return { status: res.status, data: await res.json() };
});
expect(rateLimitResponse.status).toBe(429);
expect(rateLimitResponse.data.message).toContain('بیش از حد مجاز');
expect(sendOtpAttempts).toBe(2);
});
});