Airbnb clone, safe HTML for the house description

Join the 2022 Full-Stack Web Dev Bootcamp!


This post is part of a new series where we build a clone of Airbnb with Next.js. See the first post here.

Our form is great, but we have a problem: the house description.

It is handling the description as HTML:

Unless our target audience for house owners is people that know HTML, this is not going to do a good job.

We need a more user friendly editor.

Second thing, even more important, is that we still don’t have it in the single page view, in the pages/houses/[id].js file.

I deferred this task until now, because I want to show you something.

If we add the house description to pages/houses/[id].js:

...
<p>{props.house.title}</p>
<div>{props.house.description}</div>
...

We’ll find a bad surprise:

The HTML is fully visible, and not interpreted! Just like in the editor above.

The reason is that JSX escapes all content by default, which is great to prevent XSS for example, but not great in this case.

What we must do is, we have to use dangerouslySetInnerHTML:

pages/houses/[id].js

<div
  dangerouslySetInnerHTML={{
    __html: props.house.description
  }}></div>

to make the description interpreted as HTML.

But if we don’t do any kind of filtering to the description, a house owner could also write JavaScript into the description, for example:

<script>alert('test')</script>

And this would run on the house page to people browsing for houses.

This is an XSS vulnerability.

Let’s do the first step first - we add an editor.

Add the Pell editor

The editor I chose is Pell.

It’s easy to use and small in size.

There’s also a React wrapper available at https://github.com/bpetetot/react-pell.

Install it using npm:

npm install react-pell

then go into components/HouseForm.js and import it:

import Editor from 'react-pell'

Now change

<p>
  <label>House description</label>
  <textarea
    required
    onChange={event => setDescription(event.target.value)}
    value={description}></textarea>
</p>

to this:

<div>
  <Editor
    onChange={html => setDescription(html)}
    defaultContent={description}
    actions={['bold', 'underline', 'italic']}
  />
</div>

and add this CSS block at the bottom, before the closing </div> tag of the component:

<style jsx global>{`
  .pell-container {
    border: 1px solid #ccc;
  }
  .pell,
  .pell-content {
    box-sizing: border-box;
  }
  .pell-content {
    height: 300px;
    outline: 0;
    overflow-y: auto;
    padding: 10px;
  }
  .pell-actionbar {
    background-color: #fff;
    border-bottom: 1px solid hsla(0, 0%, 4%, 0.1);
  }
  .pell-button {
    background-color: transparent;
    border: none;
    cursor: pointer;
    height: 30px;
    outline: 0;
    width: 30px;
    vertical-align: bottom;
    color: black;
  }
  .pell-button-selected {
    background-color: #f0f0f0;
  }
`}</style>

This is the end result:

You should now be able to save the house description.

Solving the XSS problem

Now off to the second part - the XSS vulnerability caused by the use of dangerouslySetInnerHTML.

How do we solve that? In the backend we are going to whitelist some tags in the description, eliminating all other tags. I’m going to use the sanitize-html npm package.

Run

npm install sanitize-html

Now in server.js we add this line at the top:

const sanitizeHtml = require('sanitize-html')

and before saving the house using Sequelize in the /api/host/edit and /api/host/new endpoints, we add:

houseData.description = sanitizeHtml(houseData.description, {
  allowedTags: [ 'b', 'i', 'em', 'strong', 'p', 'br' ]
})

This will clean the description and remove all tags except the ones we specifically allow.

Next part: Airbnb clone, upload the house image

Want to become a better Web Developer? Join the 2022 Web Development Bootcamp!

⭐️⭐️⭐️ Join the 2022 Web Development Bootcamp ⭐️⭐️⭐️