diff --git a/client/src/pages/home.tsx b/client/src/pages/home.tsx index fe74f79..41f8d63 100644 --- a/client/src/pages/home.tsx +++ b/client/src/pages/home.tsx @@ -11,11 +11,10 @@ import { Calendar, RefreshCw, Share2, - Twitter, - Facebook, - Rss, Bell, - BellOff + BellOff, + Rss, + ImageOff } from "lucide-react"; import { useNewsletters, useNewsletterSearch } from "@/lib/newsletter-data"; import { useToast } from "@/hooks/use-toast"; @@ -25,6 +24,34 @@ import { motion, AnimatePresence } from "framer-motion"; const ITEMS_PER_PAGE = 20; +function NewsletterImage({ src, alt, onError }: { src: string | null, alt: string, onError: () => void }) { + const [error, setError] = useState(false); + + const handleError = () => { + setError(true); + onError(); + }; + + if (!src || error) { + return ( +
+ +
+ ); + } + + return ( + {alt} + ); +} + export default function Home() { const [searchQuery, setSearchQuery] = useState(""); const [isImporting, setIsImporting] = useState(false); @@ -236,66 +263,61 @@ export default function Home() { exit={{ opacity: 0, y: -20 }} layout > - - - - - {newsletter.title} -
- - -
-
- - - {format(new Date(newsletter.date), 'MMMM d, yyyy')} - -
- {(newsletter.thumbnail || newsletter.description) && ( - - {newsletter.thumbnail && ( - {newsletter.title} - )} - {newsletter.description && ( -

- {newsletter.description} -

- )} -
- )} -
-
+ + + + {newsletter.title} +
+ + +
+
+ + + {format(new Date(newsletter.date), 'MMMM d, yyyy')} + +
+ {(newsletter.thumbnail || newsletter.description) && ( + + {newsletter.thumbnail && ( + { + console.warn(`Failed to load image for newsletter: ${newsletter.id}`); + }} + /> + )} + {newsletter.description && ( +

+ {newsletter.description} +

+ )} +
+ )} +
)) ) : ( diff --git a/server/index.ts b/server/index.ts index 5a97cc7..71f11f1 100644 --- a/server/index.ts +++ b/server/index.ts @@ -6,6 +6,17 @@ const app = express(); app.use(express.json()); app.use(express.urlencoded({ extended: false })); +// Add security headers to prevent script injection +app.use((req, res, next) => { + // Prevent content injection + res.setHeader('Content-Security-Policy', "default-src 'self'; img-src 'self' https: data:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';"); + // Prevent browsers from MIME-sniffing + res.setHeader('X-Content-Type-Options', 'nosniff'); + // XSS protection + res.setHeader('X-XSS-Protection', '1; mode=block'); + next(); +}); + app.use((req, res, next) => { const start = Date.now(); const path = req.path; @@ -47,19 +58,14 @@ app.use((req, res, next) => { throw err; }); - // importantly only setup vite in development and after - // setting up all the other routes so the catch-all route - // doesn't interfere with the other routes if (app.get("env") === "development") { await setupVite(app, server); } else { serveStatic(app); } - // ALWAYS serve the app on port 5000 - // this serves both the API and the client const PORT = 5000; server.listen(PORT, "0.0.0.0", () => { log(`serving on port ${PORT}`); }); -})(); +})(); \ No newline at end of file