diff --git a/src/backend/src/routers/hosting/puterSiteMiddleware.js b/src/backend/src/routers/hosting/puterSiteMiddleware.js index 994163efe..d10960095 100644 --- a/src/backend/src/routers/hosting/puterSiteMiddleware.js +++ b/src/backend/src/routers/hosting/puterSiteMiddleware.js @@ -318,6 +318,28 @@ function getPrivateAccessRejectionReason (error) { return error?.code || error?.message || 'unknown'; } +function stripBootstrapAuthTokenFromOriginalUrl (originalUrl) { + if ( typeof originalUrl !== 'string' || !originalUrl ) return null; + + try { + const placeholderOrigin = 'https://placeholder.puter.local'; + const parsedUrl = new URL(originalUrl, placeholderOrigin); + const hadToken = + parsedUrl.searchParams.has('puter.auth.token') + || parsedUrl.searchParams.has('auth_token'); + if ( ! hadToken ) return null; + + parsedUrl.searchParams.delete('puter.auth.token'); + parsedUrl.searchParams.delete('auth_token'); + + const search = parsedUrl.searchParams.toString(); + const cleanPath = parsedUrl.pathname || '/'; + return search ? `${cleanPath}?${search}` : cleanPath; + } catch { + return null; + } +} + function getTokenFromAuthorizationHeader (req) { const authorizationHeader = req.headers?.authorization; if ( typeof authorizationHeader !== 'string' ) return null; @@ -905,6 +927,20 @@ async function evaluatePrivateAppAccess ({ req, res, services, app, requestPath requestHostname: req.hostname, }), ); + + const sanitizedUrl = stripBootstrapAuthTokenFromOriginalUrl(req.originalUrl); + if ( sanitizedUrl ) { + logPrivateAccessEvent('private_access.allowed_cookie_redirect', { + appUid: app.uid, + userUid: identity.userUid ?? null, + requestHost: req.hostname, + requestPath, + source: identity.source, + redirectUrl: sanitizedUrl, + }); + res.redirect(sanitizedUrl); + return false; + } } logPrivateAccessEvent('private_access.allowed', { diff --git a/src/backend/src/routers/hosting/puterSiteMiddleware.test.js b/src/backend/src/routers/hosting/puterSiteMiddleware.test.js index d391d8b4d..0df8ce981 100644 --- a/src/backend/src/routers/hosting/puterSiteMiddleware.test.js +++ b/src/backend/src/routers/hosting/puterSiteMiddleware.test.js @@ -1056,11 +1056,12 @@ describe('PuterSiteMiddleware', () => { is_custom_domain: false, baseUrl: '', path: '/asset.js', - originalUrl: '/asset.js?puter.auth.token=bootstrap-token', + originalUrl: '/asset.js?puter.auth.token=bootstrap-token&foo=bar', cookies: {}, headers: {}, query: { 'puter.auth.token': 'bootstrap-token', + foo: 'bar', }, ctx: mockContextInstance, }; @@ -1097,6 +1098,7 @@ describe('PuterSiteMiddleware', () => { 'private-token', { sameSite: 'none' }, ); + expect(mockRes.redirect).toHaveBeenCalledWith('/asset.js?foo=bar'); expect(mockNext).not.toHaveBeenCalled(); });