agnes-the-ai-analyst/app/web/templates/base_login.html
Vojtech Rysanek 58001af27d fix(web): address PR #372 review — meta charset ordering + enabled coercion
Two issues raised by @minasarustamyan:

1. head_start loop rendered BEFORE <meta charset="UTF-8"> in both
   base.html and base_login.html. HTML5 requires the charset declaration
   within the first 1024 bytes; a long operator-injected snippet could
   push it past that window, dropping browsers into locale-default
   encoding (historically a UTF-7 charset-confusion XSS vector). Move
   the head_start loop after charset + viewport meta tags — vendor
   hooks (GTM dataLayer init, etc.) still install before any CSS/JS, the
   two required meta tags just come first.

2. `entry.get("enabled") is False` matched only the Python False
   singleton — `enabled: "false"` (quoted YAML string), `enabled: 0`,
   `enabled: "no"` etc. all slipped through (bool("false") == True in
   Python). For what is meant to be a kill switch on admin-injected JS,
   an operator who fat-fingers the quoting would silently leave the
   snippet live. Add `_custom_script_enabled()` coercion helper that
   honours quoted booleans, numeric 0, and the usual `no`/`off`/`""`
   variants. Default-on for missing / None to preserve the
   default-enabled field semantics.

13 new parametrized tests cover every YAML truthy-shape the operator
might paste. Render test now asserts head_start lands after both
required <meta> tags.
2026-05-21 13:59:11 +04:00

42 lines
1.7 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
{# HTML5 requires <meta charset> within the first 1024 bytes; any
operator-injected snippet must come AFTER charset + viewport. See
base.html for the full rationale. #}
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{# Operator-injected scripts (placement=head_start). Mirrors base.html
so login/auth pages surface custom_scripts too. #}
{% for s in custom_scripts | default([]) if s.placement == 'head_start' %}
{{ s.html | safe }}
{% endfor %}
<title>{% block title %}Data Analyst Portal{% endblock %}</title>
<link rel="stylesheet" href="{{ static_url('style-custom.css') }}">
{% include '_theme.html' %}
{# Operator-injected scripts (placement=head_end). Mirrors base.html. #}
{% for s in custom_scripts | default([]) if s.placement == 'head_end' %}
{{ s.html | safe }}
{% endfor %}
</head>
<body>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="flash-messages" style="position: fixed; top: 20px; left: 50%; transform: translateX(-50%); z-index: 1000; max-width: 500px;">
{% for category, message in messages %}
<div class="flash flash-{{ category }}">
{{ message }}
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
{% include "_version_badge.html" %}
{# Operator-injected scripts (placement=body_end). Mirrors base.html. #}
{% for s in custom_scripts | default([]) if s.placement == 'body_end' %}
{{ s.html | safe }}
{% endfor %}
</body>
</html>