Skip to content

Commit 74eec5e

Browse files
committed
Further GraphiQL fixes
1 parent a9c6079 commit 74eec5e

3 files changed

Lines changed: 28 additions & 1 deletion

File tree

irrd/server/graphql/graphiql_csp.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import base64
1313
import hashlib
14+
import re
1415
from html.parser import HTMLParser
1516
from typing import NamedTuple
1617

@@ -96,6 +97,25 @@ def handle_endtag(self, tag):
9697
self._capturing = None
9798

9899

100+
def _hoist_importmap(html: str) -> str:
101+
# Ariadne's inline module script imports bare specifiers like `from 'react'`;
102+
# its <script type="importmap"> maps those names to URLs. Per spec the
103+
# importmap must come before any module reference, but ariadne emits
104+
# <link rel="modulepreload"> tags first, so the map is ignored and imports
105+
# fail. Move the importmap to just before the first modulepreload.
106+
# See https://github.com/mirumee/ariadne/issues/1326
107+
match = re.search(r'<script type="importmap">.*?</script>\n', html, re.DOTALL)
108+
if not match or '<link rel="modulepreload"' not in html[: match.start()]:
109+
return html
110+
importmap = match.group(0)
111+
html = html.replace(importmap, "", 1)
112+
return html.replace(
113+
'<link rel="modulepreload"',
114+
importmap + ' <link rel="modulepreload"',
115+
1,
116+
)
117+
118+
99119
def _inject_sri(html: str) -> str:
100120
# ariadne already emits `crossorigin="anonymous"` on its external loads;
101121
# we add `integrity=` so SRI verification runs. If ariadne ever changes
@@ -119,6 +139,7 @@ def build_explorer() -> GraphiQLExplorerBuild:
119139
import time so the failure is loud.
120140
"""
121141
explorer = ExplorerGraphiQL(title="IRRD GraphQL", explorer_plugin=True)
142+
explorer.parsed_html = _hoist_importmap(explorer.parsed_html)
122143
explorer.parsed_html = _inject_sri(explorer.parsed_html)
123144

124145
scan = _ExplorerScan()

irrd/server/graphql/tests/test_graphiql_csp.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ def test_inject_sri_silent_noop_fails(self, monkeypatch):
3434
with pytest.raises(RuntimeError, match="integrity"):
3535
build_explorer()
3636

37+
def test_hoist_importmap_noop_when_already_correct(self):
38+
html = '<script type="importmap"></script>\n<link rel="modulepreload" />'
39+
assert graphiql_csp._hoist_importmap(html) == html
40+
3741
def test_explorer_scan_captures_script_src(self):
3842
# ariadne 1.1.0 emits ESM via <link rel=modulepreload>, but _ExplorerScan
3943
# still recognises <script src=...> as defense if ariadne ever reverts.

irrd/server/http/app.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,9 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
209209
# (/graphql, /v1/*), this header must be changed to cross-origin for those routes,
210210
# otherwise CORP will override CORS and block cross-origin browser clients.
211211
b"cross-origin-resource-policy": b"same-origin",
212-
b"cross-origin-embedder-policy": b"require-corp",
212+
# Cross-Origin-Embedder-Policy is intentionally not set: esm.sh, which
213+
# serves the GraphiQL bundles, does not send Cross-Origin-Resource-Policy,
214+
# so `require-corp` would block GraphiQL.
213215
b"x-permitted-cross-domain-policies": b"none",
214216
}
215217

0 commit comments

Comments
 (0)