diff --git a/keyserver/src/responders/landing-handler.js b/keyserver/src/responders/landing-handler.js index 8c111faaa..464c40d45 100644 --- a/keyserver/src/responders/landing-handler.js +++ b/keyserver/src/responders/landing-handler.js @@ -1,217 +1,218 @@ // @flow import html from 'common-tags/lib/html'; import type { $Response, $Request } from 'express'; import fs from 'fs'; import * as React from 'react'; import ReactDOMServer from 'react-dom/server'; import { promisify } from 'util'; import { isValidPrimaryIdentityPublicKey, isValidSIWENonce, } from 'lib/utils/siwe-utils.js'; import { type LandingSSRProps } from '../landing/landing-ssr.react'; import { waitForStream } from '../utils/json-stream'; import { getAndAssertLandingURLFacts } from '../utils/urls'; import { getMessageForException } from './utils'; async function landingHandler(req: $Request, res: $Response) { try { await landingResponder(req, res); } catch (e) { console.warn(e); if (!res.headersSent) { res.status(500).send(getMessageForException(e)); } } } const access = promisify(fs.access); const readFile = promisify(fs.readFile); const googleFontsURL = 'https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500&family=IBM+Plex+Sans:wght@400;500&display=swap'; const iaDuoFontsURL = 'fonts/duo.css'; const localFontsURL = 'fonts/local-fonts.css'; async function getDevFontURLs(): Promise<$ReadOnlyArray> { try { await access(localFontsURL); return [localFontsURL, iaDuoFontsURL]; } catch { return [googleFontsURL, iaDuoFontsURL]; } } type AssetInfo = { +jsURL: string, +fontURLs: $ReadOnlyArray, +cssInclude: string, }; let assetInfo: ?AssetInfo = null; async function getAssetInfo() { if (assetInfo) { return assetInfo; } if (process.env.NODE_ENV === 'development') { const fontURLs = await getDevFontURLs(); assetInfo = { jsURL: 'http://localhost:8082/dev.build.js', fontURLs, cssInclude: '', }; return assetInfo; } try { const assetsString = await readFile('../landing/dist/assets.json', 'utf8'); const assets = JSON.parse(assetsString); assetInfo = { jsURL: `compiled/${assets.browser.js}`, fontURLs: [googleFontsURL, iaDuoFontsURL], cssInclude: html` `, }; return assetInfo; } catch { throw new Error( 'Could not load assets.json for landing build. ' + 'Did you forget to run `yarn dev` in the landing folder?', ); } } type LandingApp = React.ComponentType; let webpackCompiledRootComponent: ?LandingApp = null; async function getWebpackCompiledRootComponentForSSR() { if (webpackCompiledRootComponent) { return webpackCompiledRootComponent; } try { // $FlowFixMe landing/dist doesn't always exist const webpackBuild = await import('landing/dist/landing.build.cjs'); webpackCompiledRootComponent = webpackBuild.default.default; return webpackCompiledRootComponent; } catch { throw new Error( 'Could not load landing.build.cjs. ' + 'Did you forget to run `yarn dev` in the landing folder?', ); } } const { renderToNodeStream } = ReactDOMServer; async function landingResponder(req: $Request, res: $Response) { const siweNonce = req.header('siwe-nonce'); if ( siweNonce !== null && siweNonce !== undefined && !isValidSIWENonce(siweNonce) ) { res.status(400).send({ message: 'Invalid nonce in siwe-nonce header.', }); return; } const siwePrimaryIdentityPublicKey = req.header( 'siwe-primary-identity-public-key', ); if ( siwePrimaryIdentityPublicKey !== null && siwePrimaryIdentityPublicKey !== undefined && !isValidPrimaryIdentityPublicKey(siwePrimaryIdentityPublicKey) ) { res.status(400).send({ message: 'Invalid primary identity public key in siwe-primary-identity-public-key header.', }); return; } const [{ jsURL, fontURLs, cssInclude }, LandingSSR] = await Promise.all([ getAssetInfo(), getWebpackCompiledRootComponentForSSR(), ]); const fontsInclude = fontURLs .map(url => ``) .join(''); const urlFacts = getAndAssertLandingURLFacts(); const { basePath } = urlFacts; // prettier-ignore res.write(html` Comm ${fontsInclude} ${cssInclude}
`); // We remove trailing slash for `react-router` const routerBasename = basePath.replace(/\/$/, ''); const clientPath = routerBasename + req.url; const reactStream = renderToNodeStream( , ); reactStream.pipe(res, { end: false }); await waitForStream(reactStream); const siweNonceString = siweNonce ? `"${siweNonce}"` : 'null'; const siwePrimaryIdentityPublicKeyString = siwePrimaryIdentityPublicKey ? `"${siwePrimaryIdentityPublicKey}"` : 'null'; // prettier-ignore res.end(html`
`); } export default landingHandler; diff --git a/landing/landing-ssr.react.js b/landing/landing-ssr.react.js index 988dbd301..9c8d10f2c 100644 --- a/landing/landing-ssr.react.js +++ b/landing/landing-ssr.react.js @@ -1,33 +1,35 @@ // @flow import * as React from 'react'; import { StaticRouter } from 'react-router'; import Landing from './landing.react'; import { SIWEContext } from './siwe-context.js'; export type LandingSSRProps = { +url: string, +basename: string, +siweNonce: ?string, + +siwePrimaryIdentityPublicKey: ?string, }; function LandingSSR(props: LandingSSRProps): React.Node { - const { url, basename, siweNonce } = props; + const { url, basename, siweNonce, siwePrimaryIdentityPublicKey } = props; const siweContextValue = React.useMemo( () => ({ siweNonce, + siwePrimaryIdentityPublicKey, }), - [siweNonce], + [siweNonce, siwePrimaryIdentityPublicKey], ); const routerContext = React.useMemo(() => ({}), []); return ( ); } export default LandingSSR; diff --git a/landing/root.js b/landing/root.js index 3019208d3..58e886359 100644 --- a/landing/root.js +++ b/landing/root.js @@ -1,28 +1,30 @@ // @flow import * as React from 'react'; import { BrowserRouter } from 'react-router-dom'; import Landing from './landing.react'; import { SIWEContext } from './siwe-context.js'; declare var routerBasename: string; declare var siweNonce: ?string; +declare var siwePrimaryIdentityPublicKey: ?string; function RootComponent(): React.Node { const siweContextValue = React.useMemo( () => ({ siweNonce, + siwePrimaryIdentityPublicKey, }), [], ); return ( ); } export default RootComponent; diff --git a/landing/siwe-context.js b/landing/siwe-context.js index c55b183d3..e93e48e55 100644 --- a/landing/siwe-context.js +++ b/landing/siwe-context.js @@ -1,13 +1,15 @@ // @flow import * as React from 'react'; export type SIWEContextType = { +siweNonce: ?string, + +siwePrimaryIdentityPublicKey: ?string, }; const SIWEContext: React.Context = React.createContext({ siweNonce: null, + siwePrimaryIdentityPublicKey: null, }); export { SIWEContext };