Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stripes 953v2 - Export sanitize logic for usage at the module level. #79

Merged
merged 8 commits into from
Dec 10, 2024
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## IN PROGRESS
* Only change `value` prop to `ReactQuill` if `DOMPurify` made changes. Refs STRIPES-953.
* Export `sanitize` function for module-level value sanitization. Refs STRIPES-953 also.

## [3.4.1](https://github.com/folio-org/stripes-template-editor/tree/v3.4.1) (2024-11-13)
[Full Changelog](https://github.com/folio-org/stripes-template-editor/compare/v3.4.0...v3.4.1)
Expand Down
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,40 @@ Version 2.0. See the file "[LICENSE](LICENSE)" for more information.

This is a NPM module to aid with embedding the Quill editor in [Stripes](https://github.com/folio-org/stripes-core/) applications for building templates with token substitution.


## Value Sanitization

In any case where a user-created HTML string will be rendered directly to the UI, it should be sanitized to eliminate any issues with malformed tags/attributes. This library exports a `sanitize` function that should be used within the ui-module prior to passing the value to the form. The function accepts the value to be rendered and an optional overriding configuration for the sanitization library. It will return the sanitized string if any removals were necessary, otherwise it will return the original parameter value.

```
import { sanitize, TemplateEditor } from '@folio/stripes-template-editor'


const value = persistedValue // value obtained from backend...

const appliedValue = sanitize(value);

<Form initialValues={{ template: appliedValue }}>
<Field component="TemplateEditor">
</Form>


```

If the sanitization needs to be adjusted for specific use-cases, it can be imported and extended...

```
import { SANITIZE_CONFIG } from '@folio/stripes-template-editor`;

const localConfig = { ...SANITIZE_CONFIG, ...MY_CONFIG, };

const appliedValue = sanitize(value, localConfig);

```

For the configuration possibilities, reference the [`DOMPurify` configuration details](https://github.com/cure53/DOMPurify?tab=readme-ov-file#can-i-configure-dompurify) if needed!


## Attribution

@skomorokh extracted this from ui-circulation in [this commit](https://github.com/folio-org/ui-circulation/commit/ead94d580d7e0be4e8b9f17d9fc99a2e43fb8cae). The code was largely written by @maximdidenkoepam and @skomorokh probably should have made more of an effort to bring the commit history along. However, you can view it at the originating module.
Expand Down
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { TemplateEditor, PreviewModal, TokensSection, tokensReducer } from './src';
export { TemplateEditor, PreviewModal, TokensSection, tokensReducer, sanitize, SANITIZE_CONFIG } from './src';
14 changes: 3 additions & 11 deletions src/TemplateEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import ReactQuill, { Quill } from 'react-quill';
import { v4 as uuidv4 } from 'uuid';
import DOMPurify from 'dompurify';

import {
isNull,
forEach,
Expand All @@ -19,6 +19,7 @@ import EditorToolbar from './EditorToolbar';
import PreviewModal from './PreviewModal';
import ControlHeader from './ControlHeader';
import ValidationContainer from './ValidationContainer';
import { sanitize } from './sanitizer';

import tokensReducer from './tokens-reducer';
import IndentStyle from './Attributors/indent';
Expand Down Expand Up @@ -212,16 +213,7 @@ class TemplateEditor extends React.Component {

const invalid = (touched || submitFailed) && !valid && !showTokensDialog;

// DOMPurify reverses the order of attributes, so any supplied tags with attributes will ALWAYS
// have different output - so we just check for any changes DOMPurify might have made before using
// the string it produces.
let appliedValue = DOMPurify.sanitize(value, { ADD_TAGS: ['Barcode'], ADD_ATTR: ['target', 'rel'] });
if (value !== appliedValue) {
const removed = DOMPurify.removed.map((item) => item.attribute?.name || item.element?.outerHTML);
if (removed && removed.length === 0) {
appliedValue = value;
}
}
const appliedValue = sanitize(value);

return (
<>
Expand Down
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export { default as TemplateEditor } from './TemplateEditor';
export { default as PreviewModal } from './PreviewModal';
export { default as TokensSection } from './TokensSection';
export { default as tokensReducer } from './tokens-reducer';
export { sanitize, SANITIZE_CONFIG } from './sanitizer';
16 changes: 16 additions & 0 deletions src/sanitizer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import DOMPurify from 'dompurify';

export const SANITIZE_CONFIG = { ADD_TAGS: ['Barcode'], ADD_ATTR: ['target', 'rel'] };

export const sanitize = (value, config = SANITIZE_CONFIG) => {
// since DOMPurify has a known issue of reversing the order of attributes in perfectly admissible HTML
// we check to see if the value was affected - and if not, we just return the unaffected value.
let resultValue = DOMPurify.sanitize(value, config);
if (value !== resultValue) {
const removed = DOMPurify.removed.map((item) => item.attribute?.name || item.element?.outerHTML);
if (removed && removed.length === 0) {
resultValue = value;
}
}
return resultValue;
};
Loading