diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index 60dd928..138e689 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -3,14 +3,14 @@ import { Module } from '@nestjs/common'; import { JwtModule } from '@nestjs/jwt'; import { JuniorModule } from '~/junior/junior.module'; import { UserModule } from '~/user/user.module'; -import { AuthController, AuthV2Controller } from './controllers'; +import { AuthController } from './controllers'; import { AuthService, Oauth2Service } from './services'; import { AccessTokenStrategy } from './strategies'; @Module({ imports: [JwtModule.register({}), UserModule, JuniorModule, HttpModule], providers: [AuthService, AccessTokenStrategy, Oauth2Service], - controllers: [AuthController, AuthV2Controller], + controllers: [AuthController], exports: [], }) export class AuthModule {} diff --git a/src/auth/controllers/auth.controller.ts b/src/auth/controllers/auth.controller.ts index d402ad6..f651e69 100644 --- a/src/auth/controllers/auth.controller.ts +++ b/src/auth/controllers/auth.controller.ts @@ -1,29 +1,20 @@ import { Body, Controller, HttpCode, HttpStatus, Post, Req, UseGuards } from '@nestjs/common'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; import { Request } from 'express'; -import { AuthenticatedUser, Public } from '~/common/decorators'; +import { Public } from '~/common/decorators'; import { AccessTokenGuard } from '~/common/guards'; -import { ApiDataResponse, ApiLangRequestHeader } from '~/core/decorators'; +import { ApiLangRequestHeader } from '~/core/decorators'; import { ResponseFactory } from '~/core/utils'; import { - AppleLoginRequestDto, CreateUnverifiedUserRequestDto, - DisableBiometricRequestDto, - EnableBiometricRequestDto, ForgetPasswordRequestDto, - GoogleLoginRequestDto, + LoginRequestDto, RefreshTokenRequestDto, SendForgetPasswordOtpRequestDto, - SendLoginOtpRequestDto, - SetEmailRequestDto, - setJuniorPasswordRequestDto, - SetPasscodeRequestDto, - VerifyLoginOtpRequestDto, VerifyUserRequestDto, } from '../dtos/request'; import { SendForgetPasswordOtpResponseDto, SendRegisterOtpResponseDto } from '../dtos/response'; import { LoginResponseDto } from '../dtos/response/login.response.dto'; -import { IJwtPayload } from '../interfaces'; import { AuthService } from '../services'; @Controller('auth') @@ -44,85 +35,16 @@ export class AuthController { return ResponseFactory.data(new LoginResponseDto(res, user)); } - @Post('login/otp') - @HttpCode(HttpStatus.NO_CONTENT) - async sendLoginOtp(@Body() data: SendLoginOtpRequestDto) { - return this.authService.sendLoginOtp(data); - } - - @Post('login/verify') - @HttpCode(HttpStatus.OK) - @ApiDataResponse(LoginResponseDto) - async verifyLoginOtp(@Body() data: VerifyLoginOtpRequestDto) { - const [token, user] = await this.authService.verifyLoginOtp(data); - return ResponseFactory.data(new LoginResponseDto(token, user)); - } - - @Post('login/google') - @HttpCode(HttpStatus.OK) - @ApiDataResponse(LoginResponseDto) - async loginWithGoogle(@Body() data: GoogleLoginRequestDto) { - const [token, user] = await this.authService.loginWithGoogle(data); - return ResponseFactory.data(new LoginResponseDto(token, user)); - } - - @Post('login/apple') - @HttpCode(HttpStatus.OK) - @ApiDataResponse(LoginResponseDto) - async loginWithApple(@Body() data: AppleLoginRequestDto) { - const [token, user] = await this.authService.loginWithApple(data); - return ResponseFactory.data(new LoginResponseDto(token, user)); - } - - @Post('register/set-email') - @HttpCode(HttpStatus.NO_CONTENT) - @UseGuards(AccessTokenGuard) - async setEmail(@AuthenticatedUser() { sub }: IJwtPayload, @Body() setEmailDto: SetEmailRequestDto) { - await this.authService.setEmail(sub, setEmailDto); - } - - @Post('register/set-passcode') - @HttpCode(HttpStatus.NO_CONTENT) - @UseGuards(AccessTokenGuard) - async setPasscode(@AuthenticatedUser() { sub }: IJwtPayload, @Body() { passcode }: SetPasscodeRequestDto) { - await this.authService.setPasscode(sub, passcode); - } - - // @Post('register/set-phone/otp') - // @UseGuards(AccessTokenGuard) - // async setPhoneNumber( - // @AuthenticatedUser() { sub }: IJwtPayload, - // @Body() setPhoneNumberDto: CreateUnverifiedUserRequestDto, - // ) { - // const phoneNumber = await this.authService.setPhoneNumber(sub, setPhoneNumberDto); - // return ResponseFactory.data(new SendRegisterOtpResponseDto(phoneNumber)); - // } - - // @Post('register/set-phone/verify') - // @HttpCode(HttpStatus.NO_CONTENT) - // @UseGuards(AccessTokenGuard) - // async verifyPhoneNumber(@AuthenticatedUser() { sub }: IJwtPayload, @Body() { otp }: VerifyOtpRequestDto) { - // await this.authService.verifyPhoneNumber(sub, otp); - // } - - @Post('biometric/enable') - @HttpCode(HttpStatus.NO_CONTENT) - @UseGuards(AccessTokenGuard) - enableBiometric(@AuthenticatedUser() { sub }: IJwtPayload, @Body() enableBiometricDto: EnableBiometricRequestDto) { - return this.authService.enableBiometric(sub, enableBiometricDto); - } - - @Post('biometric/disable') - @HttpCode(HttpStatus.NO_CONTENT) - @UseGuards(AccessTokenGuard) - disableBiometric(@AuthenticatedUser() { sub }: IJwtPayload, @Body() disableBiometricDto: DisableBiometricRequestDto) { - return this.authService.disableBiometric(sub, disableBiometricDto); + @Post('login') + async login(@Body() verifyUserDto: LoginRequestDto) { + const [res, user] = await this.authService.loginWithPassword(verifyUserDto); + return ResponseFactory.data(new LoginResponseDto(res, user)); } @Post('forget-password/otp') async forgetPassword(@Body() sendForgetPasswordOtpDto: SendForgetPasswordOtpRequestDto) { - const email = await this.authService.sendForgetPasswordOtp(sendForgetPasswordOtpDto); - return ResponseFactory.data(new SendForgetPasswordOtpResponseDto(email)); + const maskedNumber = await this.authService.sendForgetPasswordOtp(sendForgetPasswordOtpDto); + return ResponseFactory.data(new SendForgetPasswordOtpResponseDto(maskedNumber)); } @Post('forget-password/reset') @@ -131,13 +53,6 @@ export class AuthController { return this.authService.verifyForgetPasswordOtp(forgetPasswordDto); } - @Post('junior/set-passcode') - @HttpCode(HttpStatus.NO_CONTENT) - @Public() - setJuniorPasscode(@Body() setPasscodeDto: setJuniorPasswordRequestDto) { - return this.authService.setJuniorPasscode(setPasscodeDto); - } - @Post('refresh-token') @Public() async refreshToken(@Body() { refreshToken }: RefreshTokenRequestDto) { @@ -151,4 +66,25 @@ export class AuthController { async logout(@Req() request: Request) { await this.authService.logout(request); } + + // @Post('biometric/enable') + // @HttpCode(HttpStatus.NO_CONTENT) + // @UseGuards(AccessTokenGuard) + // enableBiometric(@AuthenticatedUser() { sub }: IJwtPayload, @Body() enableBiometricDto: EnableBiometricRequestDto) { + // return this.authService.enableBiometric(sub, enableBiometricDto); + // } + + // @Post('biometric/disable') + // @HttpCode(HttpStatus.NO_CONTENT) + // @UseGuards(AccessTokenGuard) + // disableBiometric(@AuthenticatedUser() { sub }: IJwtPayload, @Body() disableBiometricDto: DisableBiometricRequestDto) { + // return this.authService.disableBiometric(sub, disableBiometricDto); + // } + + // @Post('junior/set-passcode') + // @HttpCode(HttpStatus.NO_CONTENT) + // @Public() + // setJuniorPasscode(@Body() setPasscodeDto: setJuniorPasswordRequestDto) { + // return this.authService.setJuniorPasscode(setPasscodeDto); + // } } diff --git a/src/auth/controllers/auth.v2.controller.ts b/src/auth/controllers/auth.v2.controller.ts deleted file mode 100644 index 3b79641..0000000 --- a/src/auth/controllers/auth.v2.controller.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Body, Controller, Post } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { ApiDataResponse } from '~/core/decorators'; -import { ResponseFactory } from '~/core/utils'; -import { CreateUnverifiedUserV2RequestDto, VerifyUserV2RequestDto } from '../dtos/request'; -import { LoginResponseDto } from '../dtos/response/login.response.dto'; -import { SendRegisterOtpV2ResponseDto } from '../dtos/response/send-register-otp.v2.response.dto'; -import { AuthService } from '../services'; - -@Controller('auth/v2') -@ApiTags('Auth V2') -export class AuthV2Controller { - constructor(private readonly authService: AuthService) {} - @Post('register/otp') - @ApiDataResponse(SendRegisterOtpV2ResponseDto) - async register(@Body() createUnverifiedUserDto: CreateUnverifiedUserV2RequestDto) { - const phoneNumber = await this.authService.sendPhoneRegisterOtp(createUnverifiedUserDto); - return ResponseFactory.data(new SendRegisterOtpV2ResponseDto(phoneNumber)); - } - - @Post('register/verify') - @ApiDataResponse(LoginResponseDto) - async verifyUser(@Body() verifyUserDto: VerifyUserV2RequestDto) { - const [loginResponse, user] = await this.authService.verifyUserV2(verifyUserDto); - return ResponseFactory.data(new LoginResponseDto(loginResponse, user)); - } -} diff --git a/src/auth/controllers/index.ts b/src/auth/controllers/index.ts index 37a52b8..04d02fa 100644 --- a/src/auth/controllers/index.ts +++ b/src/auth/controllers/index.ts @@ -1,2 +1 @@ export * from './auth.controller'; -export * from './auth.v2.controller'; diff --git a/src/auth/dtos/request/create-unverified-user.request.dto.ts b/src/auth/dtos/request/create-unverified-user.request.dto.ts index 9fdc3bd..520f99e 100644 --- a/src/auth/dtos/request/create-unverified-user.request.dto.ts +++ b/src/auth/dtos/request/create-unverified-user.request.dto.ts @@ -1,14 +1,4 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsEmail } from 'class-validator'; -import { i18nValidationMessage as i18n } from 'nestjs-i18n'; +import { OmitType } from '@nestjs/swagger'; +import { VerifyUserRequestDto } from './verify-user.request.dto'; -export class CreateUnverifiedUserRequestDto { - @ApiProperty({ example: 'test@test.com' }) - @IsEmail( - {}, - { - message: i18n('validation.IsEmail', { path: 'general', property: 'auth.email' }), - }, - ) - email!: string; -} +export class CreateUnverifiedUserRequestDto extends OmitType(VerifyUserRequestDto, ['otp']) {} diff --git a/src/auth/dtos/request/create-unverified-user.request.v2.dto.ts b/src/auth/dtos/request/create-unverified-user.request.v2.dto.ts deleted file mode 100644 index 7df2980..0000000 --- a/src/auth/dtos/request/create-unverified-user.request.v2.dto.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { OmitType } from '@nestjs/swagger'; -import { VerifyUserV2RequestDto } from './verify-user.v2.request.dto'; - -export class CreateUnverifiedUserV2RequestDto extends OmitType(VerifyUserV2RequestDto, ['otp']) {} diff --git a/src/auth/dtos/request/forget-password.request.dto.ts b/src/auth/dtos/request/forget-password.request.dto.ts index 97f7236..4153a96 100644 --- a/src/auth/dtos/request/forget-password.request.dto.ts +++ b/src/auth/dtos/request/forget-password.request.dto.ts @@ -1,20 +1,32 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsEmail, IsNotEmpty, IsNumberString, IsString, MaxLength, MinLength } from 'class-validator'; +import { ApiProperty, PickType } from '@nestjs/swagger'; +import { IsEmail, IsNotEmpty, IsNumberString, IsString, Matches, MaxLength, MinLength } from 'class-validator'; import { i18nValidationMessage as i18n } from 'nestjs-i18n'; +import { COUNTRY_CODE_REGEX, PASSWORD_REGEX } from '~/auth/constants'; import { DEFAULT_OTP_LENGTH } from '~/common/modules/otp/constants'; +import { IsValidPhoneNumber } from '~/core/decorators/validations'; export class ForgetPasswordRequestDto { - @ApiProperty({ example: 'test@test.com' }) - @IsEmail({}, { message: i18n('validation.IsEmail', { path: 'general', property: 'auth.email' }) }) - email!: string; + @ApiProperty({ example: '+962' }) + @Matches(COUNTRY_CODE_REGEX, { + message: i18n('validation.Matches', { path: 'general', property: 'auth.countryCode' }), + }) + countryCode!: string; - @ApiProperty({ example: 'password' }) - @IsString({ message: i18n('validation.IsString', { path: 'general', property: 'auth.password' }) }) - @IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'auth.password' }) }) + @ApiProperty({ example: '787259134' }) + @IsValidPhoneNumber({ + message: i18n('validation.IsValidPhoneNumber', { path: 'general', property: 'auth.phoneNumber' }), + }) + phoneNumber!: string; + + @ApiProperty({ example: 'Abcd1234@' }) + @Matches(PASSWORD_REGEX, { + message: i18n('validation.Matches', { path: 'general', property: 'auth.password' }), + }) password!: string; - @ApiProperty({ example: 'password' }) - @IsString({ message: i18n('validation.IsString', { path: 'general', property: 'auth.confirmPassword' }) }) - @IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'auth.confirmPassword' }) }) + @ApiProperty({ example: 'Abcd1234@' }) + @Matches(PASSWORD_REGEX, { + message: i18n('validation.Matches', { path: 'general', property: 'auth.confirmPassword' }), + }) confirmPassword!: string; @ApiProperty({ example: '111111' }) diff --git a/src/auth/dtos/request/index.ts b/src/auth/dtos/request/index.ts index ab51125..a329e46 100644 --- a/src/auth/dtos/request/index.ts +++ b/src/auth/dtos/request/index.ts @@ -1,6 +1,5 @@ export * from './apple-login.request.dto'; export * from './create-unverified-user.request.dto'; -export * from './create-unverified-user.request.v2.dto'; export * from './disable-biometric.request.dto'; export * from './enable-biometric.request.dto'; export * from './forget-password.request.dto'; @@ -8,11 +7,8 @@ export * from './google-login.request.dto'; export * from './login.request.dto'; export * from './refresh-token.request.dto'; export * from './send-forget-password-otp.request.dto'; -export * from './send-login-otp.request.dto'; export * from './set-email.request.dto'; export * from './set-junior-password.request.dto'; export * from './set-passcode.request.dto'; -export * from './verify-login-otp.request.dto'; export * from './verify-otp.request.dto'; export * from './verify-user.request.dto'; -export * from './verify-user.v2.request.dto'; diff --git a/src/auth/dtos/request/login.request.dto.ts b/src/auth/dtos/request/login.request.dto.ts index 476961d..ed48ae3 100644 --- a/src/auth/dtos/request/login.request.dto.ts +++ b/src/auth/dtos/request/login.request.dto.ts @@ -1,43 +1,24 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsEmail, IsEnum, IsNotEmpty, IsOptional, IsString, ValidateIf } from 'class-validator'; +import { IsEmail, IsEnum, IsNotEmpty, IsOptional, IsString, Matches, ValidateIf } from 'class-validator'; import { i18nValidationMessage as i18n } from 'nestjs-i18n'; +import { COUNTRY_CODE_REGEX } from '~/auth/constants'; import { GrantType } from '~/auth/enums'; +import { IsValidPhoneNumber } from '~/core/decorators/validations'; export class LoginRequestDto { - @ApiProperty({ example: GrantType.APPLE }) - @IsEnum(GrantType, { message: i18n('validation.IsEnum', { path: 'general', property: 'auth.grantType' }) }) - grantType!: GrantType; + @ApiProperty({ example: '+962' }) + @Matches(COUNTRY_CODE_REGEX, { + message: i18n('validation.Matches', { path: 'general', property: 'auth.countryCode' }), + }) + countryCode!: string; - @ApiProperty({ example: 'test@test.com' }) - @IsString({ message: i18n('validation.IsString', { path: 'general', property: 'auth.email' }) }) - @IsEmail({}, { message: i18n('validation.IsEmail', { path: 'general', property: 'auth.email' }) }) - @ValidateIf((o) => o.grantType !== GrantType.APPLE && o.grantType !== GrantType.GOOGLE) - email!: string; + @ApiProperty({ example: '787259134' }) + @IsValidPhoneNumber({ + message: i18n('validation.IsValidPhoneNumber', { path: 'general', property: 'auth.phoneNumber' }), + }) + phoneNumber!: string; - @ApiProperty({ example: '123456' }) + @ApiProperty({ example: 'Abcd1234@' }) @IsString({ message: i18n('validation.IsString', { path: 'general', property: 'auth.password' }) }) @ValidateIf((o) => o.grantType === GrantType.PASSWORD) password!: string; - - @ApiProperty({ example: 'Login signature' }) - @IsString({ message: i18n('validation.IsString', { path: 'general', property: 'auth.signature' }) }) - @ValidateIf((o) => o.grantType === GrantType.BIOMETRIC) - signature!: string; - - @ApiProperty({ example: 'google_token' }) - @IsString({ message: i18n('validation.IsString', { path: 'general', property: 'auth.googleToken' }) }) - @IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'auth.googleToken' }) }) - @ValidateIf((o) => o.grantType === GrantType.GOOGLE) - googleToken!: string; - - @ApiProperty({ example: 'apple_token' }) - @IsString({ message: i18n('validation.IsString', { path: 'general', property: 'auth.appleToken' }) }) - @IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'auth.appleToken' }) }) - @ValidateIf((o) => o.grantType === GrantType.APPLE) - appleToken!: string; - - @ApiProperty({ example: 'fcm-device-token' }) - @IsString({ message: i18n('validation.IsString', { path: 'general', property: 'auth.fcmToken' }) }) - @IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'auth.fcmToken' }) }) - @IsOptional() - fcmToken?: string; } diff --git a/src/auth/dtos/request/send-forget-password-otp.request.dto.ts b/src/auth/dtos/request/send-forget-password-otp.request.dto.ts index 077b14a..ebb7941 100644 --- a/src/auth/dtos/request/send-forget-password-otp.request.dto.ts +++ b/src/auth/dtos/request/send-forget-password-otp.request.dto.ts @@ -1,4 +1,4 @@ import { PickType } from '@nestjs/swagger'; import { LoginRequestDto } from './login.request.dto'; -export class SendForgetPasswordOtpRequestDto extends PickType(LoginRequestDto, ['email']) {} +export class SendForgetPasswordOtpRequestDto extends PickType(LoginRequestDto, ['countryCode', 'phoneNumber']) {} diff --git a/src/auth/dtos/request/send-login-otp.request.dto.ts b/src/auth/dtos/request/send-login-otp.request.dto.ts deleted file mode 100644 index 45388f3..0000000 --- a/src/auth/dtos/request/send-login-otp.request.dto.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsEmail } from 'class-validator'; -import { i18nValidationMessage as i18n } from 'nestjs-i18n'; - -export class SendLoginOtpRequestDto { - @ApiProperty({ example: 'test@test.com' }) - @IsEmail({}, { message: i18n('validation.IsEmail', { path: 'general', property: 'auth.email' }) }) - email!: string; -} diff --git a/src/auth/dtos/request/verify-login-otp.request.dto.ts b/src/auth/dtos/request/verify-login-otp.request.dto.ts deleted file mode 100644 index 819ead5..0000000 --- a/src/auth/dtos/request/verify-login-otp.request.dto.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNumberString, MaxLength, MinLength } from 'class-validator'; -import { i18nValidationMessage as i18n } from 'nestjs-i18n'; -import { DEFAULT_OTP_LENGTH } from '~/common/modules/otp/constants'; -import { SendLoginOtpRequestDto } from './send-login-otp.request.dto'; - -export class VerifyLoginOtpRequestDto extends SendLoginOtpRequestDto { - @ApiProperty({ example: '111111' }) - @IsNumberString( - { no_symbols: true }, - { message: i18n('validation.IsNumberString', { path: 'general', property: 'auth.otp' }) }, - ) - @MaxLength(DEFAULT_OTP_LENGTH, { - message: i18n('validation.MaxLength', { path: 'general', property: 'auth.otp', length: DEFAULT_OTP_LENGTH }), - }) - @MinLength(DEFAULT_OTP_LENGTH, { - message: i18n('validation.MinLength', { path: 'general', property: 'auth.otp', length: DEFAULT_OTP_LENGTH }), - }) - otp!: string; -} diff --git a/src/auth/dtos/request/verify-user.request.dto.ts b/src/auth/dtos/request/verify-user.request.dto.ts index c1adc87..f0d7289 100644 --- a/src/auth/dtos/request/verify-user.request.dto.ts +++ b/src/auth/dtos/request/verify-user.request.dto.ts @@ -1,21 +1,34 @@ -import { ApiProperty, PickType } from '@nestjs/swagger'; +import { ApiProperty } from '@nestjs/swagger'; import { IsDateString, + IsEmail, IsEnum, IsNotEmpty, IsNumberString, IsOptional, IsString, + Matches, MaxLength, MinLength, } from 'class-validator'; import { i18nValidationMessage as i18n } from 'nestjs-i18n'; +import { COUNTRY_CODE_REGEX, PASSWORD_REGEX } from '~/auth/constants'; import { CountryIso } from '~/common/enums'; import { DEFAULT_OTP_LENGTH } from '~/common/modules/otp/constants'; -import { IsAbove18 } from '~/core/decorators/validations'; -import { CreateUnverifiedUserRequestDto } from './create-unverified-user.request.dto'; +import { IsAbove18, IsValidPhoneNumber } from '~/core/decorators/validations'; -export class VerifyUserRequestDto extends PickType(CreateUnverifiedUserRequestDto, ['email']) { +export class VerifyUserRequestDto { + @ApiProperty({ example: '+962' }) + @Matches(COUNTRY_CODE_REGEX, { + message: i18n('validation.Matches', { path: 'general', property: 'auth.countryCode' }), + }) + countryCode!: string; + + @ApiProperty({ example: '787259134' }) + @IsValidPhoneNumber({ + message: i18n('validation.IsValidPhoneNumber', { path: 'general', property: 'auth.phoneNumber' }), + }) + phoneNumber!: string; @ApiProperty({ example: 'John' }) @IsString({ message: i18n('validation.IsString', { path: 'general', property: 'customer.firstName' }) }) @IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'customer.firstName' }) }) @@ -38,6 +51,23 @@ export class VerifyUserRequestDto extends PickType(CreateUnverifiedUserRequestDt @IsOptional() countryOfResidence: CountryIso = CountryIso.SAUDI_ARABIA; + @ApiProperty({ example: 'test@test.com' }) + @IsEmail({}, { message: i18n('validation.IsEmail', { path: 'general', property: 'auth.email' }) }) + @IsOptional() + email!: string; + + @ApiProperty({ example: 'Abcd1234@' }) + @Matches(PASSWORD_REGEX, { + message: i18n('validation.Matches', { path: 'general', property: 'auth.password' }), + }) + password!: string; + + @ApiProperty({ example: 'Abcd1234@' }) + @Matches(PASSWORD_REGEX, { + message: i18n('validation.Matches', { path: 'general', property: 'auth.confirmPassword' }), + }) + confirmPassword!: string; + @ApiProperty({ example: '111111' }) @IsNumberString( { no_symbols: true }, diff --git a/src/auth/dtos/request/verify-user.v2.request.dto.ts b/src/auth/dtos/request/verify-user.v2.request.dto.ts deleted file mode 100644 index ce0f216..0000000 --- a/src/auth/dtos/request/verify-user.v2.request.dto.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { - IsDateString, - IsEmail, - IsEnum, - IsNotEmpty, - IsNumberString, - IsOptional, - IsString, - Matches, - MaxLength, - MinLength, -} from 'class-validator'; -import { i18nValidationMessage as i18n } from 'nestjs-i18n'; -import { COUNTRY_CODE_REGEX } from '~/auth/constants'; -import { CountryIso } from '~/common/enums'; -import { DEFAULT_OTP_LENGTH } from '~/common/modules/otp/constants'; -import { IsAbove18, IsValidPhoneNumber } from '~/core/decorators/validations'; - -export class VerifyUserV2RequestDto { - @ApiProperty({ example: '+962' }) - @Matches(COUNTRY_CODE_REGEX, { - message: i18n('validation.Matches', { path: 'general', property: 'auth.countryCode' }), - }) - countryCode!: string; - - @ApiProperty({ example: '787259134' }) - @IsValidPhoneNumber({ - message: i18n('validation.IsValidPhoneNumber', { path: 'general', property: 'auth.phoneNumber' }), - }) - phoneNumber!: string; - @ApiProperty({ example: 'John' }) - @IsString({ message: i18n('validation.IsString', { path: 'general', property: 'customer.firstName' }) }) - @IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'customer.firstName' }) }) - firstName!: string; - - @ApiProperty({ example: 'Doe' }) - @IsString({ message: i18n('validation.IsString', { path: 'general', property: 'customer.lastName' }) }) - @IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'customer.lastName' }) }) - lastName!: string; - - @ApiProperty({ example: '2021-01-01' }) - @IsDateString({}, { message: i18n('validation.IsDateString', { path: 'general', property: 'customer.dateOfBirth' }) }) - @IsAbove18({ message: i18n('validation.IsAbove18', { path: 'general', property: 'customer.dateOfBirth' }) }) - dateOfBirth!: Date; - - @ApiProperty({ example: 'JO' }) - @IsEnum(CountryIso, { - message: i18n('validation.IsEnum', { path: 'general', property: 'customer.countryOfResidence' }), - }) - @IsOptional() - countryOfResidence: CountryIso = CountryIso.SAUDI_ARABIA; - - @ApiProperty({ example: 'test@test.com' }) - @IsEmail({}, { message: i18n('validation.IsEmail', { path: 'general', property: 'auth.email' }) }) - @IsOptional() - email!: string; - - @ApiProperty({ example: '111111' }) - @IsNumberString( - { no_symbols: true }, - { message: i18n('validation.IsNumberString', { path: 'general', property: 'auth.otp' }) }, - ) - @MaxLength(DEFAULT_OTP_LENGTH, { - message: i18n('validation.MaxLength', { path: 'general', property: 'auth.otp', length: DEFAULT_OTP_LENGTH }), - }) - @MinLength(DEFAULT_OTP_LENGTH, { - message: i18n('validation.MinLength', { path: 'general', property: 'auth.otp', length: DEFAULT_OTP_LENGTH }), - }) - otp!: string; -} diff --git a/src/auth/dtos/response/send-forget-password.response.dto.ts b/src/auth/dtos/response/send-forget-password.response.dto.ts index 1bbf6b8..40e338f 100644 --- a/src/auth/dtos/response/send-forget-password.response.dto.ts +++ b/src/auth/dtos/response/send-forget-password.response.dto.ts @@ -1,7 +1,7 @@ export class SendForgetPasswordOtpResponseDto { - email!: string; + maskedNumber!: string; - constructor(email: string) { - this.email = email; + constructor(maskedNumber: string) { + this.maskedNumber = maskedNumber; } } diff --git a/src/auth/dtos/response/send-register-otp.response.dto.ts b/src/auth/dtos/response/send-register-otp.response.dto.ts index 0597b90..ee27ad5 100644 --- a/src/auth/dtos/response/send-register-otp.response.dto.ts +++ b/src/auth/dtos/response/send-register-otp.response.dto.ts @@ -2,9 +2,9 @@ import { ApiProperty } from '@nestjs/swagger'; export class SendRegisterOtpResponseDto { @ApiProperty() - email!: string; + maskedNumber!: string; - constructor(email: string) { - this.email = email; + constructor(maskedNumber: string) { + this.maskedNumber = maskedNumber; } } diff --git a/src/auth/enums/grant-type.enum.ts b/src/auth/enums/grant-type.enum.ts index 952f851..4a16d92 100644 --- a/src/auth/enums/grant-type.enum.ts +++ b/src/auth/enums/grant-type.enum.ts @@ -1,6 +1,4 @@ export enum GrantType { PASSWORD = 'PASSWORD', BIOMETRIC = 'BIOMETRIC', - GOOGLE = 'GOOGLE', - APPLE = 'APPLE', } diff --git a/src/auth/services/auth.service.ts b/src/auth/services/auth.service.ts index 908e6ed..ca5e4dd 100644 --- a/src/auth/services/auth.service.ts +++ b/src/auth/services/auth.service.ts @@ -3,7 +3,6 @@ import { ConfigService } from '@nestjs/config'; import { JwtService } from '@nestjs/jwt'; import * as bcrypt from 'bcrypt'; import { Request } from 'express'; -import { ArrayContains } from 'typeorm'; import { CacheService } from '~/common/modules/cache/services'; import { OtpScope, OtpType } from '~/common/modules/otp/enums'; import { OtpService } from '~/common/modules/otp/services'; @@ -12,23 +11,15 @@ import { DeviceService, UserService, UserTokenService } from '~/user/services'; import { User } from '../../user/entities'; import { PASSCODE_REGEX } from '../constants'; import { - AppleLoginRequestDto, CreateUnverifiedUserRequestDto, - CreateUnverifiedUserV2RequestDto, DisableBiometricRequestDto, EnableBiometricRequestDto, ForgetPasswordRequestDto, - GoogleLoginRequestDto, LoginRequestDto, SendForgetPasswordOtpRequestDto, - SendLoginOtpRequestDto, - SetEmailRequestDto, setJuniorPasswordRequestDto, - VerifyLoginOtpRequestDto, VerifyUserRequestDto, - VerifyUserV2RequestDto, } from '../dtos/request'; -import { Roles } from '../enums'; import { IJwtPayload, ILoginResponse } from '../interfaces'; import { removePadding, verifySignature } from '../utils'; import { Oauth2Service } from './oauth2.service'; @@ -49,19 +40,8 @@ export class AuthService { private readonly cacheService: CacheService, private readonly oauth2Service: Oauth2Service, ) {} + async sendRegisterOtp(body: CreateUnverifiedUserRequestDto) { - this.logger.log(`Sending OTP to ${body.email}`); - const user = await this.userService.findOrCreateUser(body); - - return this.otpService.generateAndSendOtp({ - userId: user.id, - recipient: user.email, - scope: OtpScope.VERIFY_EMAIL, - otpType: OtpType.EMAIL, - }); - } - - async sendPhoneRegisterOtp(body: CreateUnverifiedUserV2RequestDto) { if (body.email) { const isEmailUsed = await this.userService.findUser({ email: body.email, isEmailVerified: true }); if (isEmailUsed) { @@ -70,8 +50,13 @@ export class AuthService { } } + if (body.password !== body.confirmPassword) { + this.logger.error('Password and confirm password do not match'); + throw new BadRequestException('AUTH.PASSWORD_MISMATCH'); + } + this.logger.log(`Sending OTP to ${body.countryCode + body.phoneNumber}`); - const user = await this.userService.findOrCreateByPhoneNumber(body); + const user = await this.userService.findOrCreateUser(body); return this.otpService.generateAndSendOtp({ userId: user.id, recipient: user.fullPhoneNumber, @@ -81,36 +66,6 @@ export class AuthService { } async verifyUser(verifyUserDto: VerifyUserRequestDto): Promise<[ILoginResponse, User]> { - this.logger.log(`Verifying user with email ${verifyUserDto.email}`); - const user = await this.userService.findUserOrThrow({ email: verifyUserDto.email }); - - if (user.isEmailVerified) { - this.logger.error(`User with email ${verifyUserDto.email} already verified`); - throw new BadRequestException('USER.EMAIL_ALREADY_VERIFIED'); - } - - const isOtpValid = await this.otpService.verifyOtp({ - userId: user.id, - scope: OtpScope.VERIFY_EMAIL, - otpType: OtpType.EMAIL, - value: verifyUserDto.otp, - }); - - if (!isOtpValid) { - this.logger.error(`Invalid OTP for user with email ${verifyUserDto.email}`); - throw new BadRequestException('OTP.INVALID_OTP'); - } - - await this.userService.verifyUser(user.id, verifyUserDto); - - await user.reload(); - - const tokens = await this.generateAuthToken(user); - this.logger.log(`User with email ${verifyUserDto.email} verified successfully`); - return [tokens, user]; - } - - async verifyUserV2(verifyUserDto: VerifyUserV2RequestDto): Promise<[ILoginResponse, User]> { this.logger.log(`Verifying user with phone number ${verifyUserDto.countryCode + verifyUserDto.phoneNumber}`); const user = await this.userService.findUserOrThrow({ phoneNumber: verifyUserDto.phoneNumber, @@ -134,7 +89,7 @@ export class AuthService { throw new BadRequestException('OTP.INVALID_OTP'); } - await this.userService.verifyUserV2(user.id, verifyUserDto); + await this.userService.verifyUser(user.id, verifyUserDto); await user.reload(); @@ -143,81 +98,6 @@ export class AuthService { return [tokens, user]; } - async setEmail(userId: string, { email }: SetEmailRequestDto) { - this.logger.log(`Setting email for user with id ${userId}`); - const user = await this.userService.findUserOrThrow({ id: userId }); - - if (user.email) { - this.logger.error(`Email already set for user with id ${userId}`); - throw new BadRequestException('USER.EMAIL_ALREADY_SET'); - } - - const existingUser = await this.userService.findUser({ email }); - - if (existingUser) { - this.logger.error(`Email ${email} already taken`); - throw new BadRequestException('USER.EMAIL_ALREADY_TAKEN'); - } - - return this.userService.setEmail(userId, email); - } - - async setPasscode(userId: string, passcode: string) { - this.logger.log(`Setting passcode for user with id ${userId}`); - const user = await this.userService.findUserOrThrow({ id: userId }); - - if (user.password) { - this.logger.error(`Passcode already set for user with id ${userId}`); - throw new BadRequestException('AUTH.PASSCODE_ALREADY_SET'); - } - const salt = bcrypt.genSaltSync(SALT_ROUNDS); - const hashedPasscode = bcrypt.hashSync(passcode, salt); - - await this.userService.setPasscode(userId, hashedPasscode, salt); - this.logger.log(`Passcode set successfully for user with id ${userId}`); - } - - // async setPhoneNumber(userId: string, { phoneNumber, countryCode }: CreateUnverifiedUserRequestDto) { - // const user = await this.userService.findUserOrThrow({ id: userId }); - - // if (user.phoneNumber || user.countryCode) { - // this.logger.error(`Phone number already set for user with id ${userId}`); - // throw new BadRequestException('USER.PHONE_NUMBER_ALREADY_SET'); - // } - - // const existingUser = await this.userService.findUser({ phoneNumber, countryCode }); - - // if (existingUser) { - // this.logger.error(`Phone number ${countryCode + phoneNumber} already taken`); - // throw new BadRequestException('USER.PHONE_NUMBER_ALREADY_TAKEN'); - // } - - // await this.userService.setPhoneNumber(userId, phoneNumber, countryCode); - - // return this.otpService.generateAndSendOtp({ - // userId, - // recipient: countryCode + phoneNumber, - // scope: OtpScope.VERIFY_PHONE, - // otpType: OtpType.SMS, - // }); - // } - - // async verifyPhoneNumber(userId: string, otp: string) { - // const isOtpValid = await this.otpService.verifyOtp({ - // otpType: OtpType.SMS, - // scope: OtpScope.VERIFY_PHONE, - // userId, - // value: otp, - // }); - - // if (!isOtpValid) { - // this.logger.error(`Invalid OTP for user with id ${userId}`); - // throw new BadRequestException('OTP.INVALID_OTP'); - // } - - // return this.userService.verifyPhoneNumber(userId); - // } - async enableBiometric(userId: string, { deviceId, publicKey }: EnableBiometricRequestDto) { this.logger.log(`Enabling biometric for user with id ${userId}`); const device = await this.deviceService.findUserDeviceById(deviceId, userId); @@ -255,48 +135,49 @@ export class AuthService { return this.deviceService.updateDevice(deviceId, { publicKey: null }); } - async sendForgetPasswordOtp({ email }: SendForgetPasswordOtpRequestDto) { - this.logger.log(`Sending forget password OTP to ${email}`); - const user = await this.userService.findUserOrThrow({ email }); - - if (!user.isProfileCompleted) { - this.logger.error(`Profile not completed for user with email ${email}`); - throw new BadRequestException('USER.PROFILE_NOT_COMPLETED'); - } + async sendForgetPasswordOtp({ countryCode, phoneNumber }: SendForgetPasswordOtpRequestDto) { + this.logger.log(`Sending forget password OTP to ${countryCode + phoneNumber}`); + const user = await this.userService.findUserOrThrow({ countryCode, phoneNumber }); return this.otpService.generateAndSendOtp({ userId: user.id, - recipient: user.email, + recipient: user.fullPhoneNumber, scope: OtpScope.FORGET_PASSWORD, - otpType: OtpType.EMAIL, + otpType: OtpType.SMS, }); } - async verifyForgetPasswordOtp({ email, otp, password, confirmPassword }: ForgetPasswordRequestDto) { - this.logger.log(`Verifying forget password OTP for ${email}`); - const user = await this.userService.findUserOrThrow({ email }); - if (!user.isProfileCompleted) { - this.logger.error(`Profile not completed for user with email ${email}`); - throw new BadRequestException('USER.PROFILE_NOT_COMPLETED'); - } + async verifyForgetPasswordOtp({ + countryCode, + phoneNumber, + otp, + password, + confirmPassword, + }: ForgetPasswordRequestDto) { + this.logger.log(`Verifying forget password OTP for ${countryCode + phoneNumber}`); + const user = await this.userService.findUserOrThrow({ countryCode, phoneNumber }); + const isOtpValid = await this.otpService.verifyOtp({ userId: user.id, scope: OtpScope.FORGET_PASSWORD, - otpType: OtpType.EMAIL, + otpType: OtpType.SMS, value: otp, }); if (!isOtpValid) { - this.logger.error(`Invalid OTP for user with email ${email}`); + this.logger.error(`Invalid OTP for user with phone number ${user.fullPhoneNumber}`); throw new BadRequestException('OTP.INVALID_OTP'); } - this.validatePassword(password, confirmPassword, user); + if (password !== confirmPassword) { + this.logger.error('Password and confirm password do not match'); + throw new BadRequestException('AUTH.PASSWORD_MISMATCH'); + } const hashedPassword = bcrypt.hashSync(password, user.salt); - await this.userService.setPasscode(user.id, hashedPassword, user.salt); - this.logger.log(`Passcode updated successfully for user with email ${email}`); + await this.userService.setPassword(user.id, hashedPassword, user.salt); + this.logger.log(`Passcode updated successfully for user with phone number ${user.fullPhoneNumber}`); } async setJuniorPasscode(body: setJuniorPasswordRequestDto) { @@ -304,7 +185,7 @@ export class AuthService { const juniorId = await this.userTokenService.validateToken(body.qrToken, UserType.JUNIOR); const salt = bcrypt.genSaltSync(SALT_ROUNDS); const hashedPasscode = bcrypt.hashSync(body.passcode, salt); - await this.userService.setPasscode(juniorId!, hashedPasscode, salt); + await this.userService.setPassword(juniorId!, hashedPasscode, salt); await this.userTokenService.invalidateToken(body.qrToken); this.logger.log(`Passcode set successfully for junior with id ${juniorId}`); } @@ -345,40 +226,6 @@ export class AuthService { } } - async sendLoginOtp({ email }: SendLoginOtpRequestDto) { - const user = await this.userService.findUserOrThrow({ email }); - - this.logger.log(`Sending login OTP to ${email}`); - return this.otpService.generateAndSendOtp({ - recipient: email, - scope: OtpScope.LOGIN, - otpType: OtpType.EMAIL, - userId: user.id, - }); - } - - async verifyLoginOtp({ email, otp }: VerifyLoginOtpRequestDto): Promise<[ILoginResponse, User]> { - const user = await this.userService.findUserOrThrow({ email }); - - this.logger.log(`Verifying login OTP for ${email}`); - const isOtpValid = await this.otpService.verifyOtp({ - otpType: OtpType.EMAIL, - scope: OtpScope.LOGIN, - userId: user.id, - value: otp, - }); - - if (!isOtpValid) { - this.logger.error(`Invalid OTP for user with email ${email}`); - throw new BadRequestException('OTP.INVALID_OTP'); - } - - this.logger.log(`Login OTP verified successfully for ${email}`); - - const token = await this.generateAuthToken(user); - return [token, user]; - } - logout(req: Request) { this.logger.log('Logging out'); const accessToken = req.headers.authorization?.split(' ')[1] as string; @@ -386,147 +233,68 @@ export class AuthService { return this.cacheService.set(accessToken, 'BLACKLISTED', expiryInTtl); } - private async loginWithPassword(loginDto: LoginRequestDto): Promise<[ILoginResponse, User]> { - const user = await this.userService.findUserOrThrow({ email: loginDto.email }); + async loginWithPassword(loginDto: LoginRequestDto): Promise<[ILoginResponse, User]> { + const user = await this.userService.findUser({ + countryCode: loginDto.countryCode, + phoneNumber: loginDto.phoneNumber, + }); - this.logger.log(`validating password for user with email ${loginDto.email}`); + if (!user) { + this.logger.error(`User not found with phone number ${loginDto.countryCode + loginDto.phoneNumber}`); + throw new UnauthorizedException('AUTH.INVALID_CREDENTIALS'); + } + + if (!user.password) { + this.logger.error(`Password not set for user with phone number ${loginDto.countryCode + loginDto.phoneNumber}`); + throw new UnauthorizedException('AUTH.PHONE_NUMBER_NOT_VERIFIED'); + } + + this.logger.log(`validating password for user with phone ${loginDto.countryCode + loginDto.phoneNumber}`); const isPasswordValid = bcrypt.compareSync(loginDto.password, user.password); if (!isPasswordValid) { - this.logger.error(`Invalid password for user with email ${loginDto.email}`); + this.logger.error(`Invalid password for user with phone ${loginDto.countryCode + loginDto.phoneNumber}`); throw new UnauthorizedException('AUTH.INVALID_CREDENTIALS'); } const tokens = await this.generateAuthToken(user); - this.logger.log(`Password validated successfully for user with email ${loginDto.email}`); + this.logger.log(`Password validated successfully for user`); return [tokens, user]; } - private async loginWithBiometric(loginDto: LoginRequestDto, deviceId: string): Promise<[ILoginResponse, User]> { - const user = await this.userService.findUserOrThrow({ email: loginDto.email }); + // private async loginWithBiometric(loginDto: LoginRequestDto, deviceId: string): Promise<[ILoginResponse, User]> { + // const user = await this.userService.findUserOrThrow({ email: loginDto.email }); - this.logger.log(`validating biometric for user with email ${loginDto.email}`); - const device = await this.deviceService.findUserDeviceById(deviceId, user.id); + // this.logger.log(`validating biometric for user with email ${loginDto.email}`); + // const device = await this.deviceService.findUserDeviceById(deviceId, user.id); - if (!device) { - this.logger.error(`Device not found for user with email ${loginDto.email} and device id ${deviceId}`); - throw new UnauthorizedException('AUTH.DEVICE_NOT_FOUND'); - } + // if (!device) { + // this.logger.error(`Device not found for user with email ${loginDto.email} and device id ${deviceId}`); + // throw new UnauthorizedException('AUTH.DEVICE_NOT_FOUND'); + // } - if (!device.publicKey) { - this.logger.error(`Biometric not enabled for user with email ${loginDto.email}`); - throw new UnauthorizedException('AUTH.BIOMETRIC_NOT_ENABLED'); - } + // if (!device.publicKey) { + // this.logger.error(`Biometric not enabled for user with email ${loginDto.email}`); + // throw new UnauthorizedException('AUTH.BIOMETRIC_NOT_ENABLED'); + // } - const cleanToken = removePadding(loginDto.signature); - const isValidToken = await verifySignature( - device.publicKey, - cleanToken, - `${user.email} - ${device.deviceId}`, - 'SHA1', - ); + // const cleanToken = removePadding(loginDto.signature); + // const isValidToken = await verifySignature( + // device.publicKey, + // cleanToken, + // `${user.email} - ${device.deviceId}`, + // 'SHA1', + // ); - if (!isValidToken) { - this.logger.error(`Invalid biometric for user with email ${loginDto.email}`); - throw new UnauthorizedException('AUTH.INVALID_BIOMETRIC'); - } + // if (!isValidToken) { + // this.logger.error(`Invalid biometric for user with email ${loginDto.email}`); + // throw new UnauthorizedException('AUTH.INVALID_BIOMETRIC'); + // } - const tokens = await this.generateAuthToken(user); - this.logger.log(`Biometric validated successfully for user with email ${loginDto.email}`); - return [tokens, user]; - } - - async loginWithGoogle(loginDto: GoogleLoginRequestDto): Promise<[ILoginResponse, User]> { - const { - email, - sub, - given_name: firstName, - family_name: lastName, - } = await this.oauth2Service.verifyGoogleToken(loginDto.googleToken); - const [existingUser, isJunior, existingUserWithEmail] = await Promise.all([ - this.userService.findUser({ googleId: sub }), - this.userService.findUser({ email, roles: ArrayContains([Roles.JUNIOR]) }), - this.userService.findUser({ email }), - ]); - - if (isJunior) { - this.logger.error(`User with email ${email} is an already registered junior`); - throw new BadRequestException('USER.JUNIOR_UPGRADE_NOT_SUPPORTED_YET'); - } - - if (!existingUser && existingUserWithEmail) { - this.logger.error(`User with email ${email} already exists adding google id to existing user`); - await this.userService.updateUser(existingUserWithEmail.id, { googleId: sub }); - const tokens = await this.generateAuthToken(existingUserWithEmail); - return [tokens, existingUserWithEmail]; - } - - if (!existingUser && !existingUserWithEmail) { - this.logger.debug(`User with google id ${sub} or email ${email} not found, creating new user`); - const user = await this.userService.createGoogleUser(sub, email, firstName, lastName); - - const tokens = await this.generateAuthToken(user); - - return [tokens, user]; - } - - const tokens = await this.generateAuthToken(existingUser!); - - return [tokens, existingUser!]; - } - - async loginWithApple(loginDto: AppleLoginRequestDto): Promise<[ILoginResponse, User]> { - const { sub, email } = await this.oauth2Service.verifyAppleToken(loginDto.appleToken); - - const [existingUserWithSub, isJunior] = await Promise.all([ - this.userService.findUser({ appleId: sub }), - this.userService.findUser({ email, roles: ArrayContains([Roles.JUNIOR]) }), - ]); - - if (isJunior) { - this.logger.error(`User with apple id ${sub} is an already registered junior`); - throw new BadRequestException('USER.JUNIOR_UPGRADE_NOT_SUPPORTED_YET'); - } - - if (email) { - const existingUserWithEmail = await this.userService.findUser({ email }); - - if (existingUserWithEmail && !existingUserWithSub) { - { - this.logger.error(`User with email ${email} already exists adding apple id to existing user`); - await this.userService.updateUser(existingUserWithEmail.id, { appleId: sub }); - const tokens = await this.generateAuthToken(existingUserWithEmail); - return [tokens, existingUserWithEmail]; - } - } - } - - if (!existingUserWithSub) { - // Apple only provides email if user authorized zod for the first time - if (!email || !loginDto.additionalData) { - this.logger.error(`User authorized zod before but his email is not stored in the database`); - throw new BadRequestException('AUTH.APPLE_RE-CONSENT_REQUIRED'); - } - - this.logger.debug(`User with apple id ${sub} not found, creating new user`); - const user = await this.userService.createAppleUser( - sub, - email, - loginDto.additionalData.firstName, - loginDto.additionalData.lastName, - ); - - const tokens = await this.generateAuthToken(user); - - return [tokens, user]; - } - - const tokens = await this.generateAuthToken(existingUserWithSub); - - this.logger.log(`User with apple id ${sub} logged in successfully`); - - return [tokens, existingUserWithSub]; - } + // const tokens = await this.generateAuthToken(user); + // this.logger.log(`Biometric validated successfully for user with email ${loginDto.email}`); + // return [tokens, user]; + // } private async generateAuthToken(user: User) { this.logger.log(`Generating auth token for user with id ${user.id}`); @@ -550,19 +318,4 @@ export class AuthService { this.logger.log(`Auth token generated successfully for user with id ${user.id}`); return { accessToken, refreshToken, expiresAt: new Date(this.jwtService.decode(accessToken).exp * ONE_THOUSAND) }; } - - private validatePassword(password: string, confirmPassword: string, user: User) { - this.logger.log(`Validating password for user with id ${user.id}`); - if (password !== confirmPassword) { - this.logger.error(`Password mismatch for user with id ${user.id}`); - throw new BadRequestException('AUTH.PASSWORD_MISMATCH'); - } - - if (!PASSCODE_REGEX.test(password)) { - this.logger.error(`Invalid password for user with id ${user.id}`); - throw new BadRequestException('AUTH.INVALID_PASSCODE'); - } - } - - private validateGoogleToken(googleToken: string) {} } diff --git a/src/user/services/user.service.ts b/src/user/services/user.service.ts index 3279780..2640cbe 100644 --- a/src/user/services/user.service.ts +++ b/src/user/services/user.service.ts @@ -7,12 +7,7 @@ import { Transactional } from 'typeorm-transactional'; import { CountryIso } from '~/common/enums'; import { NotificationsService } from '~/common/modules/notification/services'; import { CustomerService } from '~/customer/services'; -import { - CreateUnverifiedUserRequestDto, - CreateUnverifiedUserV2RequestDto, - VerifyUserRequestDto, - VerifyUserV2RequestDto, -} from '../../auth/dtos/request'; +import { CreateUnverifiedUserRequestDto, VerifyUserRequestDto } from '../../auth/dtos/request'; import { Roles } from '../../auth/enums'; import { CreateCheckerRequestDto, @@ -49,9 +44,9 @@ export class UserService { return this.userRepository.update(userId, { email }); } - setPasscode(userId: string, passcode: string, salt: string) { + setPassword(userId: string, password: string, salt: string) { this.logger.log(`Setting passcode for user ${userId}`); - return this.userRepository.update(userId, { password: passcode, salt, isProfileCompleted: true }); + return this.userRepository.update(userId, { password, salt }); } setPhoneNumber(userId: string, phoneNumber: string, countryCode: string) { @@ -60,23 +55,9 @@ export class UserService { } @Transactional() - async verifyUser(userId: string, body: VerifyUserRequestDto | VerifyUserV2RequestDto) { - this.logger.log(`Verifying user with id ${userId}`); - await Promise.all([ - this.customerService.createGuardianCustomer(userId, { - firstName: body.firstName, - lastName: body.lastName, - dateOfBirth: body.dateOfBirth, - countryOfResidence: body.countryOfResidence, - }), - this.userRepository.update(userId, { - isEmailVerified: true, - }), - ]); - } - - @Transactional() - async verifyUserV2(userId: string, body: VerifyUserV2RequestDto) { + async verifyUser(userId: string, body: VerifyUserRequestDto) { + const salt = bcrypt.genSaltSync(SALT_ROUNDS); + const hashedPassword = bcrypt.hashSync(body.password, salt); this.logger.log(`Verifying user with id ${userId}`); await Promise.all([ this.customerService.createGuardianCustomer(userId, { @@ -87,7 +68,8 @@ export class UserService { }), this.userRepository.update(userId, { isPhoneVerified: true, - + password: hashedPassword, + salt, ...(body.email && { email: body.email }), }), ]); @@ -113,29 +95,6 @@ export class UserService { @Transactional() async findOrCreateUser(body: CreateUnverifiedUserRequestDto) { - this.logger.log(`Finding or creating user with email ${body.email}`); - const user = await this.userRepository.findOne({ email: body.email }); - - if (!user) { - this.logger.log(`User with email ${body.email} not found, creating new user`); - return this.userRepository.createUnverifiedUser({ email: body.email, roles: [Roles.GUARDIAN] }); - } - if (user && user.roles.includes(Roles.GUARDIAN) && user.isEmailVerified) { - this.logger.error(`User with email ${body.email} already exists`); - throw new BadRequestException('USER.EMAIL_ALREADY_TAKEN'); - } - - if (user && user.roles.includes(Roles.JUNIOR)) { - this.logger.error(`User with email ${body.email} is an already registered junior`); - throw new BadRequestException('USER.JUNIOR_UPGRADE_NOT_SUPPORTED_YET'); - //TODO add role Guardian to the existing user and send OTP - } - - this.logger.log(`User with email ${body.email}`); - return user; - } - - async findOrCreateByPhoneNumber(body: CreateUnverifiedUserV2RequestDto) { this.logger.log(`Finding or creating user with phone number ${body.countryCode + body.phoneNumber}`); const user = await this.userRepository.findOne({ phoneNumber: body.phoneNumber, @@ -167,25 +126,6 @@ export class UserService { return user; } - async findOrCreateByEmail(email: string) { - this.logger.log(`Finding or creating user with email ${email} `); - const user = await this.userRepository.findOne({ email }); - - if (!user) { - this.logger.log(`User with email ${email} not found, creating new user`); - return this.userRepository.createUser({ email, roles: [Roles.GUARDIAN] }); - } - - if (user && user.roles.includes(Roles.JUNIOR)) { - this.logger.error(`User with email ${email} is an already registered junior`); - throw new BadRequestException('USER.JUNIOR_UPGRADE_NOT_SUPPORTED_YET'); - //TODO add role Guardian to the existing user and send OTP - } - - this.logger.log(`User with email ${email} found successfully`); - return user; - } - async createUser(data: Partial) { this.logger.log(`Creating user with data ${JSON.stringify(data)}`); const user = await this.userRepository.createUser(data);