Sistema de autenticación con NestJS – Semana 3

nestjs with auth

Antes de empezar

Esta es la semana 3 de la serie Sistema de autenticación con NestJS. Puedes encontrar todas las semanas publicadas aquí.

Puedes encontrar todo el código de esta serie en el repositorio.

Y recuerda que siguiendo la serie de youtube vas a tener explicaciones mucho más detalladas.

Y si quieres estar al día con nuestros contenidos suscríbete a nuestro canal de youtube y síguenos en twitter y suscríbete a nuestro newsletter.

Encriptar la contraseña

Hasta ahora hemos conseguido guardar el usuario pero estamos guardando la contraseña en texto pleno.

Vamos a utilizar la librería de bcrypt que instalamos en la primera semana. Primero generamos un salt y despues generamos un hash utilizando la contraseña y el salt. Es importante utilizar await ya que las operaciones de bcrypt so asincronas.

				
					// src/auth/users.repository.ts
import {
  ConflictException,
  InternalServerErrorException,
} from '@nestjs/common';
import { EntityRepository, Repository } from 'typeorm';
import { RegisterUserDto } from './dto/register-user.dto';
import { User } from './user.entity';
import * as bcrypt from 'bcrypt';

@EntityRepository(User)
export class UsersRepository extends Repository<User> {
  async createUser(registerUserDto: RegisterUserDto): Promise<void> {
    const { name, email, password } = registerUserDto;

    const salt = await bcrypt.genSalt();
    const hashedPassword = await bcrypt.hash(password, salt);

    const user = this.create({ name, email, password: hashedPassword });

    try {
      await this.save(user);
    } catch (e) {
      if (e.code === 'ER_DUP_ENTRY') {
        throw new ConflictException('This email is already registered');
      }
      throw new InternalServerErrorException();
    }
  }
}
				
			

Probamos con postman y vemos si se guarda la contraseña encriptada.

Ahora que vemos que funciona vamos a mover la parte de hashing a un servicio dedicado y así solo pasamos la contraseña encriptada al repositorio.

Primer creamos el servicio encoder.

 

				
					// src/auth/encoder.service.ts
import { Injectable } from '@nestjs/common';
import * as bcrypt from 'bcrypt';

@Injectable()
export class EncoderService {
  async encodePassword(password: string): Promise<string> {
    const salt = await bcrypt.genSalt();
    return await bcrypt.hash(password, salt);
  }
}
				
			

Después actualizamos el repository para que reciba la contraseña directamente. También vamos a pasar directamente los parametros necesarios al método de createUser y así desacomplamos nuestro repositorio del dto.

				
					// src/auth/users.repository.ts
import {
  ConflictException,
  InternalServerErrorException,
} from '@nestjs/common';
import { EntityRepository, Repository } from 'typeorm';
import { User } from './user.entity';

@EntityRepository(User)
export class UsersRepository extends Repository<User> {
  async createUser(
    name: string,
    email: string,
    password: string,
  ): Promise<void> {
    const user = this.create({ name, email, password });

    try {
      await this.save(user);
    } catch (e) {
      if (e.code === 'ER_DUP_ENTRY') {
        throw new ConflictException('This email is already registered');
      }
      throw new InternalServerErrorException();
    }
  }
}
				
			

Y delegamos la responsabilidad de llamar al encoder service a auth service.

				
					// src/auth/auth.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { RegisterUserDto } from './dto/register-user.dto';
import { EncoderService } from './encoder.service';
import { UsersRepository } from './users.repository';

@Injectable()
export class AuthService {
  constructor(
    @InjectRepository(UsersRepository)
    private usersRepository: UsersRepository,
    private encoderService: EncoderService,
  ) {}

  async registerUser(registerUserDto: RegisterUserDto): Promise<void> {
    const { name, email, password } = registerUserDto;
    const hashedPassword = await this.encoderService.encodePassword(password);
    return this.usersRepository.createUser(name, email, hashedPassword);
  }
}
				
			

Login

Igual que register vamos a crear un dto para login.

				
					// src/auth/dto/login.dto.ts
import { IsNotEmpty, IsEmail, Length } from 'class-validator';

export class LoginDto {
  @IsNotEmpty()
  @IsEmail()
  email: string;

  @IsNotEmpty()
  @Length(6, 20)
  password: string;
}
				
			

Y creamos un método en user repository para poder buscar el usuario en nuestra base de datos con el email.

				
					// src/auth/users.repository.ts
import {
  ConflictException,
  InternalServerErrorException,
} from '@nestjs/common';
import { EntityRepository, Repository } from 'typeorm';
import { User } from './user.entity';

@EntityRepository(User)
export class UsersRepository extends Repository<User> {
  async createUser(
    name: string,
    email: string,
    password: string,
  ): Promise<void> {
    const user = this.create({ name, email, password });

    try {
      await this.save(user);
    } catch (e) {
      if (e.code === 'ER_DUP_ENTRY') {
        throw new ConflictException('This email is already registered');
      }
      throw new InternalServerErrorException();
    }
  }

  async findOneByEmail(email: string): Promise<User> {
    return await this.findOne({ email });
  }
}
				
			

En el controlador creamos la ruta login que va a llamar al método login de auth service.

				
					// src/auth/auth.controller.ts
import { Body, Controller, Post } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LoginDto } from './dto/login.dto';
import { RegisterUserDto } from './dto/register-user.dto';

@Controller('auth')
export class AuthController {
  constructor(private authService: AuthService) {}

  @Post('/register')
  register(@Body() registerUserDto: RegisterUserDto): Promise<void> {
    return this.authService.registerUser(registerUserDto);
  }

  @Post('/login')
  login(@Body() loginDto: LoginDto): Promise<string> {
    return this.authService.login(loginDto);
  }
}
				
			

Y ahora vamos crear un método en el encoder service para poder comparar una contraseña en texto pleno con el texto encriptado que tenemos en la base de datos.

				
					// src/auth/encoder.service.ts
import { Injectable } from '@nestjs/common';
import * as bcrypt from 'bcrypt';

@Injectable()
export class EncoderService {
  async encodePassword(password: string): Promise<string> {
    const salt = await bcrypt.genSalt();
    return await bcrypt.hash(password, salt);
  }

  async checkPassword(
    password: string,
    userPassword: string,
  ): Promise<boolean> {
    return await bcrypt.compare(password, userPassword);
  }
}
				
			

Y por lo ultimo implementamos la lógica del servicio. Para ello primero consultamos el usuario del repositorio y después llamamos al encoder service para validar la contraseña. En el caso de que no exista el usuario o la contraseña este incorrecta lanzamos una excepción y en el caso contrario devolvemos el jwt (por ahora devolvemos el string jwt y luego implementamos la lógica de creación de jwt).

				
					// src/auth/auth.service.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { LoginDto } from './dto/login.dto';
import { RegisterUserDto } from './dto/register-user.dto';
import { EncoderService } from './encoder.service';
import { UsersRepository } from './users.repository';

@Injectable()
export class AuthService {
  constructor(
    @InjectRepository(UsersRepository)
    private usersRepository: UsersRepository,
    private encoderService: EncoderService,
  ) {}

  async registerUser(registerUserDto: RegisterUserDto): Promise<void> {
    const { name, email, password } = registerUserDto;
    const hashedPassword = await this.encoderService.encodePassword(password);
    return this.usersRepository.createUser(name, email, hashedPassword);
  }

  async login(loginDto: LoginDto): Promise<string> {
    const { email, password } = loginDto;
    const user = await this.usersRepository.findOneByEmail(email);

    if (
      user &&
      (await this.encoderService.checkPassword(password, user.password))
    ) {
      return 'jwt';
    }
    throw new UnauthorizedException('Please check your credentials');
  }
}
				
			

Esto es todo para esta semana.

Recuerda que tenemos esta misma serie en nuestro canal de youtube donde Juan explica todo con mucho más detalles.

Subscribete a nuestro newsletter

Contenidos semanales y descuentos exclusivos
Exclusivo

Social share

Share on facebook
Facebook
Share on twitter
Twitter
Share on linkedin
LinkedIn
Share on telegram
Telegram