Make the Secure cookie attribute configurable for TLS-less dev stands

Session cookies were hardcoded Secure, which is correct for prod but
silently breaks every non-localhost HTTP dev deployment: browsers (and
any spec-compliant HTTP client, verified with curl against a container IP
and against dev.netrunner-vpn.com directly) refuse to store a Secure
cookie received over plain HTTP unless the host is localhost. The dev VPS
serves everything over plain HTTP with no reverse-proxy/TLS by design, so
every login there was silently failing to persist a session at all —
this is a bigger, more fundamental gap than the cookie-sharing-with-the-
landing question that led to finding it.

Added COOKIE_SECURE (defaults true) alongside the existing COOKIE_DOMAIN,
logs a loud warning when disabled, and turned it off for the dev-remote
and local docker-dev env templates. Verified end-to-end against a
non-localhost address that the cookie now actually gets stored and a
follow-up authenticated request succeeds.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-04 19:20:14 +07:00
parent a7607e4b33
commit acfcdbe90e
6 changed files with 62 additions and 8 deletions
+14
View File
@@ -180,6 +180,19 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Домен для Set-Cookie: пусто локально (host-only cookie), в проде
// `.netrunner-vpn.com` — расшаривает сессию между лендингом и ЛК на сабдомене.
let cookie_domain = std::env::var("COOKIE_DOMAIN").unwrap_or_default();
// По умолчанию true (обязателен в проде, там HTTPS). Явно выключается через
// COOKIE_SECURE=false на стендах без TLS — иначе браузер молча дропает
// Secure-cookie, полученную по голому HTTP (кроме localhost), и сессия не
// переживает ни одного запроса.
let cookie_secure = std::env::var("COOKIE_SECURE")
.map(|v| v.trim().to_lowercase() != "false")
.unwrap_or(true);
if !cookie_secure {
tracing::warn!(
"⚠️ COOKIE_SECURE=false — сессионные cookie без атрибута Secure. \
Только для стендов без TLS, никогда не для прода!"
);
}
let csrf_allowed_origins: Vec<String> = std::env::var("CSRF_ALLOWED_ORIGINS")
.unwrap_or_else(|_| {
"https://netrunner-vpn.com,https://account.netrunner-vpn.com".to_string()
@@ -232,6 +245,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
bot_token: bot_token.clone(),
admin_tg_id,
cookie_domain,
cookie_secure,
csrf_allowed_origins,
};