Fix bugs in data validation

This commit is contained in:
rubikscraft 2022-02-26 18:16:28 +01:00
parent 0f1eec81a7
commit 85bd389a2b
No known key found for this signature in database
GPG key ID: 1463EBE9200A5CD4
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 { GetCols } from '../collectionutils';
import { EImage } from 'imagur-shared/dist/entities/image.entity';
import { plainToClass } from 'class-transformer';
@Injectable()
export class ImageDBService {
@ -27,15 +28,19 @@ export class ImageDBService {
const find = await this.findOne(hash);
if (HasSuccess(find)) return find;
const imageEntity = new EImage();
let imageEntity = new EImage();
imageEntity.data = image;
imageEntity.mime = type;
imageEntity.hash = hash;
try {
return await this.imageRepository.save(imageEntity);
imageEntity = await this.imageRepository.save(imageEntity);
} catch (e: any) {
return Fail(e?.message);
}
// Strips unwanted data
return plainToClass(EImage, imageEntity);
}
public async findOne<B extends true | undefined = undefined>(

View file

@ -1,5 +1,6 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { plainToClass } from 'class-transformer';
import { validate } from 'class-validator';
import { EUser } from 'imagur-shared/dist/entities/user.entity';
import {
@ -26,17 +27,20 @@ export class UsersService {
): AsyncFailable<EUser> {
if (await this.exists(username)) return Fail('User already exists');
const user = new EUser();
let user = new EUser();
user.username = username;
user.password = hashedPassword;
try {
return await this.usersRepository.save(user);
user = await this.usersRepository.save(user, { reload: true });
} catch (e: any) {
return Fail(e?.message);
}
return plainToClass(EUser, user); // Strips unwanted data
}
// Returns user object without id
public async delete(user: string | EUser): AsyncFailable<EUser> {
const userToModify = await this.resolve(user);
if (HasFailed(userToModify)) return userToModify;
@ -94,7 +98,8 @@ export class UsersService {
if (typeof user === 'string') {
return await this.findOne(user);
} else {
const errors = await validate(user);
user = plainToClass(EUser, user);
const errors = await validate(user, { forbidUnknownValues: true });
if (errors.length > 0) {
this.logger.warn(errors);
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 * as bcrypt from 'bcrypt';
import { plainToClass } from 'class-transformer';
import { validate } from 'class-validator';
import { JwtDataDto } from 'imagur-shared/dist/dto/auth.dto';
import { EUser } from 'imagur-shared/dist/entities/user.entity';
import { AsyncFailable, HasFailed, Fail } from 'imagur-shared/dist/types';
@ -8,6 +10,8 @@ import { UsersService } from '../../../collections/userdb/userdb.service';
@Injectable()
export class AuthService {
private readonly logger = new Logger('AuthService');
constructor(
private usersService: UsersService,
private jwtService: JwtService,
@ -37,9 +41,15 @@ export class AuthService {
}
async createToken(user: EUser): Promise<string> {
const jwtData: JwtDataDto = {
const jwtData: JwtDataDto = plainToClass(JwtDataDto, {
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);
}

View file

@ -22,7 +22,10 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
async validate(payload: any): Promise<EUser> {
const jwt = plainToClass(JwtDataDto, payload);
const errors = await validate(jwt, { forbidUnknownValues: true });
const errors = await validate(jwt, {
forbidUnknownValues: true,
});
if (errors.length > 0) {
this.logger.warn(errors);
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 { SupportedMime, SupportedMimes } from '../dto/mimes.dto';
@Entity()
export class EImage {
@PrimaryGeneratedColumn()
@IsNumber()
@IsDefined()
id: number;
@IsOptional()
id?: number;
@Index()
@Column({ unique: true })
@IsString()
@IsHash('sha256')
hash: string;
// Binary data
@Column({ type: 'bytea', nullable: false, select: false })
@IsOptional()
@Exclude()
data?: Buffer;
@Column({ enum: SupportedMimes })

View file

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