Skip to content

Commit

Permalink
refactor(security): use bcrypt the right way,
Browse files Browse the repository at this point in the history
not hashing refresh token
  • Loading branch information
crazyoptimist committed Jun 17, 2024
1 parent 73a1834 commit e625bbc
Show file tree
Hide file tree
Showing 7 changed files with 45 additions and 9 deletions.
5 changes: 1 addition & 4 deletions src/modules/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,7 @@ export class AuthService {

const user = await this.userService.findOne(sub);

const isMatchedRefreshToken = Hash.compare(
dto.refreshToken,
user.refreshToken,
);
const isMatchedRefreshToken = dto.refreshToken === user.refreshToken;
if (!isMatchedRefreshToken) {
throw new UnauthorizedException('Invalid refresh token');
}
Expand Down
5 changes: 4 additions & 1 deletion src/modules/auth/dto/login.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsNotEmpty, MinLength } from 'class-validator';
import { IsEmail, IsNotEmpty, MaxLength, MinLength } from 'class-validator';

export class LoginDto {
@ApiProperty({
Expand All @@ -10,8 +10,11 @@ export class LoginDto {

@ApiProperty({
required: true,
minLength: 5,
maxLength: 72,
})
@IsNotEmpty()
@MinLength(5)
@MaxLength(72)
password: string;
}
5 changes: 4 additions & 1 deletion src/modules/auth/dto/signup.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsNotEmpty, MinLength } from 'class-validator';
import { IsEmail, IsNotEmpty, MaxLength, MinLength } from 'class-validator';
import { SameAs } from '@modules/common/validator/same-as.validator';

export class SignupDto {
Expand All @@ -23,9 +23,12 @@ export class SignupDto {

@ApiProperty({
required: true,
minLength: 5,
maxLength: 72,
})
@IsNotEmpty()
@MinLength(5)
@MaxLength(72)
password: string;

@ApiProperty({ required: true })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class AllExceptionsFilter implements ExceptionFilter {
error: 'Record Not Found Error',
};
} else {
this.logger.error(exception);
this.logger.error((exception as Error).stack);
statusCode = HttpStatus.INTERNAL_SERVER_ERROR;
responseBody = {
statusCode,
Expand Down
2 changes: 0 additions & 2 deletions src/modules/user/user.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ export class User {

@Column({
name: 'refresh_token',
length: 255,
transformer: new PasswordTransformer(),
nullable: true,
})
@Exclude()
Expand Down
22 changes: 22 additions & 0 deletions src/utils/hash.util.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Hash } from './hash.util';

describe('Hash.make', () => {
test('panics if a password longer than 72 is given', () => {
const tooLongPassword =
"abcdefghijklmnopqrstuvwxyz1234567890!@#$%^&*(),.:;'-+_abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890";

expect(() => Hash.make(tooLongPassword)).toThrow();
});

test('panics if an empty password is given', () => {
expect(() => Hash.make('')).toThrow();
});
});

describe('Hash.compare', () => {
test('panics if password or hash is an empty string', () => {
const anyHash = Hash.make('any string');
expect(() => Hash.compare('', anyHash)).toThrow();
expect(() => Hash.compare('any string', '')).toThrow();
});
});
13 changes: 13 additions & 0 deletions src/utils/hash.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,24 @@ import * as bcrypt from 'bcrypt';

export class Hash {
static make(plainText: string) {
// Per bcrypt implementation, only the first 72 **bytes** of a string are used. Any extra bytes are ignored when matching passwords.
if (plainText === '' || plainText.length > 72) {
throw new Error('Password can not be empty or longer than 72');
}

const salt = bcrypt.genSaltSync();

return bcrypt.hashSync(plainText, salt);
}

static compare(plainText: string, hash: string) {
if (plainText.length === 0) {
throw new Error('Password can not be empty');
}
if (hash.length === 0) {
throw new Error('Hash can not be empty');
}

return bcrypt.compareSync(plainText, hash);
}
}

0 comments on commit e625bbc

Please sign in to comment.