@gen3/workspaces 0.12.73 → 0.12.75
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.
- package/dist/dts/server/workspaceAssetsHandler.d.ts.map +1 -1
- package/dist/dts/server/workspaceAssetsHandler.js +65 -5
- package/dist/dts/server/workspaceAssetsHandler.js.map +1 -1
- package/dist/server/cjs/server.js +44 -6
- package/dist/server/cjs/server.js.map +1 -1
- package/dist/server/esm/server.js +44 -6
- package/dist/server/esm/server.js.map +1 -1
- package/package.json +4 -4
|
@@ -342,18 +342,48 @@ async function proxyServerExtension(req, res, targetUrl, getToken) {
|
|
|
342
342
|
// Only point serverExtensionProxyBaseUrl at a network path a user's own request cannot
|
|
343
343
|
// otherwise forge, or add real signature verification (e.g. against Fence's JWKS)
|
|
344
344
|
// here before trusting these claims.
|
|
345
|
+
// --- auth header injection with diagnostic logging ---
|
|
346
|
+
const tokenSource = getToken ? 'getToken(injected)' : 'extractTokenFallback';
|
|
345
347
|
const jwt = getToken ? getToken(req) : extractTokenFallback(req);
|
|
348
|
+
// Log incoming auth signals so we can diagnose missing REMOTE_USER / 502s.
|
|
349
|
+
const hasBearerHeader = typeof req.headers['authorization'] === 'string' && req.headers['authorization'].startsWith('Bearer ');
|
|
350
|
+
const cookieHeader = req.headers['cookie'] ?? '';
|
|
351
|
+
const hasAccessTokenCookie = /(?:^|;\s*)access_token=/.test(cookieHeader);
|
|
352
|
+
const cookieNames = cookieHeader.split(';').map((p)=>p.trim().split('=')[0]).filter(Boolean);
|
|
353
|
+
console.warn(`[workspace-assets] proxy auth-diag: tokenSource=${tokenSource} jwtFound=${!!jwt}` + ` hasBearerHeader=${hasBearerHeader} hasAccessTokenCookie=${hasAccessTokenCookie}` + ` cookieNames=[${cookieNames.join(',')}]`);
|
|
346
354
|
if (jwt) {
|
|
347
355
|
const claims = decodeJwtClaims(jwt);
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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) {
|
|
351
372
|
const safeJwt = jwt.replace(/[\r\n]/g, '');
|
|
373
|
+
const remoteUserValue = safeSub && safeUsername !== safeSub ? `uid:${safeSub},${safeUsername}` : safeUsername;
|
|
352
374
|
forwardHeaders['Authorization'] = `Bearer ${safeJwt}`;
|
|
353
|
-
forwardHeaders['REMOTE_USER'] =
|
|
354
|
-
forwardHeaders['
|
|
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(',')}]`);
|
|
379
|
+
} else {
|
|
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)`);
|
|
355
381
|
}
|
|
382
|
+
} else {
|
|
383
|
+
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`);
|
|
356
384
|
}
|
|
385
|
+
// Log the set of header keys being forwarded (no values — headers can contain tokens).
|
|
386
|
+
console.warn(`[workspace-assets] proxy forwarding headers: [${Object.keys(forwardHeaders).join(',')}]`);
|
|
357
387
|
const method = (req.method || 'GET').toUpperCase();
|
|
358
388
|
let body;
|
|
359
389
|
if (![
|
|
@@ -382,7 +412,10 @@ async function proxyServerExtension(req, res, targetUrl, getToken) {
|
|
|
382
412
|
hostname: parsed.hostname,
|
|
383
413
|
port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
|
|
384
414
|
path: parsed.pathname + parsed.search,
|
|
385
|
-
headers: forwardHeaders
|
|
415
|
+
headers: forwardHeaders,
|
|
416
|
+
// 30-second socket timeout — without this, a stalled upstream holds the
|
|
417
|
+
// connection open indefinitely and the client sees no response.
|
|
418
|
+
timeout: 30_000
|
|
386
419
|
};
|
|
387
420
|
const proxyReq = transport.request(reqOptions, (proxyRes)=>{
|
|
388
421
|
const statusCode = proxyRes.statusCode ?? 502;
|
|
@@ -398,6 +431,11 @@ async function proxyServerExtension(req, res, targetUrl, getToken) {
|
|
|
398
431
|
}
|
|
399
432
|
const log = statusCode >= 400 ? console.error : console.warn;
|
|
400
433
|
log(`[workspace-assets] ${method} ${targetUrl} -> ${statusCode} (${Date.now() - startedAt}ms)`);
|
|
434
|
+
if (statusCode >= 400) {
|
|
435
|
+
// Log upstream response headers for non-2xx to surface auth/routing rejections.
|
|
436
|
+
const upstreamHeaders = Object.entries(proxyRes.headers).filter(([k])=>!k.toLowerCase().includes('cookie')).map(([k, v])=>`${k}:${Array.isArray(v) ? v.join(',') : v}`).join(' | ');
|
|
437
|
+
console.error(`[workspace-assets] upstream response headers: ${upstreamHeaders}`);
|
|
438
|
+
}
|
|
401
439
|
proxyRes.pipe(res, {
|
|
402
440
|
end: true
|
|
403
441
|
});
|