# Crawlery social media (Facebook, WhatsApp, Messenger, Twitter/X, LinkedIn...) nie wykonuja # JavaScriptu, wiec meta tagi ustawiane w aplikacji React sa dla nich niewidoczne. Po User-Agent # rozpoznajemy bota i dla adresu /oferta/{id} kierujemy go na serwerowy podglad z tagami Open Graph # (backend), zamiast na aplikacje SPA. Zwykly uzytkownik dostaje niezmieniona aplikacje. map $http_user_agent $pl_is_crawler { default 0; "~*facebookexternalhit|Facebot|Twitterbot|WhatsApp|Slackbot|LinkedInBot|TelegramBot|Discordbot|Pinterest|redditbot|Applebot|SkypeUriPreview|vkShare|Google-InspectionTool|Googlebot|bingbot|Embedly|Iframely|OpenGraph" 1; } # Limity zadan na adres IP klienta (po realip - patrz blok server). Przekroczenie = 429. # Limitujemy tylko ruch z naglowkiem X-Real-IP, czyli przychodzacy przez nginx hosta - na # produkcji to caly ruch (kontener slucha tylko na 127.0.0.1 hosta, a nginx hosta zawsze ustawia # X-Real-IP na prawdziwy adres). Lokalny ruch bez tego naglowka (dev, testy e2e) ma pusty klucz, # a nginx nie limituje zadan z pustym kluczem. map $http_x_real_ip $pl_limit_key { "" ""; default $binary_remote_addr; } # Logowanie i hasla: zgadywanie hasel i kodow. limit_req_zone $pl_limit_key zone=pl_auth:10m rate=10r/m; # Akcje wysylajace e-mail lub platny SMS: rejestracja, reset hasla, ponowne kody, formularz reklamowy. limit_req_zone $pl_limit_key zone=pl_send:10m rate=3r/m; # Tlumaczenia (kazde zadanie to do 150 zapytan do Google). # Cale API - ochrona przed zalewem zadan; zwykle korzystanie z serwisu jest daleko ponizej. limit_req_zone $pl_limit_key zone=pl_api:10m rate=30r/s; limit_req_status 429; server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; include /etc/nginx/snippets/security-headers.conf; # Na produkcji przed kontenerem stoi nginx hosta (TLS), wiec $remote_addr to bramka Dockera - # wszyscy uzytkownicy mieliby jeden adres i wspolny limit. Prawdziwe IP bierzemy z X-Real-IP, # ktore ustawia nginx hosta, ale ufamy mu tylko z sieci prywatnych (port kontenera jest # przypiety do 127.0.0.1 hosta, wiec z internetu nie da sie go podrobic). set_real_ip_from 127.0.0.1; set_real_ip_from 10.0.0.0/8; set_real_ip_from 172.16.0.0/12; set_real_ip_from 192.168.0.0/16; real_ip_header X-Real-IP; # Przekroczony limit: JSON z komunikatem, ktory frontend pokazuje uzytkownikowi (apiFetch czyta # pole "message") zamiast ogolnego "Wystapil blad". error_page 429 = @too_many_requests; location @too_many_requests { default_type application/json; return 429 '{"status":429,"message":"Zbyt wiele prób w krótkim czasie. Odczekaj chwilę i spróbuj ponownie."}'; } # Wbudowany DNS Dockera - potrzebny, bo proxy_pass ze zmienna ($listing_id) rozwiazuje host # "backend" dopiero przy zadaniu, a nie przy starcie nginx. resolver 127.0.0.11 ipv6=off valid=30s; location /api/ { include /etc/nginx/snippets/api-proxy.conf; limit_req zone=pl_api burst=200 nodelay; location ~ ^/api/auth/(login|activate|reset-password|verify-phone|verify-password|change-password)$ { include /etc/nginx/snippets/api-proxy.conf; limit_req zone=pl_auth burst=10 nodelay; } location ~ ^/api/(auth/(register|forgot-password|resend-activation|resend-phone-otp)|advertising-enquiries)$ { include /etc/nginx/snippets/api-proxy.conf; limit_req zone=pl_send burst=5 nodelay; } } # Szczegoly ogloszenia: crawler dostaje serwerowy podglad Open Graph, czlowiek - aplikacje SPA. location ~ ^/oferta/(?\d+) { if ($pl_is_crawler) { proxy_pass http://backend:8080/api/og/listing/$listing_id; } proxy_hide_header X-Frame-Options; proxy_hide_header X-Content-Type-Options; try_files $uri /index.html; } # Pliki z hashem w nazwie (assets/index-XXXX.js) sa niezmienne - mozna je trzymac dlugo. location /assets/ { try_files $uri =404; add_header Cache-Control "public, max-age=31536000, immutable"; include /etc/nginx/snippets/security-headers.conf; } # index.html nigdy nie moze byc cache'owany - inaczej przegladarka po wdrozeniu # nadal laduje stary bundle i uzytkownik pracuje na nieaktualnej wersji aplikacji. location = /index.html { add_header Cache-Control "no-store, must-revalidate"; include /etc/nginx/snippets/security-headers.conf; } location / { try_files $uri $uri/ /index.html; } }