feat(güvenlik): giriş ve kayıtta Cloudflare Turnstile, IP başına 3 hatalı denemede 15 dk kilit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-14 00:29:00 +03:00
parent 5cbf97d884
commit a2eff1a984
27 changed files with 1247 additions and 164 deletions
+33
View File
@@ -14,6 +14,11 @@ pub struct Config {
pub static_dir: Option<PathBuf>,
/// Otomatik kur yenileme aralığı (dakika, `RATES_REFRESH_MINUTES`, varsayılan 60); 0 → kapalı
pub rates_refresh_minutes: u64,
/// Cloudflare Turnstile (`TURNSTILE_SITE_KEY` + `TURNSTILE_SECRET_KEY`); ikisi de yoksa kapalı
pub turnstile_site_key: Option<String>,
pub turnstile_secret_key: Option<String>,
/// Ters vekil arkasında (Dokploy/Traefik) gerçek IP `X-Forwarded-For`'dan (`TRUST_PROXY`, varsayılan açık)
pub trust_proxy: bool,
}
/// Yeni hane kaydı politikası (`REGISTRATION` ortam değişkeni).
@@ -78,6 +83,31 @@ impl Config {
_ => 60,
};
let opt = |k: &str| {
env::var(k)
.ok()
.map(|v| v.trim().to_string())
.filter(|v| !v.is_empty())
};
let turnstile_site_key = opt("TURNSTILE_SITE_KEY");
let turnstile_secret_key = opt("TURNSTILE_SECRET_KEY");
if turnstile_site_key.is_some() != turnstile_secret_key.is_some() {
return Err(ConfigError::Invalid(
"TURNSTILE_SITE_KEY / TURNSTILE_SECRET_KEY",
"ikisi birlikte tanımlanmalı (ya da ikisi de boş)",
));
}
let trust_proxy = match env::var("TRUST_PROXY").as_deref().map(str::trim) {
Err(_) | Ok("") | Ok("true") | Ok("1") => true,
Ok("false") | Ok("0") => false,
Ok(_) => {
return Err(ConfigError::Invalid(
"TRUST_PROXY",
"\"true\" veya \"false\" olmalı",
));
}
};
Ok(Self {
mongodb_uri,
jwt_secret,
@@ -86,6 +116,9 @@ impl Config {
registration,
static_dir,
rates_refresh_minutes,
turnstile_site_key,
turnstile_secret_key,
trust_proxy,
})
}