ux i nowa funkcja blokady blednych logowan

This commit is contained in:
Mateusz Gruszczyński
2025-07-05 15:13:18 +02:00
parent d0b6d939c0
commit 11617755fb
4 changed files with 159 additions and 46 deletions

81
app.py
View File

@ -3,7 +3,7 @@ import secrets
import time
import mimetypes
from datetime import datetime, timedelta
from flask import Flask, render_template, redirect, url_for, request, flash, Blueprint, send_from_directory, request
from flask import Flask, render_template, redirect, url_for, request, flash, Blueprint, send_from_directory, request, abort
from markupsafe import Markup
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
@ -14,6 +14,7 @@ from PIL import Image
from werkzeug.utils import secure_filename
from werkzeug.middleware.proxy_fix import ProxyFix
from sqlalchemy import func, extract
from collections import defaultdict, deque
app = Flask(__name__)
app.config.from_object(Config)
@ -27,6 +28,10 @@ AUTHORIZED_COOKIE_VALUE = app.config.get('AUTHORIZED_COOKIE_VALUE', '80d31cdfe63
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
failed_login_attempts = defaultdict(deque)
MAX_ATTEMPTS = 10
TIME_WINDOW = 60 * 60
db = SQLAlchemy(app)
socketio = SocketIO(app)
login_manager = LoginManager(app)
@ -73,6 +78,26 @@ class Expense(db.Model):
added_at = db.Column(db.DateTime, default=datetime.utcnow)
receipt_filename = db.Column(db.String(255), nullable=True)
with app.app_context():
# Twój kod inicjalizacyjny, np. utworzenie konta admina
db.create_all()
from werkzeug.security import generate_password_hash
admin = User.query.filter_by(is_admin=True).first()
username = app.config.get('DEFAULT_ADMIN_USERNAME', 'admin')
password = app.config.get('DEFAULT_ADMIN_PASSWORD', 'admin123')
password_hash = generate_password_hash(password)
if admin:
# Aktualizacja jeśli dane się różnią
if admin.username != username or not check_password_hash(admin.password_hash, password):
admin.username = username
admin.password_hash = password_hash
db.session.commit()
else:
# Brak admina utwórz nowe konto
admin = User(username=username, password_hash=password_hash, is_admin=True)
db.session.add(admin)
db.session.commit()
@static_bp.route('/static/js/<path:filename>')
def serve_js(filename):
response = send_from_directory('static/js', filename)
@ -108,6 +133,30 @@ def get_progress(list_id):
percent = (purchased_count / total_count * 100) if total_count > 0 else 0
return purchased_count, total_count, percent
# zabezpieczenie logowani do systemy - błędne hasła
def is_ip_blocked(ip):
now = time.time()
attempts = failed_login_attempts[ip]
while attempts and now - attempts[0] > TIME_WINDOW:
attempts.popleft()
return len(attempts) >= MAX_ATTEMPTS
def register_failed_attempt(ip):
now = time.time()
attempts = failed_login_attempts[ip]
while attempts and now - attempts[0] > TIME_WINDOW:
attempts.popleft()
attempts.append(now)
def reset_failed_attempts(ip):
failed_login_attempts[ip].clear()
def attempts_remaining(ip):
attempts = failed_login_attempts[ip]
return max(0, MAX_ATTEMPTS - len(attempts))
####################################################
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
@ -158,6 +207,10 @@ def filesizeformat_filter(path):
def page_not_found(e):
return render_template('404.html'), 404
@app.errorhandler(403)
def forbidden(e):
return '403 Forbidden', 403
@app.route('/favicon.svg')
def favicon():
svg = '''
@ -202,25 +255,27 @@ def index_guest():
@app.route('/system-auth', methods=['GET', 'POST'])
def system_auth():
ip = request.remote_addr
next_page = request.args.get('next') or url_for('index_guest')
if is_ip_blocked(ip):
flash('Przekroczono limit prób logowania. Dostęp zablokowany na 1 godzinę.', 'danger')
return render_template('system_auth.html'), 403
if request.method == 'POST':
if request.form['password'] == SYSTEM_PASSWORD:
db.create_all()
if not User.query.filter_by(is_admin=True).first():
admin_user = User(
username=DEFAULT_ADMIN_USERNAME,
password_hash=generate_password_hash(DEFAULT_ADMIN_PASSWORD),
is_admin=True
)
db.session.add(admin_user)
db.session.commit()
flash(f'Utworzono konto administratora: login={DEFAULT_ADMIN_USERNAME}, hasło={DEFAULT_ADMIN_PASSWORD}')
reset_failed_attempts(ip)
resp = redirect(next_page)
resp.set_cookie('authorized', AUTHORIZED_COOKIE_VALUE)
return resp
flash('Nieprawidłowe hasło do systemu','danger')
else:
register_failed_attempt(ip)
if is_ip_blocked(ip):
flash('Przekroczono limit prób logowania. Dostęp zablokowany na 1 godzinę.', 'danger')
return render_template('system_auth.html'), 403
remaining = attempts_remaining(ip)
flash(f'Nieprawidłowe hasło do systemu. Pozostało prób: {remaining}', 'warning')
return render_template('system_auth.html')
@app.route('/archive_my_list/<int:list_id>')

View File

@ -8,4 +8,4 @@ class Config:
DEFAULT_ADMIN_USERNAME = os.environ.get('DEFAULT_ADMIN_USERNAME', 'admin')
DEFAULT_ADMIN_PASSWORD = os.environ.get('DEFAULT_ADMIN_PASSWORD', 'admin123')
UPLOAD_FOLDER = os.environ.get('UPLOAD_FOLDER', 'uploads')
AUTHORIZED_COOKIE_VALUE = os.environ.get('AUTHORIZED_COOKIE_VALUE', 'cookievalue')
AUTHORIZED_COOKIE_VALUE = os.environ.get('AUTHORIZED_COOKIE_VALUE', 'cookievalue')

View File

@ -1,31 +1,84 @@
.large-checkbox {
width: 1.5em;
height: 1.5em;
}
.clickable-item {
cursor: pointer;
}
.bg-success {
background-color: #1e7e34 !important;
}
.bg-light {
background-color: #2c2f33 !important;
}
/* --- Rozmiary i kursory --- */
.large-checkbox {
width: 1.5em;
height: 1.5em;
}
.clickable-item {
cursor: pointer;
}
input[type="file"]::file-selector-button {
background-color: #127429;
color: #fff;
border: none;
padding: 0.5em 1em;
border-radius: 4px;
font-weight: bold;
cursor: pointer;
transition: background 0.2s;
}
/* --- Kolory tła (nadpisane klasy Bootstrapa) --- */
.bg-success {
background-color: #1e7e34 !important;
}
.bg-light {
background-color: #2c2f33 !important;
}
.bg-success {
background-color: #1e7e34 !important;
}
.bg-light {
background-color: #2c2f33 !important;
}
/* --- Styl przycisku wyboru pliku --- */
input[type="file"]::file-selector-button {
background-color: #127429;
color: #fff;
border: none;
padding: 0.5em 1em;
border-radius: 4px;
font-weight: bold;
cursor: pointer;
transition: background 0.2s;
}
/* --- Ciemniejsze alerty Bootstrapa --- */
.alert-success {
background-color: #225d36 !important;
color: #eaffea !important;
border-color: #174428 !important;
}
.alert-danger {
background-color: #7a1f23 !important;
color: #ffeaea !important;
border-color: #531417 !important;
}
.alert-info {
background-color: #1d3a4d !important;
color: #eaf6ff !important;
border-color: #152837 !important;
}
.alert-warning {
background-color: #665c1e !important;
color: #fffbe5 !important;
border-color: #4d4415 !important;
}
/* Badge - kolory pasujące do ciemnych alertów */
.badge.bg-success, .badge.text-bg-success {
background-color: #225d36 !important; /* jak alert-success */
color: #eaffea !important;
}
.badge.bg-danger, .badge.text-bg-danger {
background-color: #7a1f23 !important; /* jak alert-danger */
color: #ffeaea !important;
}
.badge.bg-info, .badge.text-bg-info {
background-color: #1d3a4d !important; /* jak alert-info */
color: #eaf6ff !important;
}
.badge.bg-warning, .badge.text-bg-warning {
background-color: #665c1e !important; /* jak alert-warning */
color: #fffbe5 !important;
}
.badge.bg-secondary, .badge.text-bg-secondary {
background-color: #343a40 !important; /* ciemny szary */
color: #e2e3e5 !important;
}
.badge.bg-primary, .badge.text-bg-primary {
background-color: #184076 !important; /* ciemny niebieski */
color: #e6f0ff !important;
}
.badge.bg-light, .badge.text-bg-light {
background-color: #444950 !important; /* ciemniejszy jasny */
color: #f8f9fa !important;
}
.badge.bg-dark, .badge.text-bg-dark {
background-color: #181a1b !important; /* bardzo ciemny */
color: #f8f9fa !important;
}

View File

@ -21,13 +21,18 @@
{% if has_authorized_cookie %}
{% if current_user.is_authenticated %}
<span class="mx-auto text-white">Zalogowany jako: <strong>{{ current_user.username }}</strong></span>
<span class="mx-auto text-white">
Zalogowany jako:
<span class="badge bg-success">{{ current_user.username }}</span>
</span>
{% else %}
<span class="mx-auto text-white">Przeglądasz jako <strong>gość</strong></span>
<span class="mx-auto text-white">
Przeglądasz jako
<span class="badge bg-info">gość</span>
</span>
{% endif %}
{% endif %}
<div class="d-flex align-items-center gap-2">
{% if current_user.is_authenticated and current_user.is_admin %}
<a href="{{ url_for('admin_panel') }}" class="btn btn-outline-warning btn-sm">⚙️ Panel admina</a>