7.9 KiB
7.9 KiB
Anonim Geziler Güvenlik Açığı Düzeltmesi
🔴 Sorun
Güvenlik Açığı
Önceki RLS politikaları, user_id IS NULL olan tüm anonim gezilere herkesin erişmesine izin veriyordu:
-- ❌ ESKİ VE GÜVENSİZ
USING (is_public = true OR auth.uid() = user_id OR user_id IS NULL);
Bu politika şu sorunlara yol açıyordu:
- Herkes başkasının anonim gezisini görebilir, düzenleyebilir ve silebilirdi
- Anonim geziler sahipsiz kalıyordu - kullanıcı giriş yaptıktan sonra gezisi kayboluyordu
- Veri tabanı kirliliği - eski anonim geziler hiç temizlenmiyordu
Etki
- Gizlilik ihlali: Kullanıcıların anonim gezileri herkese açık
- Veri kaybı: Login sonrası kullanıcı kendi gezisini bulamıyor
- Performans: Sahipsiz geziler birikerek veritabanını şişiriyor
✅ Çözüm
1. Token Tabanlı Erişim Sistemi
Her anonim gezi için benzersiz bir token oluşturulur ve localStorage'da saklanır:
// Gezi oluşturulurken
const anonymousToken = crypto.randomUUID();
localStorage.setItem(`trip_token_${tripId}`, anonymousToken);
2. Güvenli RLS Politikaları
Yeni politikalar sadece token sahibinin erişimine izin verir:
-- ✅ YENİ VE GÜVENLİ
CREATE POLICY "Seyahatleri görüntüleme"
ON trips FOR SELECT
USING (
is_public = true
OR user_id = auth.uid()
);
Anonim geziler için özel RPC fonksiyonları:
update_anonymous_trip(trip_id, token, updates)- Token ile güncellemedelete_anonymous_trip(trip_id, token)- Token ile silmeget_anonymous_trip(trip_id, token)- Token ile okuma
3. Otomatik Ownership Transfer
Kullanıcı giriş yaptığında, anonim gezisi otomatik olarak hesabına bağlanır:
// AuthContext.tsx - onAuthStateChange
if (event === 'SIGNED_IN') {
const currentTripId = localStorage.getItem('currentTripId');
if (currentTripId) {
await tripsApi.claimAnonymousTrip(currentTripId);
}
}
4. Otomatik Temizlik
7 günden eski anonim geziler otomatik olarak silinir:
CREATE FUNCTION cleanup_old_anonymous_trips()
RETURNS INTEGER
AS $$
DELETE FROM trips
WHERE user_id IS NULL
AND created_at < NOW() - INTERVAL '7 days'
$$;
📋 Değişiklikler
Database Migrations
00053_add_anonymous_trip_token_system.sql
tripstablosunaanonymous_tokensütunu eklendi- Unique index oluşturuldu
- Güvenli RLS politikaları eklendi
- RPC fonksiyonları oluşturuldu:
claim_anonymous_trip(trip_id, token)- Ownership transferupdate_anonymous_trip(trip_id, token, updates)- Token ile güncellemedelete_anonymous_trip(trip_id, token)- Token ile silmeget_anonymous_trip(trip_id, token)- Token ile okumacleanup_old_anonymous_trips()- Eski gezileri temizle
00054_fix_anonymous_trip_rls_policies.sql
- RLS politikalarını optimize etti
- Application layer'da token kontrolü için yapı oluşturdu
Frontend Değişiklikleri
src/db/api.ts
tripsApi.create()
// Anonim kullanıcı için token oluştur
if (!user) {
anonymousToken = crypto.randomUUID();
tripData = { ...trip, user_id: null, anonymous_token: anonymousToken };
localStorage.setItem(`trip_token_${data.id}`, anonymousToken);
}
tripsApi.update()
// Anonim kullanıcı için RPC kullan
if (!user) {
const token = localStorage.getItem(`trip_token_${tripId}`);
return await supabase.rpc('update_anonymous_trip', {
trip_id_param: tripId,
token_param: token,
updates: updates
});
}
tripsApi.delete()
// Anonim kullanıcı için RPC kullan
if (!user) {
const token = localStorage.getItem(`trip_token_${tripId}`);
await supabase.rpc('delete_anonymous_trip', {
trip_id_param: tripId,
token_param: token
});
localStorage.removeItem(`trip_token_${tripId}`);
}
Yeni Fonksiyonlar:
claimAnonymousTrip(tripId)- Anonim geziyi kullanıcıya transfer etcleanupOldAnonymousTrips()- Eski anonim gezileri temizlecanAccessTrip(tripId)- Geziye erişim kontrolü
src/contexts/AuthContext.tsx
// Login sonrası otomatik ownership transfer
if (event === 'SIGNED_IN') {
const currentTripId = localStorage.getItem('currentTripId');
if (currentTripId) {
await tripsApi.claimAnonymousTrip(currentTripId);
}
}
src/types/trip-ui.ts
export interface TripDataRaw {
// ...
anonymous_token?: string; // Yeni alan
}
🔒 Güvenlik Özellikleri
1. Token Güvenliği
- UUID v4 kullanılır (128-bit rastgele)
- localStorage'da saklanır (sadece aynı origin erişebilir)
- Tek kullanımlık: Transfer sonrası token silinir
2. RLS Koruması
- Kayıtlı kullanıcılar sadece kendi gezilerini görebilir
- Anonim geziler sadece token ile erişilebilir
- Public geziler herkese açık
3. Veri Temizliği
- 7 günden eski anonim geziler otomatik silinir
- Orphan data birikmesi önlenir
4. Ownership Transfer
- Login sonrası otomatik transfer
- Token doğrulaması ile güvenli transfer
- Transfer sonrası token temizlenir
🧪 Test Senaryoları
Senaryo 1: Anonim Kullanıcı
- ✅ Kullanıcı giriş yapmadan gezi oluşturur
- ✅ Token localStorage'a kaydedilir
- ✅ Kullanıcı geziyi düzenleyebilir
- ✅ Kullanıcı geziyi silebilir
- ✅ Başka bir tarayıcıdan geziye erişilemez
Senaryo 2: Login Sonrası Transfer
- ✅ Anonim kullanıcı gezi oluşturur
- ✅ Kullanıcı giriş yapar
- ✅ Gezi otomatik olarak kullanıcıya transfer edilir
- ✅ Token temizlenir
- ✅ Gezi "Seyahat Planlarım"da görünür
Senaryo 3: Güvenlik Testi
- ✅ Kullanıcı A anonim gezi oluşturur
- ✅ Kullanıcı B aynı trip_id ile erişmeye çalışır
- ✅ Erişim reddedilir (token yok)
- ✅ Kullanıcı B geziyi düzenleyemez
- ✅ Kullanıcı B geziyi silemez
Senaryo 4: Temizlik
- ✅ 7 günden eski anonim gezi oluşturulur
- ✅
cleanup_old_anonymous_trips()çağrılır - ✅ Eski gezi silinir
- ✅ Yeni anonim geziler korunur
📊 Performans
Öncesi
- Tüm anonim geziler herkese açık
- N+1 query problemi
- Gereksiz veri transferi
Sonrası
- Token kontrolü ile hızlı erişim
- RPC fonksiyonları ile optimize edilmiş sorgular
- Otomatik temizlik ile veritabanı boyutu kontrolü
🚀 Kullanım
Anonim Gezi Oluşturma
const trip = await tripsApi.create({
title: 'Kapadokya Gezim',
destination: 'Kapadokya',
// ...
});
// Token otomatik oluşturulur ve kaydedilir
Anonim Gezi Güncelleme
await tripsApi.update(tripId, {
title: 'Yeni Başlık'
});
// Token otomatik kontrol edilir
Anonim Gezi Silme
await tripsApi.delete(tripId);
// Token otomatik kontrol edilir ve temizlenir
Manuel Ownership Transfer
const success = await tripsApi.claimAnonymousTrip(tripId);
if (success) {
console.log('Gezi başarıyla transfer edildi');
}
Eski Gezileri Temizleme (Admin)
const deletedCount = await tripsApi.cleanupOldAnonymousTrips();
console.log(`${deletedCount} adet eski gezi temizlendi`);
⚠️ Önemli Notlar
- Token Kaybı: Kullanıcı localStorage'ı temizlerse token kaybolur ve geziye erişemez
- Tarayıcı Değişimi: Farklı tarayıcıda token olmadığı için geziye erişilemez
- 7 Gün Sınırı: Anonim geziler 7 gün sonra otomatik silinir
- Public Geziler: Public yapılan geziler token olmadan da görüntülenebilir (ama düzenlenemez)
🔄 Migration Sırası
00053_add_anonymous_trip_token_system.sql- Token sistemi ve RPC fonksiyonları00054_fix_anonymous_trip_rls_policies.sql- RLS politikalarını optimize et
📝 Sonuç
Bu güvenlik düzeltmesi ile:
- ✅ Anonim geziler artık güvenli
- ✅ Kullanıcılar giriş sonrası gezilerini kaybetmiyor
- ✅ Veritabanı temiz kalıyor
- ✅ Performans optimize edildi
- ✅ Kullanıcı deneyimi iyileştirildi