Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Пользователи и хеширование паролей

Чтобы статьи писали только зарегистрированные авторы, в блоге нужны пользователи. Пароль нельзя хранить в базе как есть: при утечке базы злоумышленник получит пароли всех пользователей. Вместо пароля хранят хеш — строку, полученную из пароля необратимым преобразованием. Проверить пароль по хешу можно, восстановить пароль из хеша — нет.

Установка и ресурс

npm i bcrypt
npm i -D @types/bcrypt
nest g resource users

На вопрос о CRUD-точках ответьте n. bcrypt — библиотека хеширования паролей, @types/bcrypt — описание её типов для TypeScript.

Сущность пользователя

// src/users/entities/user.entity.ts
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
 
@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;
 
  @Column({ unique: true })
  email: string;
 
  @Column()
  name: string;
 
  @Column()
  password: string;
 
  @Column({ default: 'user' })
  role: string;
}

unique: true запрещает двух пользователей с одинаковой почтой. Поле role понадобится в уроке о ролях. В UsersModule добавьте imports: [TypeOrmModule.forFeature([User])] и exports: [UsersService] — сервис пользователей понадобится модулю входа.

DTO регистрации

// src/users/dto/create-user.dto.ts
import { IsEmail, IsNotEmpty, IsString, MinLength } from 'class-validator';
 
export class CreateUserDto {
  @IsEmail()
  email: string;
 
  @IsString()
  @IsNotEmpty()
  name: string;
 
  @IsString()
  @MinLength(8)
  password: string;
}

Сервис

// src/users/users.service.ts
import { ConflictException, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import * as bcrypt from 'bcrypt';
import { Repository } from 'typeorm';
import { CreateUserDto } from './dto/create-user.dto';
import { User } from './entities/user.entity';
 
@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly usersRepository: Repository<User>,
  ) {}
 
  async create(dto: CreateUserDto): Promise<User> {
    const exists = await this.usersRepository.existsBy({ email: dto.email });
    if (exists) {
      throw new ConflictException('Эта почта уже зарегистрирована');
    }
 
    const password = await bcrypt.hash(dto.password, 10);
    return this.usersRepository.save(this.usersRepository.create({ ...dto, password }));
  }
 
  findByEmail(email: string): Promise<User | null> {
    return this.usersRepository.findOneBy({ email });
  }
}

bcrypt.hash() превращает пароль в хеш. Число 10 — сложность: чем оно больше, тем дольше вычисляется хеш и тем труднее его подобрать. Каждый раз в хеш добавляется случайная «соль», поэтому одинаковые пароли дают разные хеши.

Маршрут регистрации

// src/users/users.controller.ts
import { Body, Controller, Post } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UsersService } from './users.service';
 
@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}
 
  @Post()
  async register(@Body() dto: CreateUserDto) {
    const user = await this.usersService.create(dto);
    return { id: user.id, email: user.email, name: user.name };
  }
}

Отправьте POST /users с полями email, name и password — придёт новый пользователь без пароля. В базе в столбце password окажется строка вида $2b$10$.... Повторная регистрация с той же почтой вернёт 409.

Проверка пароля

При входе пароль сравнивают с хешем функцией bcrypt.compare():

// src/auth/auth.service.ts
import * as bcrypt from 'bcrypt';
 
const isValid = await bcrypt.compare('введённый пароль', user.password);

Она возвращает true или false. Этим займётся урок о JWT.

Частая ошибка

Метод возвращает сущность пользователя целиком, и хеш пароля уходит клиенту в JSON. Даже хеш раскрывать нельзя: его можно подбирать без ограничений на своём компьютере. Пока возвращайте только нужные поля, как в контроллере выше, а в уроке о сериализации поле password будет скрыто автоматически.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.