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,8 +13,11 @@ createNewAnonymousCookie, } from '../session/cookies'; import type { Viewer } from '../session/viewer'; +import { getNewAppURLFacts } from '../utils/urls'; import { getMessageForException } from './utils'; +const { basePath } = getNewAppURLFacts(); + export type JSONResponder = (viewer: Viewer, input: any) => Promise<*>; export type DownloadResponder = ( viewer: Viewer, @@ -41,10 +44,22 @@ return; } const result = { ...responderResult }; - addCookieToJSONResponse(viewer, res, result, expectCookieInvalidation); + addCookieToJSONResponse( + viewer, + res, + result, + expectCookieInvalidation, + req.url.startsWith(basePath), + ); res.json({ success: true, ...result }); } catch (e) { - await handleException(e, res, viewer, expectCookieInvalidation); + await handleException( + e, + res, + req.url.startsWith(basePath), + viewer, + expectCookieInvalidation, + ); } }; } @@ -58,7 +73,7 @@ viewer = await fetchViewerForJSONRequest(req); await responder(viewer, req, res); } catch (e) { - await handleException(e, res, viewer); + await handleException(e, res, req.url.startsWith(basePath), viewer); } }; } @@ -73,7 +88,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, req.url.startsWith(basePath)); } }; } @@ -81,6 +96,7 @@ async function handleException( error: Error, res: $Response, + usingNewAppURL: boolean, viewer?: ?Viewer, expectCookieInvalidation?: boolean, ) { @@ -110,7 +126,13 @@ viewer.cookieInvalidated = true; } // This can mutate the result object - addCookieToJSONResponse(viewer, res, result, !!expectCookieInvalidation); + addCookieToJSONResponse( + viewer, + res, + result, + !!expectCookieInvalidation, + usingNewAppURL, + ); } res.json(result); } @@ -121,7 +143,7 @@ return async (req: $Request, res: $Response) => { try { const viewer = await fetchViewerForHomeRequest(req); - addCookieToHomeResponse(viewer, res); + addCookieToHomeResponse(viewer, res, req.url.startsWith(basePath)); res.type('html'); await responder(viewer, req, res); } catch (e) { @@ -165,10 +187,16 @@ return; } const result = { ...responderResult }; - addCookieToJSONResponse(viewer, res, result, false); + addCookieToJSONResponse( + viewer, + res, + result, + false, + req.url.startsWith(basePath), + ); res.json({ success: true, ...result }); } catch (e) { - await handleException(e, res, viewer); + await handleException(e, res, req.url.startsWith(basePath), 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 { getAppURLFacts } from '../utils/urls'; +import { getAppURLFacts, getNewAppURLFacts } from '../utils/urls'; -const { basePath, baseDomain } = getAppURLFacts(); 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,13 @@ req: $Request, res: $Response, ): Promise { + const newBasePath = getNewAppURLFacts().basePath; + const { basePath, baseDomain } = req.url.startsWith(newBasePath) + ? getNewAppURLFacts() + : getAppURLFacts(); + const baseURL = basePath.replace(/\/$/, ''); + const baseHref = baseDomain + baseURL; + const appPromise = getWebpackCompiledRootComponentForSSR(); let initialNavInfo; diff --git a/server/src/server.js b/server/src/server.js --- a/server/src/server.js +++ b/server/src/server.js @@ -30,9 +30,14 @@ multimediaUploadResponder, uploadDownloadResponder, } from './uploads/uploads'; -import { getGlobalURLFacts, getLandingURLFacts } from './utils/urls'; +import { + getGlobalURLFacts, + getLandingURLFacts, + getNewAppURLFacts, +} from './utils/urls'; const { baseRoutePath } = getGlobalURLFacts(); +const { basePath } = getNewAppURLFacts(); const landingBaseRoutePath = getLandingURLFacts().baseRoutePath; if (cluster.isMaster) { @@ -95,6 +100,10 @@ `/${endpoint}`, jsonHandler(responder, expectCookieInvalidation), ); + router.post( + `${basePath}${endpoint}`, + jsonHandler(responder, expectCookieInvalidation), + ); } router.post('/commlanding/subscribe_email', emailSubscriptionResponder); @@ -120,6 +129,7 @@ // $FlowFixMe express-ws has side effects that can't be typed router.ws('/ws', onConnection); router.get(`${landingBaseRoutePath}*`, landingHandler); + router.get(`${basePath}*`, htmlHandler(websiteResponder)); router.get('*', htmlHandler(websiteResponder)); router.post( 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 { getAppURLFacts } from '../utils/urls'; +import { getAppURLFacts, getNewAppURLFacts } from '../utils/urls'; import { Viewer } from './viewer'; import type { AnonymousViewerData, UserViewerData } from './viewer'; -const { baseDomain, basePath, https } = getAppURLFacts(); - 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, + usingNewAppURL: boolean, ) { let threadInfos = {}, userInfos = {}; @@ -566,7 +564,7 @@ if (viewer.cookieSource === cookieSources.BODY) { sessionChange.cookie = viewer.cookiePairString; } else { - addActualHTTPCookie(viewer, res); + addActualHTTPCookie(viewer, res, usingNewAppURL); } if (viewer.sessionIdentifierType === sessionIdentifierTypes.BODY_SESSION_ID) { sessionChange.sessionID = viewer.sessionID ? viewer.sessionID : null; @@ -721,6 +719,7 @@ res: $Response, result: Object, expectCookieInvalidation: boolean, + usingNewAppURL: boolean, ) { if (expectCookieInvalidation) { viewer.cookieInvalidated = false; @@ -729,31 +728,50 @@ handleAsyncPromise(extendCookieLifespan(viewer.cookieID)); } if (viewer.sessionChanged) { - addSessionChangeInfoToResult(viewer, res, result); + addSessionChangeInfoToResult(viewer, res, result, usingNewAppURL); } else if (viewer.cookieSource !== cookieSources.BODY) { - addActualHTTPCookie(viewer, res); + addActualHTTPCookie(viewer, res, usingNewAppURL); } } -function addCookieToHomeResponse(viewer: Viewer, res: $Response) { +function addCookieToHomeResponse( + viewer: Viewer, + res: $Response, + usingNewAppURL: boolean, +) { if (!viewer.getData().cookieInsertedThisRequest) { handleAsyncPromise(extendCookieLifespan(viewer.cookieID)); } - addActualHTTPCookie(viewer, res); + addActualHTTPCookie(viewer, res, usingNewAppURL); } -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(usingNewAppURL: boolean) { + const { baseDomain, basePath, https } = usingNewAppURL + ? getNewAppURLFacts() + : getAppURLFacts(); + 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, + usingNewAppURL: boolean, +) { + res.cookie( + viewer.cookieName, + viewer.cookieString, + getCookieOptions(usingNewAppURL), + ); if (viewer.cookieName !== viewer.initialCookieName) { - res.clearCookie(viewer.initialCookieName, cookieOptions); + res.clearCookie(viewer.initialCookieName, getCookieOptions(usingNewAppURL)); } } 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,14 @@ import type { $Request } from 'express'; -import { getAppURLFacts } from './urls'; +import { getAppURLFacts, getNewAppURLFacts } from './urls'; -const { https } = getAppURLFacts(); +const { basePath } = getNewAppURLFacts(); function assertSecureRequest(req: $Request) { + const { https } = req.url.startsWith(basePath) + ? getNewAppURLFacts() + : getAppURLFacts(); if (https && req.get('X-Forwarded-SSL') !== 'on') { throw new Error('insecure request'); } diff --git a/server/src/utils/urls.js b/server/src/utils/urls.js --- a/server/src/utils/urls.js +++ b/server/src/utils/urls.js @@ -2,6 +2,7 @@ import appURLFacts from '../../facts/app_url'; import landingURLFacts from '../../facts/landing_url'; +import newAppURLFacts from '../../facts/new_app_url'; import baseURLFacts from '../../facts/url'; type GlobalURLFacts = { @@ -26,8 +27,17 @@ return appURLFacts; } +function getNewAppURLFacts(): AppURLFacts { + return newAppURLFacts; +} + function getLandingURLFacts(): LandingURLFacts { return landingURLFacts; } -export { getGlobalURLFacts, getAppURLFacts, getLandingURLFacts }; +export { + getGlobalURLFacts, + getAppURLFacts, + getNewAppURLFacts, + getLandingURLFacts, +};