Compare commits

..

9 Commits

Author SHA1 Message Date
Mateusz Gruszczyński
d71d33cfe0 zmiany w acl 2025-05-13 08:50:50 +02:00
Mateusz Gruszczyński
8d29328103 zmiany w acl 2025-05-13 08:33:10 +02:00
Mateusz Gruszczyński
748b4e47a2 zmiany w szablonach 2025-05-13 08:28:42 +02:00
Mateusz Gruszczyński
6935cefaf7 zmiany w acl 2025-05-13 08:25:23 +02:00
Mateusz Gruszczyński
1a62bbae2a zmiany w acl 2025-05-13 08:21:44 +02:00
Mateusz Gruszczyński
3d54f95a01 zmiany w acl 2025-05-13 07:57:56 +02:00
Mateusz Gruszczyński
9cc6730291 zmiany w acl 2025-05-13 07:41:40 +02:00
Mateusz Gruszczyński
cb5666e9b9 zmiany w acl 2025-05-13 07:36:58 +02:00
Mateusz Gruszczyński
214bd4f2c6 zmiany w acl 2025-05-13 07:36:45 +02:00
3 changed files with 91 additions and 33 deletions

101
app.py
View File

@@ -62,12 +62,27 @@ class GlobalSettings(db.Model):
def load_user(user_id):
return User.query.get(int(user_id))
def get_real_ip():
headers = request.headers
cf_ip = headers.get("CF-Connecting-IP")
if cf_ip:
return cf_ip.split(",")[0].strip()
xff = headers.get("X-Forwarded-For")
if xff:
return xff.split(",")[0].strip()
x_real_ip = headers.get("X-Real-IP")
if x_real_ip:
return x_real_ip.strip()
return request.remote_addr
def is_allowed_ip(remote_ip, allowed_hosts_str):
# Jeśli istnieje plik awaryjny, zawsze zezwalamy na dostęp
if remote_ip in ("127.0.0.1", "::1"):
return True
if os.path.exists("emergency_access.txt"):
return True
# Rozdzielamy wpisy mogą być oddzielone przecinkami lub znakami nowej linii
allowed_hosts = re.split(r'[\n,]+', allowed_hosts_str.strip())
allowed_ips = set()
for host in allowed_hosts:
@@ -75,19 +90,32 @@ def is_allowed_ip(remote_ip, allowed_hosts_str):
if not host:
continue
try:
# Rozwiązywanie nazwy domeny do adresu IP.
resolved_ip = socket.gethostbyname(host)
allowed_ips.add(resolved_ip)
except Exception:
# Jeśli rozwiązywanie nazwy nie powiedzie się, pomijamy ten wpis.
continue
try:
hostname = socket.gethostbyaddr(remote_ip)[0]
app.logger.info(f"Odwiedzający IP: {remote_ip}, host: {hostname}")
except Exception as e:
app.logger.warning(f"Reverse DNS nieudane dla {remote_ip}: {e}")
return remote_ip in allowed_ips
# Dodaj filtr Markdown pozwala na zagnieżdżanie linków i obrazków w opisie
@app.template_filter('markdown')
def markdown_filter(text):
return Markup(md.markdown(text))
@app.context_processor
def inject_ip_allowed():
settings = GlobalSettings.query.first()
allowed_hosts_str = settings.allowed_login_hosts if settings and settings.allowed_login_hosts else ""
client_ip = get_real_ip()
return {'is_ip_allowed': is_allowed_ip(client_ip, allowed_hosts_str)}
# TRASY PUBLICZNE
@app.route('/')
@@ -112,19 +140,6 @@ def zbiorka(zbiorka_id):
abort(404)
return render_template('zbiorka.html', zbiorka=zb)
def get_real_ip():
# Cloudflare
if "CF-Connecting-IP" in request.headers:
return request.headers.get("CF-Connecting-IP")
# Nginx proxy (Nginx Proxy Manager / standard reverse proxy)
elif "X-Real-IP" in request.headers:
return request.headers.get("X-Real-IP")
elif "X-Forwarded-For" in request.headers:
forwarded_for = request.headers.get("X-Forwarded-For").split(",")
return forwarded_for[0].strip()
# Fallback
return request.remote_addr
# TRASY LOGOWANIA I REJESTRACJI
@app.route('/login', methods=['GET', 'POST'])
@@ -322,20 +337,46 @@ def create_admin_account():
db.session.add(main_admin)
db.session.commit()
@app.after_request
def add_security_headers(response):
if app.config.get("BLOCK_BOTS", False):
cache_control = app.config.get("CACHE_CONTROL_HEADER")
if cache_control:
response.headers["Cache-Control"] = cache_control
# Jeśli Cache-Control jest ustawiony, usuwamy Pragma
response.headers.pop("Pragma", None)
else:
response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0"
response.headers["Pragma"] = app.config.get("PRAGMA_HEADER", "no-cache")
response.headers["X-Robots-Tag"] = app.config.get("ROBOTS_TAG", "noindex, nofollow, nosnippet, noarchive")
def apply_headers(response):
# Nagłówki niestandardowe
custom_headers = app.config.get("ADD_HEADERS", {})
if isinstance(custom_headers, dict):
for header, value in custom_headers.items():
response.headers[header] = str(value)
# Wykluczenia
if response.status_code in (301, 302, 303, 307, 308):
response.headers.pop("Vary", None)
return response
if 400 <= response.status_code < 500:
response.headers["Cache-Control"] = "no-store"
response.headers["Content-Type"] = "text/html; charset=utf-8"
response.headers.pop("Vary", None)
elif 500 <= response.status_code < 600:
response.headers["Cache-Control"] = "no-store"
response.headers["Content-Type"] = "text/html; charset=utf-8"
response.headers["Retry-After"] = "120"
response.headers.pop("Vary", None)
elif request.path.startswith("/admin"):
response.headers.pop("Vary", None)
response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0"
else:
response.headers["Vary"] = "Cookie, Accept-Encoding"
default_cache = app.config.get("CACHE_CONTROL_HEADER") or "private, max-age=0"
response.headers["Cache-Control"] = default_cache
# Blokowanie botów
if app.config.get("BLOCK_BOTS", False):
cc = app.config.get("CACHE_CONTROL_HEADER") or "no-store, no-cache, must-revalidate, max-age=0"
response.headers["Cache-Control"] = cc
response.headers["X-Robots-Tag"] = app.config.get("ROBOTS_TAG") or "noindex, nofollow, nosnippet, noarchive"
return response
@app.route('/admin/settings', methods=['GET', 'POST'])
@login_required
def admin_settings():
@@ -343,6 +384,7 @@ def admin_settings():
flash('Brak uprawnień do panelu administracyjnego', 'danger')
return redirect(url_for('index'))
client_ip = get_real_ip()
settings = GlobalSettings.query.first()
if request.method == 'POST':
numer_konta = request.form.get('numer_konta')
@@ -365,7 +407,7 @@ def admin_settings():
flash('Ustawienia globalne zostały zaktualizowane', 'success')
return redirect(url_for('admin_dashboard'))
return render_template('admin/settings.html', settings=settings)
return render_template('admin/settings.html', settings=settings, client_ip=client_ip)
@app.route('/admin/zbiorka/oznacz/<int:zbiorka_id>', methods=['POST'])
@login_required
@@ -389,6 +431,7 @@ def robots():
robots_txt = "User-agent: *\nAllow: /"
return robots_txt, 200, {'Content-Type': 'text/plain'}
if __name__ == '__main__':
with app.app_context():
db.create_all()

View File

@@ -23,7 +23,7 @@
<div class="card shadow-sm mb-4">
<div class="card-header bg-secondary text-white d-flex justify-content-between align-items-center">
<h3 class="card-title mb-0">Dozwolone adresy IP</h3>
<button type="button" class="btn btn-sm btn-light text-dark" onclick="dodajMojeIP()">Dodaj moje IP</button>
</div>
<div class="card-body">
<div class="mb-3">
@@ -31,6 +31,7 @@
<textarea class="form-control" id="allowed_login_hosts" name="allowed_login_hosts" rows="4" placeholder="Podaj adresy IP lub nazwy domen oddzielone przecinkami lub nowymi liniami">{{ settings.allowed_login_hosts if settings and settings.allowed_login_hosts else '' }}</textarea>
</div>
<p class="text-muted">Twój aktualny adres IP: <strong>{{ client_ip }}</strong></p>
<button type="button" class="btn btn-sm btn-light text-dark" onclick="dodajMojeIP()">Dodaj moje IP</button>
</div>
</div>

View File

@@ -13,20 +13,34 @@
<nav class="navbar navbar-expand-lg navbar-dark bg-secondary">
<div class="container">
<a class="navbar-brand" href="{{ url_for('index') }}">Zbiórki unitraklub.pl</a>
<div class="collapse navbar-collapse">
<!-- Przycisk rozwijania dla urządzeń mobilnych -->
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNavbar"
aria-controls="mainNavbar" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<!-- Nawigacja ukrywana na małych ekranach -->
<div class="collapse navbar-collapse" id="mainNavbar">
<ul class="navbar-nav ms-auto">
<li class="nav-item"><a class="nav-link" href="{{ url_for('index') }}">Aktualne zbiórki</a></li>
<li class="nav-item"><a class="nav-link" href="{{ url_for('zbiorki_zrealizowane') }}">Zrealizowane zbiórki</a></li>
{% if current_user.is_authenticated %}
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin_dashboard') }}">Panel Admina</a></li>
<li class="nav-item"><a class="nav-link" href="{{ url_for('logout') }}">Wyloguj</a></li>
{% else %}
{% if is_ip_allowed %}
<li class="nav-item"><a class="nav-link" href="{{ url_for('login') }}">Zaloguj</a></li>
{% endif %}
{% endif %}
</ul>
</div>
</div>
</nav>
<div class="container mt-4">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}