Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 3 additions & 26 deletions Composer/packages/client/config/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,6 @@ const paths = require('./paths');

// Source maps are resource heavy and can cause out of memory issue for large source files.
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
// Some apps do not need the benefits of saving a web request, so not inlining the chunk
// makes for a smoother build process.
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';

// Check if TypeScript is setup
const useTypeScript = fs.existsSync(paths.appTsConfig);
Expand Down Expand Up @@ -284,6 +281,7 @@ module.exports = function(webpackEnv) {
options: {
formatter: require.resolve('react-dev-utils/eslintFormatter'),
eslintPath: require.resolve('eslint'),
quiet: true,
},
loader: require.resolve('eslint-loader'),
},
Expand Down Expand Up @@ -421,23 +419,14 @@ module.exports = function(webpackEnv) {
{},
{
inject: true,
filename: 'index.html',
filename: 'index.ejs',
template: paths.appHtml,
chunks: ['main'],
},
isEnvProduction
? {
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}
: undefined
Expand All @@ -449,31 +438,19 @@ module.exports = function(webpackEnv) {
{},
{
inject: true,
filename: 'extensionContainer.html',
filename: 'extensionContainer.ejs',
template: paths.extensionContainerHtml,
chunks: ['extension'],
},
isEnvProduction
? {
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}
: undefined
)
),
// Inlines the webpack runtime script. This script is too small to warrant
// a network request.
isEnvProduction && shouldInlineRuntimeChunk && new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime~.+[.]js/]),
// Makes some environment variables available in index.html.
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
Expand Down
17 changes: 16 additions & 1 deletion Composer/packages/client/public/extensionContainer.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,23 @@
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
<? if (__nonce__) { ?>
<script nonce="<?= __nonce__ ?>">
window.__nonce__ = "<?= __nonce__ ?>";

window.FabricConfig = {
mergeStyles: {
cspSettings: { nonce: window.__nonce__ }
}
};

window.CSPSettings = {
nonce: window.__nonce__
};
</script>
<? } ?>
</head>
<style>
<style nonce="<?= __nonce__ ?>">
html, body, #root {
width: 100%;
height: 100%;
Expand Down
Binary file modified Composer/packages/client/public/favicon.ico
Binary file not shown.
18 changes: 17 additions & 1 deletion Composer/packages/client/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="https://www.microsoft.com/favicon.ico" />
<link rel="shortcut icon" href="favicon.ico" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
Expand All @@ -23,6 +23,22 @@
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>BotFramework Composer</title>

<? if (__nonce__) { ?>
<script nonce="<?= __nonce__ ?>">
window.__nonce__ = "<?= __nonce__ ?>";

window.FabricConfig = {
mergeStyles: {
cspSettings: { nonce: window.__nonce__ }
}
};

window.CSPSettings = {
nonce: window.__nonce__
};
</script>
<? } ?>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
Expand Down
2 changes: 1 addition & 1 deletion Composer/packages/client/public/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "BotFramework Composer",
"icons": [
{
"src": "https://www.microsoft.com/favicon.ico",
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
Expand Down
2 changes: 1 addition & 1 deletion Composer/packages/client/scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,6 @@ function build(previousFileSizes) {
function copyPublicFolder() {
fs.copySync(paths.appPublic, paths.appBuild, {
dereference: true,
filter: file => file !== paths.appHtml,
filter: file => ![paths.appHtml, paths.extensionContainerHtml].includes(file),
});
}
17 changes: 13 additions & 4 deletions Composer/packages/client/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React from 'react';
import ReactDOM from 'react-dom';
import formatMessage from 'format-message';
import { CacheProvider } from '@emotion/core';
import createCache from '@emotion/cache';
import './index.css';

import { App } from './App';
Expand All @@ -11,10 +13,17 @@ formatMessage.setup({
missingTranslation: 'ignore',
});

const emotionCache = createCache({
// @ts-ignore
nonce: window.__nonce__,
});

ReactDOM.render(
<StoreProvider>
<App />
<ShellApi />
</StoreProvider>,
<CacheProvider value={emotionCache}>
<StoreProvider>
<App />
<ShellApi />
</StoreProvider>
</CacheProvider>,
document.getElementById('root')
);
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@ import ErrorBoundary, { FallbackProps } from 'react-error-boundary';
import { MessageBar, MessageBarType } from 'office-ui-fabric-react';
import debounce from 'lodash.debounce';
import get from 'lodash.get';
import { CacheProvider } from '@emotion/core';
import createCache from '@emotion/cache';

import { FormEditor, FormEditorProps } from './FormEditor';

const emotionCache = createCache({
// @ts-ignore
nonce: window.__nonce__,
});

const ErrorInfo: React.FC<FallbackProps> = ({ componentStack, error }) => (
<div style={{ marginRight: '20px' }}>
<MessageBar messageBarType={MessageBarType.error} isMultiline={false}>
Expand Down Expand Up @@ -37,9 +44,11 @@ const ObiFormEditor: React.FC<FormEditorProps> = props => {
const key = get(props.data, '$designer.id', props.focusPath);

return (
<ErrorBoundary key={key} FallbackComponent={ErrorInfo}>
<FormEditor {...props} onChange={debouncedOnChange} />
</ErrorBoundary>
<CacheProvider value={emotionCache}>
<ErrorBoundary key={key} FallbackComponent={ErrorInfo}>
<FormEditor {...props} onChange={debouncedOnChange} />
</ErrorBoundary>
</CacheProvider>
);
};

Expand Down
46 changes: 27 additions & 19 deletions Composer/packages/extensions/visual-designer/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/** @jsx jsx */
import { jsx } from '@emotion/core';
import { jsx, CacheProvider } from '@emotion/core';
import createCache from '@emotion/cache';
import React, { useRef, useState, useEffect } from 'react';
import { isEqual } from 'lodash';
import formatMessage from 'format-message';
Expand All @@ -11,6 +12,11 @@ formatMessage.setup({
missingTranslation: 'ignore',
});

const emotionCache = createCache({
// @ts-ignore
nonce: window.__nonce__,
});

const VisualDesigner: React.FC<VisualDesignerProps> = ({
dialogId,
focusedEvent,
Expand Down Expand Up @@ -64,24 +70,26 @@ const VisualDesigner: React.FC<VisualDesignerProps> = ({
}, [focusedEvent, focusedSteps, focusedTab]);

return (
<NodeRendererContext.Provider value={context}>
<div data-testid="visualdesigner-container" css={{ width: '100%', height: '100%', overflow: 'scroll' }}>
<ObiEditor
key={dialogId}
path={dialogId}
data={data}
focusedSteps={focusedSteps}
onFocusSteps={onFocusSteps}
focusedEvent={focusedEvent}
onFocusEvent={onFocusEvent}
onOpen={(x, rest) => navTo(x, rest)}
onChange={x => saveData(x)}
onSelect={onSelect}
undo={undo}
redo={redo}
/>
</div>
</NodeRendererContext.Provider>
<CacheProvider value={emotionCache}>
<NodeRendererContext.Provider value={context}>
<div data-testid="visualdesigner-container" css={{ width: '100%', height: '100%', overflow: 'scroll' }}>
<ObiEditor
key={dialogId}
path={dialogId}
data={data}
focusedSteps={focusedSteps}
onFocusSteps={onFocusSteps}
focusedEvent={focusedEvent}
onFocusEvent={onFocusEvent}
onOpen={(x, rest) => navTo(x, rest)}
onChange={x => saveData(x)}
onSelect={onSelect}
undo={undo}
redo={redo}
/>
</div>
</NodeRendererContext.Provider>
</CacheProvider>
);
};

Expand Down
1 change: 1 addition & 0 deletions Composer/packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"cookie-parser": "^1.4.4",
"debug": "^4.1.1",
"dotenv": "^8.1.0",
"ejs": "^2.7.1",
"express": "^4.16.4",
"form-data": "^2.3.3",
"globby": "^9.1.0",
Expand Down
39 changes: 37 additions & 2 deletions Composer/packages/server/src/server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dotenv/config';
import path from 'path';
import crypto from 'crypto';

import express, { Express, Request, Response, NextFunction } from 'express';
import bodyParser from 'body-parser';
Expand All @@ -10,13 +11,43 @@ import { apiRouter } from './router/api';
import { BASEURL } from './constants';

const app: Express = express();
app.set('view engine', 'ejs');
app.set('view options', { delimiter: '?' });

const { login, authorize } = getAuthProvider();

const CS_POLICIES = [
"default-src 'none';",
"font-src 'self' https:;",
"img-src 'self' data:;",
"base-uri 'none';",
"connect-src 'self';",
"frame-src 'self';",
"worker-src 'self';",
"form-action 'none';",
"frame-ancestors 'self';",
"manifest-src 'self';",
'upgrade-insecure-requests;',
];

app.all('*', function(req: Request, res: Response, next: NextFunction) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET,HEAD,OPTIONS,POST,PUT,DELETE');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');

if (process.env.NODE_ENV === 'production' || process.env.ENABLE_CSP === 'true') {
req.__nonce__ = crypto.randomBytes(16).toString('base64');
res.header(
'Content-Security-Policy',
CS_POLICIES.concat([
`script-src 'self' 'nonce-${req.__nonce__}';`,
// TODO: use nonce strategy after addressing issues with monaco-editor pacakge
"style-src 'self' 'unsafe-inline'",
// `style-src 'self' 'nonce-${req.__nonce__}';`,
]).join(' ')
);
}

next();
});

Expand Down Expand Up @@ -51,8 +82,12 @@ app.use(function(err: Error, req: Request, res: Response, _next: NextFunction) {
}
});

app.get('*', function(req, res, _next) {
res.sendFile(path.resolve(__dirname, './public/index.html'));
app.get('/extensionContainer.html', function(req, res) {
res.render(path.resolve(__dirname, './public/extensionContainer.ejs'), { __nonce__: req.__nonce__ });
});

app.get('*', function(req, res) {
res.render(path.resolve(__dirname, './public/index.ejs'), { __nonce__: req.__nonce__ });
});

const port = process.env.PORT || 5000;
Expand Down
5 changes: 5 additions & 0 deletions Composer/packages/server/src/types/express.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
declare namespace Express {
export interface Request {
__nonce__?: string;
}
}
15 changes: 14 additions & 1 deletion Composer/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2397,14 +2397,22 @@
dependencies:
"@types/react" "*"

"@types/react@*", "@types/[email protected]", "@types/react@^16.9.2":
"@types/react@*", "@types/[email protected]":
version "16.9.0"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.0.tgz#27434f16d889a335eb4626d1f1e67eda54039e5b"
integrity sha512-eOct1hyZI9YZf/eqNlYu7jxA9qyTw1EGXruAJhHhBDBpc00W0C1vwlnh+hkOf7UFZkNK+UxnFBpwAZe3d7XJhQ==
dependencies:
"@types/prop-types" "*"
csstype "^2.2.0"

"@types/react@^16.9.2":
version "16.9.9"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.9.tgz#a62c6f40f04bc7681be5e20975503a64fe783c3a"
integrity sha512-L+AudFJkDukk+ukInYvpoAPyJK5q1GanFOINOJnM0w6tUgITuWvJ4jyoBPFL7z4/L8hGLd+K/6xR5uUjXu0vVg==
dependencies:
"@types/prop-types" "*"
csstype "^2.2.0"

"@types/rimraf@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-2.0.2.tgz#7f0fc3cf0ff0ad2a99bb723ae1764f30acaf8b6e"
Expand Down Expand Up @@ -7916,6 +7924,11 @@ [email protected]:
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=

ejs@^2.7.1:
version "2.7.1"
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.1.tgz#5b5ab57f718b79d4aca9254457afecd36fa80228"
integrity sha512-kS/gEPzZs3Y1rRsbGX4UOSjtP/CeJP0CxSNZHYxGfVM/VgLcv0ZqM7C45YyTj2DI2g7+P9Dd24C+IMIg6D0nYQ==

electron-to-chromium@^1.3.103, electron-to-chromium@^1.3.116, electron-to-chromium@^1.3.47:
version "1.3.118"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.118.tgz#5c82b0445a40934e6cae9c2f40bfaaa986ea44a3"
Expand Down