@gen3/workspaces 0.12.74 → 0.12.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -353,22 +353,33 @@ async function proxyServerExtension(req, res, targetUrl, getToken) {
353
353
  console.warn(`[workspace-assets] proxy auth-diag: tokenSource=${tokenSource} jwtFound=${!!jwt}` + ` hasBearerHeader=${hasBearerHeader} hasAccessTokenCookie=${hasAccessTokenCookie}` + ` cookieNames=[${cookieNames.join(',')}]`);
354
354
  if (jwt) {
355
355
  const claims = decodeJwtClaims(jwt);
356
- const rawUsername = claims?.sub ?? claims?.preferred_username ?? claims?.username;
357
- if (typeof rawUsername === 'string') {
358
- const safeUsername = rawUsername.replace(/[\r\n]/g, '');
356
+ // Gen3 nginx extracts the username from context.user.name (not sub, which is
357
+ // a numeric ID). Check context.user.name first so REMOTE_USER matches what
358
+ // workspace-proxy expects for per-user container routing.
359
+ const contextUserName = claims?.context?.user?.name;
360
+ // String username (what Ambassador stores in k8s service names via Hatchery).
361
+ const rawUsername = (typeof contextUserName === 'string' ? contextUserName : undefined) ?? claims?.preferred_username ?? claims?.username;
362
+ // Numeric sub — used as UID in workspace-proxy's uid:<UID>,<username> format.
363
+ const rawSub = claims?.sub;
364
+ const safeSub = typeof rawSub === 'string' ? rawSub.replace(/[\r\n]/g, '') : undefined;
365
+ // workspace-proxy identity.go parses the composite "uid:<UID>,<username>" format:
366
+ // normalizeUsername → extracts the username part (used for k8s service lookup)
367
+ // parseUID → extracts the UID part (used for UID-based fallback lookup)
368
+ // Ambassador normally injects this format; we must replicate it here since we
369
+ // bypass nginx when calling workspace-proxy server-side.
370
+ const safeUsername = typeof rawUsername === 'string' ? rawUsername.replace(/[\r\n]/g, '') : safeSub;
371
+ if (safeUsername !== undefined) {
359
372
  const safeJwt = jwt.replace(/[\r\n]/g, '');
373
+ const remoteUserValue = safeSub && safeUsername !== safeSub ? `uid:${safeSub},${safeUsername}` : safeUsername;
360
374
  forwardHeaders['Authorization'] = `Bearer ${safeJwt}`;
361
- forwardHeaders['REMOTE_USER'] = safeUsername;
362
- forwardHeaders['X-Gen3-User-ID'] = safeUsername;
363
- console.warn(`[workspace-assets] proxy auth-diag: injected REMOTE_USER/X-Gen3-User-ID for sub="${safeUsername}"` + ` claimsKeys=[${Object.keys(claims ?? {}).join(',')}]`);
375
+ forwardHeaders['REMOTE_USER'] = remoteUserValue;
376
+ forwardHeaders['remote_user'] = remoteUserValue;
377
+ forwardHeaders['X-Gen3-User-ID'] = remoteUserValue;
378
+ console.warn(`[workspace-assets] proxy auth-diag: injected REMOTE_USER="${remoteUserValue}"` + ` (contextUserName=${String(contextUserName)} sub=${String(rawSub)})` + ` claimsKeys=[${Object.keys(claims ?? {}).join(',')}]`);
364
379
  } else {
365
380
  console.warn(`[workspace-assets] proxy auth-diag: JWT decoded but no sub/preferred_username/username claim found` + ` claimsKeys=[${Object.keys(claims ?? {}).join(',')}]` + ` (REMOTE_USER NOT injected — workspace-proxy will likely reject)`);
366
381
  }
367
- } else {
368
- console.warn(`[workspace-assets] proxy auth-diag: no JWT found via ${tokenSource}` + ` — REMOTE_USER/X-Gen3-User-ID NOT injected — workspace-proxy will likely 502`);
369
382
  }
370
- // Log the set of header keys being forwarded (no values — headers can contain tokens).
371
- console.warn(`[workspace-assets] proxy forwarding headers: [${Object.keys(forwardHeaders).join(',')}]`);
372
383
  const method = (req.method || 'GET').toUpperCase();
373
384
  let body;
374
385
  if (![