Fix bugs in data validation

This commit is contained in:
rubikscraft
2022-02-26 18:16:28 +01:00
parent 0f1eec81a7
commit 85bd389a2b
6 changed files with 49 additions and 23 deletions

View File

@@ -11,6 +11,7 @@ import {
import { SupportedMime } from 'imagur-shared/dist/dto/mimes.dto'; import { SupportedMime } from 'imagur-shared/dist/dto/mimes.dto';
import { GetCols } from '../collectionutils'; import { GetCols } from '../collectionutils';
import { EImage } from 'imagur-shared/dist/entities/image.entity'; import { EImage } from 'imagur-shared/dist/entities/image.entity';
import { plainToClass } from 'class-transformer';
@Injectable() @Injectable()
export class ImageDBService { export class ImageDBService {
@@ -27,15 +28,19 @@ export class ImageDBService {
const find = await this.findOne(hash); const find = await this.findOne(hash);
if (HasSuccess(find)) return find; if (HasSuccess(find)) return find;
const imageEntity = new EImage(); let imageEntity = new EImage();
imageEntity.data = image; imageEntity.data = image;
imageEntity.mime = type; imageEntity.mime = type;
imageEntity.hash = hash; imageEntity.hash = hash;
try { try {
return await this.imageRepository.save(imageEntity); imageEntity = await this.imageRepository.save(imageEntity);
} catch (e: any) { } catch (e: any) {
return Fail(e?.message); return Fail(e?.message);
} }
// Strips unwanted data
return plainToClass(EImage, imageEntity);
} }
public async findOne<B extends true | undefined = undefined>( public async findOne<B extends true | undefined = undefined>(

View File

@@ -1,5 +1,6 @@
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { plainToClass } from 'class-transformer';
import { validate } from 'class-validator'; import { validate } from 'class-validator';
import { EUser } from 'imagur-shared/dist/entities/user.entity'; import { EUser } from 'imagur-shared/dist/entities/user.entity';
import { import {
@@ -26,17 +27,20 @@ export class UsersService {
): AsyncFailable<EUser> { ): AsyncFailable<EUser> {
if (await this.exists(username)) return Fail('User already exists'); if (await this.exists(username)) return Fail('User already exists');
const user = new EUser(); let user = new EUser();
user.username = username; user.username = username;
user.password = hashedPassword; user.password = hashedPassword;
try { try {
return await this.usersRepository.save(user); user = await this.usersRepository.save(user, { reload: true });
} catch (e: any) { } catch (e: any) {
return Fail(e?.message); return Fail(e?.message);
} }
return plainToClass(EUser, user); // Strips unwanted data
} }
// Returns user object without id
public async delete(user: string | EUser): AsyncFailable<EUser> { public async delete(user: string | EUser): AsyncFailable<EUser> {
const userToModify = await this.resolve(user); const userToModify = await this.resolve(user);
if (HasFailed(userToModify)) return userToModify; if (HasFailed(userToModify)) return userToModify;
@@ -94,7 +98,8 @@ export class UsersService {
if (typeof user === 'string') { if (typeof user === 'string') {
return await this.findOne(user); return await this.findOne(user);
} else { } else {
const errors = await validate(user); user = plainToClass(EUser, user);
const errors = await validate(user, { forbidUnknownValues: true });
if (errors.length > 0) { if (errors.length > 0) {
this.logger.warn(errors); this.logger.warn(errors);
return Fail('Invalid user'); return Fail('Invalid user');

View File

@@ -1,6 +1,8 @@
import { Injectable } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt'; import { JwtService } from '@nestjs/jwt';
import * as bcrypt from 'bcrypt'; import * as bcrypt from 'bcrypt';
import { plainToClass } from 'class-transformer';
import { validate } from 'class-validator';
import { JwtDataDto } from 'imagur-shared/dist/dto/auth.dto'; import { JwtDataDto } from 'imagur-shared/dist/dto/auth.dto';
import { EUser } from 'imagur-shared/dist/entities/user.entity'; import { EUser } from 'imagur-shared/dist/entities/user.entity';
import { AsyncFailable, HasFailed, Fail } from 'imagur-shared/dist/types'; import { AsyncFailable, HasFailed, Fail } from 'imagur-shared/dist/types';
@@ -8,6 +10,8 @@ import { UsersService } from '../../../collections/userdb/userdb.service';
@Injectable() @Injectable()
export class AuthService { export class AuthService {
private readonly logger = new Logger('AuthService');
constructor( constructor(
private usersService: UsersService, private usersService: UsersService,
private jwtService: JwtService, private jwtService: JwtService,
@@ -37,9 +41,15 @@ export class AuthService {
} }
async createToken(user: EUser): Promise<string> { async createToken(user: EUser): Promise<string> {
const jwtData: JwtDataDto = { const jwtData: JwtDataDto = plainToClass(JwtDataDto, {
user, user,
}; });
const errors = await validate(jwtData, { forbidUnknownValues: true });
if (errors.length > 0) {
this.logger.warn(errors);
throw new Error('Invalid jwt token generated');
}
return this.jwtService.signAsync(jwtData); return this.jwtService.signAsync(jwtData);
} }

View File

@@ -22,7 +22,10 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
async validate(payload: any): Promise<EUser> { async validate(payload: any): Promise<EUser> {
const jwt = plainToClass(JwtDataDto, payload); const jwt = plainToClass(JwtDataDto, payload);
const errors = await validate(jwt, { forbidUnknownValues: true }); const errors = await validate(jwt, {
forbidUnknownValues: true,
});
if (errors.length > 0) { if (errors.length > 0) {
this.logger.warn(errors); this.logger.warn(errors);
throw new UnauthorizedException(); throw new UnauthorizedException();

View File

@@ -1,23 +1,28 @@
import { IsDefined, IsEnum, IsHash, IsNumber, IsOptional, IsString } from 'class-validator'; import { Exclude } from 'class-transformer';
import {
IsDefined,
IsEnum,
IsHash,
IsOptional,
} from 'class-validator';
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'; import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
import { SupportedMime, SupportedMimes } from '../dto/mimes.dto'; import { SupportedMime, SupportedMimes } from '../dto/mimes.dto';
@Entity() @Entity()
export class EImage { export class EImage {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
@IsNumber() @IsOptional()
@IsDefined() id?: number;
id: number;
@Index() @Index()
@Column({ unique: true }) @Column({ unique: true })
@IsString()
@IsHash('sha256') @IsHash('sha256')
hash: string; hash: string;
// Binary data // Binary data
@Column({ type: 'bytea', nullable: false, select: false }) @Column({ type: 'bytea', nullable: false, select: false })
@IsOptional() @IsOptional()
@Exclude()
data?: Buffer; data?: Buffer;
@Column({ enum: SupportedMimes }) @Column({ enum: SupportedMimes })

View File

@@ -1,33 +1,31 @@
import { Exclude, Expose } from 'class-transformer';
import { import {
IsBoolean,
IsDefined, IsDefined,
IsEmpty,
IsNotEmpty, IsNotEmpty,
IsNumber,
IsOptional, IsOptional,
IsString,
} from 'class-validator'; } from 'class-validator';
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'; import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
// Different data for public and private
@Entity() @Entity()
export class EUser { export class EUser {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
@IsNumber() @IsOptional()
@IsDefined() id?: number;
id: number;
@Index() @Index()
@Column({ unique: true }) @Column({ unique: true })
@IsString()
@IsNotEmpty() @IsNotEmpty()
username: string; username: string;
@Column({ default: false }) @Column({ default: false })
@IsDefined() @IsDefined()
@IsBoolean()
isAdmin: boolean; isAdmin: boolean;
@Column({ select: false }) @Column({ select: false })
@IsOptional() @IsOptional()
@IsString() @Exclude()
password?: string; password?: string;
} }