Loading backend/src/app.module.ts +2 −0 Original line number Diff line number Diff line Loading @@ -36,6 +36,7 @@ import { EmailService } from './email/email.service'; import { UserResetCode } from './auth/user/entities/user-reset-code.entity'; import { MailerModule } from '@nestjs-modules/mailer'; import { MailConstants } from './constants/mail.constants'; import { UserConfirmCode } from './auth/user/entities/user-confirm-code.entity'; @Module({ imports: [ Loading @@ -62,6 +63,7 @@ import { MailConstants } from './constants/mail.constants'; TravelPlace, Visited, UserResetCode, UserConfirmCode, ], synchronize: DbConstants.DB_SYNC, logging: false, Loading backend/src/auth/user/authUser.module.ts +2 −1 Original line number Diff line number Diff line Loading @@ -10,10 +10,11 @@ import { Category } from 'src/category/entities/category.entity'; import { CategoryService } from 'src/category/category.service'; import { UserResetCode } from './entities/user-reset-code.entity'; import { EmailService } from 'src/email/email.service'; import { UserConfirmCode } from './entities/user-confirm-code.entity'; @Module({ controllers: [AuthUserController], providers: [UserService, JwtService, EncryptionService, AuthUserService, CategoryService, EmailService], imports: [TypeOrmModule.forFeature([User, Category, UserResetCode])], imports: [TypeOrmModule.forFeature([User, Category, UserResetCode, UserConfirmCode])], }) export class AuthUserModule {} backend/src/auth/user/authUsercontroller.ts +38 −4 Original line number Diff line number Diff line import { Body, Controller, Get, Patch, Post, Req, UseGuards } from '@nestjs/common'; import { Body, Controller, Patch, Post, Req, UseGuards } from '@nestjs/common'; import { ApiBearerAuth, ApiBody, ApiCreatedResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; import { AuthUserService } from './authUserservice'; import { CreateUserDto } from 'src/user/dto/create-user.dto'; Loading @@ -7,14 +7,22 @@ import { UserSigninResDto } from './dto/user-signin-res.dto'; import { AuthUserGuard } from './authUser.guard'; import { CustomUserRequest } from './interface/customUserReq'; import { UpdatePwdDto } from './dto/update-pwd.dto'; import { UserRequestCodeBody, UserResetPasswordBody, UserResetPasswordDto } from './dto/user-reset-password.dto'; import { UserConfirmEmailBody, UserRequestCodeBody, UserResetPasswordBody, UserResetPasswordDto, } from './dto/user-reset-password.dto'; import { GetResetCode } from './dto/get-reset-code.dto'; import { EmailService } from 'src/email/email.service'; @Controller('') @ApiTags('Create user account and sign in as user') export class AuthUserController { constructor(private readonly authUserService: AuthUserService, private readonly mailService: EmailService) {} constructor( private readonly authUserService: AuthUserService, private readonly mailService: EmailService, ) {} @ApiBody({ type: CreateUserDto }) @ApiCreatedResponse({ Loading Loading @@ -67,4 +75,30 @@ export class AuthUserController { throw e; } } @ApiBearerAuth('jwt') @UseGuards(AuthUserGuard) @Post('user/resend-confirmation-code') async resendConfirmationCode(@Req() req: CustomUserRequest) { try { const email = req.user.email; await this.authUserService.sendConfirmationCode(email); return { message: 'Confirmation code sent' }; } catch (e) { throw e; } } @ApiBody({ type: UserConfirmEmailBody }) @ApiBearerAuth('jwt') @UseGuards(AuthUserGuard) @Post('user/confirm-email') async confirmEmail(@Body() { code }, @Req() req: CustomUserRequest) { try { const email = req.user.email; return await this.authUserService.confirmEmail(email, { code }); } catch (e) { throw e; } } } backend/src/auth/user/authUserservice.ts +37 −3 Original line number Diff line number Diff line Loading @@ -14,15 +14,19 @@ import { InjectRepository } from '@nestjs/typeorm'; import { UserResetCode } from './entities/user-reset-code.entity'; import { Repository } from 'typeorm'; import { randomInt } from 'crypto'; import { UserResetPasswordDto } from './dto/user-reset-password.dto'; import { UserConfirmEmailBody, UserResetPasswordDto } from './dto/user-reset-password.dto'; import { UserConfirmCode } from './entities/user-confirm-code.entity'; import { EmailService } from 'src/email/email.service'; @Injectable() export class AuthUserService { constructor( @InjectRepository(UserResetCode) private userResetCodeRepository: Repository<UserResetCode>, @InjectRepository(UserConfirmCode) private userConfirmCodeRepository: Repository<UserConfirmCode>, private userService: UserService, private jwtService: JwtService, private encryptionService: EncryptionService, private readonly mailService: EmailService, ) {} async signUp(createAdminDto: CreateUserDto): Promise<UserSigninResDto> { Loading @@ -38,6 +42,8 @@ export class AuthUserService { await this.userService.create(createAdminDto); const adminSigninResDto: UserSigninResDto = await this.signIn(loginAdminDto); await this.sendConfirmationCode(createAdminDto.email); return adminSigninResDto; } Loading Loading @@ -112,4 +118,32 @@ export class AuthUserService { await this.userService.updatePassword(email, newPwdHashed); await this.userResetCodeRepository.delete(userResetCode[0].id); } async sendConfirmationCode(email: string) { const user: User = await this.userService.findOne(email); if (!user) throw new UnauthorizedException('Invalid email'); const resetCode = randomInt(100000, 999999).toString(); const expirationDate = new Date(); expirationDate.setHours(expirationDate.getHours() + 1); const existUserCode = await this.userConfirmCodeRepository.findBy({ user, code: resetCode }); if (existUserCode.length > 0) { await this.userConfirmCodeRepository.delete(existUserCode[0].id); } await this.userConfirmCodeRepository.save({ user, code: resetCode, expirationDate }); await this.mailService.sendConfirmationCode(email, resetCode); return resetCode; } async confirmEmail(email: string, { code }: UserConfirmEmailBody) { const user: User = await this.userService.findOne(email); if (!user) throw new UnauthorizedException('Invalid email'); const userConfirmCode: UserResetCode[] = await this.userConfirmCodeRepository.findBy({ user, code: code }); if (userConfirmCode.length === 0) throw new UnauthorizedException('Invalid code'); if (userConfirmCode[0].expirationDate < new Date()) throw new UnauthorizedException('Code expired'); await this.userService.confirmEmail(email); await this.userConfirmCodeRepository.delete(userConfirmCode[0].id); } } backend/src/auth/user/dto/user-reset-password.dto.ts +19 −14 Original line number Diff line number Diff line import { ApiProperty } from "@nestjs/swagger"; import { ApiProperty } from '@nestjs/swagger'; export interface UserResetPasswordDto { email: string; Loading @@ -19,3 +19,8 @@ export class UserRequestCodeBody { @ApiProperty() email: string; } export class UserConfirmEmailBody { @ApiProperty() code: string; } Loading
backend/src/app.module.ts +2 −0 Original line number Diff line number Diff line Loading @@ -36,6 +36,7 @@ import { EmailService } from './email/email.service'; import { UserResetCode } from './auth/user/entities/user-reset-code.entity'; import { MailerModule } from '@nestjs-modules/mailer'; import { MailConstants } from './constants/mail.constants'; import { UserConfirmCode } from './auth/user/entities/user-confirm-code.entity'; @Module({ imports: [ Loading @@ -62,6 +63,7 @@ import { MailConstants } from './constants/mail.constants'; TravelPlace, Visited, UserResetCode, UserConfirmCode, ], synchronize: DbConstants.DB_SYNC, logging: false, Loading
backend/src/auth/user/authUser.module.ts +2 −1 Original line number Diff line number Diff line Loading @@ -10,10 +10,11 @@ import { Category } from 'src/category/entities/category.entity'; import { CategoryService } from 'src/category/category.service'; import { UserResetCode } from './entities/user-reset-code.entity'; import { EmailService } from 'src/email/email.service'; import { UserConfirmCode } from './entities/user-confirm-code.entity'; @Module({ controllers: [AuthUserController], providers: [UserService, JwtService, EncryptionService, AuthUserService, CategoryService, EmailService], imports: [TypeOrmModule.forFeature([User, Category, UserResetCode])], imports: [TypeOrmModule.forFeature([User, Category, UserResetCode, UserConfirmCode])], }) export class AuthUserModule {}
backend/src/auth/user/authUsercontroller.ts +38 −4 Original line number Diff line number Diff line import { Body, Controller, Get, Patch, Post, Req, UseGuards } from '@nestjs/common'; import { Body, Controller, Patch, Post, Req, UseGuards } from '@nestjs/common'; import { ApiBearerAuth, ApiBody, ApiCreatedResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; import { AuthUserService } from './authUserservice'; import { CreateUserDto } from 'src/user/dto/create-user.dto'; Loading @@ -7,14 +7,22 @@ import { UserSigninResDto } from './dto/user-signin-res.dto'; import { AuthUserGuard } from './authUser.guard'; import { CustomUserRequest } from './interface/customUserReq'; import { UpdatePwdDto } from './dto/update-pwd.dto'; import { UserRequestCodeBody, UserResetPasswordBody, UserResetPasswordDto } from './dto/user-reset-password.dto'; import { UserConfirmEmailBody, UserRequestCodeBody, UserResetPasswordBody, UserResetPasswordDto, } from './dto/user-reset-password.dto'; import { GetResetCode } from './dto/get-reset-code.dto'; import { EmailService } from 'src/email/email.service'; @Controller('') @ApiTags('Create user account and sign in as user') export class AuthUserController { constructor(private readonly authUserService: AuthUserService, private readonly mailService: EmailService) {} constructor( private readonly authUserService: AuthUserService, private readonly mailService: EmailService, ) {} @ApiBody({ type: CreateUserDto }) @ApiCreatedResponse({ Loading Loading @@ -67,4 +75,30 @@ export class AuthUserController { throw e; } } @ApiBearerAuth('jwt') @UseGuards(AuthUserGuard) @Post('user/resend-confirmation-code') async resendConfirmationCode(@Req() req: CustomUserRequest) { try { const email = req.user.email; await this.authUserService.sendConfirmationCode(email); return { message: 'Confirmation code sent' }; } catch (e) { throw e; } } @ApiBody({ type: UserConfirmEmailBody }) @ApiBearerAuth('jwt') @UseGuards(AuthUserGuard) @Post('user/confirm-email') async confirmEmail(@Body() { code }, @Req() req: CustomUserRequest) { try { const email = req.user.email; return await this.authUserService.confirmEmail(email, { code }); } catch (e) { throw e; } } }
backend/src/auth/user/authUserservice.ts +37 −3 Original line number Diff line number Diff line Loading @@ -14,15 +14,19 @@ import { InjectRepository } from '@nestjs/typeorm'; import { UserResetCode } from './entities/user-reset-code.entity'; import { Repository } from 'typeorm'; import { randomInt } from 'crypto'; import { UserResetPasswordDto } from './dto/user-reset-password.dto'; import { UserConfirmEmailBody, UserResetPasswordDto } from './dto/user-reset-password.dto'; import { UserConfirmCode } from './entities/user-confirm-code.entity'; import { EmailService } from 'src/email/email.service'; @Injectable() export class AuthUserService { constructor( @InjectRepository(UserResetCode) private userResetCodeRepository: Repository<UserResetCode>, @InjectRepository(UserConfirmCode) private userConfirmCodeRepository: Repository<UserConfirmCode>, private userService: UserService, private jwtService: JwtService, private encryptionService: EncryptionService, private readonly mailService: EmailService, ) {} async signUp(createAdminDto: CreateUserDto): Promise<UserSigninResDto> { Loading @@ -38,6 +42,8 @@ export class AuthUserService { await this.userService.create(createAdminDto); const adminSigninResDto: UserSigninResDto = await this.signIn(loginAdminDto); await this.sendConfirmationCode(createAdminDto.email); return adminSigninResDto; } Loading Loading @@ -112,4 +118,32 @@ export class AuthUserService { await this.userService.updatePassword(email, newPwdHashed); await this.userResetCodeRepository.delete(userResetCode[0].id); } async sendConfirmationCode(email: string) { const user: User = await this.userService.findOne(email); if (!user) throw new UnauthorizedException('Invalid email'); const resetCode = randomInt(100000, 999999).toString(); const expirationDate = new Date(); expirationDate.setHours(expirationDate.getHours() + 1); const existUserCode = await this.userConfirmCodeRepository.findBy({ user, code: resetCode }); if (existUserCode.length > 0) { await this.userConfirmCodeRepository.delete(existUserCode[0].id); } await this.userConfirmCodeRepository.save({ user, code: resetCode, expirationDate }); await this.mailService.sendConfirmationCode(email, resetCode); return resetCode; } async confirmEmail(email: string, { code }: UserConfirmEmailBody) { const user: User = await this.userService.findOne(email); if (!user) throw new UnauthorizedException('Invalid email'); const userConfirmCode: UserResetCode[] = await this.userConfirmCodeRepository.findBy({ user, code: code }); if (userConfirmCode.length === 0) throw new UnauthorizedException('Invalid code'); if (userConfirmCode[0].expirationDate < new Date()) throw new UnauthorizedException('Code expired'); await this.userService.confirmEmail(email); await this.userConfirmCodeRepository.delete(userConfirmCode[0].id); } }
backend/src/auth/user/dto/user-reset-password.dto.ts +19 −14 Original line number Diff line number Diff line import { ApiProperty } from "@nestjs/swagger"; import { ApiProperty } from '@nestjs/swagger'; export interface UserResetPasswordDto { email: string; Loading @@ -19,3 +19,8 @@ export class UserRequestCodeBody { @ApiProperty() email: string; } export class UserConfirmEmailBody { @ApiProperty() code: string; }