tfrere HF Staff Cursor commited on
Commit
038fa8d
·
1 Parent(s): 7652473

chore(ws): verbose auth logs to triage Disconnected reports

Browse files

Add structured logging on both ends of the WS handshake so a
"Disconnected" label in the editor top bar can be pinpointed
without guessing:

- Upgrade handler logs the cookie header size and whether
extractToken found our session cookie. This catches the case
where the browser doesn't ship cookies on the WS upgrade
(some HF Space gating + private/org setups strip them on
upgrade even when plain HTTP requests send them fine).
- onAuthenticate logs which source supplied the token (the
client subprotocol or the server-side cookie fallback), the
resolved user, the configured Space owner, and any
accessIssue surfaced by resolveUser. Errors thrown to
Hocuspocus now also include the username + accessIssue so the
reason is visible in the rejected-connection log line, not
just a generic "Unauthorized".

Logs intentionally avoid printing the token itself - they only
expose its length and origin, matching the existing privacy
posture of the auth pipeline.

Co-authored-by: Cursor <cursoragent@cursor.com>

Files changed (1) hide show
  1. backend/src/create-app.ts +52 -2
backend/src/create-app.ts CHANGED
@@ -306,11 +306,51 @@ export function createApp() {
306
  if (!oauthEnabled) return;
307
 
308
  const { resolveUser } = await import("./auth.js");
 
 
 
 
309
  const authToken = token || context?.token;
 
 
 
 
 
 
 
 
 
 
 
 
 
310
  const user = await resolveUser(authToken);
311
- if (!user || !user.canEdit) {
312
- throw new Error("Unauthorized: no write access");
 
 
 
 
 
 
 
313
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
  if (authToken) setUserToken(authToken);
315
  return { user };
316
  },
@@ -389,6 +429,16 @@ export function createApp() {
389
  });
390
  }
391
  const token = extractToken(req.headers.cookie);
 
 
 
 
 
 
 
 
 
 
392
  hocuspocus.handleConnection(ws, req, { token });
393
  });
394
  } else {
 
306
  if (!oauthEnabled) return;
307
 
308
  const { resolveUser } = await import("./auth.js");
309
+ // Two-source pattern: the HocuspocusProvider client sends `token`
310
+ // via the WS sub-protocol, but our cookie is httpOnly so the
311
+ // client can't read it and sends "" instead. Fall back to the
312
+ // cookie the upgrade handler stuffed into context.token.
313
  const authToken = token || context?.token;
314
+
315
+ // Surface enough info in the Space logs to triage "Disconnected"
316
+ // reports without leaking the token itself. We log:
317
+ // - whether each source produced a token (just truthiness)
318
+ // - the resolved user (or null) + accessIssue when present
319
+ // - the SPACE_ID owner being checked, to spot org mismatches
320
+ const tokenSource = token
321
+ ? "client"
322
+ : context?.token
323
+ ? "cookie"
324
+ : "none";
325
+ const tokenLen = authToken ? authToken.length : 0;
326
+
327
  const user = await resolveUser(authToken);
328
+ const spaceOwner = (process.env.SPACE_ID || "").split("/")[0] || "(none)";
329
+
330
+ if (!user) {
331
+ console.warn(
332
+ `[ws-auth] reject: no user resolved` +
333
+ ` source=${tokenSource} tokenLen=${tokenLen}` +
334
+ ` spaceOwner=${spaceOwner}`,
335
+ );
336
+ throw new Error("Unauthorized: invalid or missing HF token");
337
  }
338
+ if (!user.canEdit) {
339
+ console.warn(
340
+ `[ws-auth] reject: ${user.name} can't write to ${spaceOwner}` +
341
+ ` issue=${user.accessIssue ?? "unknown"}` +
342
+ ` source=${tokenSource}`,
343
+ );
344
+ throw new Error(
345
+ `Unauthorized: ${user.name} has no write access to ${spaceOwner}` +
346
+ (user.accessIssue ? ` (${user.accessIssue})` : ""),
347
+ );
348
+ }
349
+
350
+ console.log(
351
+ `[ws-auth] accept user=${user.name} spaceOwner=${spaceOwner}` +
352
+ ` source=${tokenSource}`,
353
+ );
354
  if (authToken) setUserToken(authToken);
355
  return { user };
356
  },
 
429
  });
430
  }
431
  const token = extractToken(req.headers.cookie);
432
+ // Diagnostic for "Disconnected" reports: confirms whether the
433
+ // browser actually attached our session cookie to the WS
434
+ // upgrade. On HF Spaces, some gating setups occasionally
435
+ // strip cookies on WS upgrades even when they're sent for
436
+ // plain HTTP, which manifests as a working /editor route
437
+ // but a permanently-failing WS auth.
438
+ const cookieHeader = req.headers.cookie || "";
439
+ console.log(
440
+ `[ws] upgrade cookies=${cookieHeader.length}B hasToken=${Boolean(token)}`,
441
+ );
442
  hocuspocus.handleConnection(ws, req, { token });
443
  });
444
  } else {