Implement shared HTML sanitization configuration
**Backend Changes:** - Add html-sanitization-config.json with allowedTags, allowedAttributes, and allowedCssProperties - Create HtmlSanitizationConfigDto for configuration data transfer - Update HtmlSanitizationService to load configuration from JSON file with fallback - Add HtmlSanitizationController with public API endpoint at /api/config/html-sanitization - Update SecurityConfig to allow public access to /api/config/** endpoints **Frontend Changes:** - Add configApi.getHtmlSanitizationConfig() to fetch backend configuration - Create sanitization.ts utility with sanitizeHtml() and sanitizeHtmlSync() functions - Update story reading page to use shared sanitization configuration - Add preloadSanitizationConfig() for early configuration loading - Handle TrustedHTML type conversion and DOMPurify config compatibility **Benefits:** - Consistent HTML sanitization rules between frontend and backend - Centralized configuration in JSON file for easy maintenance - Automatic fallback to safe defaults if configuration loading fails - API-driven approach allows runtime configuration updates - Maintains security while providing flexibility for content formatting Resolves HTML sanitization inconsistencies and provides foundation for configurable content safety rules. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@ import { Story } from '../../../types/api';
|
||||
import LoadingSpinner from '../../../components/ui/LoadingSpinner';
|
||||
import Button from '../../../components/ui/Button';
|
||||
import StoryRating from '../../../components/stories/StoryRating';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { sanitizeHtml, preloadSanitizationConfig } from '../../../lib/sanitization';
|
||||
|
||||
export default function StoryReadingPage() {
|
||||
const params = useParams();
|
||||
@@ -18,6 +18,7 @@ export default function StoryReadingPage() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [readingProgress, setReadingProgress] = useState(0);
|
||||
const [sanitizedContent, setSanitizedContent] = useState<string>('');
|
||||
|
||||
const storyId = params.id as string;
|
||||
|
||||
@@ -25,9 +26,19 @@ export default function StoryReadingPage() {
|
||||
const loadStory = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const storyData = await storyApi.getStory(storyId);
|
||||
|
||||
// Preload sanitization config and load story in parallel
|
||||
const [storyData] = await Promise.all([
|
||||
storyApi.getStory(storyId),
|
||||
preloadSanitizationConfig()
|
||||
]);
|
||||
|
||||
setStory(storyData);
|
||||
|
||||
// Sanitize story content
|
||||
const sanitized = await sanitizeHtml(storyData.contentHtml || '');
|
||||
setSanitizedContent(sanitized);
|
||||
|
||||
// Load series stories if part of a series
|
||||
if (storyData.seriesId) {
|
||||
const seriesData = await seriesApi.getSeriesStories(storyData.seriesId);
|
||||
@@ -119,8 +130,6 @@ export default function StoryReadingPage() {
|
||||
);
|
||||
}
|
||||
|
||||
const sanitizedContent = DOMPurify.sanitize(story.contentHtml);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen theme-bg">
|
||||
{/* Progress Bar */}
|
||||
|
||||
Reference in New Issue
Block a user