From 49fe236b30c356e4e4249a292a512655f62d2a5d Mon Sep 17 00:00:00 2001 From: Kallesh B S Date: Thu, 3 Jul 2025 14:35:45 +0530 Subject: [PATCH] updated forgot password --- package-lock.json | 45 ++++--- package.json | 4 +- src/auth/auth.controller.ts | 3 +- src/auth/auth.service.ts | 242 +++++++++++++++++++++++++++-------- src/guards/jwt-auth.guard.ts | 10 ++ src/guards/roles.guard.ts | 9 ++ 6 files changed, 241 insertions(+), 72 deletions(-) diff --git a/package-lock.json b/package-lock.json index a27c0b9..762f825 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,8 @@ "pg": "^8.13.3", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", - "typeorm": "^0.3.20" + "typeorm": "^0.3.20", + "uuid": "^11.1.0" }, "devDependencies": { "@eslint/eslintrc": "^3.2.0", @@ -53,6 +54,7 @@ "@types/node": "^22.10.7", "@types/oracledb": "^6.5.4", "@types/supertest": "^6.0.2", + "@types/uuid": "^10.0.0", "eslint": "^9.18.0", "eslint-config-prettier": "^10.0.1", "eslint-plugin-prettier": "^5.2.2", @@ -541,6 +543,15 @@ "node": ">=16" } }, + "node_modules/@azure/msal-node/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -4155,6 +4166,13 @@ "@types/superagent": "^8.1.0" } }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/validator": { "version": "13.12.2", "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.2.tgz", @@ -16041,19 +16059,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/typeorm/node_modules/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" - } - }, "node_modules/typescript": { "version": "5.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", @@ -16253,12 +16258,16 @@ } }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "license": "MIT", "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist/esm/bin/uuid" } }, "node_modules/v8-compile-cache-lib": { diff --git a/package.json b/package.json index 74c643a..fea14aa 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,8 @@ "pg": "^8.13.3", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", - "typeorm": "^0.3.20" + "typeorm": "^0.3.20", + "uuid": "^11.1.0" }, "devDependencies": { "@eslint/eslintrc": "^3.2.0", @@ -64,6 +65,7 @@ "@types/node": "^22.10.7", "@types/oracledb": "^6.5.4", "@types/supertest": "^6.0.2", + "@types/uuid": "^10.0.0", "eslint": "^9.18.0", "eslint-config-prettier": "^10.0.1", "eslint-plugin-prettier": "^5.2.2", diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index ed81164..ef4f726 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -8,6 +8,7 @@ import { RegisterGuard } from 'src/guards/register.guard'; import { UserMaintenanceService } from 'src/oracle/user-maintenance/user-maintenance.service'; import { RolesGuard } from 'src/guards/roles.guard'; import { Roles } from 'src/decorators/roles.decorator'; +import { JwtAuthGuard } from 'src/guards/jwt-auth.guard'; @ApiTags('Auth') @Controller() @@ -95,7 +96,7 @@ export class AuthController { return { statusCode: 200, message: "Tokens refreshed successfully" } } - @UseGuards(RolesGuard) + @UseGuards(JwtAuthGuard, RolesGuard) @Roles('sa') @Post('sendmail') @HttpCode(200) diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 1e59573..05bc24e 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -13,6 +13,8 @@ import { BadRequestException } from 'src/exceptions/badRequest.exception'; import { MailerService } from '@nestjs-modules/mailer'; import { JwtService } from '@nestjs/jwt'; import { UserMaintenanceService } from 'src/oracle/user-maintenance/user-maintenance.service'; +import { v4 as uuidv4 } from 'uuid'; + interface DecodedToken { email: string; @@ -36,8 +38,7 @@ export class AuthService { private readonly CLIENT_SECRET: string; private readonly TOKEN_URL: string; private readonly USERS_URL: string; - private readonly JWT_REGISTER_PRIVATE_KEY: string; - private readonly JWT_REGISTER_PUBLIC_KEY: string; + constructor( private readonly oracleDBService: OracleDBService, @@ -47,13 +48,6 @@ export class AuthService { @Inject("REGISTER_SIGN_JWT") readonly REGISTER_SIGN_JWT: JwtService, @Inject("REGISTER_VERIFY_JWT") readonly REGISTER_VERIFY_JWT: JwtService ) { - const JWT_REGISTER_PRIVATE_KEY = this.configService.get('JWT_REGISTER_PRIVATE_KEY'); - if (!JWT_REGISTER_PRIVATE_KEY) throw new Error('Environment variable JWT_REGISTER_PRIVATE_KEY is not set'); - this.JWT_REGISTER_PRIVATE_KEY = Buffer.from(JWT_REGISTER_PRIVATE_KEY, 'base64').toString('utf-8'); - - const JWT_REGISTER_PUBLIC_KEY = this.configService.get('JWT_REGISTER_PUBLIC_KEY'); - if (!JWT_REGISTER_PUBLIC_KEY) throw new Error('Environment variable JWT_REGISTER_PRIVATE_KEY is not set'); - this.JWT_REGISTER_PUBLIC_KEY = Buffer.from(JWT_REGISTER_PUBLIC_KEY, 'base64').toString('utf-8'); const KEYCLOAK_URL = this.configService.get('KEYCLOAK_URL'); if (!KEYCLOAK_URL) throw new Error('Environment variable KEYCLOAK_URL is not set'); @@ -80,6 +74,7 @@ export class AuthService { this.TOKEN_URL = `${this.KEYCLOAK_BASE_URL}/token` this.USERS_URL = `${this.KEYCLOAK_URL}/admin/realms/${this.KEYCLOAK_REALM}/users`; + } async login(body: AuthLoginDTO) { @@ -330,6 +325,60 @@ export class AuthService { } } + async getUserDetailsByEmail(email: string): Promise { + try { + const adminAccessToken = await this.getAdminAccessToken(); + + const userResponse = await axios.get(`${this.USERS_URL}?email=${encodeURIComponent(email)}`, { + headers: { + Authorization: `Bearer ${adminAccessToken}` + }, + }); + + if (userResponse.status !== 200 || !Array.isArray(userResponse.data)) { + console.log("userResponse : ", userResponse); + console.log("userResponse status : ", userResponse.status); + + throw new UnauthorizedException("Authentication failed"); + } + + const user = userResponse.data[0]; + + if (userResponse.data.length === 0 || !user?.id) { + throw new BadRequestException("Invalid User"); + } + const roleUrl = `${this.USERS_URL}/${user.id}/role-mappings`; + + const roleResponse = await axios.get(roleUrl, { + headers: { + Authorization: `Bearer ${adminAccessToken}` + }, + }); + + const clientRoles = roleResponse.data; + + return { + id: user.id, + email: user.email, + forgotPassword: user.attributes?.["forgot-password"][0] || null, + role: clientRoles.clientMappings?.[this.CLIENT_ID]?.mappings?.map((x) => x.name) || [] + }; + + } catch (error) { + console.log(error.message); + + console.log("Error in getUserDetailsByEmail ..........."); + if (error instanceof UnauthorizedException) { + throw error; + } + else if (error instanceof BadRequestException) { + throw error; + } + throw new InternalServerErrorException(); + } + } + + async forgotPassword(body: AuthLoginDTO, req: Request) { let det = await req['user']; @@ -337,25 +386,42 @@ export class AuthService { throw new BadRequestException(); } - const validatePasswordURLSearchParams = new URLSearchParams({ - grant_type: 'password', - client_id: `${this.CLIENT_ID}`, - client_secret: `${this.CLIENT_SECRET}`, - username: `${body.P_EMAILADDR}`, - password: `${body.P_PASSWORD}`, - }); + const userDetails = await this.getUserDetailsByEmail(body.P_EMAILADDR); + + if (userDetails.forgotPassword !== det.rstID) { + throw new UnauthorizedException("Token verification failed"); + } + + let adminAccessToken = await this.getAdminAccessToken(); + + console.log(`${this.USERS_URL}/${userDetails.id}/reset-password`); + const options2: AxiosRequestConfig = { - method: 'POST', - url: this.TOKEN_URL, + method: 'PUT', + url: `${this.USERS_URL}/${userDetails.id}/reset-password`, headers: { - 'Content-Type': 'application/x-www-form-urlencoded', + Authorization: `Bearer ${adminAccessToken}`, + 'Content-Type': 'application/json', }, - data: validatePasswordURLSearchParams + data: { + type: 'password', + value: body.P_PASSWORD, + temporary: false, + } }; try { const { data } = await axios.request(options2); + + console.log("forgot password status API request", data.status); + + const forgotPasswordStatus = await this.updateForgotPassword(userDetails) + + if (!forgotPasswordStatus) { + throw new InternalServerErrorException("Update forgot-password failed") + } + return { statusCode: 200, message: 'password reset successfull' } } catch (error) { // console.error(error); @@ -405,6 +471,9 @@ export class AuthService { email: body.P_EMAILADDR, emailVerified: true, enabled: true, + attributes: { + "forgot-password": ["none"] + }, credentials: [ { type: 'password', @@ -670,31 +739,101 @@ export class AuthService { } } + async generateUUID() { + try { + const uuid = uuidv4(); + + if (!uuid || typeof uuid !== 'string') { + throw new InternalServerErrorException('Invalid UUID generated'); + } + + return uuid; + } catch (error) { + console.error('UUID generation failed:', error.message); + throw new InternalServerErrorException('Failed to generate unique ID'); + } + } + + async generateToken(payload: { P_TO: string, uuid?: string }) { + let token: string; + try { + if (payload.uuid) { + token = this.REGISTER_SIGN_JWT.sign({ email: payload.P_TO, rstID: payload.uuid }); + } + else { + token = this.REGISTER_SIGN_JWT.sign({ email: payload.P_TO }); + } + console.log("Register token : ", token); + + if (!token) { + console.log("Register Token Generation failed"); + throw new InternalServerErrorException(); + } + + return token; + } + catch (error) { + console.log(error.message); + + throw new InternalServerErrorException("Register Token Generation failed") + } + } + + async updateForgotPassword(user: any, rstID?: string) { + try { + const adminAccessToken = await this.getAdminAccessToken(); + + const options2: AxiosRequestConfig = { + method: 'PUT', + url: `${this.USERS_URL}/${user.id}`, + headers: { + Authorization: `Bearer ${adminAccessToken}`, + 'Content-Type': 'application/json', + }, + data: { + "username": `${user.email}`, + // firstName:"A", + // lastName:"B", + "email": `${user.email}`, + "emailVerified": true, + "enabled": true, + "attributes": { + "forgot-password": [`${rstID ? rstID : "none"}`] + } + } + }; + + const userResponse = await axios.request(options2); + + if (userResponse.status !== 204) { + console.log("Error updating forgot-password : "); + throw new InternalServerErrorException(); + } + + return true; + + } catch (error) { + + console.log("Error updating forgot password"); + console.log("Error : ", error.message); + + return false; + } + } + async sendMail(body: SendMailDTO) { console.log("mail body : ", body); let token: string; let ROLE: string = ""; + let uuid: string | undefined = undefined; + let userDetails: any | null = null; if (body.P_MAIL_TYPE === MailTypeDTO.FORGOT_PASSWORD) { try { - const response: any = await this.userMaintenanceService.ValidateEmail({ P_EMAILADDR: body.P_TO }) + uuid = await this.generateUUID(); + userDetails = await this.getUserDetailsByEmail(body.P_TO); - if (Array.isArray(response) && !response[0].ERRORMESG) { - switch (response[0].SOURCE) { - case "USCIB": ROLE = 'ua'; break; - case "SP": ROLE = 'sa'; break; - case "CLIENT": ROLE = 'ca'; break; - } - } - else if (Array.isArray(response) && response[0].ERRORMESG) { - throw new BadRequestException(); - } - - if (!ROLE) { - console.log("ERROR while giving access : "); - throw new InternalServerErrorException(); - } } catch (error) { if (error instanceof BadRequestException) { throw error @@ -704,21 +843,7 @@ export class AuthService { } } - - try { - token = this.REGISTER_SIGN_JWT.sign({ email: body.P_TO }); - console.log("Register token : ", token); - - if (!token) { - console.log("Register Token Generation failed"); - throw new InternalServerErrorException(); - } - } - catch (error) { - console.log(error.message); - - throw new InternalServerErrorException("Register Token Generation failed") - } + token = await this.generateToken({ P_TO: body.P_TO, uuid: uuid }) const mailTemplate: MailTemplateDTO | null = await this.getMailTemplate(body, token, ROLE); @@ -727,9 +852,22 @@ export class AuthService { } try { - await this.mailService.sendMail({ ...mailTemplate }); + // await this.mailService.sendMail({ ...mailTemplate }); console.log('Email sent successfully'); + + if (body.P_MAIL_TYPE === MailTypeDTO.FORGOT_PASSWORD) { + let forgotPasswordStatus = await this.updateForgotPassword(userDetails, uuid); + + if (forgotPasswordStatus) { + + return { statusCode: 200, message: 'Email sent successfully' }; + } + + throw new InternalServerErrorException(); + + } return { statusCode: 200, message: 'Email sent successfully' }; + } catch (error) { console.error('Failed to send email:', error.message, error); throw new InternalServerErrorException(); diff --git a/src/guards/jwt-auth.guard.ts b/src/guards/jwt-auth.guard.ts index 490be9e..d5a762b 100644 --- a/src/guards/jwt-auth.guard.ts +++ b/src/guards/jwt-auth.guard.ts @@ -9,6 +9,7 @@ import { AuthService } from 'src/auth/auth.service'; import * as jwt from 'jsonwebtoken'; import { InternalServerException } from 'src/exceptions/internalServerError.exception'; import { UnauthorizedException as UE } from 'src/exceptions/unauthorized.exception'; +import { MailTypeDTO } from 'src/auth/auth.dto'; @Global() @Injectable() @@ -19,6 +20,15 @@ export class JwtAuthGuard implements CanActivate { const request = context.switchToHttp().getRequest(); const token = request.cookies?.access_token; + const body = request.body; + + console.log("from JWT guard body : ", body); + + + if (body.P_MAIL_TYPE && body.P_MAIL_TYPE === MailTypeDTO.FORGOT_PASSWORD) { + return true; + } + if (!token) { console.log("No token found from Request"); throw new UnauthorizedException('Authentication failed'); diff --git a/src/guards/roles.guard.ts b/src/guards/roles.guard.ts index 3f5b98f..c91ba87 100644 --- a/src/guards/roles.guard.ts +++ b/src/guards/roles.guard.ts @@ -9,6 +9,7 @@ import { Reflector } from '@nestjs/core'; import { ROLES_KEY } from '../decorators/roles.decorator'; import * as jwt from 'jsonwebtoken'; import { UnauthorizedException } from 'src/exceptions/unauthorized.exception'; +import { MailTypeDTO } from 'src/auth/auth.dto'; @Injectable() export class RolesGuard implements CanActivate { @@ -24,6 +25,14 @@ export class RolesGuard implements CanActivate { const request = context.switchToHttp().getRequest(); const token = request.cookies?.access_token; // Token is stored as HttpOnly cookie + const body = request.body; + + console.log("from JWT guard body : ", body); + + if (body.P_MAIL_TYPE && body.P_MAIL_TYPE === MailTypeDTO.FORGOT_PASSWORD) { + return true; + } + if (!token) { throw new UnauthorizedException('Authentication Failed'); }