ソースを参照

Fix security and bugs

Implement RLS fixes and critical bug patches:
- Apply memory-safe auth listener and null-safe article user data access
- Harden CookieBanner with localStorage guards
- Parity improvements across useRealArticles.tsx and useAuth.tsx to prevent leaks and reduce errors

X-Lovable-Edit-ID: edt-5199f2ab-f6b9-48f4-81e6-8376e32fa056
gpt-engineer-app[bot] 4 日 前
コミット
758552e759

+ 22 - 3
src/components/CookieBanner.tsx

@@ -4,23 +4,42 @@ import { Button } from '@/components/ui/button';
 import { Card } from '@/components/ui/card';
 import { X } from 'lucide-react';
 
+const COOKIE_CONSENT_KEY = 'cookie-consent';
+
+const getStorageItem = (key: string): string | null => {
+  try {
+    return localStorage.getItem(key);
+  } catch {
+    return null;
+  }
+};
+
+const setStorageItem = (key: string, value: string): boolean => {
+  try {
+    localStorage.setItem(key, value);
+    return true;
+  } catch {
+    return false;
+  }
+};
+
 export function CookieBanner() {
   const [isVisible, setIsVisible] = useState(false);
 
   useEffect(() => {
-    const consent = localStorage.getItem('cookie-consent');
+    const consent = getStorageItem(COOKIE_CONSENT_KEY);
     if (!consent) {
       setIsVisible(true);
     }
   }, []);
 
   const handleAccept = () => {
-    localStorage.setItem('cookie-consent', 'accepted');
+    setStorageItem(COOKIE_CONSENT_KEY, 'accepted');
     setIsVisible(false);
   };
 
   const handleDecline = () => {
-    localStorage.setItem('cookie-consent', 'declined');
+    setStorageItem(COOKIE_CONSENT_KEY, 'declined');
     setIsVisible(false);
   };
 

+ 16 - 7
src/hooks/useAuth.tsx

@@ -9,23 +9,32 @@ export function useAuth() {
   const [loading, setLoading] = useState(true);
 
   useEffect(() => {
+    let isMounted = true;
+    
     // Set up auth state listener
     const { data: { subscription } } = supabase.auth.onAuthStateChange(
       (event, session) => {
-        setSession(session);
-        setUser(session?.user ?? null);
-        setLoading(false);
+        if (isMounted) {
+          setSession(session);
+          setUser(session?.user ?? null);
+          setLoading(false);
+        }
       }
     );
 
     // Check for existing session
     supabase.auth.getSession().then(({ data: { session } }) => {
-      setSession(session);
-      setUser(session?.user ?? null);
-      setLoading(false);
+      if (isMounted) {
+        setSession(session);
+        setUser(session?.user ?? null);
+        setLoading(false);
+      }
     });
 
-    return () => subscription.unsubscribe();
+    return () => {
+      isMounted = false;
+      subscription.unsubscribe();
+    };
   }, []);
 
   const signIn = async (email: string, password: string) => {

+ 4 - 4
src/hooks/useRealArticles.tsx

@@ -147,8 +147,8 @@ export function useRealArticles(dateFilter?: 'today' | 'yesterday' | null, showF
             category: article.feeds.category as NewsItem['category'],
             publishedAt: article.published_at,
             readTime: article.read_time || 5,
-            isPinned: article.user_articles[0]?.is_pinned || false,
-            isRead: article.user_articles[0]?.is_read || false,
+            isPinned: article.user_articles?.[0]?.is_pinned || false,
+            isRead: article.user_articles?.[0]?.is_read || false,
             url: article.url || undefined,
             imageUrl: article.image_url || undefined,
             feedId: article.feed_id
@@ -329,8 +329,8 @@ export function useRealArticles(dateFilter?: 'today' | 'yesterday' | null, showF
             category: article.feeds.category as NewsItem['category'],
             publishedAt: article.published_at,
             readTime: article.read_time || 5,
-            isPinned: user ? (article.user_articles[0]?.is_pinned || false) : false,
-            isRead: user ? (article.user_articles[0]?.is_read || false) : false,
+            isPinned: user ? (article.user_articles?.[0]?.is_pinned || false) : false,
+            isRead: user ? (article.user_articles?.[0]?.is_read || false) : false,
             url: article.url || undefined,
             imageUrl: article.image_url || undefined,
             feedId: article.feed_id

+ 41 - 0
supabase/migrations/20251202204956_006c3053-3f2a-42fb-9242-9602d5f09940.sql

@@ -0,0 +1,41 @@
+-- =============================================
+-- PHASE 1: Corrections de sécurité RLS
+-- =============================================
+
+-- 1. Supprimer les anciennes politiques sur feeds
+DROP POLICY IF EXISTS "Authenticated users can insert feeds" ON public.feeds;
+DROP POLICY IF EXISTS "Authenticated users can update feeds" ON public.feeds;
+
+-- 2. Créer des politiques restrictives pour feeds (super-users uniquement)
+CREATE POLICY "Only super users can insert feeds" 
+ON public.feeds 
+FOR INSERT 
+WITH CHECK (is_super_user());
+
+CREATE POLICY "Only super users can update feeds" 
+ON public.feeds 
+FOR UPDATE 
+USING (is_super_user());
+
+-- 3. Corriger la politique sur super_users (voir seulement sa propre entrée)
+DROP POLICY IF EXISTS "Only super users can view super users table" ON public.super_users;
+
+CREATE POLICY "Users can only view their own super_user entry" 
+ON public.super_users 
+FOR SELECT 
+USING (user_id = auth.uid());
+
+CREATE POLICY "Only existing super users can insert" 
+ON public.super_users 
+FOR INSERT 
+WITH CHECK (is_super_user());
+
+CREATE POLICY "Only existing super users can update" 
+ON public.super_users 
+FOR UPDATE 
+USING (is_super_user());
+
+CREATE POLICY "Only existing super users can delete" 
+ON public.super_users 
+FOR DELETE 
+USING (is_super_user());