Files
twenty/packages/twenty-front/src/modules/blocknote-editor/utils/prepareBodyWithSignedUrls.ts
T
Charles Bochet 7b6fb52df7 fix: validate blocknote JSON in rich text fields (#18902)
## Summary
- **Backend**: Add JSON validation for the `blocknote` subfield in rich
text API inputs — rejects values that aren't valid JSON or aren't arrays
(BlockNote content is always `PartialBlock[]`). This prevents corrupted
data from being persisted to the database.
- **Frontend**: Replace all 5 unprotected `JSON.parse` calls on
blocknote content with the safe `parseJson` utility from
`twenty-shared`. Invalid content now degrades gracefully (empty block /
empty string / unchanged passthrough) instead of crashing the app.
- **Tests**: Added integration tests for invalid blocknote JSON (both
GraphQL and REST), unit tests for the new validation, and updated
existing test constants to use valid BlockNote JSON.

## Context
A user reported a `SyntaxError: Expected ',' or ']' after array element`
crash caused by malformed blocknote JSON stored in the database. The
data had `"children":[]` nested inside the `content` array instead of as
a sibling property. The API accepted this invalid JSON because it only
validated that `blocknote` was a string, not that it contained valid
JSON. On the frontend, 5 call sites used bare `JSON.parse` with no error
handling, causing a white-screen crash.

## Test plan
- [x] Unit tests pass: `validate-rich-text-field-or-throw.util.spec.ts`
(10/10)
- [x] Integration tests pass: `rich-text-field-create-input-validation`
(8/8)
- [ ] Verify creating a note with valid rich text still works end-to-end
- [ ] Verify API returns clear error when blocknote contains invalid
JSON
- [ ] Verify frontend renders empty block instead of crashing when
encountering corrupted data

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-03-24 14:53:15 +01:00

33 lines
946 B
TypeScript

import { parseInitialBlocknote } from '@/blocknote-editor/utils/parseInitialBlocknote';
// TODO: This function is extracted but its not doing what it is supposed to do. It is not signing the urls. It is just parsing the image urls.
// tracking issue - https://github.com/twentyhq/twenty/issues/8351
export const prepareBodyWithSignedUrls = (
newStringifiedBody: string,
): string => {
if (!newStringifiedBody) return newStringifiedBody;
const body = parseInitialBlocknote(newStringifiedBody);
if (!body) return newStringifiedBody;
const bodyWithSignedPayload = body.map((block) => {
if (block.type !== 'image' || !block.props?.url) {
return block;
}
const imageUrl = block.props.url;
const parsedImageUrl = new URL(imageUrl);
return {
...block,
props: {
...block.props,
url: parsedImageUrl.toString(),
},
};
});
return JSON.stringify(bodyWithSignedPayload);
};