diff --git a/package-lock.json b/package-lock.json index b1fc1ff..7814a12 100644 --- a/package-lock.json +++ b/package-lock.json @@ -500,6 +500,11 @@ "@types/superagent": "*" } }, + "@types/validator": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.0.0.tgz", + "integrity": "sha512-WAy5txG7aFX8Vw3sloEKp5p/t/Xt8jD3GRD9DacnFv6Vo8ubudAsRTXgxpQwU0mpzY/H8U4db3roDuCMjShBmw==" + }, "@typescript-eslint/eslint-plugin": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.4.0.tgz", @@ -1149,6 +1154,17 @@ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" }, + "class-validator": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.12.2.tgz", + "integrity": "sha512-TDzPzp8BmpsbPhQpccB3jMUE/3pK0TyqamrK0kcx+ZeFytMA+O6q87JZZGObHHnoo9GM8vl/JppIyKWeEA/EVw==", + "requires": { + "@types/validator": "13.0.0", + "google-libphonenumber": "^3.2.8", + "tslib": ">=1.9.0", + "validator": "13.0.0" + } + }, "clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -2466,6 +2482,11 @@ } } }, + "google-libphonenumber": { + "version": "3.2.13", + "resolved": "https://registry.npmjs.org/google-libphonenumber/-/google-libphonenumber-3.2.13.tgz", + "integrity": "sha512-USnpjJkD8St+wyshy154lF74JeauNCd8vrcusSlWjSFWitXzl7ZSuCunA/XxeVLqBv6DShrSfFMYdwGZ7x4hOw==" + }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -5467,6 +5488,11 @@ "spdx-expression-parse": "^3.0.0" } }, + "validator": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.0.0.tgz", + "integrity": "sha512-anYx5fURbgF04lQV18nEQWZ/3wHGnxiKdG4aL8J+jEDsm98n/sU/bey+tYk6tnGJzm7ioh5FoqrAiQ6m03IgaA==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 918485e..a12ab01 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@typescript-eslint/parser": "^4.4.0", "bcrypt": "^5.0.0", "chai": "^4.2.0", + "class-validator": "^0.12.2", "concurrently": "^5.3.0", "cross-env": "^7.0.2", "deasync": "^0.1.20", diff --git a/src/entity/Photo.ts b/src/entity/Photo.ts index 0dc2937..6c69f53 100644 --- a/src/entity/Photo.ts +++ b/src/entity/Photo.ts @@ -6,7 +6,9 @@ import { constants as fsConstants } from "fs"; import { AfterRemove, BaseEntity, + BeforeInsert, BeforeRemove, + BeforeUpdate, Column, Entity, Index, @@ -14,6 +16,15 @@ import { PrimaryGeneratedColumn, } from "typeorm"; import { User } from "./User"; +import { + isAlphanumeric, + IsAlphanumeric, + IsHash, + IsMimeType, + Length, + Matches, + validateOrReject, +} from "class-validator"; export interface IPhotoJSON { id: number; @@ -32,14 +43,16 @@ export class Photo extends BaseEntity { @Column({ length: 190 }) @Index() + @IsHash("md5") public hash: string; @Column({ length: 190 }) - @Index() + @IsAlphanumeric() + @Matches(/\d*x\d*/) public size: string; @Column({ length: 190 }) - @Index() + @IsMimeType() public format: string; @Column({ type: "timestamp", default: null }) @@ -61,6 +74,16 @@ export class Photo extends BaseEntity { return path.join(this.user.getDataPath(), this.getFileName()); } + @BeforeInsert() + async beforeInsertValidate(): Promise { + return validateOrReject(this); + } + + @BeforeUpdate() + async beforeUpdateValidate(): Promise { + return validateOrReject(this); + } + @BeforeRemove() async cleanupFiles(): Promise { try { diff --git a/src/entity/User.ts b/src/entity/User.ts index 1b3b0a5..f4ab5dc 100644 --- a/src/entity/User.ts +++ b/src/entity/User.ts @@ -8,6 +8,7 @@ import { BaseEntity, BeforeInsert, BeforeRemove, + BeforeUpdate, Column, Entity, Index, @@ -16,6 +17,14 @@ import { } from "typeorm"; import { config } from "../config"; import { Photo } from "./Photo"; +import { + IsAlphanumeric, + IsBase32, + IsBase64, + IsEmail, + IsHash, + validateOrReject, +} from "class-validator"; export type IUserJSON = Pick; @@ -35,10 +44,12 @@ export class User extends BaseEntity { @Column({ length: 190 }) @Index({ unique: true }) + @IsAlphanumeric() public username: string; @Column({ length: 190 }) @Index({ unique: true }) + @IsEmail() public email: string; @Column({ length: 190 }) @@ -75,6 +86,16 @@ export class User extends BaseEntity { await fs.rmdir(this.getDataPath(), { recursive: true }); } + @BeforeInsert() + async beforeInsertValidate(): Promise { + return validateOrReject(this); + } + + @BeforeUpdate() + async beforeUpdateValidate(): Promise { + return validateOrReject(this); + } + public toJSON(): IUserJSON { const { id, username } = this; return { id, username }; diff --git a/src/tests/integration/photos.test.ts b/src/tests/integration/photos.test.ts index a9fb5b3..3aa38a6 100644 --- a/src/tests/integration/photos.test.ts +++ b/src/tests/integration/photos.test.ts @@ -222,6 +222,21 @@ describe("photos", function () { .expect(404); }); + it("should not create a photo with weird properties", async function () { + const response = await request(callback) + .post("/photos/new") + .set({ + Authorization: `Bearer ${seed.user1.toJWT()}`, + "Content-Type": "application/json", + }) + .send({ + hash: "../test", + size: "33333", + format: dogFormat, + } as IPhotosNewPostBody) + .expect(400); + }); + /* it("should update a photo", async function () { const response = await request(callback)