├── .babelrc
├── .eslintrc.json
├── .gitattributes
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── SECURITY.md
├── assets
├── react-safe-src-doc-iframe-logo.png
└── safe-src-doc-iframe-logo.psd
├── package-lock.json
├── package.json
├── setup-tests.js
├── src
├── __snapshots__
│ └── safe-src-doc-iframe.test.js.snap
├── safe-src-doc-iframe.js
└── safe-src-doc-iframe.test.js
├── webpack.dev.config.js
├── webpack.prod.config.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "umd": {
4 | "presets": ["env", "react"],
5 | "plugins": ["transform-object-rest-spread", "transform-class-properties"]
6 | },
7 | "es": {
8 | "presets": [
9 | [
10 | "env",
11 | {
12 | "modules": false
13 | }
14 | ],
15 | "react"
16 | ],
17 | "plugins": ["transform-object-rest-spread", "transform-class-properties"]
18 | },
19 | "test": {
20 | "presets": ["env", "react"],
21 | "plugins": ["transform-object-rest-spread", "transform-class-properties"]
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "node": true,
4 | "es6": true,
5 | "jest": true,
6 | "browser": true
7 | },
8 | "parser": "babel-eslint",
9 | "extends": ["godaddy-react"],
10 | "plugins": ["jsx-a11y"],
11 | "rules": {
12 | "jsx-a11y/iframe-has-title": "error"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | package-lock.json binary
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | coverage/
3 | *.log
4 | .DS_Store
5 | .vscode
6 | es
7 | dev
8 | dist
9 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
7 |
8 | ## [1.1.0] - 2019-04-22
9 |
10 | ### Chore
11 |
12 | - add peer dependency react 16, react dom 16
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018 GoDaddy Operating Company, LLC.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Safe Srcdoc Iframe
2 |
3 | > A component which applies guards to srcdoc iframes in order to provide a predictable and safe experience to the user. Complements the `sandbox` native iframe attribute.
4 |
5 |
6 |
7 |
8 |
9 | ## Table of Contents
10 |
11 | - [The Problem](#the-problem-)
12 | - [This Solution](#this-solution-)
13 | - [Guards](#guards-)
14 | - [Install](#install)
15 | - [Import Examples](#import-examples)
16 | - [Usage Example](#usage-example-)
17 | - [Component Props](#component-props)
18 | - [referrerPolicy](#referrerpolicy)
19 | - [sandbox](#sandbox)
20 | - [srcDoc](#srcdoc)
21 | - [title](#title)
22 | - [Special Thanks](#special-thanks-)
23 | - [License](#license-)
24 | - [Changelog](/CHANGELOG.md)
25 | - [Security](/SECURITY.md)
26 |
27 | ## The Problem 🔴
28 |
29 | You need to render an html document, for which you have the source locally, into an iframe on your `React` application. However, you would like some guards applied to the document in order to provide a safe and consistent experience to the user. For example, preventing clicks on elements which could lead to page navigation.
30 |
31 | ## This Solution 🔵
32 |
33 | This component compliments the `sandbox` iframe attribute. It will take the html document source code which you provide through the `srcDoc` component prop and inject safeguards on document load.
34 |
35 | ## Guards 👮
36 |
37 | The following guards are applied to the document:
38 |
39 | - disable pointer events on any element with an href attribute, buttons, and images.
40 | - only whitelist the "allow-same-origin" `sandbox` attribute flag for guard injection into the document.
41 | - `referrerPolicy` set to "no-referrer".
42 |
43 | ## Install
44 |
45 | ```npm install --save react-safe-src-doc-iframe```
46 |
47 | Or
48 |
49 | ```yarn add react-safe-src-doc-iframe```
50 |
51 | > Note this package also depends on `react` and `prop-types`. Ensure they are installed or available beforehand.
52 |
53 | ### Import Examples
54 |
55 | ```javascript
56 | // 1) es6 module
57 | import SafeSrcDocIframe from 'react-safe-src-doc-iframe';
58 |
59 | // 2) commonjs
60 | const SafeSrcDocIframe = require('react-safe-src-doc-iframe').default;
61 |
62 | // 3) window
63 | const SafeSrcDocIframe = window.SafeSrcDocIframe;
64 | ```
65 |
66 | ## Usage Example 📝
67 |
68 | > ▶️ [Try it out on CodeSandbox!](https://codesandbox.io/s/2z4nk0nq4j)
69 |
70 | ```jsx
71 | import React from 'react';
72 | import SafeSrcDocIframe from 'react-safe-src-doc-iframe';
73 |
74 | const html = `
75 |
76 |
77 |
78 | My Cats Page
79 |
80 |
81 | About
82 |
83 |
84 |
85 |
86 | `;
87 |
88 | const App = () => {
89 | return ;
90 | };
91 | ```
92 |
93 | ## Component Props
94 |
95 | > Note: any prop not specified here will be forwarded to the native iframe element. **However**, if `src` is passed, it will always be filtered out.
96 |
97 | ### referrerPolicy
98 |
99 | > `string` | optional. Default value: `no-referrer`.
100 |
101 | ### sandbox
102 |
103 | > `string` | optional. Default value: `allow-same-origin` (for safeguard injection).
104 |
105 | The value for the `sandbox` iframe attribute.
106 |
107 | ### srcDoc
108 |
109 | > `string` | required.
110 |
111 | Source of the html document to render.
112 |
113 | ### title
114 |
115 | > `string` | required.
116 |
117 | Provide a title for the iframe in order to help screen reader users. [More info](https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/iframe-has-title.md)
118 |
119 | ## Special Thanks 👏
120 |
121 | - ["guard" icon](https://thenounproject.com/search/?q=guard&i=225519) created by Jason Gray, from [the Noun Project](https://thenounproject.com/). Used with attribution under Creative Commons.
122 | - ["Browser" icon](https://thenounproject.com/search/?q=browser&i=1850566) created by Wira, from [the Noun Project](https://thenounproject.com/). Used with attribution under Creative Commons.
123 | - [Downshift for the README format inspiration :)](https://github.com/paypal/downshift)
124 |
125 | ## License 📋
126 |
127 | [MIT](LICENSE)
128 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Reporting Security Issues
2 |
3 | We take security very seriously at GoDaddy. We appreciate your efforts to
4 | responsibly disclose your findings, and will make every effort to acknowledge
5 | your contributions.
6 |
7 | ## Where should I report security issues?
8 |
9 | In order to give the community time to respond and upgrade, we strongly urge you
10 | report all security issues privately.
11 |
12 | To report a security issue in one of our Open Source projects email us directly
13 | at **oss@godaddy.com** and include the word "SECURITY" in the subject line.
14 |
15 | This mail is delivered to our Open Source Security team.
16 |
17 | After the initial reply to your report, the team will keep you informed of the
18 | progress being made towards a fix and announcement, and may ask for additional
19 | information or guidance.
--------------------------------------------------------------------------------
/assets/react-safe-src-doc-iframe-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/godaddy/react-safe-src-doc-iframe/a6e6493a4531a142ba7395c5042b971cc564b593/assets/react-safe-src-doc-iframe-logo.png
--------------------------------------------------------------------------------
/assets/safe-src-doc-iframe-logo.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/godaddy/react-safe-src-doc-iframe/a6e6493a4531a142ba7395c5042b971cc564b593/assets/safe-src-doc-iframe-logo.psd
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-safe-src-doc-iframe",
3 | "version": "1.1.0",
4 | "description": "A component which applies guards to srcdoc iframes, providing a predictable and safe experience to the user.",
5 | "license": "MIT",
6 | "main": "dist/safe-src-doc-iframe.js",
7 | "browser": "dist/safe-src-doc-iframe.js",
8 | "module": "es/safe-src-doc-iframe.js",
9 | "scripts": {
10 | "lint": "eslint --fix --ext jsx --ext js src/",
11 | "test": "npm run lint && NODE_ENV=test jest src/",
12 | "build:dev": "BABEL_ENV=umd webpack --config webpack.dev.config.js",
13 | "build:dist": "BABEL_ENV=umd webpack --config webpack.prod.config.js",
14 | "build:es": "BABEL_ENV=es babel src --out-dir es --source-maps inline",
15 | "build-all": "npm run build:dev && npm run build:dist && npm run build:es",
16 | "prepublishOnly": "npm run build-all"
17 | },
18 | "files": [
19 | "src",
20 | "dist",
21 | "es"
22 | ],
23 | "repository": {
24 | "type": "git",
25 | "url": "https://github.com/godaddy/react-safe-src-doc-iframe.git"
26 | },
27 | "keywords": [
28 | "react",
29 | "react component",
30 | "component",
31 | "iframe",
32 | "safety",
33 | "safe guards",
34 | "srcdoc",
35 | "library"
36 | ],
37 | "author": "GoDaddy Operating Company, LLC",
38 | "contributors": [
39 | "Mayank Jethva ",
40 | "Srinath Ganesan ",
41 | "Brandon Kidd "
42 | ],
43 | "jest": {
44 | "setupTestFrameworkScriptFile": "setup-tests.js"
45 | },
46 | "devDependencies": {
47 | "babel-cli": "^6.26.0",
48 | "babel-core": "^6.26.3",
49 | "babel-eslint": "^8.2.6",
50 | "babel-loader": "^7.1.5",
51 | "babel-plugin-transform-class-properties": "^6.24.1",
52 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
53 | "babel-preset-env": "^1.7.0",
54 | "babel-preset-react": "^6.24.1",
55 | "enzyme": "^3.3.0",
56 | "enzyme-adapter-react-16": "^1.1.1",
57 | "eslint": "^5.1.0",
58 | "eslint-config-godaddy-react": "^2.2.1",
59 | "eslint-plugin-json": "^1.2.0",
60 | "eslint-plugin-jsx-a11y": "^6.1.1",
61 | "eslint-plugin-mocha": "^5.1.0",
62 | "eslint-plugin-react": "^7.10.0",
63 | "jest": "^23.4.1",
64 | "react": "^16.4.1",
65 | "react-dom": "^16.4.1",
66 | "webpack": "^4.16.1",
67 | "webpack-cli": "^3.0.8"
68 | },
69 | "peerDependencies": {
70 | "prop-types": "^15.0.0",
71 | "react": "^15.0.0 || ^16.0.0",
72 | "react-dom": "^15.0.0 || ^16.0.0"
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/setup-tests.js:
--------------------------------------------------------------------------------
1 | const Enzyme = require('enzyme');
2 | const Adapter = require('enzyme-adapter-react-16');
3 |
4 | Enzyme.configure({ adapter: new Adapter() });
5 |
--------------------------------------------------------------------------------
/src/__snapshots__/safe-src-doc-iframe.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` renders as expected 1`] = `
4 | ShallowWrapper {
5 | "length": 1,
6 | Symbol(enzyme.__root__): [Circular],
7 | Symbol(enzyme.__unrendered__): ,
22 | Symbol(enzyme.__renderer__): Object {
23 | "batchedUpdates": [Function],
24 | "getNode": [Function],
25 | "render": [Function],
26 | "simulateEvent": [Function],
27 | "unmount": [Function],
28 | },
29 | Symbol(enzyme.__node__): Object {
30 | "instance": null,
31 | "key": undefined,
32 | "nodeType": "host",
33 | "props": Object {
34 | "referrerPolicy": "no-referrer",
35 | "sandbox": "allow-same-origin",
36 | "srcDoc": "
37 |
38 |
39 |
40 |
41 | mock html page
42 |
43 |
44 | ",
45 | "title": "some-title",
46 | },
47 | "ref": [Function],
48 | "rendered": null,
49 | "type": "iframe",
50 | },
51 | Symbol(enzyme.__nodes__): Array [
52 | Object {
53 | "instance": null,
54 | "key": undefined,
55 | "nodeType": "host",
56 | "props": Object {
57 | "referrerPolicy": "no-referrer",
58 | "sandbox": "allow-same-origin",
59 | "srcDoc": "
60 |
61 |
62 |
63 |
64 | mock html page
65 |
66 |
67 | ",
68 | "title": "some-title",
69 | },
70 | "ref": [Function],
71 | "rendered": null,
72 | "type": "iframe",
73 | },
74 | ],
75 | Symbol(enzyme.__options__): Object {
76 | "adapter": ReactSixteenAdapter {
77 | "options": Object {
78 | "enableComponentDidUpdateOnSetState": true,
79 | },
80 | },
81 | },
82 | }
83 | `;
84 |
--------------------------------------------------------------------------------
/src/safe-src-doc-iframe.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const disableStylesRaw = `
5 | *[href], button, img {
6 | pointer-events: none !important;
7 | display: inline-block !important;
8 | }
9 | `;
10 |
11 | class SafesrcDocIframe extends Component {
12 |
13 | static propTypes = {
14 | title: PropTypes.string.isRequired,
15 | srcDoc: PropTypes.string.isRequired,
16 | sandbox: PropTypes.string,
17 | referrerPolicy: PropTypes.string,
18 | src: PropTypes.string
19 | };
20 |
21 | static defaultProps = {
22 | // set all restrictions for sandbox except same origin
23 | // to allow us to inject the safe guards.
24 | sandbox: 'allow-same-origin',
25 | referrerPolicy: 'no-referrer',
26 | // will be omitted from props passed to the iframe
27 | src: ''
28 | };
29 |
30 | constructor(...args) {
31 | super(...args);
32 | this.iframeElement = null;
33 | this.disableStyleTag = document.createElement('style');
34 | this.disableStylesTextNode = document.createTextNode(disableStylesRaw);
35 | this.disableStyleTag.appendChild(this.disableStylesTextNode);
36 | }
37 |
38 | componentDidMount() {
39 | if (this.iframeElement) {
40 | this.iframeElement.onload = () => {
41 | this.applySafeguards();
42 | };
43 | }
44 | }
45 |
46 | componentWillUnmount() {
47 | this.disableStylesTextNode = null;
48 | this.disableStyleTag = null;
49 | }
50 |
51 | applySafeguards() {
52 | if (!this.iframeElement.contentDocument) {
53 | return;
54 | }
55 | const [
56 | iframeBody
57 | ] = this.iframeElement.contentDocument.getElementsByTagName('body');
58 | if (iframeBody) {
59 | // add safety guards last to ensure they are always applied.
60 | iframeBody.appendChild(this.disableStyleTag);
61 | }
62 | }
63 |
64 | render() {
65 | const {
66 | title,
67 | referrerPolicy,
68 | sandbox,
69 | srcDoc,
70 | src: omit, /* eslint-disable-line no-unused-vars */
71 | ...rest
72 | } = this.props;
73 | return (
74 |