reZach / secure-electron-template

The best way to build Electron apps with security in mind.
MIT License
1.64k stars 153 forks source link

Unable to use Material-UI framework #14

Closed Salmon42 closed 4 years ago

Salmon42 commented 4 years ago

I tried to add React Material-UI to the project template.

This is how it looks in package.json (by simply following the installation routine on material-ui page):

"dependencies": {
    ...
    "@material-ui/core": "^4.9.5",
    "@material-ui/icons": "^4.9.1",
    "@material-ui/styles": "^4.9.0",
    ...
}

And in one of the components simply used material button:

... (ommited other react imports)
import Button from '@material-ui/core/Button';
...
const Component = () => {
    return (
        <Button variant="contained" color="primary">
            Hello World!
        </Button>
    )
}

Somehow, I seem to hit unsolvable error similar to this issue.

jss.esm.js:1712 Refused to apply inline style because it violates the following Content Security Policy directive: "style-src 'self' 'unsafe-inline' 'nonce-qahp5MvuRSU3PA6LfMc+Vg=='". Note that 'unsafe-inline' is ignored if either a hash or nonce value is present in the source list.

I tried to modify this in webpack.development.js:

new CspHtmlWebpackPlugin({
      'base-uri': ["'self'"],
      'object-src': ["'none'"],
      'script-src': ["'self'"],
      'style-src': ["'self' 'unsafe-inline'"],  // Added unsafe-inline
      'frame-src': ["'none'"],
      'worker-src': ["'none'"],
}),

but it had no effect.

Is there a way to get Material-UI working along with this template? I feel like I'm terribly missing something trivial, but I don't know what else to try. Thanks in advance.

reZach commented 4 years ago

Thanks for reporting this, @Salmon42. If the suggestion in the other issue to add 'unsafe-inline' doesn't work, I'd suggest modifying your webpack.config.js in this way.

Before

// loads .css files
{
    test: /\.css$/,
    include: [path.resolve(__dirname, "app/src")],
    use: [
        MiniCssExtractPlugin.loader,
        {
            loader: "css-loader"
        }
    ],
    resolve: {
        extensions: [".css"]
    }
},

After

// loads .css files
{
    test: /\.css$/,
    //include: [path.resolve(__dirname, "app/src")], // removed!
    use: [
        MiniCssExtractPlugin.loader,
        {
            loader: "css-loader"
        }
    ],
    resolve: {
        extensions: [".css"]
    }
},

My thought is that because webpack is only pulling in .css based on the app directory (instead of the node_modules folder, where material ui might have its assets stored), you aren't able to import your css. If this doesn't work, which I hope it would, I would take the question to the material ui team and file an issue/question.

It appears that they are doing some kind of process to import the css into each component. I took a little bit to look at it for you but I don't seem to make sense of it. If my suggestion above doesn't work, I recommend asking the material ui team how the css files are imported, so then we can better understand how to change the webpack/csp settings so material ui works in this template.

FYI this file might also be helpful to look at, but that's just a guess of mine.

Salmon42 commented 4 years ago

I suppose the problem could be related to the idea how material-ui processes CSS styles for each component. If I take a look at the DOM through Inspect Element, we can see the following:

image

This might be incorrect idea, but I have a feeling that these 4 styles (each containing few css declarations for the given component) are ignored because they are not contained in the "compiled" main.css, which is built from all CSS files inside app/src. I tried to search in material-ui whether it's the only way to use default styles of the components, if there is something I could force it to generate the styles into the main.css, but that is way out of my understanding.

Sadly, the only thing I found that "fixes" this issue was by commenting out the whole CSP Webpack Plugin (CspHtmlWebpackPlugin) in webpack.development.js, which I suppose defeats the purpose of safe approach of this template. Without the policy, the multiple style sheets injected by material-ui into element works without any warning, with all styles and component animations.

About the sources you shared, I took a look into them, but I feel that codes only supports writing style sheets in JSS (defining CSS as JS object and then using those methods to bind together with a component). That is (as I understood) moreless a addition to the basic styles and it would not change the way material-ui injects the final styles into DOM.

I'm pretty new to this combination of technologies, so if I said anything that doesn't make any sense, my apologies.

reZach commented 4 years ago

@Salmon42 I agree with everything you just said.

Have a take a look here and (I think) more importantly here. It'd be getting around the actual CspHtmlWebpackPlugin, or it might break it, I'm not sure yet - but I think it's the next place to start.

reZach commented 4 years ago

@Salmon42 have you made any progress on this issue? I want to understand if it is still open or if you solved for it.

Salmon42 commented 4 years ago

I solved it only partially, by removing the CspHtmlWebpackPlugin from webpack.development.js. Which I suppose isn't a convenient way to solve this. As I was using this template for study purposes, I do not intend to release any electron app, it was sufficient enough for me. Another thing might be the wrong way of importing materialui in the project - when I began using this template, I had zero knowledge about webpack. Either way, the issue is closed for me, but there may be someone in future that will stumble upon the same situation, but they won't be able to just delete some important code to enable usage of some react UI package.

I plan to look at this issue again within few months, when I have more time and knowledge about webpack, babel and some other stuff you are using in this template.

Thanks for help for now!

ecolman commented 4 years ago

I figured this out by following your links @reZach. The material-ui doc link was to an old version and once I looked at the latest docs, I saw the answer.

CspHtmlWebpackPlugin and HtmlWebpackPlugin need to be provided a nonce, and a special meta-tag needs to be added to the index.html file for material-ui (really jss) to pick up on it. Both the index.html and index.prod.html files need to be renamed with the .ejs extension to enable templating.

Also need to add uuid for creation of the nonce: npm i -S uuid

create-nonce.js

const { v4: uuidv4 } = require('uuid');

module.exports = function() {
  return new Buffer(uuidv4()).toString('base64');
};

webpack.development.js

const nonce = require("./create-nonce")();

  ...
  plugins: [
    new MiniCssExtractPlugin(),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "app/src/index.ejs"), // notice the change to ejs
      filename: "index.html",
      nonce: nonce  // added a new property for ejs template
    }),
    new CspHtmlWebpackPlugin({
      "base-uri": ["'self'"],
      "object-src": ["'none'"],
      "script-src": ["'self'"],
      "style-src": ["'self'", `'nonce-${nonce}'`],  // added a nonce for the style-src
      "frame-src": ["'none'"],
      "worker-src": ["'none'"]
    })
  ]

index.ejs (formally index.html)

<head>
  <meta charset="UTF-8">

  <meta property="csp-nonce" content="<%= htmlWebpackPlugin.options.nonce %>" />
</head>

The same change made to webpack.development.js/index.html should be made in webpack.production.js/index-prod.html as well.

reZach commented 4 years ago

@ecolman Looks good to me, thanks for sharing your solution here! I am going to close this issue.

b-zurg commented 4 years ago

@ecolman you're the best! One addition, I found that the CspHtmlWebpackPlugin actually generates its own nonce on the script element, which conflicted with the UUID generated nonce. So I removed the UUID one in favour of the plugin-generated nonce. Was there a reason why you made your own?

ecolman commented 4 years ago

@b-zurg I wasn't aware that the the CspHtmlWebpackPlugin was generating it's own nonce. I basically was following the directions from Material-UI for it to work with that plugin. Can you provide a code snippet or documentation link showing how it works without a user-provided nonce?

b-zurg commented 4 years ago

Well I can post my plugin call, it's almost nothing,

  new CspHtmlWebpackPlugin({
    "base-uri": ["'self'"],
    "object-src": ["'none'"],
    "script-src": ["'self'", ...(!isProd ? ["'unsafe-eval'"] : [])], //included for react-hot-loader
    "frame-src": ["'none'"],
    "worker-src": ["'none'"],
  }),

I have a boolean called isProd declared elsewhere to ensure that unsafe-eval is only included in the dev environment. This flag is necessary for react-hot-moculd to work.

The CSP appears to build a sha-based nonce. Here's my generated html:

<!DOCTYPE html>
<html>

<head>
  <meta http-equiv="Content-Security-Policy"
    content="base-uri 'self'; object-src 'none'; script-src 'self' 'unsafe-eval' 'nonce-tVZvi9VxJuouaojo+5nChg=='; style-src 'unsafe-inline' 'self' 'unsafe-eval'; frame-src 'none'; worker-src 'none'">
  <meta charset="utf-8">
  <title>myapp</title>
</head>

<body>
  <div id="app"></div>
  <script src="/window/index.js" nonce="tVZvi9VxJuouaojo+5nChg=="></script>

</body>

</html>