From 2431957df116a570522b9e03a47b1603fbaf1f12 Mon Sep 17 00:00:00 2001 From: Alexander Campbell Date: Thu, 26 May 2022 15:44:22 -0600 Subject: [PATCH] implement scrubber for end user data in exceptions (#4999) --- src/index.ts | 3 ++- src/interfaces/node.ts | 22 ++++++++++++------- src/interfaces/scrubber.ts | 33 ++++++++++++++++++++++++++++ src/transforms/general.ts | 23 ++++++++++--------- src/transforms/node.ts | 9 ++++---- src/transforms/selection.ts | 4 ++-- test/interfaces/Scrubber/scrubber.ts | 24 ++++++++++++++++++++ 7 files changed, 93 insertions(+), 25 deletions(-) create mode 100644 src/interfaces/scrubber.ts create mode 100644 test/interfaces/Scrubber/scrubber.ts diff --git a/src/index.ts b/src/index.ts index 2088071146..73c70a952e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ export * from './create-editor' +export * from './interfaces/custom-types' export * from './interfaces/editor' export * from './interfaces/element' export * from './interfaces/location' @@ -10,6 +11,6 @@ export * from './interfaces/point' export * from './interfaces/point-ref' export * from './interfaces/range' export * from './interfaces/range-ref' +export * from './interfaces/scrubber' export * from './interfaces/text' -export * from './interfaces/custom-types' export * from './transforms' diff --git a/src/interfaces/node.ts b/src/interfaces/node.ts index 7489b094a8..a316e88c81 100644 --- a/src/interfaces/node.ts +++ b/src/interfaces/node.ts @@ -1,5 +1,5 @@ import { produce } from 'immer' -import { Editor, Path, Range, Text } from '..' +import { Editor, Path, Range, Text, Scrubber } from '..' import { Element, ElementEntry } from './element' /** @@ -112,7 +112,9 @@ export const Node: NodeInterface = { if (Text.isText(node)) { throw new Error( - `Cannot get the ancestor node at path [${path}] because it refers to a text node instead: ${node}` + `Cannot get the ancestor node at path [${path}] because it refers to a text node instead: ${Scrubber.stringify( + node + )}` ) } @@ -145,7 +147,7 @@ export const Node: NodeInterface = { child(root: Node, index: number): Descendant { if (Text.isText(root)) { throw new Error( - `Cannot get the child of a text node: ${JSON.stringify(root)}` + `Cannot get the child of a text node: ${Scrubber.stringify(root)}` ) } @@ -153,7 +155,7 @@ export const Node: NodeInterface = { if (c == null) { throw new Error( - `Cannot get child at index \`${index}\` in node: ${JSON.stringify( + `Cannot get child at index \`${index}\` in node: ${Scrubber.stringify( root )}` ) @@ -203,7 +205,9 @@ export const Node: NodeInterface = { if (Editor.isEditor(node)) { throw new Error( - `Cannot get the descendant node at path [${path}] because it refers to the root editor node instead: ${node}` + `Cannot get the descendant node at path [${path}] because it refers to the root editor node instead: ${Scrubber.stringify( + node + )}` ) } @@ -287,7 +291,7 @@ export const Node: NodeInterface = { fragment(root: Node, range: Range): Descendant[] { if (Text.isText(root)) { throw new Error( - `Cannot get a fragment starting from a root text node: ${JSON.stringify( + `Cannot get a fragment starting from a root text node: ${Scrubber.stringify( root )}` ) @@ -339,7 +343,7 @@ export const Node: NodeInterface = { if (Text.isText(node) || !node.children[p]) { throw new Error( - `Cannot find a descendant at path [${path}] in node: ${JSON.stringify( + `Cannot find a descendant at path [${path}] in node: ${Scrubber.stringify( root )}` ) @@ -428,7 +432,9 @@ export const Node: NodeInterface = { if (!Text.isText(node)) { throw new Error( - `Cannot get the leaf node at path [${path}] because it refers to a non-leaf node: ${node}` + `Cannot get the leaf node at path [${path}] because it refers to a non-leaf node: ${Scrubber.stringify( + node + )}` ) } diff --git a/src/interfaces/scrubber.ts b/src/interfaces/scrubber.ts new file mode 100644 index 0000000000..5ed99b005f --- /dev/null +++ b/src/interfaces/scrubber.ts @@ -0,0 +1,33 @@ +export type Scrubber = (key: string, value: unknown) => unknown + +export interface ScrubberInterface { + setScrubber(scrubber: Scrubber | undefined): void + stringify(value: any): string +} + +let _scrubber: Scrubber | undefined = undefined + +/** + * This interface implements a stringify() function, which is used by Slate + * internally when generating exceptions containing end user data. Developers + * using Slate may call Scrubber.setScrubber() to alter the behavior of this + * stringify() function. + * + * For example, to prevent the cleartext logging of 'text' fields within Nodes: + * + * import { Scrubber } from 'slate'; + * Scrubber.setScrubber((key, val) => { + * if (key === 'text') return '...scrubbed...' + * return val + * }); + * + */ +export const Scrubber: ScrubberInterface = { + setScrubber(scrubber: Scrubber | undefined): void { + _scrubber = scrubber + }, + + stringify(value: any): string { + return JSON.stringify(value, _scrubber) + }, +} diff --git a/src/transforms/general.ts b/src/transforms/general.ts index dea89c243b..c9d1dce76e 100644 --- a/src/transforms/general.ts +++ b/src/transforms/general.ts @@ -1,17 +1,18 @@ import { createDraft, finishDraft, isDraft } from 'immer' import { - Node, + Ancestor, + Descendant, Editor, - Selection, - Range, - Point, - Text, Element, - Operation, - Descendant, + Node, NodeEntry, + Operation, Path, - Ancestor, + Point, + Range, + Scrubber, + Selection, + Text, } from '..' export interface GeneralTransforms { @@ -73,7 +74,9 @@ const applyToDraft = (editor: Editor, selection: Selection, op: Operation) => { prev.children.push(...node.children) } else { throw new Error( - `Cannot apply a "merge_node" operation at path [${path}] to nodes of different interfaces: ${node} ${prev}` + `Cannot apply a "merge_node" operation at path [${path}] to nodes of different interfaces: ${Scrubber.stringify( + node + )} ${Scrubber.stringify(prev)}` ) } @@ -236,7 +239,7 @@ const applyToDraft = (editor: Editor, selection: Selection, op: Operation) => { if (selection == null) { if (!Range.isRange(newProperties)) { throw new Error( - `Cannot apply an incomplete "set_selection" operation properties ${JSON.stringify( + `Cannot apply an incomplete "set_selection" operation properties ${Scrubber.stringify( newProperties )} when there is no current selection.` ) diff --git a/src/transforms/node.ts b/src/transforms/node.ts index 7574ebc843..c3bfa46949 100644 --- a/src/transforms/node.ts +++ b/src/transforms/node.ts @@ -1,15 +1,16 @@ import { + Ancestor, Editor, Element, Location, Node, + NodeEntry, Path, Point, Range, + Scrubber, Text, Transforms, - NodeEntry, - Ancestor, } from '..' import { NodeMatch, PropsCompare, PropsMerge } from '../interfaces/editor' import { PointRef } from '../interfaces/point-ref' @@ -406,9 +407,9 @@ export const NodeTransforms: NodeTransforms = { properties = rest as Partial } else { throw new Error( - `Cannot merge the node at path [${path}] with the previous sibling because it is not the same kind: ${JSON.stringify( + `Cannot merge the node at path [${path}] with the previous sibling because it is not the same kind: ${Scrubber.stringify( node - )} ${JSON.stringify(prevNode)}` + )} ${Scrubber.stringify(prevNode)}` ) } diff --git a/src/transforms/selection.ts b/src/transforms/selection.ts index 91275caed2..5cfaac45f1 100644 --- a/src/transforms/selection.ts +++ b/src/transforms/selection.ts @@ -1,4 +1,4 @@ -import { Editor, Location, Point, Range, Transforms } from '..' +import { Editor, Location, Point, Range, Scrubber, Transforms } from '..' import { SelectionEdge, MoveUnit } from '../interfaces/types' export interface SelectionCollapseOptions { @@ -132,7 +132,7 @@ export const SelectionTransforms: SelectionTransforms = { if (!Range.isRange(target)) { throw new Error( - `When setting the selection and the current selection is \`null\` you must provide at least an \`anchor\` and \`focus\`, but you passed: ${JSON.stringify( + `When setting the selection and the current selection is \`null\` you must provide at least an \`anchor\` and \`focus\`, but you passed: ${Scrubber.stringify( target )}` ) diff --git a/test/interfaces/Scrubber/scrubber.ts b/test/interfaces/Scrubber/scrubber.ts new file mode 100644 index 0000000000..c36e3038a5 --- /dev/null +++ b/test/interfaces/Scrubber/scrubber.ts @@ -0,0 +1,24 @@ +import { Node, Scrubber } from 'slate' + +export const input = { + customField: 'some very long custom field value that will get scrubbed', + anotherField: 'this field should not get scrambled', +} + +export const test = (value: Node) => { + Scrubber.setScrubber((key, value) => + key === 'customField' ? '... scrubbed ...' : value + ) + const stringified = Scrubber.stringify(value) + Scrubber.setScrubber(undefined) + + const unmarshaled = JSON.parse(stringified) + return ( + // ensure that first field has been scrubbed + unmarshaled.customField === '... scrubbed ...' && + // ensure that second field is unaltered + unmarshaled.anotherField === input.anotherField + ) +} + +export const output = true