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