From 1db5ae1352c642c511930cda37391616ef05bbb8 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Fri, 8 Nov 2024 16:44:16 +0530 Subject: [PATCH 1/3] refactor: forgot password API Signed-off-by: bhavanakarwade --- .../src/authz/dtos/forgot-password.dto.ts | 28 +++++++++++++++++-- apps/user/interfaces/user.interface.ts | 6 ++++ apps/user/src/user.controller.ts | 4 +-- apps/user/src/user.service.ts | 17 ++++++----- .../user/templates/reset-password-template.ts | 13 +++++---- 5 files changed, 52 insertions(+), 16 deletions(-) diff --git a/apps/api-gateway/src/authz/dtos/forgot-password.dto.ts b/apps/api-gateway/src/authz/dtos/forgot-password.dto.ts index 9101a7d98..794abbd16 100644 --- a/apps/api-gateway/src/authz/dtos/forgot-password.dto.ts +++ b/apps/api-gateway/src/authz/dtos/forgot-password.dto.ts @@ -1,6 +1,6 @@ -import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; +import { IsEmail, IsNotEmpty, IsOptional, IsString, IsUrl } from 'class-validator'; -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { trim } from '@credebl/common/cast.helper'; @@ -11,4 +11,28 @@ export class ForgotPasswordDto { @IsString({ message: 'Email should be a string' }) @Transform(({ value }) => trim(value)) email: string; + + @ApiPropertyOptional({ example: 'https://example.com/logo.png' }) + @Transform(({ value }) => trim(value)) + @IsOptional() + @IsUrl({ + // eslint-disable-next-line camelcase + require_protocol: true, + // eslint-disable-next-line camelcase + require_tld: true + }, + { message: 'brandLogoUrl should be a valid URL' }) + brandLogoUrl?: string; + + @ApiPropertyOptional({ example: 'MyPlatform' }) + @Transform(({ value }) => trim(value)) + @IsOptional() + @IsString({ message: 'platformName should be string' }) + platformName?: string; + + @ApiPropertyOptional({ example: 'http://0.0.0.0:5000' }) + @Transform(({ value }) => trim(value)) + @IsOptional() + @IsString({ message: 'endpoint should be string' }) + endpoint?: string; } \ No newline at end of file diff --git a/apps/user/interfaces/user.interface.ts b/apps/user/interfaces/user.interface.ts index 3c726cd00..0324f0d05 100644 --- a/apps/user/interfaces/user.interface.ts +++ b/apps/user/interfaces/user.interface.ts @@ -194,6 +194,12 @@ export interface IUserResetPassword{ token?: string; password?: string; } +export interface IUserForgotPassword{ + email: string; + brandLogoUrl?: string, + platformName?: string, + endpoint?: string +} export interface IIssueCertificate { courseCode: string; courseName: string; diff --git a/apps/user/src/user.controller.ts b/apps/user/src/user.controller.ts index c9f668ace..3ea07cf2a 100644 --- a/apps/user/src/user.controller.ts +++ b/apps/user/src/user.controller.ts @@ -1,4 +1,4 @@ -import { IOrgUsers, Payload, ICheckUserDetails, PlatformSettings, IShareUserCertificate, UpdateUserProfile, IUsersProfile, IUserInformation, IUserSignIn, IUserCredentials, IUserResetPassword, IUserDeletedActivity, UserKeycloakId} from '../interfaces/user.interface'; +import { IOrgUsers, Payload, ICheckUserDetails, PlatformSettings, IShareUserCertificate, UpdateUserProfile, IUsersProfile, IUserInformation, IUserSignIn, IUserCredentials, IUserResetPassword, IUserDeletedActivity, UserKeycloakId, IUserForgotPassword} from '../interfaces/user.interface'; import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; import { Controller } from '@nestjs/common'; import { MessagePattern } from '@nestjs/microservices'; @@ -61,7 +61,7 @@ export class UserController { } @MessagePattern({ cmd: 'user-forgot-password' }) - async forgotPassword(payload: IUserResetPassword): Promise { + async forgotPassword(payload: IUserForgotPassword): Promise { return this.userService.forgotPassword(payload); } diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index b1f78056a..09e8c855a 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -44,7 +44,8 @@ import { IShareDegreeCertificateRes, IUserDeletedActivity, UserKeycloakId, - IEcosystemConfig + IEcosystemConfig, + IUserForgotPassword } from '../interfaces/user.interface'; import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; import { UserActivityService } from '@credebl/user-activity'; @@ -436,8 +437,8 @@ export class UserService { * @param forgotPasswordDto * @returns */ - async forgotPassword(forgotPasswordDto: IUserResetPassword): Promise { - const { email } = forgotPasswordDto; + async forgotPassword(forgotPasswordDto: IUserForgotPassword): Promise { + const { email, brandLogoUrl, platformName, endpoint } = forgotPasswordDto; try { this.validateEmail(email.toLowerCase()); @@ -461,7 +462,7 @@ export class UserService { } try { - await this.sendEmailForResetPassword(email, tokenCreated.token); + await this.sendEmailForResetPassword(email, brandLogoUrl, platformName, endpoint, tokenCreated.token); } catch (error) { throw new InternalServerErrorException(ResponseMessages.user.error.emailSend); } @@ -483,7 +484,7 @@ export class UserService { * @param verificationCode * @returns */ - async sendEmailForResetPassword(email: string, verificationCode: string): Promise { + async sendEmailForResetPassword(email: string, brandLogoUrl: string, platformName: string, endpoint: string, verificationCode: string): Promise { try { const platformConfigData = await this.prisma.platform_config.findMany(); @@ -491,9 +492,11 @@ export class UserService { const emailData = new EmailDto(); emailData.emailFrom = platformConfigData[0].emailFrom; emailData.emailTo = email; - emailData.emailSubject = `[${process.env.PLATFORM_NAME}] Important: Password Reset Request`; - emailData.emailHtml = await urlEmailTemplate.getUserResetPasswordTemplate(email, verificationCode); + const platform = platformName || process.env.PLATFORM_NAME; + emailData.emailSubject = `[${platform}] Important: Password Reset Request`; + + emailData.emailHtml = await urlEmailTemplate.getUserResetPasswordTemplate(email, platform, brandLogoUrl, endpoint, verificationCode); const isEmailSent = await sendEmail(emailData); if (isEmailSent) { return isEmailSent; diff --git a/apps/user/templates/reset-password-template.ts b/apps/user/templates/reset-password-template.ts index ec05ecdde..daa0701a4 100644 --- a/apps/user/templates/reset-password-template.ts +++ b/apps/user/templates/reset-password-template.ts @@ -1,12 +1,15 @@ import * as url from 'url'; export class URLUserResetPasswordTemplate { - public getUserResetPasswordTemplate(email: string, verificationCode: string): string { - const endpoint = `${process.env.FRONT_END_URL}`; + public getUserResetPasswordTemplate(email: string, platform: string, brandLogoUrl: string, uiEndpoint: string, verificationCode: string): string { + const endpoint = uiEndpoint || process.env.FRONT_END_URL; const apiUrl = url.parse( `${endpoint}/reset-password?verificationCode=${verificationCode}&email=${encodeURIComponent(email)}` ); + const logoUrl = brandLogoUrl || process.env.BRAND_LOGO; + const poweredBy = platform || process.env.POWERED_BY; + const validUrl = apiUrl.href.replace('/:', ':'); try { @@ -23,7 +26,7 @@ export class URLUserResetPasswordTemplate {
- ${process.env.PLATFORM_NAME} logo + ${platform} logo
@@ -50,7 +53,7 @@ export class URLUserResetPasswordTemplate {

- © ${process.env.POWERED_BY} + © ${poweredBy}

From 2b470e2aaf4646389bfeec119fc3aa4765913bd9 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Fri, 8 Nov 2024 16:51:23 +0530 Subject: [PATCH 2/3] feat: added logger Signed-off-by: bhavanakarwade --- apps/api-gateway/src/authz/authz.controller.ts | 2 +- apps/user/src/user.service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api-gateway/src/authz/authz.controller.ts b/apps/api-gateway/src/authz/authz.controller.ts index 7d382d030..d00df07f1 100644 --- a/apps/api-gateway/src/authz/authz.controller.ts +++ b/apps/api-gateway/src/authz/authz.controller.ts @@ -146,7 +146,7 @@ export class AuthzController { }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) async forgotPassword(@Body() forgotPasswordDto: ForgotPasswordDto, @Res() res: Response): Promise { - + this.logger.log(`forgotPasswordDto::: ${JSON.stringify(forgotPasswordDto)}`); const userData = await this.authzService.forgotPassword(forgotPasswordDto); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index 09e8c855a..5ee933aac 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -439,7 +439,7 @@ export class UserService { */ async forgotPassword(forgotPasswordDto: IUserForgotPassword): Promise { const { email, brandLogoUrl, platformName, endpoint } = forgotPasswordDto; - + this.logger.log(`forgotPasswordDto::::::: ${forgotPasswordDto}`); try { this.validateEmail(email.toLowerCase()); const userData = await this.userRepository.checkUserExist(email.toLowerCase()); From 39941b85bd57b33082a7b343bb8660001c19dde7 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Fri, 8 Nov 2024 16:53:33 +0530 Subject: [PATCH 3/3] fix: resolve security hotspot issue Signed-off-by: bhavanakarwade --- apps/api-gateway/src/authz/dtos/forgot-password.dto.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api-gateway/src/authz/dtos/forgot-password.dto.ts b/apps/api-gateway/src/authz/dtos/forgot-password.dto.ts index 794abbd16..2292a6a36 100644 --- a/apps/api-gateway/src/authz/dtos/forgot-password.dto.ts +++ b/apps/api-gateway/src/authz/dtos/forgot-password.dto.ts @@ -30,7 +30,7 @@ export class ForgotPasswordDto { @IsString({ message: 'platformName should be string' }) platformName?: string; - @ApiPropertyOptional({ example: 'http://0.0.0.0:5000' }) + @ApiPropertyOptional({ example: 'https://0.0.0.0:5000' }) @Transform(({ value }) => trim(value)) @IsOptional() @IsString({ message: 'endpoint should be string' })