diff --git a/binderhub/base.py b/binderhub/base.py
index b002bbd95..2d91ef0c9 100644
--- a/binderhub/base.py
+++ b/binderhub/base.py
@@ -137,11 +137,19 @@ def get_current_user(self):
@property
def template_namespace(self):
- return dict(
+
+ ns = dict(
static_url=self.static_url,
banner=self.settings["banner_message"],
- **self.settings.get("template_variables", {}),
+ auth_enabled=self.settings["auth_enabled"],
+ )
+ if self.settings["auth_enabled"]:
+ ns["api_token"] = self.hub_auth.get_token(self) or ""
+
+ ns.update(
+ self.settings.get("template_variables", {}),
)
+ return ns
def set_default_headers(self):
headers = self.settings.get("headers", {})
diff --git a/binderhub/builder.py b/binderhub/builder.py
index ca1deafbe..c97704726 100644
--- a/binderhub/builder.py
+++ b/binderhub/builder.py
@@ -247,6 +247,10 @@ def _get_build_only(self):
return build_only
+ def redirect(self, *args, **kwargs):
+ # disable redirect to login, which won't work for EventSource
+ raise HTTPError(403)
+
@authenticated
async def get(self, provider_prefix, _unescaped_spec):
"""Get a built image for a given spec and repo provider.
diff --git a/binderhub/static/js/index.js b/binderhub/static/js/index.js
index 536a4e278..d677e78ae 100644
--- a/binderhub/static/js/index.js
+++ b/binderhub/static/js/index.js
@@ -1,7 +1,6 @@
/* If this file gets over 200 lines of code long (not counting docs / comments), start using a framework
*/
import ClipboardJS from "clipboard";
-import "event-source-polyfill";
import { BinderRepository } from "@jupyterhub/binderhub-client";
import { updatePathText } from "./src/path";
@@ -61,11 +60,14 @@ async function build(providerSpec, log, fitAddon, path, pathType) {
$(".on-build").removeClass("hidden");
const buildToken = $("#build-token").data("token");
+ let apiToken = $("#api-token").data("token");
const buildEndpointUrl = new URL("build", BASE_URL);
const image = new BinderRepository(
providerSpec,
buildEndpointUrl,
buildToken,
+ false,
+ { apiToken },
);
for await (const data of image.fetch()) {
diff --git a/binderhub/templates/index.html b/binderhub/templates/index.html
index 6260c9c9d..be2235260 100644
--- a/binderhub/templates/index.html
+++ b/binderhub/templates/index.html
@@ -3,6 +3,7 @@
{% block head %}
+
{{ super() }}
{% endblock head %}
diff --git a/binderhub/templates/loading.html b/binderhub/templates/loading.html
index dd6369ef2..16abb8199 100644
--- a/binderhub/templates/loading.html
+++ b/binderhub/templates/loading.html
@@ -14,6 +14,7 @@
+
{{ super() }}
diff --git a/js/packages/binderhub-client/lib/index.js b/js/packages/binderhub-client/lib/index.js
index 9401eea7f..c9a622cc5 100644
--- a/js/packages/binderhub-client/lib/index.js
+++ b/js/packages/binderhub-client/lib/index.js
@@ -1,9 +1,21 @@
-import { NativeEventSource, EventSourcePolyfill } from "event-source-polyfill";
+import { fetchEventSource } from "@microsoft/fetch-event-source";
import { EventIterator } from "event-iterator";
-// Use native browser EventSource if available, and use the polyfill if not available
-const EventSource = NativeEventSource || EventSourcePolyfill;
-
+function _getXSRFToken() {
+ // from @jupyterlab/services
+ let cookie = "";
+ try {
+ cookie = document.cookie;
+ } catch (e) {
+ // e.g. SecurityError in case of CSP Sandbox
+ return null;
+ }
+ const xsrfTokenMatch = cookie.match("\\b_xsrf=([^;]*)\\b");
+ if (xsrfTokenMatch) {
+ return xsrfTokenMatch[1];
+ }
+ return null;
+}
/**
* Build (and optionally launch) a repository by talking to a BinderHub API endpoint
*/
@@ -14,8 +26,15 @@ export class BinderRepository {
* @param {URL} buildEndpointUrl API URL of the build endpoint to talk to
* @param {string} [buildToken] Optional JWT based build token if this binderhub installation requires using build tokens
* @param {boolean} [buildOnly] Opt out of launching built image by default by passing `build_only` param
+ * @param {string} [apiToken] Optional Bearer token for authenticating requests
*/
- constructor(providerSpec, buildEndpointUrl, buildToken, buildOnly) {
+ constructor(
+ providerSpec,
+ buildEndpointUrl,
+ buildToken,
+ buildOnly,
+ { apiToken },
+ ) {
this.providerSpec = providerSpec;
// Make sure that buildEndpointUrl is a real URL - this ensures hostname is properly set
if (!(buildEndpointUrl instanceof URL)) {
@@ -40,6 +59,7 @@ export class BinderRepository {
if (buildOnly) {
this.buildUrl.searchParams.append("build_only", "true");
}
+ this.apiToken = apiToken;
this.eventIteratorQueue = null;
}
@@ -67,26 +87,37 @@ export class BinderRepository {
* @returns {AsyncIterable} An async iterator yielding responses from the API as they come in
*/
fetch() {
- this.eventSource = new EventSource(this.buildUrl);
- return new EventIterator((queue) => {
+ const headers = {};
+ if (this.apiToken && this.apiToken.length > 0) {
+ headers["Authorization"] = `Bearer ${this.apiToken}`;
+ } else {
+ const xsrf = _getXSRFToken();
+ if (xsrf) {
+ headers["X-Xsrftoken"] = xsrf;
+ }
+ }
+ return new EventIterator(async (queue) => {
this.eventIteratorQueue = queue;
- this.eventSource.onerror = () => {
- queue.push({
- phase: "failed",
- message: "Failed to connect to event stream\n",
- });
- queue.stop();
- };
-
- this.eventSource.addEventListener("message", (event) => {
- // console.log("message received")
- // console.log(event)
- const data = JSON.parse(event.data);
- // FIXME: fix case of phase/state upstream
- if (data.phase) {
- data.phase = data.phase.toLowerCase();
- }
- queue.push(data);
+ await fetchEventSource(this.buildUrl, {
+ headers,
+ onerror: () => {
+ queue.push({
+ phase: "failed",
+ message: "Failed to connect to event stream\n",
+ });
+ queue.stop();
+ },
+
+ onmessage: (event) => {
+ // console.log("message received")
+ // console.log(event)
+ const data = JSON.parse(event.data);
+ // FIXME: fix case of phase/state upstream
+ if (data.phase) {
+ data.phase = data.phase.toLowerCase();
+ }
+ queue.push(data);
+ },
});
});
}
diff --git a/js/packages/binderhub-client/package.json b/js/packages/binderhub-client/package.json
index b2ab42c3c..db3a2ec7c 100644
--- a/js/packages/binderhub-client/package.json
+++ b/js/packages/binderhub-client/package.json
@@ -14,7 +14,7 @@
},
"homepage": "https://github.com/jupyterhub/binderhub#readme",
"dependencies": {
- "event-source-polyfill": "^1.0.31",
+ "@microsoft/fetch-event-source": "^2.0.1",
"event-iterator": "^2.0.0"
}
}
diff --git a/package.json b/package.json
index e08b94bbc..9281c6407 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,6 @@
"dependencies": {
"bootstrap": "^3.4.1",
"clipboard": "^2.0.11",
- "event-source-polyfill": "^1.0.31",
"jquery": "^3.6.4",
"xterm": "^5.1.0",
"xterm-addon-fit": "^0.7.0"