From e6f3a0e104eb6538c0f917652f21b6521a7715dd Mon Sep 17 00:00:00 2001 From: rajdip-b Date: Tue, 18 Jun 2024 10:59:25 +0530 Subject: [PATCH] refactor(auth): Updated authentication logic --- src/auth/auth.e2e.spec.ts | 29 +------ src/auth/controller/auth.controller.ts | 35 ++------ src/auth/dto/signup.dto.ts | 16 ---- .../{signin.dto.ts => user-details.dto.ts} | 2 +- src/auth/service/auth.service.ts | 87 ++++++++++--------- 5 files changed, 56 insertions(+), 113 deletions(-) delete mode 100644 src/auth/dto/signup.dto.ts rename src/auth/dto/{signin.dto.ts => user-details.dto.ts} (92%) diff --git a/src/auth/auth.e2e.spec.ts b/src/auth/auth.e2e.spec.ts index 901122d..435d451 100644 --- a/src/auth/auth.e2e.spec.ts +++ b/src/auth/auth.e2e.spec.ts @@ -41,28 +41,7 @@ describe('Auth Controller Tests', () => { it('should be able to sign up using email', async () => { const response = await app.inject({ method: 'POST', - url: '/auth/sign-up', - payload: { - email: 'jane@example.com', - }, - }); - - expect(response.statusCode).toEqual(201); - expect(response.json().email).toEqual('jane@example.com'); - }); - - it('should be able to sign in using email', async () => { - await prisma.user.create({ - data: { - email: 'jane@example.com', - isEmailVerified: true, - authType: AuthType.EMAIL, - }, - }); - - const response = await app.inject({ - method: 'POST', - url: '/auth/sign-in', + url: '/auth/send-verification-email', payload: { email: 'jane@example.com', }, @@ -75,10 +54,9 @@ describe('Auth Controller Tests', () => { it('should send verification code to email on sign up', async () => { await app.inject({ method: 'POST', - url: '/auth/sign-up', + url: '/auth/send-verification-email', payload: { email: 'jane@example.com', - password: 'Password123', }, }); @@ -123,10 +101,9 @@ describe('Auth Controller Tests', () => { // Sign up await app.inject({ method: 'POST', - url: '/auth/sign-up', + url: '/auth/send-verification-email', payload: { email: 'jane@example.com', - password: 'Password123', }, }); diff --git a/src/auth/controller/auth.controller.ts b/src/auth/controller/auth.controller.ts index 6c280cb..a150f33 100644 --- a/src/auth/controller/auth.controller.ts +++ b/src/auth/controller/auth.controller.ts @@ -20,12 +20,10 @@ import { Public } from '../../decorators/public.decorator'; import { FacebookOAuthStrategyFactory } from '../../oauth/factory/facebook/facebook-strategy.factory'; import { LinkedInOAuthStrategyFactory } from '../../oauth/factory/linkedin/linkedin-strategy.factory'; import { AppleOAuthStrategyFactory } from '../../oauth/factory/apple/apple-strategy.factory'; -import { SignupDto } from '../dto/signup.dto'; -import { SigninDto } from '../dto/signin.dto'; +import { UserDetailsDto } from '../dto/user-details.dto'; import { EmailVerificationDto } from '../dto/email-verification.dto'; import { ApiBadRequestResponse, - ApiConflictResponse, ApiCreatedResponse, ApiNoContentResponse, ApiNotFoundResponse, @@ -313,29 +311,10 @@ export class AuthController { } @Public() - @Post('sign-up') + @Post('send-verification-email') @ApiOperation({ - summary: 'Sign up', - description: 'Sign up with email', - }) - @ApiCreatedResponse({ - description: 'User signed up successfully', - }) - @ApiConflictResponse({ - description: 'User with this email already exists', - }) - async signUp(@Body() dto: SignupDto) { - return await this.authService.signUp(dto); - } - - @Public() - @Post('sign-in') - @ApiOperation({ - summary: 'Sign in', - description: 'Sign in with email', - }) - @ApiNotFoundResponse({ - description: 'User not found', + summary: 'Sign in or sign up with email', + description: 'Sign in or sign up with email', }) @ApiCreatedResponse({ description: 'User signed in successfully', @@ -347,8 +326,8 @@ export class AuthController { }, }, }) - async signIn(@Body() dto: SigninDto) { - return await this.authService.signIn(dto); + async sendVerificationCode(@Body() dto: UserDetailsDto) { + return await this.authService.sendVerificationCode(dto); } @Public() @@ -396,7 +375,7 @@ export class AuthController { }, }) async verifyEmail(@Body() dto: EmailVerificationDto) { - return await this.authService.verifyEmail(dto.email, dto.code); + return await this.authService.verifyEmail(dto); } @Get('/social-accounts') diff --git a/src/auth/dto/signup.dto.ts b/src/auth/dto/signup.dto.ts deleted file mode 100644 index 0c7e4db..0000000 --- a/src/auth/dto/signup.dto.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Transform } from 'class-transformer'; -import { IsEmail } from 'class-validator'; - -export class SignupDto { - @IsEmail() - @Transform(({ value }) => value.toLowerCase()) - @ApiProperty({ - name: 'email', - description: 'User email. Must be a valid email address.', - required: true, - type: String, - example: 'johndoe@example.com', - }) - email: string; -} diff --git a/src/auth/dto/signin.dto.ts b/src/auth/dto/user-details.dto.ts similarity index 92% rename from src/auth/dto/signin.dto.ts rename to src/auth/dto/user-details.dto.ts index 20d4d6e..096c122 100644 --- a/src/auth/dto/signin.dto.ts +++ b/src/auth/dto/user-details.dto.ts @@ -2,7 +2,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { IsEmail } from 'class-validator'; -export class SigninDto { +export class UserDetailsDto { @IsEmail() @Transform(({ value }) => value.toLowerCase()) @ApiProperty({ diff --git a/src/auth/service/auth.service.ts b/src/auth/service/auth.service.ts index b06effa..2b49b52 100644 --- a/src/auth/service/auth.service.ts +++ b/src/auth/service/auth.service.ts @@ -1,6 +1,5 @@ import { BadRequestException, - ConflictException, ForbiddenException, Injectable, NotFoundException, @@ -8,9 +7,9 @@ import { import { PrismaService } from '../../prisma/prisma.service'; import { JwtService } from '@nestjs/jwt'; import { AuthType, SocialAccountType, User } from '@prisma/client'; -import { SignupDto } from '../dto/signup.dto'; +import { UserDetailsDto } from '../dto/user-details.dto'; import { MailService } from '../../mail/mail.service'; -import { SigninDto } from '../dto/signin.dto'; +import { EmailVerificationDto } from '../dto/email-verification.dto'; @Injectable() export class AuthService { @@ -20,37 +19,17 @@ export class AuthService { private mailService: MailService, ) {} - async signUp(dto: SignupDto) { + async sendVerificationCode(dto: UserDetailsDto) { const user = await this.createUserIfNotExists( dto.email, AuthType.EMAIL, null, null, - true, ); return user; } - async signIn(dto: SigninDto) { - const user = await this.prisma.user.findUnique({ - where: { - email: dto.email, - }, - select: { - isEmailVerified: true, - email: true, - }, - }); - - if (!user) { - throw new NotFoundException('User not found'); - } - - await this.sendEmailVerificationCode(dto.email); - return user; - } - async handleGoogleOAuthLogin(req: any) { const { emails, displayName: name, photos } = req.user; const email = emails[0].value; @@ -61,7 +40,6 @@ export class AuthService { AuthType.GOOGLE, name, profilePictureUrl, - false, ); const token = await this.generateToken(user); @@ -98,7 +76,6 @@ export class AuthService { AuthType.FACEBOOK, displayName, profilePictureUrl, - false, ); await this.connectSocialPlatform( SocialAccountType.FACEBOOK, @@ -137,7 +114,6 @@ export class AuthService { AuthType.LINKEDIN, displayName, picture, - false, ); await this.connectSocialPlatform( SocialAccountType.LINKEDIN, @@ -161,7 +137,6 @@ export class AuthService { AuthType.APPLE, displayName, null, - false, ); const token = await this.generateToken(user); @@ -194,7 +169,6 @@ export class AuthService { AuthType.GITHUB, name, avatar_url, - false, ); await this.connectSocialPlatform(SocialAccountType.GITHUB, user.id, req); } @@ -216,7 +190,9 @@ export class AuthService { await this.sendEmailVerificationCode(email); } - async verifyEmail(email: string, code: string) { + async verifyEmail(dto: EmailVerificationDto) { + const { email, code } = dto; + const verificationCode = await this.prisma.verificationCode.findUnique({ where: { email, @@ -235,7 +211,17 @@ export class AuthService { throw new BadRequestException('Code expired'); } - const user = await this.prisma.user.update({ + const user = await this.prisma.user.findUnique({ + where: { + email, + }, + }); + + if (!user) { + throw new NotFoundException('User not found'); + } + + const updatedUser = await this.prisma.user.update({ where: { email, }, @@ -258,12 +244,14 @@ export class AuthService { }, }); - await this.mailService.sendEmailVerifiedEmail(email); + // We send the email verified email only if the user is was not verified before + if (!user.isEmailVerified) + await this.mailService.sendEmailVerifiedEmail(email); - const token = await this.generateToken(user); + const token = await this.generateToken(updatedUser); return { - ...user, + ...updatedUser, token, }; } @@ -273,14 +261,11 @@ export class AuthService { authType: AuthType, name?: string, profilePictureUrl?: string, - throwErrorIfUserExists?: boolean, ) { email = email.toLowerCase(); let user = await this.findUserByEmail(email); - if (user && throwErrorIfUserExists) { - throw new ConflictException('User already exists'); - } + // We need to create the user if it doesn't exist yet if (!user) { user = await this.prisma.user.create({ @@ -289,7 +274,7 @@ export class AuthService { name: name, profilePictureUrl: profilePictureUrl, authType, - isEmailVerified: authType !== AuthType.EMAIL, + isEmailVerified: authType !== AuthType.EMAIL, // If the user signs up with OAuth, we consider the email as verified settings: { create: {}, }, @@ -303,9 +288,28 @@ export class AuthService { isEmailVerified: true, }, }); + } else { + // And if it exists, we need to update the user data + user = await this.prisma.user.update({ + where: { + email, + }, + data: { + name, + profilePictureUrl, + }, + select: { + id: true, + email: true, + name: true, + profilePictureUrl: true, + authType: true, + isEmailVerified: true, + }, + }); + } - await this.sendEmailVerificationCode(email); - } else if (!user.isEmailVerified) { + if (!user.isEmailVerified) { await this.sendEmailVerificationCode(email); } @@ -384,7 +388,6 @@ export class AuthService { userId: string, req: any, ) { - const socialAcc = await this.prisma.socialAccount.findMany({ where: { socialId: req.user.id, platform }, });