Bloguma İlk Yorum Geldi — ve Tarayıcının Koruduğu Kapı

Bugün üç şey yaptım: yazılarımı sonradan düzeltebilir hale getirdim, Markdown’ın sinsi bir tuzağını kapattım ve 7. Günde arka planda kurduğum yorum sistemini nihayet sitede görünür yaptım. Sonuncusu beni tam beklediğim yerde bir hatayla karşıladı.

1. Yazılarımı düzeltemiyordum

  1. Günde yazılarımı dosyalardan veritabanına taşıyan küçük bir program yazmıştım. Ama o program sadece yeni yazıları ekliyordu; var olanlara “zaten var” deyip dokunmuyordu. Yani bir yazıda yazım hatası düzeltsem, dosya değişiyor ama veritabanı eski halini tutmaya devam ediyordu. Site de eski hali gösteriyordu.

Programı “sadece ekleyen” olmaktan çıkarıp “eşitleyen” yaptık. Artık her yazı için üç ihtimale bakıyor:

  • Veritabanında yoksa → ekle
  • Var ama dosyadakinden farklıysa → güncelle (“başlık değişti”, “içerik değişti” diye neyin değiştiğini de söylüyor)
  • Birebir aynıysa → dokunma

İki küçük ama önemli detay ekledik:

Prova modu. Program gerçekten bir şey değiştirmeden önce “şunları yapacağım” listesini gösterebiliyor. Toplu veri değiştiren her işte kural bu: önce prova, sonra gerçek.

Sessiz mod. Bir yazı güncellendiğinde sitede “Güncellendi: 18 Ekim” yazıyor. Ama bugünkü düzeltme okuyucunun göreceği bir içerik değişikliği değildi, tek bir kelime değişmedi. Yedi yazının hepsine bugünün tarihini basmak okuyucuya yanlış bilgi vermek olurdu. O yüzden bu seferlik tarihlere dokunmadan güncelledik.

2. Markdown’ın sayı tuzağı

Yazılarımın çoğu şöyle başlıyor: “5. Günün sonunda şöyle demiştim…”

Markdown, satır başında sayı + nokta + boşluk görünce bunu numaralı liste sanıyor. Yani ilk paragraflarım girintili bir liste maddesi olarak görünüyordu. Haftalardır öyleymiş, fark etmemişim.

Çözüm: noktanın önüne ters bölü koymak (5\. Günün). Ekranda yine “5. Günün” görünüyor ama artık liste sayılmıyor. Dikkat ettiğimiz şey: bazı yazılarda gerçek numaralı listeler var. Onlara dokunmadık, sadece “N. Gün” ile başlayan satırları düzelttik.

3. Sitenin ilk “önceden pişmemiş” parçası

5. Günden hatırlarsan: sitenin sayfaları önceden hazırlanıp raflara diziliyor (vitrindeki ekmekler gibi). Ziyaretçi geldiğinde hazır sayfayı alıyor.

Yorumlar bu düzene uymuyor. Sayfa hazırlandıktan sonra yazılan bir yorum, o sayfada olamaz. Bu yüzden yorum bölümü farklı çalışıyor: ziyaretçinin tarayıcısı, sayfayı açtıktan sonra API’ye kendisi gidip yorumları istiyor. Fırın benzetmesiyle: müşteri raftan ekmeği alıyor, sonra kendi ayağıyla mutfağa gidip “bu ekmek hakkında not bırakan olmuş mu?” diye soruyor.

Bu, sitemin ilk canlı parçası. Ve bununla gelen yeni bir sorumluluk var: başkasının yazdığını ekranda göstermek. Biri yorum kutusuna isim yerine kod yazarsa ne olur? Yorumları sayfaya “düz metin” olarak basıyoruz. Yani biri <script> yazsa, ekranda sadece <script> yazısı görünüyor, hiçbir şey çalışmıyor. Bunu bilerek kötü niyetli örneklerle denedik.

4. Beklediğim hata: CORS

Yorum bölümünü koyup sayfayı açtığımda “Yorumlar şu an yüklenemedi” yazdı. Tarayıcının konsolunda da şu: Access-Control-Allow-Origin başlığı eksik.

Bu hatayı bilerek görmek istemiştim. Anlamı şu:

Tarayıcılar, bir sitedeki sayfanın başka bir adresteki sunucudan gelen cevabı okumasına izin vermez — o sunucu açıkça “bu siteye izin veriyorum” demediği sürece. Sitem bir adreste, API’m başka bir adreste. Tarayıcı için ikisi yabancı.

Neden böyle bir kural var? Bankana giriş yapmış olduğunu ve sonra kötü niyetli bir siteye girdiğini düşün. O site, arka planda senin tarayıcından bankanın sistemine “bakiyeyi ver” diye istek atabilir. Tarayıcın senin oturum bilgilerini otomatik ekler. Bu kural olmasaydı kötü site cevabı okurdu. Kural sayesinde okuyamıyor.

İşin en öğretici tarafı: 7. Günde aynı API’ye komut satırından yorum göndermiştim ve çalışmıştı. Çünkü bu kuralı sunucu değil, tarayıcı uyguluyor. Komut satırı tarayıcı değil, kimseyi korumuyor. Yani CORS benim sunucumu korumuyor; tarayıcıdaki kullanıcıyı koruyor.

Bir de iki farklı davranış gördüm:

  • Yorumları okurken: Tarayıcı isteği gönderdi, cevabı aldı, izin etiketi yok, cevabı sayfaya vermedi.
  • Yorum gönderirken: Tarayıcı asıl isteği hiç göndermedi. Önce bir ön kontrol yaptı: “Bu siteden sana veri gönderebilir miyim?” Olumlu cevap gelmeyince yorumu yola çıkarmadı.

Mantığı şu: okumak görece zararsız, ama göndermek karşı tarafta bir şeyi değiştirebilir. O yüzden önce izin isteniyor.

Çözüm: API’ye “şu adreslerden gelenlere izin ver” dedik. Ama herkese değil; sadece idrishanlabs.com ve kendi bilgisayarımdaki geliştirme adresi. “Herkese açık” demek de çalışırdı, ama o zaman herhangi bir site, ziyaretçilerinin tarayıcısını kullanarak benim yorum kutuma istek attırabilirdi.

5. Ondördüncü hata: yanlış kapı

CORS’u düzelttik, sayfayı açtım: yine aynı hata.

Sebep: arka planda açık kalmış bir geliştirme sunucusu olağan adresi (localhost:4321) tutuyordu. Benim açtığım sunucu boş yer bulamayıp sessizce localhost:4322’ye kaymıştı. Tarayıcı için sadece port numarası farklı olsa bile bunlar iki ayrı site. İzin listemde 4322 yoktu.

Yani sistem doğru çalışıyordu, ben yanlış kapıdan girmiştim. Fazladan sunucuyu kapatıp doğru adresten açınca yorum ekrana geldi.

4322’yi listeye eklemedik. O tesadüfi bir adresti; bir dahakine 4323 olurdu. İzin listesi bilinçli seçtiğim adresleri tutmalı, tesadüfleri değil.

İki küçük ama doğru karar

Yarım iş ayrı dalda bekledi. Yorum bölümü, CORS düzeltilmeden canlıya çıksaydı, her yazının altında “yüklenemedi” yazan bozuk bir kutu görünürdü. O yüzden kodu projenin ayrı bir kopyasında (dalında) tuttuk ve ancak her şey çalışınca ana koda birleştirdik.

Sitenin tek bir adresi oldu. Fark ettik ki site kendini hâlâ eski workers.dev adresiyle tanıtıyormuş: arama motorlarına, RSS okuyucularına giden linkler oraya gidiyordu. Resmi adresi idrishanlabs.com yaptık.

Bir yorumun yolculuğu

Bugünkü her şeyin özeti, bir yorumun baştan sona izlediği yol:

  1. Ziyaretçi yazı sayfasını açıyor; sayfa önceden hazırlanmış halde geliyor.
  2. Tarayıcı, API’ye “bu yazının onaylı yorumlarını ver” diye soruyor.
  3. API cevabın üstüne “idrishanlabs.com’a izin var” etiketini koyuyor; tarayıcı cevabı sayfaya veriyor.
  4. Ziyaretçi bir yorum yazıp gönderiyor. Tarayıcı önce izin soruyor, izin gelince yorumu gönderiyor.
  5. API yorumu kontrol ediyor (isim boş mu, çok mu uzun) ve onaysız olarak kaydediyor.
  6. Ben veritabanında yorumu onaylıyorum.
  7. Bir sonraki ziyaretçi sayfayı açtığında yorum listede görünüyor, düz metin olarak.

Açık bıraktıklarım

  • Yorum kutusunun bir sınırı yok. Biri saniyede yüzlerce yorum gönderebilir. Onaysız girdikleri için sitede görünmezler, ama veritabanı şişer.
  • Onaylamak için hâlâ SQL yazıyorum. Her yorum için veritabanı ekranına girip komut yazmak sürdürülebilir değil.
  • Deneme ile gerçek veri aynı yerde. Kendi bilgisayarımda denediğim her şey gerçek veritabanına yazılıyor.

Sırada ne var?

0. Gündeki yol haritama bakınca, bir sonraki aşamanın tam olarak bu açıkları kapattığını görüyorum: kimlik ve güvenlik. Yorumlara spam engeli ve sadece benim girebileceğim bir yönetici paneli. Orada “kimin neye yetkisi var” sorusunu öğreneceğim: sen kimsin, bunu nasıl kanıtlarsın, ve kanıtladıktan sonra neler yapabilirsin.

Yorumlar