173 lines
7.8 KiB
Nginx Configuration File
173 lines
7.8 KiB
Nginx Configuration File
# Rozpoznanie robotow komunikatorow po naglowku User-Agent.
|
|
# Aplikacja jest SPA, wiec bot nieuruchamiajacy JavaScriptu nie zobaczy tytulu
|
|
# wydarzenia. Zadania takich robotow na /w/* kierujemy do backendu, ktory zwraca
|
|
# maly dokument HTML z meta tagami. Zwykli uzytkownicy dostaja normalna aplikacje.
|
|
map $http_user_agent $is_crawler {
|
|
default 0;
|
|
"~*facebookexternalhit" 1;
|
|
"~*Twitterbot" 1;
|
|
"~*LinkedInBot" 1;
|
|
"~*WhatsApp" 1;
|
|
"~*Slackbot" 1;
|
|
"~*Discordbot" 1;
|
|
"~*TelegramBot" 1;
|
|
"~*Googlebot" 1;
|
|
}
|
|
|
|
server {
|
|
listen 80;
|
|
server_name _;
|
|
|
|
root /usr/share/nginx/html;
|
|
index index.html;
|
|
|
|
# Nginx nie zdradza swojej wersji w naglowku Server ani na stronach bledow.
|
|
server_tokens off;
|
|
|
|
# Domyslny limit tresci zadania. Duze pliki (nagrania, materialy) maja
|
|
# wlasny, znacznie wyzszy limit ustawiony punktowo na swoich sciezkach.
|
|
# Bez tej linii limit /api/ (2100m) obejmowalby KAZDY endpoint, w tym
|
|
# otwarte /api/public/** i /api/auth/login, wiec dowolny nieuwierzytelniony
|
|
# nadawca moglby przyslac 2 GB i wyczerpac dysk albo pasmo.
|
|
client_max_body_size 1m;
|
|
|
|
# --- Naglowki bezpieczenstwa ---
|
|
# Dokladane do KAZDEJ odpowiedzi (`always`, wiec takze przy bledach).
|
|
# Aplikacja renderuje tresc HTML tworzona przez administratora
|
|
# (tresc strony, banery, szablon certyfikatu) przez dangerouslySetInnerHTML.
|
|
# HtmlSanitizer po stronie backendu jest pierwsza bariera, a CSP druga,
|
|
# niezalezna warstwa: nawet gdyby cos przeszlo sanityzacje, przegladarka
|
|
# nie wykona wstrzyknietego skryptu z obcego zrodla.
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
add_header Permissions-Policy "geolocation=(), microphone=(self), camera=(self)" always;
|
|
|
|
# CSP dobrana do faktycznych zaleznosci aplikacji:
|
|
# - script-src 'self': zadnego skryptu z obcej domeny, blokuje wstrzykniecia.
|
|
# 'wasm-unsafe-eval' jest wymagane przez klienta LiveKit (dekodery WASM).
|
|
# - style-src 'unsafe-inline': React, Tailwind, TipTap i LiveKit wstrzykuja
|
|
# style inline; Google Fonts dostarcza arkusz z fonts.googleapis.com.
|
|
# - font-src: pliki fontow IBM Plex z fonts.gstatic.com.
|
|
# - img-src data:/blob:: obrazki edytora i miniatury; https: okladki lekcji.
|
|
# - connect-src: API i WebSocket ida na wlasny host (ws:/wss:); LiveKit laczy
|
|
# sie z wlasnym serwerem, wiec dopuszczamy dowolny wss:.
|
|
# - frame-src: pokoj wideo (LiveKit/Jitsi) oraz wideo lekcji (external_video_url,
|
|
# dowolny host http/https) osadzane sa w iframe.
|
|
# - frame-ancestors 'self': panel osadza wlasne iframe, wiec 'self' zamiast
|
|
# pelnego zakazu; chroni przed clickjackingiem z obcej strony.
|
|
# - object-src 'none', base-uri 'self': domkniecie klasycznych wektorow.
|
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: blob: https:; media-src 'self' data: blob: https:; connect-src 'self' ws: wss:; frame-src 'self' https: http:; frame-ancestors 'self'; object-src 'none'; base-uri 'self'; form-action 'self'" always;
|
|
|
|
# Interfejs jest po polsku, wiec kazda odpowiedz tekstowa musi jawnie
|
|
# deklarowac UTF-8. Bez tego naglowek to samo "application/javascript",
|
|
# a przegladarka zgaduje kodowanie pakietu, w ktorym siedza polskie znaki.
|
|
# charset_types domyslnie nie obejmuje JavaScriptu, stad jawna lista.
|
|
charset utf-8;
|
|
charset_types text/html text/css text/plain text/xml
|
|
application/javascript application/json image/svg+xml;
|
|
|
|
# Strona webinaru. Robot dostaje zajawke z backendu, czlowiek aplikacje.
|
|
# Rozgalezienie przez error_page zamiast if, bo "if" w kontekscie location
|
|
# w polaczeniu z try_files zachowuje sie nieprzewidywalnie.
|
|
location ~ ^/w/(?<webinar_slug>.+)$ {
|
|
error_page 418 = @crawler_preview;
|
|
recursive_error_pages on;
|
|
|
|
if ($is_crawler) {
|
|
return 418;
|
|
}
|
|
|
|
try_files $uri /index.html;
|
|
}
|
|
|
|
location @crawler_preview {
|
|
proxy_pass http://backend:8080/api/preview/w/$webinar_slug;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
}
|
|
|
|
# SPA: kazda sciezka nieodpowiadajaca plikowi trafia do index.html
|
|
location / {
|
|
try_files $uri $uri/ /index.html;
|
|
}
|
|
|
|
# Otwarte endpointy bez plikow: logowanie i webhooki. Male zadania JSON/XML,
|
|
# wiec twardy limit 1 MB. Sa dostepne bez uwierzytelnienia, wiec to one sa
|
|
# najtanszym celem do zalania duzym zadaniem. Prefiks `^~` wygrywa ze zwyklym
|
|
# `/api/` i nie pozwala regexpom nizej przejac tych sciezek.
|
|
location ^~ /api/auth/ {
|
|
proxy_pass http://backend:8080/api/auth/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
client_max_body_size 1m;
|
|
}
|
|
|
|
location ^~ /api/webhooks/ {
|
|
proxy_pass http://backend:8080/api/webhooks/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
client_max_body_size 1m;
|
|
}
|
|
|
|
# Wgrywanie i serwowanie DUZYCH plikow: nagrania i materialy webinaru.
|
|
# Tylko te sciezki dostaja limit liczony w gigabajtach; reszta API zostaje
|
|
# przy limicie posrednim, a otwarte endpointy przy 1 MB z poziomu server.
|
|
# Regexp lapie zarowno wgranie (POST na /recordings, /materials), jak i
|
|
# pobranie (/api/public/recordings/{id}, /api/public/materials/{id}).
|
|
location ~ ^/api/(admin/webinars/[0-9]+/(recordings|materials)|public/(recordings|materials)/) {
|
|
proxy_pass http://backend:8080$request_uri;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
|
|
# Nagranie potrafi miec 2 GB. Wartosc nie mniejsza niz RECORDING_MAX_SIZE_MB.
|
|
client_max_body_size 2100m;
|
|
|
|
# Wgranie i pobranie duzego pliku trwaja dluzej niz domyslne 60 s.
|
|
proxy_read_timeout 3600s;
|
|
proxy_send_timeout 3600s;
|
|
|
|
# Pliki ida strumieniowo, bez buforowania calosci przed oddaniem.
|
|
proxy_buffering off;
|
|
proxy_request_buffering off;
|
|
}
|
|
|
|
# proxy do backendu, nazwa uslugi z docker compose
|
|
location /api/ {
|
|
proxy_pass http://backend:8080/api/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
|
|
# Posredni limit dla reszty API: obrazki tresci, branding, import CSV.
|
|
# Wyzszy niz MEDIA_MAX_SIZE_MB (domyslnie 5), z zapasem na branding.
|
|
client_max_body_size 25m;
|
|
|
|
# Pobranie nagrania z tej trasy nie idzie (ma wlasna lokalizacje wyzej),
|
|
# ale zostawiamy wydluzony odczyt na dluzsze operacje panelu.
|
|
proxy_read_timeout 300s;
|
|
proxy_send_timeout 300s;
|
|
}
|
|
|
|
# WebSocket ze STOMP do czatu, pytan i zgloszen do zabrania glosu
|
|
location /ws {
|
|
proxy_pass http://backend:8080/api/ws;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_set_header Host $host;
|
|
proxy_read_timeout 3600s;
|
|
}
|
|
}
|