feat: add BlogBodySanitizer for HTML sanitization on article save

- Implemented BlogBodySanitizer to clean HTML content before saving articles, ensuring security against XSS attacks.
- Added tests for BlogBodySanitizer to verify that unsafe tags and attributes are stripped from the content.
- Introduced ApiLeastPrivilegeTest to ensure that unauthorized users cannot access sensitive API routes, maintaining strict access control.
This commit is contained in:
hamed
2026-08-07 21:13:38 +03:30
parent a4a24c51af
commit 6876135a53
114 changed files with 2067 additions and 269 deletions
@@ -16,8 +16,8 @@ const patch = api.patch as ReturnType<typeof vi.fn>;
const post = api.post as ReturnType<typeof vi.fn>;
const navigate = vi.fn();
vi.mock('react-router-dom', async () => ({
...(await vi.importActual<typeof import('react-router-dom')>('react-router-dom')),
vi.mock('react-router', async () => ({
...(await vi.importActual<typeof import('react-router')>('react-router')),
useNavigate: () => navigate,
}));
@@ -16,7 +16,7 @@ import {
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import React, { useEffect, useMemo, useRef, useState } from "react";
import ReactDOM from "react-dom";
import { useNavigate } from "react-router-dom";
import { useNavigate } from "react-router";
import { toast } from "sonner";
import type { ApiResponse } from "../lib/api";
import { api } from "../lib/api";
@@ -1,5 +1,5 @@
import { useState, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { useNavigate } from 'react-router';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import {
TrashIcon, EnvelopeIcon, ArrowPathIcon, NoSymbolIcon, EyeIcon, ShieldCheckIcon,
@@ -1,5 +1,5 @@
import { useQuery } from '@tanstack/react-query';
import { Link } from 'react-router-dom';
import { Link } from 'react-router';
import { api } from '../lib/api';
import type { ApiResponse } from '../lib/api';
import { useAuthStore } from '../stores/authStore';
@@ -1,5 +1,5 @@
import React, { useEffect, useMemo, useState } from 'react';
import { Link } from 'react-router-dom';
import { Link } from 'react-router';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';
import SearchableSelect from './ui/SearchableSelect';
@@ -9,7 +9,7 @@
* and the parent passes the query key to invalidate.
*/
import React, { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { Link, useNavigate } from 'react-router';
import { toast } from 'sonner';
import AppointmentStatusDropdown, { STATUS_META } from '../ui/AppointmentStatusDropdown';
import { findRecordUuid } from '../AppointmentActions';
@@ -5,7 +5,7 @@
* DoctorDashboard feed it their (real-API) data.
*/
import React from 'react';
import { Link } from 'react-router-dom';
import { Link } from 'react-router';
import { TauriStatCards, type DashboardStats } from './TauriStatCards';
import { TauriBarChart, TauriLineChart, type ChartPoint } from './TauriCharts';
import { NewAppointmentsTable, type ApptRow } from './NewAppointmentsTable';
@@ -1,5 +1,5 @@
import React, { useMemo } from 'react';
import { Link } from 'react-router-dom';
import { Link } from 'react-router';
import { useHolidays } from '../../hooks/useResourceCalendar';
import { formatDate, currentJalaliYear } from '../../lib/utils';
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
import { Outlet } from 'react-router-dom';
import { Outlet } from 'react-router';
import Sidebar from './Sidebar';
import Topbar from './Topbar';
import { useUiStore, applyBrand } from '../../stores/uiStore';
@@ -3,8 +3,8 @@ import { screen, fireEvent } from '@testing-library/react';
import { renderWithProviders } from '../../test/utils';
const navigateMock = vi.fn();
vi.mock('react-router-dom', async () => {
const actual = await vi.importActual<typeof import('react-router-dom')>('react-router-dom');
vi.mock('react-router', async () => {
const actual = await vi.importActual<typeof import('react-router')>('react-router');
return { ...actual, useNavigate: () => navigateMock };
});
@@ -1,6 +1,6 @@
import React, { useEffect, useRef, useState } from 'react';
import ReactDOM from 'react-dom';
import { useNavigate } from 'react-router-dom';
import { useNavigate } from 'react-router';
import {
ChevronDownIcon,
UserCircleIcon,
@@ -1,5 +1,5 @@
import React, { useMemo, useState } from 'react';
import { Link } from 'react-router-dom';
import { Link } from 'react-router';
import { SearchHeaderP } from '../../pages/subscriptionIcons';
import { useAuthStore } from '../../stores/authStore';
import { usePermissions } from '../../hooks/usePermissions';
+1 -1
View File
@@ -31,7 +31,7 @@ import {
WrenchScrewdriverIcon,
} from "@heroicons/react/24/outline";
import { useState } from "react";
import { NavLink, useLocation, useNavigate } from "react-router-dom";
import { NavLink, useLocation, useNavigate } from "react-router";
import { usePermissions } from "../../hooks/usePermissions";
import { useSecretaryEarnings } from "../../hooks/useSecretaryEarnings";
import { useSubscription } from "../../hooks/useSubscription";
@@ -3,8 +3,8 @@ import { screen, fireEvent } from '@testing-library/react';
import { renderWithProviders } from '../../test/utils';
const navigateMock = vi.fn();
vi.mock('react-router-dom', async () => {
const actual = await vi.importActual<typeof import('react-router-dom')>('react-router-dom');
vi.mock('react-router', async () => {
const actual = await vi.importActual<typeof import('react-router')>('react-router');
return { ...actual, useNavigate: () => navigateMock };
});
+1 -1
View File
@@ -1,5 +1,5 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { useNavigate } from 'react-router';
import {
BellIcon, SunIcon, MoonIcon, Bars3Icon, Cog6ToothIcon,
MagnifyingGlassIcon, XMarkIcon, CheckIcon,
@@ -1,5 +1,5 @@
import React from 'react';
import { Link, useLocation } from 'react-router-dom';
import { Link, useLocation } from 'react-router';
const LINKS = [
{ to: '/admin/resources', label: 'منابع' },
@@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { screen, fireEvent, render } from '@testing-library/react';
import { MemoryRouter, Route, Routes, Link } from 'react-router-dom';
import { MemoryRouter, Route, Routes, Link } from 'react-router';
import BackButton from './BackButton';
import PageHeader from './PageHeader';
+1 -1
View File
@@ -1,5 +1,5 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Link } from 'react-router';
import { LockClosedIcon } from '@heroicons/react/24/outline';
import { useSubscription } from '../../hooks/useSubscription';
+1 -1
View File
@@ -1,5 +1,5 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Link } from 'react-router';
import { ChevronLeftIcon } from '@heroicons/react/24/outline';
import BackButton from './BackButton';