Skip to content

Commit

Permalink
implement scrubber for end user data in exceptions (ianstormtaylor#4999)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexandercampbell authored May 26, 2022
1 parent f3c69cc commit 2431957
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 25 deletions.
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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'
22 changes: 14 additions & 8 deletions src/interfaces/node.ts
Original file line number Diff line number Diff line change
@@ -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'

/**
Expand Down Expand Up @@ -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
)}`
)
}

Expand Down Expand Up @@ -145,15 +147,15 @@ 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)}`
)
}

const c = root.children[index] as Descendant

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
)}`
)
Expand Down Expand Up @@ -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
)}`
)
}

Expand Down Expand Up @@ -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
)}`
)
Expand Down Expand Up @@ -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
)}`
)
Expand Down Expand Up @@ -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
)}`
)
}

Expand Down
33 changes: 33 additions & 0 deletions src/interfaces/scrubber.ts
Original file line number Diff line number Diff line change
@@ -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)
},
}
23 changes: 13 additions & 10 deletions src/transforms/general.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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)}`
)
}

Expand Down Expand Up @@ -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.`
)
Expand Down
9 changes: 5 additions & 4 deletions src/transforms/node.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -406,9 +407,9 @@ export const NodeTransforms: NodeTransforms = {
properties = rest as Partial<Element>
} 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)}`
)
}

Expand Down
4 changes: 2 additions & 2 deletions src/transforms/selection.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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
)}`
)
Expand Down
24 changes: 24 additions & 0 deletions test/interfaces/Scrubber/scrubber.ts
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 2431957

Please sign in to comment.