feat(active): add the active beers page, run metadata and tap numbers #5
@@ -16,6 +16,26 @@ from flask_wtf import CSRFProtect
|
||||
__all__ = ["create_app"]
|
||||
|
||||
|
||||
#: The Content-Security-Policy the app sends with every response.
|
||||
#:
|
||||
#: The app serves only its own vendored assets — CSS, JavaScript, fonts and
|
||||
#: the logo are all in ``static/`` — so a strict ``'self'`` policy is
|
||||
#: enforceable. There is no ``'unsafe-inline'`` anywhere, which is why the
|
||||
#: templates carry no inline scripts or style attributes.
|
||||
CSP = (
|
||||
"default-src 'self'; "
|
||||
"script-src 'self'; "
|
||||
"style-src 'self'; "
|
||||
"img-src 'self' data:; "
|
||||
"font-src 'self'; "
|
||||
"connect-src 'self'; "
|
||||
"form-action 'self'; "
|
||||
"object-src 'none'; "
|
||||
"frame-ancestors 'none'; "
|
||||
"base-uri 'self'"
|
||||
)
|
||||
|
||||
|
||||
class ImproperlyConfigured(RuntimeError):
|
||||
"""Raised when the app is missing configuration it cannot safely default.
|
||||
|
||||
@@ -121,12 +141,7 @@ def create_app(config: dict | None = None) -> Flask:
|
||||
response.headers.setdefault("X-Content-Type-Options", "nosniff")
|
||||
response.headers.setdefault("X-Frame-Options", "DENY")
|
||||
response.headers.setdefault("Referrer-Policy", "same-origin")
|
||||
response.headers.setdefault(
|
||||
"Content-Security-Policy",
|
||||
"default-src 'self'; script-src 'self'; style-src 'self'; "
|
||||
"img-src 'self' data:; font-src 'self'; connect-src 'self'; "
|
||||
"form-action 'self'; frame-ancestors 'none'; base-uri 'self'",
|
||||
)
|
||||
response.headers.setdefault("Content-Security-Policy", CSP)
|
||||
return response
|
||||
|
||||
@app.errorhandler(404)
|
||||
|
||||
@@ -59,8 +59,14 @@ class BeerRepository:
|
||||
[("beer_run_id", 1), ("date", 1)], name="run_date_1"
|
||||
)
|
||||
except PyMongoError:
|
||||
# An index failure must not stop the app from serving.
|
||||
pass
|
||||
# An index failure must not stop the app from serving, but it
|
||||
# should be visible: without the index the lookup is a collection
|
||||
# scan, which gets slow as the data grows.
|
||||
logger.warning(
|
||||
"Could not create the Mongo indexes; the lookups will be "
|
||||
"collection scans until they exist",
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
# -- users -------------------------------------------------------------- #
|
||||
|
||||
|
||||
@@ -196,7 +196,7 @@ def api_run(run_id: str):
|
||||
"""JSON detail of a single run."""
|
||||
run = repo().run(run_id)
|
||||
if run is None:
|
||||
return {"error": "not found"}, 404
|
||||
return jsonify({"error": "not found"}), 404
|
||||
|
||||
meta = repo().run_metadata(run_id)
|
||||
summary = run_summary(run, og_override=meta.get("og_points"))
|
||||
|
||||
@@ -491,9 +491,13 @@ def test_security_headers_are_set(logged_in):
|
||||
assert csp is not None
|
||||
assert "default-src 'self'" in csp
|
||||
assert "script-src 'self'" in csp
|
||||
assert "object-src 'none'" in csp
|
||||
assert "frame-ancestors 'none'" in csp
|
||||
# No CDN is used, so nothing external should be allowed.
|
||||
assert "https://" not in csp
|
||||
# 'unsafe-inline' would defeat the point of the policy.
|
||||
assert "unsafe-inline" not in csp
|
||||
assert "unsafe-eval" not in csp
|
||||
|
||||
|
||||
def test_security_headers_are_set_on_the_login_page(client):
|
||||
|
||||
Reference in New Issue
Block a user