diff --git a/app/__init__.py b/app/__init__.py index 85896f4..eb5032a 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -9,21 +9,53 @@ from .config import Config THEMES = {name: {} for name in ("bootstrap", "flatly", "darkly", "pulse")} +class RemoveVaryCookieMiddleware: + def __init__(self, app): + self.app = app + + def __call__(self, environ, start_response): + path = environ.get("PATH_INFO", "") + + def custom_start_response(status, headers, exc_info=None): + if path == "/favicon.ico" or path.endswith((".css", ".js")): + result, vary_values = [], [] + + for name, value in headers: + if name.lower() == "vary": + vary_values.extend(item.strip() for item in value.split(",") if item.strip()) + else: + result.append((name, value)) + + vary_values = [value for value in vary_values if value.lower() != "cookie"] + + if vary_values: + result.append(("Vary", ", ".join(dict.fromkeys(vary_values)))) + + headers = result + + return start_response(status, headers, exc_info) + + return self.app(environ, custom_start_response) + + def _static_hashes(app): static_root = Path(app.static_folder) hashes = {} + for path in static_root.rglob("*"): if path.is_file(): relative = path.relative_to(static_root).as_posix() hashes[relative] = hashlib.md5(path.read_bytes()).hexdigest() + return hashes + def _themes(app): return { - "bootstrap": {"label":"Bootstrap", "url":app.config["THEME_BOOTSTRAP_URL"], "mode":"light"}, - "flatly": {"label":"Flatly", "url":app.config["THEME_FLATLY_URL"], "mode":"light"}, - "darkly": {"label":"Ciemny", "url":app.config["THEME_BOOTSTRAP_URL"], "mode":"dark"}, - "pulse": {"label":"Pulse", "url":app.config["THEME_PULSE_URL"], "mode":"light"}, + "bootstrap": {"label": "Bootstrap", "url": app.config["THEME_BOOTSTRAP_URL"], "mode": "light"}, + "flatly": {"label": "Flatly", "url": app.config["THEME_FLATLY_URL"], "mode": "light"}, + "darkly": {"label": "Ciemny", "url": app.config["THEME_BOOTSTRAP_URL"], "mode": "dark"}, + "pulse": {"label": "Pulse", "url": app.config["THEME_PULSE_URL"], "mode": "light"}, } @@ -31,14 +63,19 @@ def create_app(test_config=None): app = Flask(__name__) app.config.from_object(Config) if test_config: app.config.update(test_config) + db.init_app(app); login_manager.init_app(app) from .database import prepare_database prepare_database(app) - from .auth import auth; from .main import main + + from .auth import auth + from .main import main from .api import api from .openapi import docs - app.register_blueprint(auth); app.register_blueprint(main); app.register_blueprint(api); app.register_blueprint(docs) + + app.register_blueprint(auth); app.register_blueprint(main) + app.register_blueprint(api); app.register_blueprint(docs) static_hashes = _static_hashes(app) @@ -50,19 +87,17 @@ def create_app(test_config=None): @app.after_request def set_cache_headers(response): + if request.path == "/favicon.ico": + response.headers.pop("Cache-Control", None) + response.headers.pop("Content-Disposition", None) + return response + if request.endpoint == "static": - filename = ( - request.view_args.get("filename", "") - if request.view_args - else "" - ) + filename = request.view_args.get("filename", "") if request.view_args else "" if filename.endswith((".css", ".js")): - response.headers["Cache-Control"] = ( - "public, max-age=3600, immutable" - ) + response.headers["Cache-Control"] = "public, max-age=3600, immutable" response.headers.pop("Content-Disposition", None) - response.headers.pop("Vary", None) elif response.mimetype == "text/html": response.headers["Cache-Control"] = "private, no-store" @@ -76,8 +111,8 @@ def create_app(test_config=None): def load_user_from_request(req): header = req.headers.get("Authorization", "") scheme, _, token = header.partition(" ") - if scheme.lower() != "bearer" or not token.strip(): - return None + if scheme.lower() != "bearer" or not token.strip(): return None + from .api_tokens import load_access_token return load_access_token(token.strip()) @@ -85,6 +120,7 @@ def create_app(test_config=None): def unauthorized(): if request.path.startswith("/api/"): return {"ok": False, "message": "Wymagane uwierzytelnienie Bearer lub aktywna sesja"}, 401 + from flask import redirect return redirect(url_for("auth.login", next=request.full_path.rstrip("?"))) @@ -93,33 +129,44 @@ def create_app(test_config=None): themes = _themes(app) record = db.session.get(AppSetting, "theme") theme_name = record.value if record and record.value in themes else "bootstrap" - return {"app_theme": themes[theme_name], "app_theme_name": theme_name, "themes": themes, - "asset_urls": {"bootstrap_js": app.config["BOOTSTRAP_JS_URL"], "choices_css": app.config["CHOICES_CSS_URL"], - "choices_js": app.config["CHOICES_JS_URL"], "chart_js": app.config["CHART_JS_URL"]}} + + return { + "app_theme": themes[theme_name], + "app_theme_name": theme_name, + "themes": themes, + "asset_urls": { + "bootstrap_js": app.config["BOOTSTRAP_JS_URL"], + "choices_css": app.config["CHOICES_CSS_URL"], + "choices_js": app.config["CHOICES_JS_URL"], + "chart_js": app.config["CHART_JS_URL"], + }, + } @app.get("/health") - def health(): return {"status":"ok"} + def health(): return {"status": "ok"} @app.get("/favicon.ico") - def favicon(): return "", 204 + def favicon(): return app.response_class(status=204) @app.cli.command("reset-admin-password") @click.option("--email", required=True, help="Adres e-mail konta administratora.") @click.option("--password", prompt=True, hide_input=True, confirmation_prompt=True, help="Nowe hasło. Bez tej opcji polecenie poprosi o hasło bez wyświetlania go.") def reset_admin_password(email, password): - """Resetuje hasło istniejącego administratora.""" normalized_email = email.strip().lower() user = User.query.filter_by(email=normalized_email).first() + if not user: raise click.ClickException("Nie znaleziono użytkownika o podanym adresie e-mail.") if user.role != "admin": raise click.ClickException("Wskazany użytkownik nie ma roli administratora.") if len(password) < 8: raise click.ClickException("Hasło musi mieć co najmniej 8 znaków.") + user.set_password(password) user.active = True db.session.commit() click.echo(f"Zresetowano hasło administratora: {normalized_email}") + app.wsgi_app = RemoveVaryCookieMiddleware(app.wsgi_app) return app