38980-vm/app-9w9pd00g5j41/ANONYMOUS_TRIP_SECURITY_FIX.md
2026-03-04 18:25:09 +00:00

307 lines
7.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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:
```sql
-- ❌ 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:
1. **Herkes başkasının anonim gezisini görebilir, düzenleyebilir ve silebilirdi**
2. **Anonim geziler sahipsiz kalıyordu** - kullanıcı giriş yaptıktan sonra gezisi kayboluyordu
3. **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:
```typescript
// 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:
```sql
-- ✅ 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üncelleme
- `delete_anonymous_trip(trip_id, token)` - Token ile silme
- `get_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:
```typescript
// 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:
```sql
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`
- `trips` tablosuna `anonymous_token` sütunu eklendi
- Unique index oluşturuldu
- Güvenli RLS politikaları eklendi
- RPC fonksiyonları oluşturuldu:
- `claim_anonymous_trip(trip_id, token)` - Ownership transfer
- `update_anonymous_trip(trip_id, token, updates)` - Token ile güncelleme
- `delete_anonymous_trip(trip_id, token)` - Token ile silme
- `get_anonymous_trip(trip_id, token)` - Token ile okuma
- `cleanup_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()`**
```typescript
// 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()`**
```typescript
// 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()`**
```typescript
// 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 et
- `cleanupOldAnonymousTrips()` - Eski anonim gezileri temizle
- `canAccessTrip(tripId)` - Geziye erişim kontrolü
#### `src/contexts/AuthContext.tsx`
```typescript
// 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`
```typescript
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ı
1. ✅ Kullanıcı giriş yapmadan gezi oluşturur
2. ✅ Token localStorage'a kaydedilir
3. ✅ Kullanıcı geziyi düzenleyebilir
4. ✅ Kullanıcı geziyi silebilir
5. ✅ Başka bir tarayıcıdan geziye erişilemez
### Senaryo 2: Login Sonrası Transfer
1. ✅ Anonim kullanıcı gezi oluşturur
2. ✅ Kullanıcı giriş yapar
3. ✅ Gezi otomatik olarak kullanıcıya transfer edilir
4. ✅ Token temizlenir
5. ✅ Gezi "Seyahat Planlarım"da görünür
### Senaryo 3: Güvenlik Testi
1. ✅ Kullanıcı A anonim gezi oluşturur
2. ✅ Kullanıcı B aynı trip_id ile erişmeye çalışır
3. ✅ Erişim reddedilir (token yok)
4. ✅ Kullanıcı B geziyi düzenleyemez
5. ✅ Kullanıcı B geziyi silemez
### Senaryo 4: Temizlik
1. ✅ 7 günden eski anonim gezi oluşturulur
2.`cleanup_old_anonymous_trips()` çağrılır
3. ✅ Eski gezi silinir
4. ✅ 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
```typescript
const trip = await tripsApi.create({
title: 'Kapadokya Gezim',
destination: 'Kapadokya',
// ...
});
// Token otomatik oluşturulur ve kaydedilir
```
### Anonim Gezi Güncelleme
```typescript
await tripsApi.update(tripId, {
title: 'Yeni Başlık'
});
// Token otomatik kontrol edilir
```
### Anonim Gezi Silme
```typescript
await tripsApi.delete(tripId);
// Token otomatik kontrol edilir ve temizlenir
```
### Manuel Ownership Transfer
```typescript
const success = await tripsApi.claimAnonymousTrip(tripId);
if (success) {
console.log('Gezi başarıyla transfer edildi');
}
```
### Eski Gezileri Temizleme (Admin)
```typescript
const deletedCount = await tripsApi.cleanupOldAnonymousTrips();
console.log(`${deletedCount} adet eski gezi temizlendi`);
```
---
## ⚠️ Önemli Notlar
1. **Token Kaybı**: Kullanıcı localStorage'ı temizlerse token kaybolur ve geziye erişemez
2. **Tarayıcı Değişimi**: Farklı tarayıcıda token olmadığı için geziye erişilemez
3. **7 Gün Sınırı**: Anonim geziler 7 gün sonra otomatik silinir
4. **Public Geziler**: Public yapılan geziler token olmadan da görüntülenebilir (ama düzenlenemez)
---
## 🔄 Migration Sırası
1. `00053_add_anonymous_trip_token_system.sql` - Token sistemi ve RPC fonksiyonları
2. `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