mirror of
https://github.com/HamzaSha1/zod-backend.git
synced 2025-08-25 05:42:27 +00:00
feat: add login and forget password and refactor code
This commit is contained in:
@ -3,14 +3,14 @@ import { Module } from '@nestjs/common';
|
|||||||
import { JwtModule } from '@nestjs/jwt';
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
import { JuniorModule } from '~/junior/junior.module';
|
import { JuniorModule } from '~/junior/junior.module';
|
||||||
import { UserModule } from '~/user/user.module';
|
import { UserModule } from '~/user/user.module';
|
||||||
import { AuthController, AuthV2Controller } from './controllers';
|
import { AuthController } from './controllers';
|
||||||
import { AuthService, Oauth2Service } from './services';
|
import { AuthService, Oauth2Service } from './services';
|
||||||
import { AccessTokenStrategy } from './strategies';
|
import { AccessTokenStrategy } from './strategies';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [JwtModule.register({}), UserModule, JuniorModule, HttpModule],
|
imports: [JwtModule.register({}), UserModule, JuniorModule, HttpModule],
|
||||||
providers: [AuthService, AccessTokenStrategy, Oauth2Service],
|
providers: [AuthService, AccessTokenStrategy, Oauth2Service],
|
||||||
controllers: [AuthController, AuthV2Controller],
|
controllers: [AuthController],
|
||||||
exports: [],
|
exports: [],
|
||||||
})
|
})
|
||||||
export class AuthModule {}
|
export class AuthModule {}
|
||||||
|
@ -1,29 +1,20 @@
|
|||||||
import { Body, Controller, HttpCode, HttpStatus, Post, Req, UseGuards } from '@nestjs/common';
|
import { Body, Controller, HttpCode, HttpStatus, Post, Req, UseGuards } from '@nestjs/common';
|
||||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||||
import { Request } from 'express';
|
import { Request } from 'express';
|
||||||
import { AuthenticatedUser, Public } from '~/common/decorators';
|
import { Public } from '~/common/decorators';
|
||||||
import { AccessTokenGuard } from '~/common/guards';
|
import { AccessTokenGuard } from '~/common/guards';
|
||||||
import { ApiDataResponse, ApiLangRequestHeader } from '~/core/decorators';
|
import { ApiLangRequestHeader } from '~/core/decorators';
|
||||||
import { ResponseFactory } from '~/core/utils';
|
import { ResponseFactory } from '~/core/utils';
|
||||||
import {
|
import {
|
||||||
AppleLoginRequestDto,
|
|
||||||
CreateUnverifiedUserRequestDto,
|
CreateUnverifiedUserRequestDto,
|
||||||
DisableBiometricRequestDto,
|
|
||||||
EnableBiometricRequestDto,
|
|
||||||
ForgetPasswordRequestDto,
|
ForgetPasswordRequestDto,
|
||||||
GoogleLoginRequestDto,
|
LoginRequestDto,
|
||||||
RefreshTokenRequestDto,
|
RefreshTokenRequestDto,
|
||||||
SendForgetPasswordOtpRequestDto,
|
SendForgetPasswordOtpRequestDto,
|
||||||
SendLoginOtpRequestDto,
|
|
||||||
SetEmailRequestDto,
|
|
||||||
setJuniorPasswordRequestDto,
|
|
||||||
SetPasscodeRequestDto,
|
|
||||||
VerifyLoginOtpRequestDto,
|
|
||||||
VerifyUserRequestDto,
|
VerifyUserRequestDto,
|
||||||
} from '../dtos/request';
|
} from '../dtos/request';
|
||||||
import { SendForgetPasswordOtpResponseDto, SendRegisterOtpResponseDto } from '../dtos/response';
|
import { SendForgetPasswordOtpResponseDto, SendRegisterOtpResponseDto } from '../dtos/response';
|
||||||
import { LoginResponseDto } from '../dtos/response/login.response.dto';
|
import { LoginResponseDto } from '../dtos/response/login.response.dto';
|
||||||
import { IJwtPayload } from '../interfaces';
|
|
||||||
import { AuthService } from '../services';
|
import { AuthService } from '../services';
|
||||||
|
|
||||||
@Controller('auth')
|
@Controller('auth')
|
||||||
@ -44,85 +35,16 @@ export class AuthController {
|
|||||||
return ResponseFactory.data(new LoginResponseDto(res, user));
|
return ResponseFactory.data(new LoginResponseDto(res, user));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('login/otp')
|
@Post('login')
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
async login(@Body() verifyUserDto: LoginRequestDto) {
|
||||||
async sendLoginOtp(@Body() data: SendLoginOtpRequestDto) {
|
const [res, user] = await this.authService.loginWithPassword(verifyUserDto);
|
||||||
return this.authService.sendLoginOtp(data);
|
return ResponseFactory.data(new LoginResponseDto(res, user));
|
||||||
}
|
|
||||||
|
|
||||||
@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('forget-password/otp')
|
@Post('forget-password/otp')
|
||||||
async forgetPassword(@Body() sendForgetPasswordOtpDto: SendForgetPasswordOtpRequestDto) {
|
async forgetPassword(@Body() sendForgetPasswordOtpDto: SendForgetPasswordOtpRequestDto) {
|
||||||
const email = await this.authService.sendForgetPasswordOtp(sendForgetPasswordOtpDto);
|
const maskedNumber = await this.authService.sendForgetPasswordOtp(sendForgetPasswordOtpDto);
|
||||||
return ResponseFactory.data(new SendForgetPasswordOtpResponseDto(email));
|
return ResponseFactory.data(new SendForgetPasswordOtpResponseDto(maskedNumber));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('forget-password/reset')
|
@Post('forget-password/reset')
|
||||||
@ -131,13 +53,6 @@ export class AuthController {
|
|||||||
return this.authService.verifyForgetPasswordOtp(forgetPasswordDto);
|
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')
|
@Post('refresh-token')
|
||||||
@Public()
|
@Public()
|
||||||
async refreshToken(@Body() { refreshToken }: RefreshTokenRequestDto) {
|
async refreshToken(@Body() { refreshToken }: RefreshTokenRequestDto) {
|
||||||
@ -151,4 +66,25 @@ export class AuthController {
|
|||||||
async logout(@Req() request: Request) {
|
async logout(@Req() request: Request) {
|
||||||
await this.authService.logout(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);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,2 +1 @@
|
|||||||
export * from './auth.controller';
|
export * from './auth.controller';
|
||||||
export * from './auth.v2.controller';
|
|
||||||
|
@ -1,14 +1,4 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { OmitType } from '@nestjs/swagger';
|
||||||
import { IsEmail } from 'class-validator';
|
import { VerifyUserRequestDto } from './verify-user.request.dto';
|
||||||
import { i18nValidationMessage as i18n } from 'nestjs-i18n';
|
|
||||||
|
|
||||||
export class CreateUnverifiedUserRequestDto {
|
export class CreateUnverifiedUserRequestDto extends OmitType(VerifyUserRequestDto, ['otp']) {}
|
||||||
@ApiProperty({ example: 'test@test.com' })
|
|
||||||
@IsEmail(
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
message: i18n('validation.IsEmail', { path: 'general', property: 'auth.email' }),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
email!: string;
|
|
||||||
}
|
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
import { OmitType } from '@nestjs/swagger';
|
|
||||||
import { VerifyUserV2RequestDto } from './verify-user.v2.request.dto';
|
|
||||||
|
|
||||||
export class CreateUnverifiedUserV2RequestDto extends OmitType(VerifyUserV2RequestDto, ['otp']) {}
|
|
@ -1,20 +1,32 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty, PickType } from '@nestjs/swagger';
|
||||||
import { IsEmail, IsNotEmpty, IsNumberString, IsString, MaxLength, MinLength } from 'class-validator';
|
import { IsEmail, IsNotEmpty, IsNumberString, IsString, Matches, MaxLength, MinLength } from 'class-validator';
|
||||||
import { i18nValidationMessage as i18n } from 'nestjs-i18n';
|
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 { DEFAULT_OTP_LENGTH } from '~/common/modules/otp/constants';
|
||||||
|
import { IsValidPhoneNumber } from '~/core/decorators/validations';
|
||||||
export class ForgetPasswordRequestDto {
|
export class ForgetPasswordRequestDto {
|
||||||
@ApiProperty({ example: 'test@test.com' })
|
@ApiProperty({ example: '+962' })
|
||||||
@IsEmail({}, { message: i18n('validation.IsEmail', { path: 'general', property: 'auth.email' }) })
|
@Matches(COUNTRY_CODE_REGEX, {
|
||||||
email!: string;
|
message: i18n('validation.Matches', { path: 'general', property: 'auth.countryCode' }),
|
||||||
|
})
|
||||||
|
countryCode!: string;
|
||||||
|
|
||||||
@ApiProperty({ example: 'password' })
|
@ApiProperty({ example: '787259134' })
|
||||||
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'auth.password' }) })
|
@IsValidPhoneNumber({
|
||||||
@IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'auth.password' }) })
|
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;
|
password!: string;
|
||||||
|
|
||||||
@ApiProperty({ example: 'password' })
|
@ApiProperty({ example: 'Abcd1234@' })
|
||||||
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'auth.confirmPassword' }) })
|
@Matches(PASSWORD_REGEX, {
|
||||||
@IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'auth.confirmPassword' }) })
|
message: i18n('validation.Matches', { path: 'general', property: 'auth.confirmPassword' }),
|
||||||
|
})
|
||||||
confirmPassword!: string;
|
confirmPassword!: string;
|
||||||
|
|
||||||
@ApiProperty({ example: '111111' })
|
@ApiProperty({ example: '111111' })
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
export * from './apple-login.request.dto';
|
export * from './apple-login.request.dto';
|
||||||
export * from './create-unverified-user.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 './disable-biometric.request.dto';
|
||||||
export * from './enable-biometric.request.dto';
|
export * from './enable-biometric.request.dto';
|
||||||
export * from './forget-password.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 './login.request.dto';
|
||||||
export * from './refresh-token.request.dto';
|
export * from './refresh-token.request.dto';
|
||||||
export * from './send-forget-password-otp.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-email.request.dto';
|
||||||
export * from './set-junior-password.request.dto';
|
export * from './set-junior-password.request.dto';
|
||||||
export * from './set-passcode.request.dto';
|
export * from './set-passcode.request.dto';
|
||||||
export * from './verify-login-otp.request.dto';
|
|
||||||
export * from './verify-otp.request.dto';
|
export * from './verify-otp.request.dto';
|
||||||
export * from './verify-user.request.dto';
|
export * from './verify-user.request.dto';
|
||||||
export * from './verify-user.v2.request.dto';
|
|
||||||
|
@ -1,43 +1,24 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
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 { i18nValidationMessage as i18n } from 'nestjs-i18n';
|
||||||
|
import { COUNTRY_CODE_REGEX } from '~/auth/constants';
|
||||||
import { GrantType } from '~/auth/enums';
|
import { GrantType } from '~/auth/enums';
|
||||||
|
import { IsValidPhoneNumber } from '~/core/decorators/validations';
|
||||||
export class LoginRequestDto {
|
export class LoginRequestDto {
|
||||||
@ApiProperty({ example: GrantType.APPLE })
|
@ApiProperty({ example: '+962' })
|
||||||
@IsEnum(GrantType, { message: i18n('validation.IsEnum', { path: 'general', property: 'auth.grantType' }) })
|
@Matches(COUNTRY_CODE_REGEX, {
|
||||||
grantType!: GrantType;
|
message: i18n('validation.Matches', { path: 'general', property: 'auth.countryCode' }),
|
||||||
|
})
|
||||||
|
countryCode!: string;
|
||||||
|
|
||||||
@ApiProperty({ example: 'test@test.com' })
|
@ApiProperty({ example: '787259134' })
|
||||||
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'auth.email' }) })
|
@IsValidPhoneNumber({
|
||||||
@IsEmail({}, { message: i18n('validation.IsEmail', { path: 'general', property: 'auth.email' }) })
|
message: i18n('validation.IsValidPhoneNumber', { path: 'general', property: 'auth.phoneNumber' }),
|
||||||
@ValidateIf((o) => o.grantType !== GrantType.APPLE && o.grantType !== GrantType.GOOGLE)
|
})
|
||||||
email!: string;
|
phoneNumber!: string;
|
||||||
|
|
||||||
@ApiProperty({ example: '123456' })
|
@ApiProperty({ example: 'Abcd1234@' })
|
||||||
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'auth.password' }) })
|
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'auth.password' }) })
|
||||||
@ValidateIf((o) => o.grantType === GrantType.PASSWORD)
|
@ValidateIf((o) => o.grantType === GrantType.PASSWORD)
|
||||||
password!: string;
|
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;
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { PickType } from '@nestjs/swagger';
|
import { PickType } from '@nestjs/swagger';
|
||||||
import { LoginRequestDto } from './login.request.dto';
|
import { LoginRequestDto } from './login.request.dto';
|
||||||
|
|
||||||
export class SendForgetPasswordOtpRequestDto extends PickType(LoginRequestDto, ['email']) {}
|
export class SendForgetPasswordOtpRequestDto extends PickType(LoginRequestDto, ['countryCode', 'phoneNumber']) {}
|
||||||
|
@ -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;
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
@ -1,21 +1,34 @@
|
|||||||
import { ApiProperty, PickType } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import {
|
import {
|
||||||
IsDateString,
|
IsDateString,
|
||||||
|
IsEmail,
|
||||||
IsEnum,
|
IsEnum,
|
||||||
IsNotEmpty,
|
IsNotEmpty,
|
||||||
IsNumberString,
|
IsNumberString,
|
||||||
IsOptional,
|
IsOptional,
|
||||||
IsString,
|
IsString,
|
||||||
|
Matches,
|
||||||
MaxLength,
|
MaxLength,
|
||||||
MinLength,
|
MinLength,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
import { i18nValidationMessage as i18n } from 'nestjs-i18n';
|
import { i18nValidationMessage as i18n } from 'nestjs-i18n';
|
||||||
|
import { COUNTRY_CODE_REGEX, PASSWORD_REGEX } from '~/auth/constants';
|
||||||
import { CountryIso } from '~/common/enums';
|
import { CountryIso } from '~/common/enums';
|
||||||
import { DEFAULT_OTP_LENGTH } from '~/common/modules/otp/constants';
|
import { DEFAULT_OTP_LENGTH } from '~/common/modules/otp/constants';
|
||||||
import { IsAbove18 } from '~/core/decorators/validations';
|
import { IsAbove18, IsValidPhoneNumber } from '~/core/decorators/validations';
|
||||||
import { CreateUnverifiedUserRequestDto } from './create-unverified-user.request.dto';
|
|
||||||
|
|
||||||
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' })
|
@ApiProperty({ example: 'John' })
|
||||||
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'customer.firstName' }) })
|
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'customer.firstName' }) })
|
||||||
@IsNotEmpty({ message: i18n('validation.IsNotEmpty', { 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()
|
@IsOptional()
|
||||||
countryOfResidence: CountryIso = CountryIso.SAUDI_ARABIA;
|
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' })
|
@ApiProperty({ example: '111111' })
|
||||||
@IsNumberString(
|
@IsNumberString(
|
||||||
{ no_symbols: true },
|
{ no_symbols: true },
|
||||||
|
@ -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;
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
export class SendForgetPasswordOtpResponseDto {
|
export class SendForgetPasswordOtpResponseDto {
|
||||||
email!: string;
|
maskedNumber!: string;
|
||||||
|
|
||||||
constructor(email: string) {
|
constructor(maskedNumber: string) {
|
||||||
this.email = email;
|
this.maskedNumber = maskedNumber;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,9 @@ import { ApiProperty } from '@nestjs/swagger';
|
|||||||
|
|
||||||
export class SendRegisterOtpResponseDto {
|
export class SendRegisterOtpResponseDto {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
email!: string;
|
maskedNumber!: string;
|
||||||
|
|
||||||
constructor(email: string) {
|
constructor(maskedNumber: string) {
|
||||||
this.email = email;
|
this.maskedNumber = maskedNumber;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
export enum GrantType {
|
export enum GrantType {
|
||||||
PASSWORD = 'PASSWORD',
|
PASSWORD = 'PASSWORD',
|
||||||
BIOMETRIC = 'BIOMETRIC',
|
BIOMETRIC = 'BIOMETRIC',
|
||||||
GOOGLE = 'GOOGLE',
|
|
||||||
APPLE = 'APPLE',
|
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ import { ConfigService } from '@nestjs/config';
|
|||||||
import { JwtService } from '@nestjs/jwt';
|
import { JwtService } from '@nestjs/jwt';
|
||||||
import * as bcrypt from 'bcrypt';
|
import * as bcrypt from 'bcrypt';
|
||||||
import { Request } from 'express';
|
import { Request } from 'express';
|
||||||
import { ArrayContains } from 'typeorm';
|
|
||||||
import { CacheService } from '~/common/modules/cache/services';
|
import { CacheService } from '~/common/modules/cache/services';
|
||||||
import { OtpScope, OtpType } from '~/common/modules/otp/enums';
|
import { OtpScope, OtpType } from '~/common/modules/otp/enums';
|
||||||
import { OtpService } from '~/common/modules/otp/services';
|
import { OtpService } from '~/common/modules/otp/services';
|
||||||
@ -12,23 +11,15 @@ import { DeviceService, UserService, UserTokenService } from '~/user/services';
|
|||||||
import { User } from '../../user/entities';
|
import { User } from '../../user/entities';
|
||||||
import { PASSCODE_REGEX } from '../constants';
|
import { PASSCODE_REGEX } from '../constants';
|
||||||
import {
|
import {
|
||||||
AppleLoginRequestDto,
|
|
||||||
CreateUnverifiedUserRequestDto,
|
CreateUnverifiedUserRequestDto,
|
||||||
CreateUnverifiedUserV2RequestDto,
|
|
||||||
DisableBiometricRequestDto,
|
DisableBiometricRequestDto,
|
||||||
EnableBiometricRequestDto,
|
EnableBiometricRequestDto,
|
||||||
ForgetPasswordRequestDto,
|
ForgetPasswordRequestDto,
|
||||||
GoogleLoginRequestDto,
|
|
||||||
LoginRequestDto,
|
LoginRequestDto,
|
||||||
SendForgetPasswordOtpRequestDto,
|
SendForgetPasswordOtpRequestDto,
|
||||||
SendLoginOtpRequestDto,
|
|
||||||
SetEmailRequestDto,
|
|
||||||
setJuniorPasswordRequestDto,
|
setJuniorPasswordRequestDto,
|
||||||
VerifyLoginOtpRequestDto,
|
|
||||||
VerifyUserRequestDto,
|
VerifyUserRequestDto,
|
||||||
VerifyUserV2RequestDto,
|
|
||||||
} from '../dtos/request';
|
} from '../dtos/request';
|
||||||
import { Roles } from '../enums';
|
|
||||||
import { IJwtPayload, ILoginResponse } from '../interfaces';
|
import { IJwtPayload, ILoginResponse } from '../interfaces';
|
||||||
import { removePadding, verifySignature } from '../utils';
|
import { removePadding, verifySignature } from '../utils';
|
||||||
import { Oauth2Service } from './oauth2.service';
|
import { Oauth2Service } from './oauth2.service';
|
||||||
@ -49,19 +40,8 @@ export class AuthService {
|
|||||||
private readonly cacheService: CacheService,
|
private readonly cacheService: CacheService,
|
||||||
private readonly oauth2Service: Oauth2Service,
|
private readonly oauth2Service: Oauth2Service,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async sendRegisterOtp(body: CreateUnverifiedUserRequestDto) {
|
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) {
|
if (body.email) {
|
||||||
const isEmailUsed = await this.userService.findUser({ email: body.email, isEmailVerified: true });
|
const isEmailUsed = await this.userService.findUser({ email: body.email, isEmailVerified: true });
|
||||||
if (isEmailUsed) {
|
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}`);
|
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({
|
return this.otpService.generateAndSendOtp({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
recipient: user.fullPhoneNumber,
|
recipient: user.fullPhoneNumber,
|
||||||
@ -81,36 +66,6 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async verifyUser(verifyUserDto: VerifyUserRequestDto): Promise<[ILoginResponse, User]> {
|
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}`);
|
this.logger.log(`Verifying user with phone number ${verifyUserDto.countryCode + verifyUserDto.phoneNumber}`);
|
||||||
const user = await this.userService.findUserOrThrow({
|
const user = await this.userService.findUserOrThrow({
|
||||||
phoneNumber: verifyUserDto.phoneNumber,
|
phoneNumber: verifyUserDto.phoneNumber,
|
||||||
@ -134,7 +89,7 @@ export class AuthService {
|
|||||||
throw new BadRequestException('OTP.INVALID_OTP');
|
throw new BadRequestException('OTP.INVALID_OTP');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.userService.verifyUserV2(user.id, verifyUserDto);
|
await this.userService.verifyUser(user.id, verifyUserDto);
|
||||||
|
|
||||||
await user.reload();
|
await user.reload();
|
||||||
|
|
||||||
@ -143,81 +98,6 @@ export class AuthService {
|
|||||||
return [tokens, user];
|
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) {
|
async enableBiometric(userId: string, { deviceId, publicKey }: EnableBiometricRequestDto) {
|
||||||
this.logger.log(`Enabling biometric for user with id ${userId}`);
|
this.logger.log(`Enabling biometric for user with id ${userId}`);
|
||||||
const device = await this.deviceService.findUserDeviceById(deviceId, userId);
|
const device = await this.deviceService.findUserDeviceById(deviceId, userId);
|
||||||
@ -255,48 +135,49 @@ export class AuthService {
|
|||||||
return this.deviceService.updateDevice(deviceId, { publicKey: null });
|
return this.deviceService.updateDevice(deviceId, { publicKey: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendForgetPasswordOtp({ email }: SendForgetPasswordOtpRequestDto) {
|
async sendForgetPasswordOtp({ countryCode, phoneNumber }: SendForgetPasswordOtpRequestDto) {
|
||||||
this.logger.log(`Sending forget password OTP to ${email}`);
|
this.logger.log(`Sending forget password OTP to ${countryCode + phoneNumber}`);
|
||||||
const user = await this.userService.findUserOrThrow({ email });
|
const user = await this.userService.findUserOrThrow({ countryCode, phoneNumber });
|
||||||
|
|
||||||
if (!user.isProfileCompleted) {
|
|
||||||
this.logger.error(`Profile not completed for user with email ${email}`);
|
|
||||||
throw new BadRequestException('USER.PROFILE_NOT_COMPLETED');
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.otpService.generateAndSendOtp({
|
return this.otpService.generateAndSendOtp({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
recipient: user.email,
|
recipient: user.fullPhoneNumber,
|
||||||
scope: OtpScope.FORGET_PASSWORD,
|
scope: OtpScope.FORGET_PASSWORD,
|
||||||
otpType: OtpType.EMAIL,
|
otpType: OtpType.SMS,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async verifyForgetPasswordOtp({ email, otp, password, confirmPassword }: ForgetPasswordRequestDto) {
|
async verifyForgetPasswordOtp({
|
||||||
this.logger.log(`Verifying forget password OTP for ${email}`);
|
countryCode,
|
||||||
const user = await this.userService.findUserOrThrow({ email });
|
phoneNumber,
|
||||||
if (!user.isProfileCompleted) {
|
otp,
|
||||||
this.logger.error(`Profile not completed for user with email ${email}`);
|
password,
|
||||||
throw new BadRequestException('USER.PROFILE_NOT_COMPLETED');
|
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({
|
const isOtpValid = await this.otpService.verifyOtp({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
scope: OtpScope.FORGET_PASSWORD,
|
scope: OtpScope.FORGET_PASSWORD,
|
||||||
otpType: OtpType.EMAIL,
|
otpType: OtpType.SMS,
|
||||||
value: otp,
|
value: otp,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isOtpValid) {
|
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');
|
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);
|
const hashedPassword = bcrypt.hashSync(password, user.salt);
|
||||||
|
|
||||||
await this.userService.setPasscode(user.id, hashedPassword, user.salt);
|
await this.userService.setPassword(user.id, hashedPassword, user.salt);
|
||||||
this.logger.log(`Passcode updated successfully for user with email ${email}`);
|
this.logger.log(`Passcode updated successfully for user with phone number ${user.fullPhoneNumber}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setJuniorPasscode(body: setJuniorPasswordRequestDto) {
|
async setJuniorPasscode(body: setJuniorPasswordRequestDto) {
|
||||||
@ -304,7 +185,7 @@ export class AuthService {
|
|||||||
const juniorId = await this.userTokenService.validateToken(body.qrToken, UserType.JUNIOR);
|
const juniorId = await this.userTokenService.validateToken(body.qrToken, UserType.JUNIOR);
|
||||||
const salt = bcrypt.genSaltSync(SALT_ROUNDS);
|
const salt = bcrypt.genSaltSync(SALT_ROUNDS);
|
||||||
const hashedPasscode = bcrypt.hashSync(body.passcode, salt);
|
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);
|
await this.userTokenService.invalidateToken(body.qrToken);
|
||||||
this.logger.log(`Passcode set successfully for junior with id ${juniorId}`);
|
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) {
|
logout(req: Request) {
|
||||||
this.logger.log('Logging out');
|
this.logger.log('Logging out');
|
||||||
const accessToken = req.headers.authorization?.split(' ')[1] as string;
|
const accessToken = req.headers.authorization?.split(' ')[1] as string;
|
||||||
@ -386,147 +233,68 @@ export class AuthService {
|
|||||||
return this.cacheService.set(accessToken, 'BLACKLISTED', expiryInTtl);
|
return this.cacheService.set(accessToken, 'BLACKLISTED', expiryInTtl);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loginWithPassword(loginDto: LoginRequestDto): Promise<[ILoginResponse, User]> {
|
async loginWithPassword(loginDto: LoginRequestDto): Promise<[ILoginResponse, User]> {
|
||||||
const user = await this.userService.findUserOrThrow({ email: loginDto.email });
|
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);
|
const isPasswordValid = bcrypt.compareSync(loginDto.password, user.password);
|
||||||
|
|
||||||
if (!isPasswordValid) {
|
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');
|
throw new UnauthorizedException('AUTH.INVALID_CREDENTIALS');
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokens = await this.generateAuthToken(user);
|
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];
|
return [tokens, user];
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loginWithBiometric(loginDto: LoginRequestDto, deviceId: string): Promise<[ILoginResponse, User]> {
|
// private async loginWithBiometric(loginDto: LoginRequestDto, deviceId: string): Promise<[ILoginResponse, User]> {
|
||||||
const user = await this.userService.findUserOrThrow({ email: loginDto.email });
|
// const user = await this.userService.findUserOrThrow({ email: loginDto.email });
|
||||||
|
|
||||||
this.logger.log(`validating biometric for user with email ${loginDto.email}`);
|
// this.logger.log(`validating biometric for user with email ${loginDto.email}`);
|
||||||
const device = await this.deviceService.findUserDeviceById(deviceId, user.id);
|
// const device = await this.deviceService.findUserDeviceById(deviceId, user.id);
|
||||||
|
|
||||||
if (!device) {
|
// if (!device) {
|
||||||
this.logger.error(`Device not found for user with email ${loginDto.email} and device id ${deviceId}`);
|
// this.logger.error(`Device not found for user with email ${loginDto.email} and device id ${deviceId}`);
|
||||||
throw new UnauthorizedException('AUTH.DEVICE_NOT_FOUND');
|
// throw new UnauthorizedException('AUTH.DEVICE_NOT_FOUND');
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (!device.publicKey) {
|
// if (!device.publicKey) {
|
||||||
this.logger.error(`Biometric not enabled for user with email ${loginDto.email}`);
|
// this.logger.error(`Biometric not enabled for user with email ${loginDto.email}`);
|
||||||
throw new UnauthorizedException('AUTH.BIOMETRIC_NOT_ENABLED');
|
// throw new UnauthorizedException('AUTH.BIOMETRIC_NOT_ENABLED');
|
||||||
}
|
// }
|
||||||
|
|
||||||
const cleanToken = removePadding(loginDto.signature);
|
// const cleanToken = removePadding(loginDto.signature);
|
||||||
const isValidToken = await verifySignature(
|
// const isValidToken = await verifySignature(
|
||||||
device.publicKey,
|
// device.publicKey,
|
||||||
cleanToken,
|
// cleanToken,
|
||||||
`${user.email} - ${device.deviceId}`,
|
// `${user.email} - ${device.deviceId}`,
|
||||||
'SHA1',
|
// 'SHA1',
|
||||||
);
|
// );
|
||||||
|
|
||||||
if (!isValidToken) {
|
// if (!isValidToken) {
|
||||||
this.logger.error(`Invalid biometric for user with email ${loginDto.email}`);
|
// this.logger.error(`Invalid biometric for user with email ${loginDto.email}`);
|
||||||
throw new UnauthorizedException('AUTH.INVALID_BIOMETRIC');
|
// throw new UnauthorizedException('AUTH.INVALID_BIOMETRIC');
|
||||||
}
|
// }
|
||||||
|
|
||||||
const tokens = await this.generateAuthToken(user);
|
// const tokens = await this.generateAuthToken(user);
|
||||||
this.logger.log(`Biometric validated successfully for user with email ${loginDto.email}`);
|
// this.logger.log(`Biometric validated successfully for user with email ${loginDto.email}`);
|
||||||
return [tokens, user];
|
// 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];
|
|
||||||
}
|
|
||||||
|
|
||||||
private async generateAuthToken(user: User) {
|
private async generateAuthToken(user: User) {
|
||||||
this.logger.log(`Generating auth token for user with id ${user.id}`);
|
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}`);
|
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) };
|
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) {}
|
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,7 @@ import { Transactional } from 'typeorm-transactional';
|
|||||||
import { CountryIso } from '~/common/enums';
|
import { CountryIso } from '~/common/enums';
|
||||||
import { NotificationsService } from '~/common/modules/notification/services';
|
import { NotificationsService } from '~/common/modules/notification/services';
|
||||||
import { CustomerService } from '~/customer/services';
|
import { CustomerService } from '~/customer/services';
|
||||||
import {
|
import { CreateUnverifiedUserRequestDto, VerifyUserRequestDto } from '../../auth/dtos/request';
|
||||||
CreateUnverifiedUserRequestDto,
|
|
||||||
CreateUnverifiedUserV2RequestDto,
|
|
||||||
VerifyUserRequestDto,
|
|
||||||
VerifyUserV2RequestDto,
|
|
||||||
} from '../../auth/dtos/request';
|
|
||||||
import { Roles } from '../../auth/enums';
|
import { Roles } from '../../auth/enums';
|
||||||
import {
|
import {
|
||||||
CreateCheckerRequestDto,
|
CreateCheckerRequestDto,
|
||||||
@ -49,9 +44,9 @@ export class UserService {
|
|||||||
return this.userRepository.update(userId, { email });
|
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}`);
|
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) {
|
setPhoneNumber(userId: string, phoneNumber: string, countryCode: string) {
|
||||||
@ -60,23 +55,9 @@ export class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Transactional()
|
@Transactional()
|
||||||
async verifyUser(userId: string, body: VerifyUserRequestDto | VerifyUserV2RequestDto) {
|
async verifyUser(userId: string, body: VerifyUserRequestDto) {
|
||||||
this.logger.log(`Verifying user with id ${userId}`);
|
const salt = bcrypt.genSaltSync(SALT_ROUNDS);
|
||||||
await Promise.all([
|
const hashedPassword = bcrypt.hashSync(body.password, salt);
|
||||||
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) {
|
|
||||||
this.logger.log(`Verifying user with id ${userId}`);
|
this.logger.log(`Verifying user with id ${userId}`);
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.customerService.createGuardianCustomer(userId, {
|
this.customerService.createGuardianCustomer(userId, {
|
||||||
@ -87,7 +68,8 @@ export class UserService {
|
|||||||
}),
|
}),
|
||||||
this.userRepository.update(userId, {
|
this.userRepository.update(userId, {
|
||||||
isPhoneVerified: true,
|
isPhoneVerified: true,
|
||||||
|
password: hashedPassword,
|
||||||
|
salt,
|
||||||
...(body.email && { email: body.email }),
|
...(body.email && { email: body.email }),
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
@ -113,29 +95,6 @@ export class UserService {
|
|||||||
|
|
||||||
@Transactional()
|
@Transactional()
|
||||||
async findOrCreateUser(body: CreateUnverifiedUserRequestDto) {
|
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}`);
|
this.logger.log(`Finding or creating user with phone number ${body.countryCode + body.phoneNumber}`);
|
||||||
const user = await this.userRepository.findOne({
|
const user = await this.userRepository.findOne({
|
||||||
phoneNumber: body.phoneNumber,
|
phoneNumber: body.phoneNumber,
|
||||||
@ -167,25 +126,6 @@ export class UserService {
|
|||||||
return user;
|
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<User>) {
|
async createUser(data: Partial<User>) {
|
||||||
this.logger.log(`Creating user with data ${JSON.stringify(data)}`);
|
this.logger.log(`Creating user with data ${JSON.stringify(data)}`);
|
||||||
const user = await this.userRepository.createUser(data);
|
const user = await this.userRepository.createUser(data);
|
||||||
|
Reference in New Issue
Block a user