typestack / class-sanitizer Goto Github PK
View Code? Open in Web Editor NEWDecorator based class property sanitation in Typescript.
License: MIT License
Decorator based class property sanitation in Typescript.
License: MIT License
The ToInt
sanitizer doesn't take into account empty string. Looks like a validator.js
upstream issue, but since it's currently locked on version 5, which is five major versions behind, it's worth testing if a simple bump will work first. Otherwise this issue should be taken there.
class Query {
@ToInt()
readonly page: number
}
const q = new Query() as any
q.page = ''
sanitize(q)
console.log(q) // { page: '' }
Reproduction: https://stackblitz.com/edit/class-sanitizer-to-int-issue?file=index.ts
This happens with query parameters sent as &page=
.
Sanitising a class that inherits properties from another class does not sanitise the inherit properties
class Class1 {
@ToInt()
id:number;
}
class Class2 extends Class1 {
@IsEmail()
email:string;
}
const instance = new Class2();
instance.email = '[email protected]'; // gets sanitised correctly
instance.id = 'Not_A_Number'; // does not get sanitised
sanitize(instance);
Probably the same as in class-validator: typestack/class-validator#31
When a decorator is applied to a field it is applied not only when validating instances of its class, but when validating any class.
Consider this:
export class A {
message: string;
}
export class B {
@Escape()
message: string;
}
let a = new A();
a.message = '<';
sanitize(a);
console.log(a);
// Expected output: A { message: '<' }
// Actual output: A { message: '<' }
I suspect that MetadataStorage.getSanitizeMetadatasForObject() is not working correctly.
Consider this TypeORM entity:
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
import { IsEmail, IsUrl, ValidateIf } from 'class-validator';
import { Trim } from 'class-sanitizer';
import { BaseEntity } from './base';
@Entity()
export class User extends BaseEntity {
@PrimaryGeneratedColumn('uuid') id: string;
@ValidateIf((_, v) => typeof v === 'string')
@IsEmail({
allow_display_name: false,
require_tld: true,
})
@Column({ type: 'varchar', unique: true, nullable: true })
email: string | null;
@Trim()
@Column({ type: 'varchar', nullable: false })
name: string;
@ValidateIf((_, v) => typeof v === 'string')
@IsUrl({
require_protocol: true,
require_valid_protocol: true,
protocols: ['https', 'http'],
})
@Column({ type: 'text', nullable: true })
photoUrl: string | null;
}
Because the email address is nullable, there is currently no simple way to sanitize it. If the the value is null, and we use normalizeEmail
, the entity will fail to be persisted in the database. It would be nice to have a decorator, sanitizeIf
, similar to validateIf
, that only calls the sanitization function when the value is a string.
Seems like a common use-case to want to add a default value if it's missing completely (undefined
, possibly extensible to null
by config).
class Query {
@ToInt()
@Default(1)
page: number
}
const q = new Query() as any
sanitize(q) // { page: 1 }
I have the following TypeORM entities:
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
import { IsNotEmpty } from 'class-validator';
import { Trim } from 'class-sanitizer';
import { BaseEntity } from './BaseEntity';
/**
* A event's label
* @example democracy, philosophy
*/
@Entity()
export class EventLabel extends BaseEntity {
@PrimaryGeneratedColumn('uuid') id: string;
@Trim()
@IsNotEmpty()
@Column({ type: 'varchar', unique: true, nullable: false })
text: string;
@Column({ type: 'datetime', nullable: false })
createdAt: Date;
}
import {
Entity,
Column,
PrimaryGeneratedColumn,
ManyToOne,
OneToMany,
} from 'typeorm';
import { IsNotEmpty, ValidateIf, IsUrl } from 'class-validator';
import { BaseEntity } from './BaseEntity';
import { EditorialSummary } from './EditorialSummary';
import { EditorialSummaryNodeType } from '../../typings/schema';
import { urlValidationOptions } from '../../helpers/validation';
const nodeTypes: Record<EditorialSummaryNodeType, string> = {
quote: '',
heading: '',
paragraph: '',
text: '',
link: '',
emphasis: '',
};
export type NodeType = keyof typeof nodeTypes;
/**
* Editorial content from the old Hollowverse website
*/
@Entity()
export class EditorialSummaryNode extends BaseEntity {
@PrimaryGeneratedColumn('uuid') id: string;
/**
* The order of the editorial summary node in the
* original text, used to reconstruct the text
*/
@Column({
nullable: false,
type: 'smallint',
})
order: number;
@Column({
nullable: false,
type: 'enum',
default: null,
enum: Object.keys(nodeTypes),
})
type: NodeType;
@Column('varchar', {
nullable: true,
length: 1000,
})
@ValidateIf((_, v) => typeof v === 'string')
@IsNotEmpty()
text: string | null;
@ValidateIf((_, v) => typeof v === 'string')
@IsUrl(urlValidationOptions)
@Column('varchar', {
nullable: true,
length: 1000,
})
sourceUrl: string | null;
@Column({
nullable: true,
type: 'varchar',
})
sourceTitle: string | null;
@ManyToOne(
_ => EditorialSummary,
editorialSUmmary => editorialSUmmary.nodes,
{
nullable: false,
},
)
editorialSummary: EditorialSummary;
@ManyToOne(_ => EditorialSummaryNode, node => node.children, {
cascade: ['insert', 'update'],
})
parent: EditorialSummaryNode | null;
@OneToMany(_ => EditorialSummaryNode, node => node.parent, {
cascade: ['insert', 'update'],
})
children: EditorialSummaryNode[];
}
This is the base entity:
import { BeforeInsert, BeforeUpdate } from 'typeorm';
import { validateOrReject } from 'class-validator';
import { sanitizeAsync } from 'class-sanitizer';
/**
* Base entity for the database layer.
*
* All entities should extend this class to automatically
* perform validations on insertions and updates.
*/
export class BaseEntity {
@BeforeInsert()
async validate() {
await sanitizeAsync(this);
return validateOrReject(this);
}
@BeforeUpdate()
async validateUpdate() {
await sanitizeAsync(this);
return validateOrReject(this, { skipMissingProperties: true });
}
}
Both entities have a property named text
, but only EventLabel.text
should be trimmed. I was debugging typeorm/typeorm#1397 and I wondered what would happen if I removed all references of @Trim
in the entire codebase, and it turns out that commenting the call to @Trim
in EventLabel
fixes the mentioned issue.
Thank you!
The docs describe how to create a custom sanitization class, but it seems impossible to add additional arguments. I'm trying to implement a Default
sanitizer (see #11), but I'm hitting some limitations with decorators.
After seeing that there are no "official" additional arguments, my first intuition was to use a function which returns a class:
export default function (defaultValue: any) {
return class implements SanitizerInterface {
public sanitize (value: any): any {
console.log('default sanitizer', value, args)
return value === undefined ? defaultValue : value
}
}
}
However, I cannot apply the decorator to a class expression, only to class declarations. I cannot add the default value to constructor either because I'm not the one calling new
.
I'll try to find a way to emulate applying decorator on the class expression instead of on a class declaration, but would like an official solution.
Installed direcory only with package.json and readme files.
I have tried this class with typeorm entities, like this:
export class Post{
@PrimaryGeneratedColumn()
post_id: number;
@Sanitize(SanitizeHtml)
@Column({type:'text',nullable:true})
comment:string;
@BeforeInsert()
purifyInsert() {
sanitize(this)
}
@BeforeUpdate()
purifyUpdate(){
sanitize(this)
}
}
SanitizeHtml.ts
@SanitizerConstraint()
export class SanitizeHtml implements SanitizerInterface {
sanitize(value: any): any {
let val = DOMPurify.sanitize(value);
console.log(val)
return val;
}
}
When I run this code:
let sell = new Post();
sell.comment ="<p>asddsafdsaassdadsa</p>";
sanitize(sell)
I found that the sanitizer was running 16 times in the console, which ,I believe, is caused by the other 15 entities also using the same custom sanitizer on columns with the same name comment
.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.