1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-09-02 11:42:53 +02:00

implement scrubber for end user data in exceptions (#4999)

This commit is contained in:
Alexander Campbell
2022-05-26 15:44:22 -06:00
committed by GitHub
parent 25be3b7031
commit fe13a8f9e7
12 changed files with 202 additions and 36 deletions

View File

@@ -1,5 +1,5 @@
import React, { useMemo, useState, useCallback, useEffect, useRef } from 'react'
import { Editor, Node, Element, Descendant } from 'slate'
import React, { useState, useCallback, useEffect, useRef } from 'react'
import { Editor, Node, Descendant, Scrubber } from 'slate'
import { ReactEditor } from '../plugin/react-editor'
import { FocusedContext } from '../hooks/use-focused'
import { EditorContext } from '../hooks/use-slate-static'
@@ -30,12 +30,13 @@ export const Slate = (props: {
if (!Node.isNodeList(value)) {
throw new Error(
`[Slate] value is invalid! Expected a list of elements` +
`but got: ${JSON.stringify(value)}`
`but got: ${Scrubber.stringify(value)}`
)
}
if (!Editor.isEditor(editor)) {
throw new Error(
`[Slate] editor is invalid! you passed:` + `${JSON.stringify(editor)}`
`[Slate] editor is invalid! you passed:` +
`${Scrubber.stringify(editor)}`
)
}
editor.children = value

View File

@@ -1,4 +1,13 @@
import { Editor, Node, Path, Point, Range, Transforms, BaseEditor } from 'slate'
import {
BaseEditor,
Editor,
Node,
Path,
Point,
Range,
Scrubber,
Transforms,
} from 'slate'
import { Key } from '../utils/key'
import {
@@ -108,7 +117,7 @@ export const ReactEditor = {
}
throw new Error(
`Unable to find the path for Slate node: ${JSON.stringify(node)}`
`Unable to find the path for Slate node: ${Scrubber.stringify(node)}`
)
},
@@ -285,7 +294,7 @@ export const ReactEditor = {
if (!domNode) {
throw new Error(
`Cannot resolve a DOM node from Slate node: ${JSON.stringify(node)}`
`Cannot resolve a DOM node from Slate node: ${Scrubber.stringify(node)}`
)
}
@@ -337,7 +346,9 @@ export const ReactEditor = {
if (!domPoint) {
throw new Error(
`Cannot resolve a DOM point from Slate point: ${JSON.stringify(point)}`
`Cannot resolve a DOM point from Slate point: ${Scrubber.stringify(
point
)}`
)
}

View File

@@ -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'

View File

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

View File

@@ -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)
},
}

View File

@@ -1,17 +1,18 @@
import { createDraft, finishDraft, isDraft } from 'immer'
import {
Node,
Editor,
Selection,
Range,
Point,
Text,
Element,
Operation,
Descendant,
NodeEntry,
Path,
Ancestor,
Descendant,
Editor,
Element,
Node,
NodeEntry,
Operation,
Path,
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.`
)

View File

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

View File

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

View File

@@ -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