React + Recompose: Calling Multiple HOC Wrappers

PROBLEM

Sometimes, wrapping a React component with multiple High Order Components (HOC) can get rather unwieldy and unreadable.

For example:-

import React from 'react';
import { withRouter } from 'react-router-dom';
import { withStyles } from 'material-ui/styles';
import withWidth from 'material-ui/utils/withWidth';

class MyComponent extends React.PureComponent {
	// ...
}

export default withRouter(withStyles(styles)(withWidth()(MyComponent)));

SOLUTION

To fix this, we can leverage recompose library.

Now, we can rewrite the above example like this:-

import React from 'react';
import { withRouter } from 'react-router-dom';
import { withStyles } from 'material-ui/styles';
import withWidth from 'material-ui/utils/withWidth';
import compose from 'recompose/compose';

class MyComponent extends React.PureComponent {
	// ...
}

export default compose(
  withRouter,
  withStyles(styles),
  withWidth(),
)(MyComponent);

Keep in mind, the HOC order defined in compose(..) is important.

Advertisements

Webpack + ESLint: Automatically Fix ESLint Errors

PROBLEM

Given the following webpack.config.js

module.exports = {
  ...
  module: {
    rules: [
      {
        enforce: 'pre',
        test: /\.js?$/,
        loader: 'eslint-loader',
        exclude: /node_modules/,
      },
	  ...
    ],
  },
  ...
};

When running any Webpack command, ESLint may find violations and halt the entire process with the following error message:-

/path/to/front-end-stack/src/js/components/home/Home.js
  43:11  error  Expected indentation of 6 space characters but found 10  react/jsx-indent
  44:14  error  Expected indentation of 6 space characters but found 13  react/jsx-indent

x 2 problems (2 errors, 0 warnings)
  2 errors, 0 warnings potentially fixable with the `--fix` option.

SOLUTION

Certain errors (ex: trailing commas, wrong indentation, extra semicolon) are easily fixable.

There’s no need to halt the process and wait for developers to fix these obvious errors.

To configure ESLint to automatically fix these “soft” errors, add the following options block to the above rule:-

module.exports = {
  ...
  module: {
    rules: [
      {
        enforce: 'pre',
        test: /\.js?$/,
        loader: 'eslint-loader',
        exclude: /node_modules/,
        options: {
          fix: true,
        },		
      },
	  ...
    ],
  },
  ...
};

If you are using any VCS, remember to commit any file changes.

PHP + Composer: The Process “php -S 0.0.0.0:8080 -t public index.php” Exceeded the Timeout of 300 Seconds

PROBLEM

Given the following composer.json

{
  ...
  "scripts": {
    "start": "php -S 0.0.0.0:8080 -t public index.php"
  }
}

When running composer start

The PHP built-in web server stops with the following error message…

[Symfony\Component\Process\Exception\ProcessTimedOutException]                              
The process "php -S 0.0.0.0:8080 -t public index.php" exceeded the timeout of 300 seconds.  

SOLUTION

There are several ways to extend the timeout value, but here is one way to do it through composer.json.

To extend the timeout value from 300 seconds to 2000 seconds, add the following config block in composer.json:-

{
  ...
  "scripts": {
    "start": "php -S 0.0.0.0:8080 -t public index.php"
  }
  "config": {
    "process-timeout": 2000
  }
}

To disable web server timeout completely, set process-timeout to 0:-

{
  ...
  "scripts": {
    "start": "php -S 0.0.0.0:8080 -t public index.php"
  }
  "config": {
    "process-timeout": 0
  }
}

Slim: Class ‘X’ Not Found

PROBLEM

Let’s assume we have the following project structure with src/model/Person.php created…

.
├── composer.json
├── composer.lock
├── logs
├── public
├── src
│   ├── model
│   │   └── Person.php
│   ├── dependencies.php
│   ├── middleware.php
│   ├── routes.php
│   └── settings.php
├── templates
├── tests
└── vendor

… and the class may look something like this…

// src/main/Person.php

class Person
{
    public $name;
    public $age;

    public function __construct(string $name, int $age)
    {
        $this->name = $name;
        $this->age = $age;
    }
}

… and it is being used in other file…

// src/routes.php

use Slim\Http\Request;
use Slim\Http\Response;

$app->get('/[{name}]', function (Request $request, Response $response, array $args) {
    $person = new Person('mike', 50);

    // ... do something
    return ...
});

When hitting the given route from the browser, we get the following error:-

Type   : Error
Message: Class 'Person' not found
File   : /path/to/app/src/routes.php
Line   : 7

SOLUTION #1: Using import/require

One way to solve this is to use include or require statement:-

// src/routes.php

require 'model/Person.php';

use Slim\Http\Request;
use Slim\Http\Response;

$app->get('/[{name}]', function (Request $request, Response $response, array $args) {
    $person = new Person('mike', 50);

    // ... do something
    return ...
});

SOLUTION #2: Using autoloading

Another approach is to leverage PSR-4 autoloading.

First, create a namespace in Person.php:-

// src/main/Person.php

namespace App\Model;

class Person
{
    public $name;
    public $age;

    public function __construct(string $name, int $age)
    {
        $this->name = $name;
        $this->age = $age;
    }
}

Second, in the file that consumes that model, specify a use statement to alias that model so it doesn’t need to be fully qualified with the given namespace in order to use it:-

// src/routes.php

use App\Model\Person;
use Slim\Http\Request;
use Slim\Http\Response;

$app->get('/[{name}]', function (Request $request, Response $response, array $args) {
    $person = new Person('mike', 50);

    // ... do something
    return ...
});

Third, in composer.json, create the autoload block and specify the namespace mapping in it:-

{
  ...
  "autoload": {
    "psr-4": {
      "App\\Model\\": "src/model/"
    }
  },
  "autoload-dev": {
    "psr-4": {
      "Tests\\": "tests/"
    }
  },
  ...
}

Finally, run composer dump-autoload to regenerate vendor/composer/autoload_psr4.php. This file keeps track the list of classes needed to be included in the project.

Spring Security: Propagating Security Context to Spawned Threads

PROBLEM

Let’s assume we have the following Parent class…

@Service
class Parent {
    @Autowired
    Child child

    void run() {
        println "Parent: ${SecurityContextHolder.context.authentication?.principal}"

        child.run()

        println "Parent: Done"
    }
}

… and Child class…

@Service
class Child {
    @Async
    void run() {
        Thread.sleep(500)
        println "Child: ${SecurityContextHolder.context.authentication?.principal}"
    }
}

Let’s also assume the user has successfully logged in and Spring Security has set up the user authentication info.

The Parent will spawn a new thread (through @Async) to run Child.

When invoking the Parent, this is what we see:-

Parent: USER_PRINCIPAL
Parent: Done
Child: null

The Child, for some reason, doesn’t get the receive the user authentication info.

SOLUTION

By default, SecurityContextHolder uses MODE_THREADLOCAL to store the user authentication info. As a result, this info is not accessible to methods outside the current execution thread.

To fix this, configure SecurityContextHolder to use MODE_INHERITABLETHREADLOCAL to pass the user authentication info to other spawned threads.

@Configuration
@EnableAsync
class AppConfig {
    AppConfig() {
        SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL)
    }
}

When invoking the Parent again, now the Child will also receive the user authentication object:-

Parent: USER_PRINCIPAL
Parent: Done
Child: USER_PRINCIPAL

JEE Security: Preventing Clickjacking Attacks

PROBLEM

Clickjacking is an attack that tricks the users to perform unintended actions… see OWASP’s Testing for Clickjacking (OTG-CLIENT-009)

SOLUTION

To prevent clickjacking attacks, the app must set X-FRAME-OPTIONS header with an appropriate value:-

  • DENY: this denies any domain using the page as an iFrame source. This is the best option.
  • SAMEORIGIN: this allows pages within the same domain to use other application pages as iFrame sources.
  • ALLOW-FROM [whitelisted domains]: this declares a list of domains that are allowed to include the pages as iFrame sources.

If set correctly, the HTTPS response should show X-FRAME-OPTIONS header:-

➜  ~ curl -i -k https://localhost:8443/
HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: DENY
X-Application-Context: application:local:8443
Set-Cookie: JSESSIONID=04ADDAF886A20AA561021E869E980BCC; Path=/; Secure; HttpOnly
Content-Type: text/html;charset=UTF-8
Content-Language: en-US
Content-Length: 631
Date: Thu, 31 Aug 2017 14:56:57 GMT

There are several ways to set this header.

Solution 1: Using a servlet filter

You may create a servlet filter that sets X-FRAME-OPTIONS in the response header.

Here’s an example using web.xml-less Spring Boot:-

@SpringBootApplication
class Application extends SpringBootServletInitializer {
    static void main(String[] args) {
        SpringApplication.run(Application, args)
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(Application)
    }

    @Bean
    FilterRegistrationBean clickjackingPreventionFilter() {
        return new FilterRegistrationBean(
                urlPatterns: ['/**'],
                filter: new Filter() {
                    @Override
                    void init(final FilterConfig filterConfig) throws ServletException {
                    }

                    @Override
                    void doFilter(final ServletRequest servletRequest,
                                  final ServletResponse servletResponse,
                                  final FilterChain filterChain) throws IOException, ServletException {
                        final HttpServletResponse response = (HttpServletResponse) servletResponse
                        response.addHeader('X-FRAME-OPTIONS', 'DENY')
                        filterChain.doFilter(servletRequest, servletResponse)
                    }

                    @Override
                    void destroy() {
                    }
                }
        )
    }
}

Solution 2: Using Spring Security

Spring Security provides a very easy way to set the X-FRAME-OPTIONS header:-

@Configuration
@EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        http.
                headers().frameOptions().deny(). 
                and().
                authorizeRequests().
                antMatchers('/**').permitAll()
    }
}

JEE Security: Disabling HTTP OPTIONS method

PROBLEM

HTTP OPTIONS method is used to provide a list of methods that are supported by the web server.

For example, the following shows both GET and HEAD are allowed on the given link:-

➜  ~ curl -i -k -X OPTIONS https://localhost:8443/
HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: DENY
X-Application-Context: application:local:8443
Allow: GET,HEAD
Content-Length: 0
Date: Thu, 31 Aug 2017 14:07:21 GMT

Enabling OPTIONS may increase the risk of cross-site tracing (XST)… see OWASP’s Test HTTP Methods (OTG-CONFIG-006).

SOLUTION

There are several ways to disable OPTIONS method.

Solution 1: Using web.xml

If your app has web.xml, you may add the following snippet:-

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
		 http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         metadata-complete="true" version="3.1">

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>restricted methods</web-resource-name>
            <url-pattern>/*</url-pattern>
            <http-method>OPTIONS</http-method>
        </web-resource-collection>
        <auth-constraint/>
    </security-constraint>
	
    <!-- Other configurations -->
</web-app>

Solution 2: Using Spring Boot

If you are using Spring Boot, there isn’t any option to mimic the above configuration programmatically.

However, you still can use web.xml in conjunction with Spring Boot by setting metadata-complete to false and use servlet version 3.0 or higher:-

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
		 http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         metadata-complete="false" version="3.1">

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>restricted methods</web-resource-name>
            <url-pattern>/*</url-pattern>
            <http-method>OPTIONS</http-method>
        </web-resource-collection>
        <auth-constraint/>
    </security-constraint>
</web-app>

Solution 3: Using Spring Security

If you don’t want to use web.xml, you may configure Spring Security to disable OPTIONS method on all URIs:-

@Configuration
@EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        http.authorizeRequests().
                antMatchers(HttpMethod.OPTIONS, '/**').denyAll().
                antMatchers('/**').permitAll()
    }
}

Now, when trying to hit the same link with OPTIONS method, the app will return 403 Forbidden:-

➜  ~ curl -i -k -X OPTIONS https://localhost:8443/
HTTP/1.1 403
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: DENY
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Thu, 31 Aug 2017 14:26:51 GMT