Repository: jazzband/django-debug-toolbar

Issue:
Refused to get unsafe header "djdt-store-id"
We're getting instances of this error in the javascript console while using the toolbar in local development.

This stems from a CORS issue where only certain headers are only allowed to be accessed unless whitelisted.  In 997387228ecb4e0787568047237ca0455dfa9ec6 this code was added:

```javascript
    update_on_ajax() {
        const sidebar_url =
            document.getElementById("djDebug").dataset.sidebarUrl;
        const slowjax = debounce(ajax, 200);

        const origOpen = XMLHttpRequest.prototype.open;
        XMLHttpRequest.prototype.open = function () {
            this.addEventListener("load", function () {
                let store_id = this.getResponseHeader("djdt-store-id");
                if (store_id !== null) {
                    store_id = encodeURIComponent(store_id);
                    const dest = `${sidebar_url}?store_id=${store_id}`;
                    slowjax(dest).then(function (data) {
                        replaceToolbarState(store_id, data);
                    });
                }
            });
            origOpen.apply(this, arguments);
        };
    },
```

Since it's patching `XMLHttpRequest.open` to attempt to examine all ajax requests, this gets triggered when making requests to things that don't allow `djdt-store-id` access (and shouldn't, because they're owned by third parties and don't know anything about this tool).  This doesn't appear to functionally break anything, but it adds confusing error messages.

I think the answer is to squash the error in this line: `let store_id = this.getResponseHeader("djdt-store-id");`.  It's not clear to me how to do this, though, since [the docs](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/getResponseHeader) don't mention any possibility of this function doing anything other than returning a result or returning null, and my searching only finds discussions of how to fix the problem when you own the other side of the requests or generally actually need to access this header.

(Also kinda sorta related to #1399, which is about documentation of CORS issues.)


Discussion:
First, is this just logged as an error or does it actually break anything? If it's the former we could add a banner (using `console.log` or something) on startup which tells people that this may happen and that people can just ignore it. If it breaks anything else it should be fixed. I assume that we do not get an exception or something there which we could catch and ignore though, so a solution may be a bit involved. Since all panels return JSON these days (not HTML) it should be relatively straightforward to insert the store ID as a field instead of sending it along as a header.

Or maybe we should just tell people how to configure their CORS headers so that this doesn't happen.
It's just a logged error; as far as I can tell it doesn't break anything, just is confusing for developers and masks real errors.

It's not something that can be fixed by configuring CORS headers, unless you convince the entire internet to add `djdt-store-id` as a whitelisted header just in case someone happens to be using their site in a piece of software that's also using django-debug-toolbar.  These are _third-party_ requests that **shouldn't** be having any non-standard headers accessed, by design; the only reason this is happening is because the toolbar is (as befits a debug tool) aggressive about trying to inspect everything, even things that normally wouldn't be in the app's direct visibility.
This appears to be an issue with Chromium. It's not an error, but an uncatchable warning. [See this blog post for more info.](https://trackjs.com/blog/refused-unsafe-header/)

The solution is:

```javascript
  if (xhr.getAllResponseHeaders().indexOf("djdt-store-id) >= 0) {
    store_id = xhr.getResponseHeader("djdt-store-id");
  }
```

I'll write up a PR

Resolving patch:
diff --git a/debug_toolbar/panels/history/panel.py b/debug_toolbar/panels/history/panel.py
--- a/debug_toolbar/panels/history/panel.py
+++ b/debug_toolbar/panels/history/panel.py
@@ -24,7 +24,7 @@ def get_headers(self, request):
         observe_request = self.toolbar.get_observe_request()
         store_id = getattr(self.toolbar, "store_id")
         if store_id and observe_request(request):
-            headers["DJDT-STORE-ID"] = store_id
+            headers["djdt-store-id"] = store_id
         return headers
 
     @property


Test patch:
diff --git a/tests/panels/test_history.py b/tests/panels/test_history.py
--- a/tests/panels/test_history.py
+++ b/tests/panels/test_history.py
@@ -99,6 +99,20 @@ def test_history_sidebar_invalid(self):
         response = self.client.get(reverse("djdt:history_sidebar"))
         self.assertEqual(response.status_code, 400)
 
+    def test_history_headers(self):
+        """Validate the headers injected from the history panel."""
+        response = self.client.get("/json_view/")
+        store_id = list(DebugToolbar._store)[0]
+        self.assertEqual(response.headers["djdt-store-id"], store_id)
+
+    @override_settings(
+        DEBUG_TOOLBAR_CONFIG={"OBSERVE_REQUEST_CALLBACK": lambda request: False}
+    )
+    def test_history_headers_unobserved(self):
+        """Validate the headers aren't injected from the history panel."""
+        response = self.client.get("/json_view/")
+        self.assertNotIn("djdt-store-id", response.headers)
+
     def test_history_sidebar(self):
         """Validate the history sidebar view."""
         self.client.get("/json_view/")