diff --git a/server/src/responders/handlers.js b/server/src/responders/handlers.js --- a/server/src/responders/handlers.js +++ b/server/src/responders/handlers.js @@ -13,6 +13,7 @@ createNewAnonymousCookie, } from '../session/cookies'; import type { Viewer } from '../session/viewer'; +import { type AppURLFacts, getAppURLFactsFromRequestURL } from '../utils/urls'; import { getMessageForException } from './utils'; export type JSONResponder = (viewer: Viewer, input: any) => Promise<*>; @@ -41,10 +42,22 @@ return; } const result = { ...responderResult }; - addCookieToJSONResponse(viewer, res, result, expectCookieInvalidation); + addCookieToJSONResponse( + viewer, + res, + result, + expectCookieInvalidation, + getAppURLFactsFromRequestURL(req.url), + ); res.json({ success: true, ...result }); } catch (e) { - await handleException(e, res, viewer, expectCookieInvalidation); + await handleException( + e, + res, + getAppURLFactsFromRequestURL(req.url), + viewer, + expectCookieInvalidation, + ); } }; } @@ -58,7 +71,12 @@ viewer = await fetchViewerForJSONRequest(req); await responder(viewer, req, res); } catch (e) { - await handleException(e, res, viewer); + await handleException( + e, + res, + getAppURLFactsFromRequestURL(req.url), + viewer, + ); } }; } @@ -73,7 +91,7 @@ } catch (e) { // Passing viewer in only makes sense if we want to handle failures as // JSON. We don't, and presume all download handlers avoid ServerError. - await handleException(e, res); + await handleException(e, res, getAppURLFactsFromRequestURL(req.url)); } }; } @@ -81,6 +99,7 @@ async function handleException( error: Error, res: $Response, + appURLFacts: AppURLFacts, viewer?: ?Viewer, expectCookieInvalidation?: boolean, ) { @@ -110,7 +129,13 @@ viewer.cookieInvalidated = true; } // This can mutate the result object - addCookieToJSONResponse(viewer, res, result, !!expectCookieInvalidation); + addCookieToJSONResponse( + viewer, + res, + result, + !!expectCookieInvalidation, + appURLFacts, + ); } res.json(result); } @@ -121,7 +146,11 @@ return async (req: $Request, res: $Response) => { try { const viewer = await fetchViewerForHomeRequest(req); - addCookieToHomeResponse(viewer, res); + addCookieToHomeResponse( + viewer, + res, + getAppURLFactsFromRequestURL(req.url), + ); res.type('html'); await responder(viewer, req, res); } catch (e) { @@ -165,10 +194,21 @@ return; } const result = { ...responderResult }; - addCookieToJSONResponse(viewer, res, result, false); + addCookieToJSONResponse( + viewer, + res, + result, + false, + getAppURLFactsFromRequestURL(req.url), + ); res.json({ success: true, ...result }); } catch (e) { - await handleException(e, res, viewer); + await handleException( + e, + res, + getAppURLFactsFromRequestURL(req.url), + viewer, + ); } }; } diff --git a/server/src/responders/website-responders.js b/server/src/responders/website-responders.js --- a/server/src/responders/website-responders.js +++ b/server/src/responders/website-responders.js @@ -41,14 +41,10 @@ import { setNewSession } from '../session/cookies'; import { Viewer } from '../session/viewer'; import { streamJSON, waitForStream } from '../utils/json-stream'; -import { getSquadCalURLFacts } from '../utils/urls'; +import { getAppURLFactsFromRequestURL } from '../utils/urls'; -const { basePath, baseDomain } = getSquadCalURLFacts(); const { renderToNodeStream } = ReactDOMServer; -const baseURL = basePath.replace(/\/$/, ''); -const baseHref = baseDomain + baseURL; - const access = promisify(fs.access); const googleFontsURL = 'https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600&family=Inter:wght@400;500;600&display=swap'; @@ -116,6 +112,10 @@ req: $Request, res: $Response, ): Promise { + const { basePath, baseDomain } = getAppURLFactsFromRequestURL(req.url); + const baseURL = basePath.replace(/\/$/, ''); + const baseHref = baseDomain + baseURL; + const appPromise = getWebpackCompiledRootComponentForSSR(); let initialNavInfo; diff --git a/server/src/session/cookies.js b/server/src/session/cookies.js --- a/server/src/session/cookies.js +++ b/server/src/session/cookies.js @@ -32,12 +32,10 @@ import { clearDeviceToken } from '../updaters/device-token-updaters'; import { updateThreadMembers } from '../updaters/thread-updaters'; import { assertSecureRequest } from '../utils/security-utils'; -import { getSquadCalURLFacts } from '../utils/urls'; +import { type AppURLFacts } from '../utils/urls'; import { Viewer } from './viewer'; import type { AnonymousViewerData, UserViewerData } from './viewer'; -const { baseDomain, basePath, https } = getSquadCalURLFacts(); - function cookieIsExpired(lastUsed: number) { return lastUsed + cookieLifetime <= Date.now(); } @@ -534,11 +532,11 @@ return viewer; } -const domainAsURL = new url.URL(baseDomain); function addSessionChangeInfoToResult( viewer: Viewer, res: $Response, result: Object, + appURLFacts: AppURLFacts, ) { let threadInfos = {}, userInfos = {}; @@ -566,7 +564,7 @@ if (viewer.cookieSource === cookieSources.BODY) { sessionChange.cookie = viewer.cookiePairString; } else { - addActualHTTPCookie(viewer, res); + addActualHTTPCookie(viewer, res, appURLFacts); } if (viewer.sessionIdentifierType === sessionIdentifierTypes.BODY_SESSION_ID) { sessionChange.sessionID = viewer.sessionID ? viewer.sessionID : null; @@ -721,6 +719,7 @@ res: $Response, result: Object, expectCookieInvalidation: boolean, + appURLFacts: AppURLFacts, ) { if (expectCookieInvalidation) { viewer.cookieInvalidated = false; @@ -729,31 +728,48 @@ handleAsyncPromise(extendCookieLifespan(viewer.cookieID)); } if (viewer.sessionChanged) { - addSessionChangeInfoToResult(viewer, res, result); + addSessionChangeInfoToResult(viewer, res, result, appURLFacts); } else if (viewer.cookieSource !== cookieSources.BODY) { - addActualHTTPCookie(viewer, res); + addActualHTTPCookie(viewer, res, appURLFacts); } } -function addCookieToHomeResponse(viewer: Viewer, res: $Response) { +function addCookieToHomeResponse( + viewer: Viewer, + res: $Response, + appURLFacts: AppURLFacts, +) { if (!viewer.getData().cookieInsertedThisRequest) { handleAsyncPromise(extendCookieLifespan(viewer.cookieID)); } - addActualHTTPCookie(viewer, res); + addActualHTTPCookie(viewer, res, appURLFacts); } -const cookieOptions = { - domain: domainAsURL.hostname, - path: basePath, - httpOnly: true, - secure: https, - maxAge: cookieLifetime, - sameSite: 'Strict', -}; -function addActualHTTPCookie(viewer: Viewer, res: $Response) { - res.cookie(viewer.cookieName, viewer.cookieString, cookieOptions); +function getCookieOptions(appURLFacts: AppURLFacts) { + const { baseDomain, basePath, https } = appURLFacts; + const domainAsURL = new url.URL(baseDomain); + return { + domain: domainAsURL.hostname, + path: basePath, + httpOnly: true, + secure: https, + maxAge: cookieLifetime, + sameSite: 'Strict', + }; +} + +function addActualHTTPCookie( + viewer: Viewer, + res: $Response, + appURLFacts: AppURLFacts, +) { + res.cookie( + viewer.cookieName, + viewer.cookieString, + getCookieOptions(appURLFacts), + ); if (viewer.cookieName !== viewer.initialCookieName) { - res.clearCookie(viewer.initialCookieName, cookieOptions); + res.clearCookie(viewer.initialCookieName, getCookieOptions(appURLFacts)); } } diff --git a/server/src/utils/security-utils.js b/server/src/utils/security-utils.js --- a/server/src/utils/security-utils.js +++ b/server/src/utils/security-utils.js @@ -2,11 +2,10 @@ import type { $Request } from 'express'; -import { getSquadCalURLFacts } from './urls'; - -const { https } = getSquadCalURLFacts(); +import { getAppURLFactsFromRequestURL } from './urls'; function assertSecureRequest(req: $Request) { + const { https } = getAppURLFactsFromRequestURL(req.url); if (https && req.get('X-Forwarded-SSL') !== 'on') { throw new Error('insecure request'); }