fix(database): preserve JSON field setters

This commit is contained in:
katherinehhh
2026-08-28 13:06:50 +08:00
parent fdf8555a06
commit 9fc42e80bc
2 changed files with 49 additions and 1 deletions
@@ -14,6 +14,10 @@ const fieldContext = {
collection: {},
database: {
inDialect: () => false,
sequelize: {
getDialect: () => 'sqlite',
normalizeDataType: (dataType: unknown) => dataType,
},
},
};
@@ -44,6 +48,37 @@ describe('rich text field sanitization', () => {
expect(field.setter(value)).toBe(value);
});
it('does not install a Sequelize setter on ordinary JSON fields', () => {
const customSetter = vi.fn();
const field = new JsonField({ type: 'json', name: 'content', set: customSetter }, fieldContext as never);
expect(field.additionalSequelizeOptions()).toEqual({});
expect(field.toSequelize().set).toBe(customSetter);
});
it('preserves a rich text JSON custom setter and sanitizes its output', () => {
const customSetter = vi.fn(function (
this: { setDataValue: (name: string, value: unknown) => void },
value: unknown,
) {
this.setDataValue('content', `${value}<script>alert(1)</script>`);
});
const field = new JsonField(
{ type: 'json', name: 'content', interface: 'richText', set: customSetter },
fieldContext as never,
);
const values = new Map<string, unknown>();
const model = {
getDataValue: (name: string) => values.get(name),
setDataValue: (name: string, value: unknown) => values.set(name, value),
};
field.toSequelize().set.call(model, '<p>safe</p><img src=x onerror="alert(1)">');
expect(customSetter).toHaveBeenCalledWith('<p>safe</p><img src="x" />');
expect(values.get('content')).toBe('<p>safe</p><img src="x" />');
});
it('leaves non-string JSON rich text values unchanged', () => {
const field = new JsonField({ type: 'json', name: 'content', interface: 'richText' }, fieldContext as never);
const value = { delta: [{ insert: '<img src=x onerror="alert(1)">' }] };
@@ -30,11 +30,24 @@ export class JsonField extends Field {
}
additionalSequelizeOptions() {
const { name } = this.options;
if (this.options.interface !== 'richText') {
return {};
}
const { name, set: originalSetter } = this.options;
const normalizeValue = (value: unknown) => this.normalizeValue(value);
return {
set(value) {
if (typeof originalSetter === 'function') {
originalSetter.call(this, normalizeValue(value));
const currentValue = this.getDataValue(name);
const normalizedValue = normalizeValue(currentValue);
if (!Object.is(normalizedValue, currentValue)) {
this.setDataValue(name, normalizedValue);
}
return;
}
this.setDataValue(name, normalizeValue(value));
},
};