برنامه نویسی

روز 02: اجرای احراز هویت JWT در NestJS با پاسپورت (قسمت 1)

اولین قدم ها

nest g resource auth

این بیشتر از شما برای انتخاب درخواست می کند
❯ REST API
GraphQL (code first)
GraphQL (schema first)
Microservice (non-HTTP)
WebSockets

REST API را انتخاب کنید و با کنترلر و ماژول خدمات dtos کل ماژول را برای شما ایجاد می کند

ثبت نام کاربر

از آنجایی که ما احراز هویت مبتنی بر ایمیل/رمز عبور را به عنوان اولین قدم اجرا می کنیم، کاربر را ثبت می کنیم.

  1. ابتدا اعتبارسنجی کنید تا مطمئن شوید داده‌ها مشروع هستند و اعتبار گذرواژه را برای کاهش حملات brute force اضافه کنید.
  2. سپس برای اطمینان از ایمن بودن استفاده از داده ها، ضدعفونی کنید.
  3. بررسی کنید که سابقه کاربر از قبل در پایگاه داده وجود داشته باشد، اگر وجود داشته باشد، به این معنی است که کاربر قبلاً یک حساب دارد، بنابراین پاسخی ارسال کنید که این ایمیل قبلاً ثبت شده است.
  4. اگر بررسی های بالا انجام نشد، به این معنی است که باید یک کاربر ثبت نام کنیم، رمز عبور کاربر را گرفته و آن را با یک کتابخانه هش خوب مانند bcrypt یا argon2 هش کنیم.
  5. پس از هش کردن، رکورد کاربر را در DB وارد کنید.
  6. یک ایمیل به کاربر ارسال کنید تا تأیید کنید که ایمیل قانونی است.
  7. برای جلوگیری از حملات DDoS، محدودیت نرخ را به مسیر اضافه کنید

1 داده های دریافتی را اعتبار سنجی کنید

از آنجایی که nest js دارای یکپارچگی قوی با بسته های اعتبارسنجی توصیه شده مانند کلاس اعتبار سنجی است، اما از تجربه قبلی خود من از zod برای اعتبار سنجی در قسمت های جلویی react js استفاده می کنم و بنابراین من یک ابزار عالی پیدا کردم.
راه حلی برای اکوسیستم nest js به نام nests zod، بنابراین من ترجیح می دهم فعلاً با این اکوسیستم همراه شوم. برای شروع ابتدا کتابخانه را نصب کنید
npm i nestjs-zod

import { createZodDto } from 'nestjs-zod';
import { z } from 'zod';
const passwordStrengthRegex =
  /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
const registerUserSchema = z
  .object({
    email: z.string().email(),
    password: z
      .string()
      .min(8)
      .regex(
        passwordStrengthRegex,
        'Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character',
      ),
    confirmPassword: z.string().min(8),
  })
  .refine((data) => data.password === data.confirmPassword, {
    message: 'Passwords do not match',
  });

export class RegisterUserDto extends createZodDto(registerUserSchema) {}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

و سپس لوله اعتبار سنجی را در مسیر اعمال کنید

import { Controller, Post, Body, Version, UsePipes } from '@nestjs/common';
import { AuthService } from './auth.service';
import { RegisterUserDto } from './dto/register.dto';
import { ZodValidationPipe } from 'nestjs-zod';

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

  @Version('1')
  @Post()
  @UsePipes(ZodValidationPipe)
  async registerUser(@Body() registerUserDto: RegisterUserDto) {
    return await this.authService.registerUser(registerUserDto);
  }
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اگر همه ورودی ها را درست ارائه کنیم

توضیحات تصویر

بنابراین ما با مرحله اول تمام شده است

بیایید داده ها را پاکسازی کنیم

ما سه ورودی داریم

  • رمز عبور: معمولاً گذرواژه‌ها نباید Sanitize شوند، زیرا هرگز برای فرانت‌اند ارسال و نمایش داده نمی‌شوند، حتی اگر شخصی یک اسکریپت مخرب به رمز عبور ارسال کند، در نهایت هش می‌شود.
  • confirmPassword: همان داستان بالا
  • ایمیل: بله، ایمیل‌ها برای مشتریان ارسال و ارائه می‌شوند، بنابراین قسمت ایمیل باید Sanitize باشد تا حملات تزریق و اسکریپت را کاهش دهد.

اما ما به صراحت ایمیل اضافه کردیم: z.string().email() که برای این مورد کافی است
توضیحات تصویر

اما برای افزودن یک لایه امنیتی اضافی می‌توانیم یک لایه بهداشتی اضافه کنیم

import { createZodDto } from 'nestjs-zod';
import { z } from 'zod';
import * as xss from 'xss'; 

const passwordStrengthRegex =
  /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;

const registerUserSchema = z
  .object({
    email: z.string().transform((input) => xss.filterXSS(input)), // Sanitizing input using xss
    password: z
      .string()
      .min(8)
      .regex(
        passwordStrengthRegex,
        'Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character',
      ),
    confirmPassword: z.string().min(8),
  })
  .refine((data) => data.password === data.confirmPassword, {
    message: 'Passwords do not match',
  });

export class RegisterUserDto extends createZodDto(registerUserSchema) {}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

توضیحات تصویر

این آزمایشی بود که ما نیز دوباره اضافه کردیم

email: z
.string()
.email()

مرحله 3،4،5

import {
  BadRequestException,
  Injectable,
  InternalServerErrorException,
} from '@nestjs/common';
import { RegisterUserDto } from './dto/register.dto';
import { PrismaService } from 'src/prismaModule/prisma.service';
import * as argon2 from 'argon2';

@Injectable()
export class AuthService {
  constructor(private readonly prismaService: PrismaService) {}
  async registerUser(registerUserDto: RegisterUserDto) {
    // data is validate and sanitized by the registerUserDto
    const { email, password } = registerUserDto;

    try {
      // check if user already exists
      const user = await this.prismaService.user.findFirst({
        where: {
          email,
        },
      });

      if (user) {
        throw new BadRequestException('user already eists ');
      }
      //if use not exists lets hash user password
      const hashedPassword = await argon2.hash(registerUserDto.password);

      // time to create user
      const userData = await this.prismaService.user.create({
        data: {
          email,
          password: hashedPassword,
        },
      });

      if (!userData) {
        throw new InternalServerErrorException(
          'some thing went wrong while registring user',
        );
      }

      // if user is created successfully then  send email to user for email varification
      return {
        success: true,
        message: 'user created successfully',
      };
    } catch (error) {
      throw error;
    }
  }
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نکته اصلی که باید به آن توجه کنید من فقط یک پیام موفقیت آمیز بدون هیچ داده مرتبطی برگرداندم
به کاربر مانند شناسه یا ایمیل، زیرا در این مرحله نیازی به ارسال مجدد اطلاعات برای کاربر نیست. پس از ثبت نام، کاربر برای تکمیل جزئیات به صفحه ورود هدایت می شود، بنابراین اجتناب از ارسال داده های غیر ضروری یک عمل امنیتی خوب است.

توضیحات تصویر

محدود کردن نرخ

اجرای محدود کردن نرخ در nestjs بسیار آسان است.
برای نصب بسته اجرا شود npm i --save @nestjs/throttler


@Module({
  imports: [
    ThrottlerModule.forRoot([{
      ttl: 60000,
      limit: 10,
    }]),
  ],
})
export class AppModule {}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

سپس محافظ گاز nestjs را به عنوان محافظ جهانی اضافه کنید

 providers: [
    AppService,

    {
      provide: APP_GUARD,
      useClass: ThrottlerGuard,
    },
  ],
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

و اینجاست

import { Controller, Post, Body, Version, UsePipes, Req } from '@nestjs/common';
import { AuthService } from './auth.service';
import { RegisterUserDto } from './dto/register.dto';
import { ZodValidationPipe } from 'nestjs-zod';
import { Throttle } from '@nestjs/throttler';

@Controller('auth')
export class AuthController {
  constructor(private readonly authService: AuthService) {}
  @Throttle({
    default: {
      ttl: 100000, // 1 minute
      limit: 5, // 5 requests per minute
    },
  })
  @Version('1')
  @Post()
  @UsePipes(ZodValidationPipe)
  async registerUser(@Body() registerUserDto: RegisterUserDto, @Req() req) {
    return await this.authService.registerUser(registerUserDto, req);
  }
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

از آنجایی که ثبت نقطه پایانی کاربر یک brute-force نقطه پایانی حساس است
یا حمله به فرهنگ لغت ممکن است رخ دهد ما محدودیت نرخ را سخت نگه داشته ایم

ارسال ایمیل تایید

برای ارسال یک ایمیل تأیید برای استفاده کاربر، ارسال مجدد یک سرویس عالی با استفاده آسان است. اما تصمیم گرفتم یک قسمت جداگانه برای کل سرویس اطلاع رسانی ایجاد کنم تا درک آن برای همه آسان تر شود

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا