Skip to content

Refactor ministry + organization into organization unit with tree structure#173

Draft
robbertbos wants to merge 8 commits intomainfrom
refactor-organization
Draft

Refactor ministry + organization into organization unit with tree structure#173
robbertbos wants to merge 8 commits intomainfrom
refactor-organization

Conversation

@robbertbos
Copy link
Member

No description provided.

@robbertbos robbertbos linked an issue Jan 26, 2026 that may be closed by this pull request
@robbertbos robbertbos self-assigned this Jan 26, 2026
@robbertbos robbertbos changed the title Refactor organization Refactor ministry + organization into organization unit with tree structure Jan 26, 2026
Comment on lines +875 to +883
li.innerHTML = `
<span class="org-filter-modal__selected-name">${name}</span>
<button type="button"
class="org-filter-modal__selected-remove"
onclick="removeOrgFromModalSelection(${id})"
aria-label="Verwijder ${name}">
<c-icon icon="kruis" color="grijs-600" size="sm"></c-icon>
</button>
`;

Check failure

Code scanning / CodeQL

DOM text reinterpreted as HTML High

DOM text
is reinterpreted as HTML without escaping meta-characters.

Copilot Autofix

AI 12 days ago

In general, the fix is to avoid interpreting potentially tainted strings as HTML. Instead of using innerHTML with template literals that directly interpolate name and type/id, construct the DOM using safe APIs: set textContent for textual content and set attributes via setAttribute or direct property assignment. If you truly need to insert HTML fragments, sanitize the input first with a robust HTML sanitizer, but in this case everything we build can be pure DOM.

Concretely for this file:

  • Keep using innerHTML only for constant, hardcoded HTML that contains no user data (like the “Geen opdrachtgevers geselecteerd” message).
  • For the selected items, replace:
li.innerHTML = `
  <span class="org-filter-modal__selected-name">${name}</span>
  <button ... onclick="removeOrgTypeFromModalSelection('${type}')" aria-label="Verwijder ${name}">
    <c-icon ...></c-icon>
  </button>
`;

and the similar block for orgs, with explicit DOM construction:

  • Create span elements, set their className and textContent to name.
  • Create button elements, set type, className, aria-label, and attach a click event listener instead of using an onclick string.
  • Create the <c-icon> element and append it to the button.
  • Append the span and button to the li.

This keeps all untrusted data (name, type, id) out of innerHTML and out of string-based event handlers, preventing HTML interpretation and XSS. The changes are all within wies/core/static/js/placement_filter_sidebar.js in the block that updates selectedList around lines 1077–1105, and they require no new imports or external libraries.

Suggested changeset 1
wies/core/static/js/placement_filter_sidebar.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/wies/core/static/js/placement_filter_sidebar.js b/wies/core/static/js/placement_filter_sidebar.js
--- a/wies/core/static/js/placement_filter_sidebar.js
+++ b/wies/core/static/js/placement_filter_sidebar.js
@@ -1078,15 +1078,29 @@
           const li = document.createElement("li");
           li.className = "org-filter-modal__selected-item";
           li.dataset.orgType = type;
-          li.innerHTML = `
-            <span class="org-filter-modal__selected-name">${name}</span>
-            <button type="button"
-                    class="org-filter-modal__selected-remove"
-                    onclick="removeOrgTypeFromModalSelection('${type}')"
-                    aria-label="Verwijder ${name}">
-              <c-icon icon="kruis" color="grijs-600" size="sm"></c-icon>
-            </button>
-          `;
+
+          const nameSpan = document.createElement("span");
+          nameSpan.className = "org-filter-modal__selected-name";
+          nameSpan.textContent = name;
+
+          const removeButton = document.createElement("button");
+          removeButton.type = "button";
+          removeButton.className = "org-filter-modal__selected-remove";
+          removeButton.setAttribute("aria-label", `Verwijder ${name}`);
+          removeButton.addEventListener("click", function () {
+            if (typeof removeOrgTypeFromModalSelection === "function") {
+              removeOrgTypeFromModalSelection(type);
+            }
+          });
+
+          const icon = document.createElement("c-icon");
+          icon.setAttribute("icon", "kruis");
+          icon.setAttribute("color", "grijs-600");
+          icon.setAttribute("size", "sm");
+
+          removeButton.appendChild(icon);
+          li.appendChild(nameSpan);
+          li.appendChild(removeButton);
           selectedList.appendChild(li);
         });
         // Then show individual orgs
@@ -1094,15 +1108,29 @@
           const li = document.createElement("li");
           li.className = "org-filter-modal__selected-item";
           li.dataset.orgId = id;
-          li.innerHTML = `
-            <span class="org-filter-modal__selected-name">${name}</span>
-            <button type="button"
-                    class="org-filter-modal__selected-remove"
-                    onclick="removeOrgFromModalSelection(${id})"
-                    aria-label="Verwijder ${name}">
-              <c-icon icon="kruis" color="grijs-600" size="sm"></c-icon>
-            </button>
-          `;
+
+          const nameSpan = document.createElement("span");
+          nameSpan.className = "org-filter-modal__selected-name";
+          nameSpan.textContent = name;
+
+          const removeButton = document.createElement("button");
+          removeButton.type = "button";
+          removeButton.className = "org-filter-modal__selected-remove";
+          removeButton.setAttribute("aria-label", `Verwijder ${name}`);
+          removeButton.addEventListener("click", function () {
+            if (typeof removeOrgFromModalSelection === "function") {
+              removeOrgFromModalSelection(id);
+            }
+          });
+
+          const icon = document.createElement("c-icon");
+          icon.setAttribute("icon", "kruis");
+          icon.setAttribute("color", "grijs-600");
+          icon.setAttribute("size", "sm");
+
+          removeButton.appendChild(icon);
+          li.appendChild(nameSpan);
+          li.appendChild(removeButton);
           selectedList.appendChild(li);
         });
       }
EOF
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +1097 to +1105
li.innerHTML = `
<span class="org-filter-modal__selected-name">${name}</span>
<button type="button"
class="org-filter-modal__selected-remove"
onclick="removeOrgFromModalSelection(${id})"
aria-label="Verwijder ${name}">
<c-icon icon="kruis" color="grijs-600" size="sm"></c-icon>
</button>
`;

Check failure

Code scanning / CodeQL

DOM text reinterpreted as HTML High

DOM text
is reinterpreted as HTML without escaping meta-characters.
DOM text
is reinterpreted as HTML without escaping meta-characters.

Copilot Autofix

AI 12 days ago

In general, the fix is to avoid inserting untrusted text into the DOM via innerHTML. Instead, create elements and text nodes with document.createElement and textContent/setAttribute, so user-controlled strings are treated strictly as text, not parsed as HTML. For event handlers, use addEventListener instead of inline onclick attributes, so you don’t need to interpolate IDs into HTML strings.

Concretely for this file:

  • Keep the overall structure (UL with LI items, buttons, icon component).
  • Replace li.innerHTML = \ ... ${name} ... ${type/id} ...`` blocks with:
    • Create the <span> and set textContent = name.
    • Create the <button>, set class, type, aria-label via setAttribute.
    • Attach the appropriate click handler with addEventListener("click", ...) instead of onclick HTML.
    • Create the <c-icon> element (custom element) and append it to the button.
  • Ensure we do not rely on innerHTML for the dynamic parts. We can leave the static “empty” message string (Geen opdrachtgevers geselecteerd) as a fixed string assigned to innerHTML since it contains no user data.

All changes are confined to the snippet shown in wies/core/static/js/placement_filter_sidebar.js, around lines 1075–1107. No new external dependencies are needed; we only use standard DOM APIs that are already in use elsewhere in the file.

Suggested changeset 1
wies/core/static/js/placement_filter_sidebar.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/wies/core/static/js/placement_filter_sidebar.js b/wies/core/static/js/placement_filter_sidebar.js
--- a/wies/core/static/js/placement_filter_sidebar.js
+++ b/wies/core/static/js/placement_filter_sidebar.js
@@ -1078,15 +1078,29 @@
           const li = document.createElement("li");
           li.className = "org-filter-modal__selected-item";
           li.dataset.orgType = type;
-          li.innerHTML = `
-            <span class="org-filter-modal__selected-name">${name}</span>
-            <button type="button"
-                    class="org-filter-modal__selected-remove"
-                    onclick="removeOrgTypeFromModalSelection('${type}')"
-                    aria-label="Verwijder ${name}">
-              <c-icon icon="kruis" color="grijs-600" size="sm"></c-icon>
-            </button>
-          `;
+
+          const nameSpan = document.createElement("span");
+          nameSpan.className = "org-filter-modal__selected-name";
+          nameSpan.textContent = name;
+          li.appendChild(nameSpan);
+
+          const removeButton = document.createElement("button");
+          removeButton.type = "button";
+          removeButton.className = "org-filter-modal__selected-remove";
+          removeButton.setAttribute("aria-label", `Verwijder ${name}`);
+          removeButton.addEventListener("click", function () {
+            if (typeof removeOrgTypeFromModalSelection === "function") {
+              removeOrgTypeFromModalSelection(type);
+            }
+          });
+
+          const icon = document.createElement("c-icon");
+          icon.setAttribute("icon", "kruis");
+          icon.setAttribute("color", "grijs-600");
+          icon.setAttribute("size", "sm");
+          removeButton.appendChild(icon);
+
+          li.appendChild(removeButton);
           selectedList.appendChild(li);
         });
         // Then show individual orgs
@@ -1094,15 +1108,29 @@
           const li = document.createElement("li");
           li.className = "org-filter-modal__selected-item";
           li.dataset.orgId = id;
-          li.innerHTML = `
-            <span class="org-filter-modal__selected-name">${name}</span>
-            <button type="button"
-                    class="org-filter-modal__selected-remove"
-                    onclick="removeOrgFromModalSelection(${id})"
-                    aria-label="Verwijder ${name}">
-              <c-icon icon="kruis" color="grijs-600" size="sm"></c-icon>
-            </button>
-          `;
+
+          const nameSpan = document.createElement("span");
+          nameSpan.className = "org-filter-modal__selected-name";
+          nameSpan.textContent = name;
+          li.appendChild(nameSpan);
+
+          const removeButton = document.createElement("button");
+          removeButton.type = "button";
+          removeButton.className = "org-filter-modal__selected-remove";
+          removeButton.setAttribute("aria-label", `Verwijder ${name}`);
+          removeButton.addEventListener("click", function () {
+            if (typeof removeOrgFromModalSelection === "function") {
+              removeOrgFromModalSelection(id);
+            }
+          });
+
+          const icon = document.createElement("c-icon");
+          icon.setAttribute("icon", "kruis");
+          icon.setAttribute("color", "grijs-600");
+          icon.setAttribute("size", "sm");
+          removeButton.appendChild(icon);
+
+          li.appendChild(removeButton);
           selectedList.appendChild(li);
         });
       }
EOF
Copilot is powered by AI and may make mistakes. Always verify output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Layeringsystem for clients

1 participant