mirror of
https://github.com/HamzaSha1/zod-backend.git
synced 2025-08-25 13:49:40 +00:00
feat: add change password api
This commit is contained in:
@ -1,11 +1,12 @@
|
|||||||
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 { Public } from '~/common/decorators';
|
import { AuthenticatedUser, Public } from '~/common/decorators';
|
||||||
import { AccessTokenGuard } from '~/common/guards';
|
import { AccessTokenGuard } from '~/common/guards';
|
||||||
import { ApiDataResponse, ApiLangRequestHeader } from '~/core/decorators';
|
import { ApiDataResponse, ApiLangRequestHeader } from '~/core/decorators';
|
||||||
import { ResponseFactory } from '~/core/utils';
|
import { ResponseFactory } from '~/core/utils';
|
||||||
import {
|
import {
|
||||||
|
ChangePasswordRequestDto,
|
||||||
CreateUnverifiedUserRequestDto,
|
CreateUnverifiedUserRequestDto,
|
||||||
ForgetPasswordRequestDto,
|
ForgetPasswordRequestDto,
|
||||||
LoginRequestDto,
|
LoginRequestDto,
|
||||||
@ -17,6 +18,7 @@ import {
|
|||||||
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 { VerifyForgetPasswordOtpResponseDto } from '../dtos/response/verify-forget-password-otp.response.dto';
|
import { VerifyForgetPasswordOtpResponseDto } from '../dtos/response/verify-forget-password-otp.response.dto';
|
||||||
|
import { IJwtPayload } from '../interfaces';
|
||||||
import { AuthService } from '../services';
|
import { AuthService } from '../services';
|
||||||
|
|
||||||
@Controller('auth')
|
@Controller('auth')
|
||||||
@ -64,6 +66,13 @@ export class AuthController {
|
|||||||
return this.authService.resetPassword(forgetPasswordDto);
|
return this.authService.resetPassword(forgetPasswordDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('change-password')
|
||||||
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
|
@UseGuards(AccessTokenGuard)
|
||||||
|
async changePassword(@AuthenticatedUser() { sub }: IJwtPayload, @Body() forgetPasswordDto: ChangePasswordRequestDto) {
|
||||||
|
return this.authService.changePassword(sub, forgetPasswordDto);
|
||||||
|
}
|
||||||
|
|
||||||
@Post('refresh-token')
|
@Post('refresh-token')
|
||||||
@Public()
|
@Public()
|
||||||
async refreshToken(@Body() { refreshToken }: RefreshTokenRequestDto) {
|
async refreshToken(@Body() { refreshToken }: RefreshTokenRequestDto) {
|
||||||
|
23
src/auth/dtos/request/change-password.request.dto.ts
Normal file
23
src/auth/dtos/request/change-password.request.dto.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsNotEmpty, IsString, Matches } from 'class-validator';
|
||||||
|
import { i18nValidationMessage as i18n } from 'nestjs-i18n';
|
||||||
|
import { PASSWORD_REGEX } from '~/auth/constants';
|
||||||
|
|
||||||
|
export class ChangePasswordRequestDto {
|
||||||
|
@ApiProperty({ example: 'currentPassword@123' })
|
||||||
|
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'auth.currentPassword' }) })
|
||||||
|
@IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'auth.currentPassword' }) })
|
||||||
|
currentPassword!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'Abcd1234@' })
|
||||||
|
@Matches(PASSWORD_REGEX, {
|
||||||
|
message: i18n('validation.Matches', { path: 'general', property: 'auth.newPassword' }),
|
||||||
|
})
|
||||||
|
newPassword!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'Abcd1234@' })
|
||||||
|
@Matches(PASSWORD_REGEX, {
|
||||||
|
message: i18n('validation.Matches', { path: 'general', property: 'auth.confirmNewPassword' }),
|
||||||
|
})
|
||||||
|
confirmNewPassword!: string;
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
export * from './apple-login.request.dto';
|
export * from './apple-login.request.dto';
|
||||||
|
export * from './change-password.request.dto';
|
||||||
export * from './create-unverified-user.request.dto';
|
export * from './create-unverified-user.request.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';
|
||||||
|
@ -11,6 +11,7 @@ import { UserType } from '~/user/enums';
|
|||||||
import { DeviceService, UserService, UserTokenService } from '~/user/services';
|
import { DeviceService, UserService, UserTokenService } from '~/user/services';
|
||||||
import { User } from '../../user/entities';
|
import { User } from '../../user/entities';
|
||||||
import {
|
import {
|
||||||
|
ChangePasswordRequestDto,
|
||||||
CreateUnverifiedUserRequestDto,
|
CreateUnverifiedUserRequestDto,
|
||||||
DisableBiometricRequestDto,
|
DisableBiometricRequestDto,
|
||||||
EnableBiometricRequestDto,
|
EnableBiometricRequestDto,
|
||||||
@ -172,6 +173,7 @@ export class AuthService {
|
|||||||
|
|
||||||
return { token, user };
|
return { token, user };
|
||||||
}
|
}
|
||||||
|
|
||||||
async resetPassword({
|
async resetPassword({
|
||||||
countryCode,
|
countryCode,
|
||||||
phoneNumber,
|
phoneNumber,
|
||||||
@ -191,6 +193,15 @@ export class AuthService {
|
|||||||
throw new BadRequestException('AUTH.PASSWORD_MISMATCH');
|
throw new BadRequestException('AUTH.PASSWORD_MISMATCH');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isOldPassword = bcrypt.compareSync(password, user.password);
|
||||||
|
|
||||||
|
if (isOldPassword) {
|
||||||
|
this.logger.error(
|
||||||
|
`New password cannot be the same as the current password for user with phone number ${user.fullPhoneNumber}`,
|
||||||
|
);
|
||||||
|
throw new BadRequestException('AUTH.PASSWORD_SAME_AS_CURRENT');
|
||||||
|
}
|
||||||
|
|
||||||
const hashedPassword = bcrypt.hashSync(password, user.salt);
|
const hashedPassword = bcrypt.hashSync(password, user.salt);
|
||||||
|
|
||||||
await this.userService.setPassword(user.id, hashedPassword, user.salt);
|
await this.userService.setPassword(user.id, hashedPassword, user.salt);
|
||||||
@ -198,6 +209,38 @@ export class AuthService {
|
|||||||
this.logger.log(`Passcode updated successfully for user with phone number ${user.fullPhoneNumber}`);
|
this.logger.log(`Passcode updated successfully for user with phone number ${user.fullPhoneNumber}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async changePassword(userId: string, { currentPassword, newPassword, confirmNewPassword }: ChangePasswordRequestDto) {
|
||||||
|
const user = await this.userService.findUserOrThrow({ id: userId });
|
||||||
|
|
||||||
|
if (!user.isPasswordSet) {
|
||||||
|
this.logger.error(`Password not set for user with id ${userId}`);
|
||||||
|
throw new BadRequestException('AUTH.PASSWORD_NOT_SET');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPassword === newPassword) {
|
||||||
|
this.logger.error('New password cannot be the same as current password');
|
||||||
|
throw new BadRequestException('AUTH.PASSWORD_SAME_AS_CURRENT');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPassword !== confirmNewPassword) {
|
||||||
|
this.logger.error('New password and confirm new password do not match');
|
||||||
|
throw new BadRequestException('AUTH.PASSWORD_MISMATCH');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.log(`Validating current password for user with id ${userId}`);
|
||||||
|
const isCurrentPasswordValid = bcrypt.compareSync(currentPassword, user.password);
|
||||||
|
|
||||||
|
if (!isCurrentPasswordValid) {
|
||||||
|
this.logger.error(`Invalid current password for user with id ${userId}`);
|
||||||
|
throw new UnauthorizedException('AUTH.INVALID_CURRENT_PASSWORD');
|
||||||
|
}
|
||||||
|
|
||||||
|
const salt = bcrypt.genSaltSync(SALT_ROUNDS);
|
||||||
|
const hashedNewPassword = bcrypt.hashSync(newPassword, salt);
|
||||||
|
await this.userService.setPassword(user.id, hashedNewPassword, salt);
|
||||||
|
this.logger.log(`Password changed successfully for user with id ${userId}`);
|
||||||
|
}
|
||||||
|
|
||||||
async setJuniorPasscode(body: setJuniorPasswordRequestDto) {
|
async setJuniorPasscode(body: setJuniorPasswordRequestDto) {
|
||||||
this.logger.log(`Setting passcode for junior with qrToken ${body.qrToken}`);
|
this.logger.log(`Setting passcode for junior with qrToken ${body.qrToken}`);
|
||||||
const juniorId = await this.userTokenService.validateToken(body.qrToken, UserType.JUNIOR);
|
const juniorId = await this.userTokenService.validateToken(body.qrToken, UserType.JUNIOR);
|
||||||
|
@ -9,6 +9,9 @@
|
|||||||
"BIOMETRIC_NOT_ENABLED": "المصادقة البيومترية لم يتم تفعيلها على حسابك. يرجى تفعيلها للمتابعة.",
|
"BIOMETRIC_NOT_ENABLED": "المصادقة البيومترية لم يتم تفعيلها على حسابك. يرجى تفعيلها للمتابعة.",
|
||||||
"INVALID_BIOMETRIC": "البيانات البيومترية المقدمة غير صالحة. يرجى المحاولة مرة أخرى أو إعادة إعداد المصادقة البيومترية.",
|
"INVALID_BIOMETRIC": "البيانات البيومترية المقدمة غير صالحة. يرجى المحاولة مرة أخرى أو إعادة إعداد المصادقة البيومترية.",
|
||||||
"PASSWORD_MISMATCH": "كلمات المرور التي أدخلتها غير متطابقة. يرجى إدخال كلمات المرور مرة أخرى.",
|
"PASSWORD_MISMATCH": "كلمات المرور التي أدخلتها غير متطابقة. يرجى إدخال كلمات المرور مرة أخرى.",
|
||||||
|
"INVALID_CURRENT_PASSWORD": "كلمة المرور الحالية التي أدخلتها غير صحيحة. يرجى المحاولة مرة أخرى.",
|
||||||
|
"PASSWORD_NOT_SET": "لم يتم تعيين كلمة مرور لهذا الحساب. يرجى تعيين كلمة مرور للمتابعة.",
|
||||||
|
"PASSWORD_SAME_AS_CURRENT": "كلمة المرور الجديدة لا يمكن أن تكون نفس كلمة المرور الحالية.",
|
||||||
"INVALID_PASSCODE": "رمز المرور الذي أدخلته غير صحيح. يرجى المحاولة مرة أخرى.",
|
"INVALID_PASSCODE": "رمز المرور الذي أدخلته غير صحيح. يرجى المحاولة مرة أخرى.",
|
||||||
"PASSCODE_ALREADY_SET": "تم تعيين رمز المرور بالفعل.",
|
"PASSCODE_ALREADY_SET": "تم تعيين رمز المرور بالفعل.",
|
||||||
"APPLE_RE-CONSENT_REQUIRED": "إعادة الموافقة على آبل مطلوبة. يرجى إلغاء تطبيقك من إعدادات معرف آبل الخاصة بك والمحاولة مرة أخرى.",
|
"APPLE_RE-CONSENT_REQUIRED": "إعادة الموافقة على آبل مطلوبة. يرجى إلغاء تطبيقك من إعدادات معرف آبل الخاصة بك والمحاولة مرة أخرى.",
|
||||||
|
@ -9,6 +9,9 @@
|
|||||||
"BIOMETRIC_NOT_ENABLED": "Biometric authentication has not been activated for your account. Please enable it to proceed.",
|
"BIOMETRIC_NOT_ENABLED": "Biometric authentication has not been activated for your account. Please enable it to proceed.",
|
||||||
"INVALID_BIOMETRIC": "The biometric data provided is invalid. Please try again or reconfigure your biometric settings.",
|
"INVALID_BIOMETRIC": "The biometric data provided is invalid. Please try again or reconfigure your biometric settings.",
|
||||||
"PASSWORD_MISMATCH": "The passwords you entered do not match. Please re-enter the passwords.",
|
"PASSWORD_MISMATCH": "The passwords you entered do not match. Please re-enter the passwords.",
|
||||||
|
"INVALID_CURRENT_PASSWORD": "The current password you entered is incorrect. Please try again.",
|
||||||
|
"PASSWORD_NOT_SET": "A password has not been set for this account. Please set a password to proceed.",
|
||||||
|
"PASSWORD_SAME_AS_CURRENT": "The new password cannot be the same as the current password.",
|
||||||
"INVALID_PASSCODE": "The passcode you entered is incorrect. Please try again.",
|
"INVALID_PASSCODE": "The passcode you entered is incorrect. Please try again.",
|
||||||
"PASSCODE_ALREADY_SET": "The pass code has already been set.",
|
"PASSCODE_ALREADY_SET": "The pass code has already been set.",
|
||||||
"APPLE_RE-CONSENT_REQUIRED": "Apple re-consent is required. Please revoke the app from your Apple ID settings and try again.",
|
"APPLE_RE-CONSENT_REQUIRED": "Apple re-consent is required. Please revoke the app from your Apple ID settings and try again.",
|
||||||
|
Reference in New Issue
Block a user