337 lines
8.5 KiB
Markdown
337 lines
8.5 KiB
Markdown
# Trip Create Security Tests
|
||
|
||
## Güvenlik Özellikleri
|
||
|
||
### 1. ✅ Zorunlu Kimlik Doğrulama (Auth Mandatory)
|
||
**Özellik**: Anonim kullanıcılar seyahat oluşturamaz.
|
||
|
||
**Test Senaryoları**:
|
||
```typescript
|
||
// ❌ BAŞARISIZ: Giriş yapmadan seyahat oluşturma
|
||
await supabase.auth.signOut();
|
||
const result = await tripsApiSafe.create({
|
||
title: "Test Seyahati"
|
||
});
|
||
// Beklenen: Error: "Seyahat oluşturmak için giriş yapmalısınız."
|
||
|
||
// ✅ BAŞARILI: Giriş yaparak seyahat oluşturma
|
||
await supabase.auth.signInWithPassword({
|
||
email: "test@example.com",
|
||
password: "password123"
|
||
});
|
||
const result = await tripsApiSafe.create({
|
||
title: "Test Seyahati"
|
||
});
|
||
// Beklenen: Trip objesi döner
|
||
```
|
||
|
||
---
|
||
|
||
### 2. ✅ Rate Limiting (5 trip/saat)
|
||
**Özellik**: Kullanıcı saatte en fazla 5 seyahat oluşturabilir.
|
||
|
||
**Test Senaryoları**:
|
||
```typescript
|
||
// ✅ İlk 5 seyahat başarılı
|
||
for (let i = 1; i <= 5; i++) {
|
||
const result = await tripsApiSafe.create({
|
||
title: `Seyahat ${i}`
|
||
});
|
||
console.log(`✅ Seyahat ${i} oluşturuldu`);
|
||
}
|
||
|
||
// ❌ 6. seyahat başarısız
|
||
try {
|
||
await tripsApiSafe.create({
|
||
title: "Seyahat 6"
|
||
});
|
||
} catch (error) {
|
||
console.log(error.message);
|
||
// Beklenen: "Saatlik limit aşıldı. X dakika sonra tekrar deneyin."
|
||
}
|
||
|
||
// ⏰ 1 saat sonra tekrar dene
|
||
setTimeout(async () => {
|
||
const result = await tripsApiSafe.create({
|
||
title: "Yeni Seyahat"
|
||
});
|
||
console.log("✅ Limit sıfırlandı, yeni seyahat oluşturuldu");
|
||
}, 3600000);
|
||
```
|
||
|
||
---
|
||
|
||
### 3. ✅ Input Validation
|
||
**Özellik**: Tüm girişler doğrulanır ve sanitize edilir.
|
||
|
||
#### 3.1 Başlık Validasyonu
|
||
```typescript
|
||
// ❌ Başlık çok kısa (min: 3 karakter)
|
||
await tripsApiSafe.create({ title: "AB" });
|
||
// Beklenen: Error: "Seyahat başlığı en az 3 karakter olmalıdır."
|
||
|
||
// ❌ Başlık çok uzun (max: 200 karakter)
|
||
await tripsApiSafe.create({
|
||
title: "A".repeat(201)
|
||
});
|
||
// Beklenen: Error: "Seyahat başlığı en fazla 200 karakter olabilir."
|
||
|
||
// ❌ Başlık boş
|
||
await tripsApiSafe.create({ title: "" });
|
||
// Beklenen: Error: "Seyahat başlığı alanı zorunludur."
|
||
|
||
// ✅ Geçerli başlık
|
||
await tripsApiSafe.create({
|
||
title: "İstanbul Gezisi"
|
||
});
|
||
```
|
||
|
||
#### 3.2 Açıklama Validasyonu
|
||
```typescript
|
||
// ❌ Açıklama çok uzun (max: 2000 karakter)
|
||
await tripsApiSafe.create({
|
||
title: "Test",
|
||
description: "A".repeat(2001)
|
||
});
|
||
// Beklenen: Error: "Açıklama en fazla 2000 karakter olabilir."
|
||
|
||
// ✅ Geçerli açıklama
|
||
await tripsApiSafe.create({
|
||
title: "Test",
|
||
description: "Harika bir seyahat planı"
|
||
});
|
||
```
|
||
|
||
#### 3.3 Tarih Validasyonu
|
||
```typescript
|
||
// ❌ Bitiş tarihi başlangıçtan önce
|
||
await tripsApiSafe.create({
|
||
title: "Test",
|
||
start_date: "2026-03-01",
|
||
end_date: "2026-02-01"
|
||
});
|
||
// Beklenen: Error: "Bitiş tarihi başlangıç tarihinden önce olamaz."
|
||
|
||
// ❌ Geçersiz tarih formatı
|
||
await tripsApiSafe.create({
|
||
title: "Test",
|
||
start_date: "invalid-date"
|
||
});
|
||
// Beklenen: Error: "Başlangıç tarihi geçersiz."
|
||
|
||
// ❌ Çok uzun seyahat (max: 365 gün)
|
||
await tripsApiSafe.create({
|
||
title: "Test",
|
||
start_date: "2026-01-01",
|
||
end_date: "2027-02-01"
|
||
});
|
||
// Beklenen: Error: "Seyahat süresi en fazla 365 gün olabilir."
|
||
|
||
// ✅ Geçerli tarihler
|
||
await tripsApiSafe.create({
|
||
title: "Test",
|
||
start_date: "2026-03-01",
|
||
end_date: "2026-03-10"
|
||
});
|
||
```
|
||
|
||
#### 3.4 Konum Validasyonu
|
||
```typescript
|
||
// ❌ Geçersiz enlem (lat: -90 ile 90 arası)
|
||
await tripsApiSafe.create({
|
||
title: "Test",
|
||
start_lat: 100,
|
||
start_lng: 30
|
||
});
|
||
// Beklenen: Error: "Enlem değeri -90 ile 90 arasında olmalıdır."
|
||
|
||
// ❌ Geçersiz boylam (lng: -180 ile 180 arası)
|
||
await tripsApiSafe.create({
|
||
title: "Test",
|
||
start_lat: 40,
|
||
start_lng: 200
|
||
});
|
||
// Beklenen: Error: "Boylam değeri -180 ile 180 arasında olmalıdır."
|
||
|
||
// ❌ Konum tipi sayı değil
|
||
await tripsApiSafe.create({
|
||
title: "Test",
|
||
start_lat: "invalid",
|
||
start_lng: 30
|
||
});
|
||
// Beklenen: Error: "Konum bilgileri geçersiz."
|
||
|
||
// ✅ Geçerli konum
|
||
await tripsApiSafe.create({
|
||
title: "Test",
|
||
start_lat: 41.0082,
|
||
start_lng: 28.9784
|
||
});
|
||
```
|
||
|
||
#### 3.5 İlgi Alanları Validasyonu
|
||
```typescript
|
||
// ❌ Çok fazla ilgi alanı (max: 20)
|
||
await tripsApiSafe.create({
|
||
title: "Test",
|
||
interests: Array(21).fill("test")
|
||
});
|
||
// Beklenen: Error: "En fazla 20 ilgi alanı seçebilirsiniz."
|
||
|
||
// ❌ Geçersiz ilgi alanı formatı
|
||
await tripsApiSafe.create({
|
||
title: "Test",
|
||
interests: ["A".repeat(51)]
|
||
});
|
||
// Beklenen: Error: "İlgi alanı formatı geçersiz."
|
||
|
||
// ✅ Geçerli ilgi alanları
|
||
await tripsApiSafe.create({
|
||
title: "Test",
|
||
interests: ["history", "culture", "food"]
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
### 4. ✅ Meaningful Error Messages
|
||
**Özellik**: Kullanıcıya anlamlı hata mesajları gösterilir.
|
||
|
||
**Hata Mesajları**:
|
||
- ✅ "Seyahat oluşturmak için giriş yapmalısınız." (Auth hatası)
|
||
- ✅ "Saatlik limit aşıldı. X dakika sonra tekrar deneyin." (Rate limit)
|
||
- ✅ "Seyahat başlığı en az 3 karakter olmalıdır." (Validation)
|
||
- ✅ "Bitiş tarihi başlangıç tarihinden önce olamaz." (Validation)
|
||
- ✅ "Seyahat oluşturulurken bir hata oluştu. Lütfen tekrar deneyin." (DB hatası)
|
||
|
||
---
|
||
|
||
### 5. ✅ Security Audit Logging
|
||
**Özellik**: Tüm güvenlik olayları loglanır.
|
||
|
||
**Log Formatı**:
|
||
```
|
||
[SECURITY AUDIT] 2026-02-08T10:30:00.000Z | Event: TRIP_CREATE_SUCCESS | User: user-uuid | Details: { tripId, title, destination }
|
||
```
|
||
|
||
**Log Olayları**:
|
||
1. `TRIP_CREATE_FAILED` - Yetkisiz erişim denemesi
|
||
2. `TRIP_CREATE_RATE_LIMITED` - Rate limit aşımı
|
||
3. `TRIP_CREATE_VALIDATION_FAILED` - Geçersiz input
|
||
4. `TRIP_CREATE_DB_ERROR` - Veritabanı hatası
|
||
5. `TRIP_CREATE_SUCCESS` - Başarılı oluşturma
|
||
|
||
**Test**:
|
||
```typescript
|
||
// Console'u izle
|
||
console.log = jest.fn();
|
||
|
||
// Başarısız deneme
|
||
try {
|
||
await supabase.auth.signOut();
|
||
await tripsApiSafe.create({ title: "Test" });
|
||
} catch (error) {
|
||
// Log kontrolü
|
||
expect(console.log).toHaveBeenCalledWith(
|
||
expect.stringContaining('[SECURITY AUDIT]'),
|
||
expect.stringContaining('TRIP_CREATE_FAILED'),
|
||
expect.stringContaining('Unauthorized')
|
||
);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Kullanım Örnekleri
|
||
|
||
### Frontend'de Güvenli Kullanım
|
||
|
||
```typescript
|
||
import { tripsApiSafe } from '@/db/api';
|
||
import { toast } from 'sonner';
|
||
|
||
// ✅ DOĞRU: tripsApiSafe kullan
|
||
const handleCreateTrip = async (formData) => {
|
||
try {
|
||
const trip = await tripsApiSafe.create({
|
||
title: formData.title,
|
||
description: formData.description,
|
||
start_date: formData.startDate,
|
||
end_date: formData.endDate,
|
||
destination: formData.destination,
|
||
interests: formData.interests
|
||
});
|
||
|
||
toast.success('Seyahat başarıyla oluşturuldu!');
|
||
return trip;
|
||
} catch (error) {
|
||
// Kullanıcıya anlamlı hata mesajı göster
|
||
toast.error(error.message);
|
||
console.error('Trip creation error:', error);
|
||
}
|
||
};
|
||
|
||
// ❌ YANLIŞ: tripsApi kullanma (güvensiz)
|
||
const handleCreateTripUnsafe = async (formData) => {
|
||
const trip = await tripsApi.create(formData); // Validation yok!
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## Performans Notları
|
||
|
||
### Rate Limiter Memory Management
|
||
Rate limiter in-memory Map kullanır. Production'da Redis kullanılması önerilir:
|
||
|
||
```typescript
|
||
// Gelecek iyileştirme: Redis ile rate limiting
|
||
import { Redis } from '@upstash/redis';
|
||
|
||
const redis = new Redis({
|
||
url: process.env.REDIS_URL,
|
||
token: process.env.REDIS_TOKEN
|
||
});
|
||
|
||
const checkRateLimitRedis = async (userId: string) => {
|
||
const key = `rate_limit:trip_create:${userId}`;
|
||
const count = await redis.incr(key);
|
||
|
||
if (count === 1) {
|
||
await redis.expire(key, 3600); // 1 saat
|
||
}
|
||
|
||
if (count > 5) {
|
||
const ttl = await redis.ttl(key);
|
||
throw new Error(`Saatlik limit aşıldı. ${Math.ceil(ttl / 60)} dakika sonra tekrar deneyin.`);
|
||
}
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## Güvenlik Checklist
|
||
|
||
- [x] ✅ Auth mandatory - Anonim kullanıcılar seyahat oluşturamaz
|
||
- [x] ✅ Rate limiting - 5 trip/saat per user
|
||
- [x] ✅ Input validation - Tüm alanlar doğrulanır
|
||
- [x] ✅ Meaningful errors - Kullanıcıya anlamlı mesajlar
|
||
- [x] ✅ Security logging - Tüm olaylar loglanır
|
||
- [x] ✅ SQL injection koruması - Input sanitization
|
||
- [x] ✅ XSS koruması - String validation
|
||
- [x] ✅ Data integrity - Tarih ve konum validasyonu
|
||
|
||
---
|
||
|
||
## Sonuç
|
||
|
||
`tripsApiSafe.create()` fonksiyonu artık production-ready ve güvenli:
|
||
|
||
1. **Auth Mandatory**: Sadece giriş yapmış kullanıcılar seyahat oluşturabilir
|
||
2. **Rate Limiting**: Spam ve abuse koruması
|
||
3. **Input Validation**: Tüm girişler doğrulanır ve sanitize edilir
|
||
4. **Error Handling**: Kullanıcıya anlamlı mesajlar
|
||
5. **Audit Trail**: Güvenlik olayları loglanır
|
||
|
||
**Kullanım**: Frontend'de her zaman `tripsApiSafe.create()` kullanın, `tripsApi.create()` kullanmayın!
|