GET /desktop — cookie-gated noVNC viewer page.
Reads the signed lablink_session cookie minted by /api/request_vm,
looks up the assigned VM by session_id, and renders the noVNC viewer
configured from the persisted browser_ws_url and browser_credential.
If the cookie is missing, invalid, or the bound VM is no longer
running, redirect to / so the student can submit their email again.
Attributes
VIEWER_TUNING_QS = '&idle_disconnect=1440&kasmvnc_mode_preference=-1029%7C-1026%7C-1028%7C-1027%7C-1025'
module-attribute
bp = Blueprint('desktop', __name__)
module-attribute
Classes
Functions:
desktop()
The participant desktop.
Auth: The signed lablink_session cookie.
Renders the noVNC viewer page. Reads the cookie minted by
POST /api/request_vm, looks up the assigned VM by session_id, and
configures the viewer from the persisted browser_ws_url and
browser_credential. If the cookie is missing, invalid, or the bound VM
is no longer running, it redirects to / so the participant can submit
their email again.
Source code in packages/allocator/src/lablink_allocator_service/routes/desktop.py
| @bp.route("/desktop")
def desktop():
"""The participant desktop.
Auth: The signed `lablink_session` cookie.
Renders the noVNC viewer page. Reads the cookie minted by
`POST /api/request_vm`, looks up the assigned VM by `session_id`, and
configures the viewer from the persisted `browser_ws_url` and
`browser_credential`. If the cookie is missing, invalid, or the bound VM
is no longer running, it redirects to `/` so the participant can submit
their email again.
"""
raw_cookie = request.cookies.get("lablink_session")
if not raw_cookie:
return redirect("/", code=302)
pool = current_app.config["DB_POOL"]
conn = pool.getconn()
try:
secret = get_or_create_cookie_secret(conn)
try:
payload = verify(raw_cookie, secret=secret)
except InvalidSignature:
return redirect("/", code=302)
session_id, _, suffix = payload.partition(":")
table = sql.Identifier(current_app.config["VM_TABLE_NAME"])
with conn.cursor() as cur:
cur.execute(
sql.SQL(
"SELECT browser_ws_url, browser_credential, hostname "
"FROM {table} "
"WHERE sessionid = %s AND status = 'running'"
).format(table=table),
(session_id,),
)
row = cur.fetchone()
finally:
pool.putconn(conn)
if row is None or row[0] is None:
return redirect("/", code=302)
ws_url, credential, hostname = row
view_only = suffix == "view_only"
if suffix == "admin_session":
return _render_admin_session_page(ws_url, hostname)
# Proxied path (relative, server-side credential). reconnect=1 makes the
# Kasm noVNC viewer retry after an unexpected drop (reconnect_delay ms
# between attempts) instead of stranding the participant on a dead
# "Disconnected" screen — transient WS kills are a fact of life on every
# connectivity path (Cloudflare 524 churn, campus middleboxes, laptop
# sleep). Deliberate disconnects don't loop: the viewer arms reconnect
# only after a successful connect and disarms it on user Disconnect.
if ws_url.startswith("proxy/"):
vo_qs = "&view_only=1" if view_only else ""
return redirect(
f"/static/novnc/vnc.html?path={ws_url}"
f"&autoconnect=1&resize=remote"
f"&reconnect=1&reconnect_delay=2000{vo_qs}{VIEWER_TUNING_QS}",
code=302,
)
# Direct ws(s):// target (manual / lan_direct connectivity):
# the browser opens the WS straight at the client KasmVNC, with no
# allocator proxy in the byte path. Modern Chromium/Firefox refuse
# to attach an `Authorization: Basic` header to a WebSocket upgrade
# (URL userinfo is stripped at the URL-parser level), so we drive
# KasmVNC in RFB `VncAuth` mode on this connectivity and pass the
# per-session 8-char credential through the bundled noVNC's
# `?password=` URL param — it consumes it in-band during the VNC
# auth handshake. The credential sits in the WS-viewer URL, but
# `/static/novnc/*` has `access_log off` in lablink-nginx.conf and
# we use `location.replace` to keep the URL out of session history.
# DevTools visibility is unavoidable for any browser-direct auth
# scheme; per-session rotation bounds the exposure window.
parts = urlsplit(ws_url)
host = parts.hostname or ""
port = parts.port or 6080
encrypt = "1" if parts.scheme == "wss" else "0"
pw_qs = "" if credential is None else f"&password={quote(credential, safe='')}"
vo_qs = "&view_only=1" if view_only else ""
target = (
f"/static/novnc/vnc.html?host={host}&port={port}&encrypt={encrypt}"
f"&autoconnect=1&resize=remote"
f"&reconnect=1&reconnect_delay=2000{pw_qs}{vo_qs}{VIEWER_TUNING_QS}"
)
return (
"<!doctype html><meta charset=utf-8>"
'<body style="margin:0">'
f"<script>location.replace({json.dumps(target)});</script>",
200,
)
|