diff --git a/src/adapters/postgres/user-adapter.ts b/src/adapters/postgres/user-adapter.ts index e4638d2..925feec 100644 --- a/src/adapters/postgres/user-adapter.ts +++ b/src/adapters/postgres/user-adapter.ts @@ -188,6 +188,92 @@ export class PostgresUserService implements IServicelocator { } } + async learnerforgotPassword(request: any, body: any, response: Response>) { + const apiId = APIID.USER_FORGOT_PASSWORD; + const tenantId = request.headers.tenantid; + const decoded: any = jwt_decode(request.headers.authorization); + + try { + // Check if User Exists + const userDetail = await this.usersRepository.findOne({ + where: { username: body.userName } + }); + if (!userDetail) { + return APIResponse.error( + response, + apiId, + API_RESPONSES.USERNAME_NOT_FOUND, + 'User not found.', + HttpStatus.NOT_FOUND + ); + } + + // Validate Learner-Tenant & Admin-Tenant Mapping in a single function + await this.validateUserandTenantMapping(userDetail.userId, tenantId); + await this.validateUserandTenantMapping(decoded.sub, tenantId); + + // Get Keycloak Admin Token + const keycloakResponse = await getKeycloakAdminToken(); + const keyClocktoken = keycloakResponse.data.access_token; + + // Check Role and Access + const role = await this.getUserRoles(decoded.sub, tenantId); + if (role.code === 'cohort_admin') { + const cohortIds = await this.getCohortIdsForTenant(decoded.sub, tenantId); + const learnercohorts = await this.cohortMemberRepository.find({ + where: { userId: userDetail.userId, cohortId: In(cohortIds) } + }); + if (learnercohorts.length === 0) { + return APIResponse.error( + response, + apiId, + 'You don\'t have access to update the password of this learner', + 'Unauthorized access attempt.', + HttpStatus.UNAUTHORIZED + ); + } + } + + // Validate new password + if (!body.newPassword || body.newPassword.length < 8) { + return APIResponse.error( + response, + apiId, + 'New password does not meet security requirements.', + 'Password too short.', + HttpStatus.BAD_REQUEST + ); + } + + // Reset Keycloak Password + await this.resetKeycloakPassword( + request, + userDetail, + keyClocktoken, + body.newPassword, + userDetail.userId + ); + + return APIResponse.success( + response, + apiId, + {}, + HttpStatus.OK, + API_RESPONSES.FORGOT_PASSWORD_SUCCESS + ); + + } catch (error) { + console.error('Error in learnerforgotPassword:', error); + return APIResponse.error( + response, + apiId, + API_RESPONSES.INTERNAL_SERVER_ERROR, + `Error: ${error.message || error}`, + error.status || HttpStatus.INTERNAL_SERVER_ERROR + ); + } + } + // Utility function to check user-tenant mapping async validateUserandTenantMapping(userId: string, tenantId: string) { let userTenantMapping = await this.userTenantMappingRepository.find({ diff --git a/src/user/dto/passwordReset.dto.ts b/src/user/dto/passwordReset.dto.ts index 79320e4..1517301 100644 --- a/src/user/dto/passwordReset.dto.ts +++ b/src/user/dto/passwordReset.dto.ts @@ -24,4 +24,15 @@ export class ForgotPasswordDto { @IsString() @IsNotEmpty() token: string; +} + +export class learnerForgotPasswordDto { + + @IsString() + @IsNotEmpty() + newPassword: string; + + @IsString() + @IsNotEmpty() + userName: string; } \ No newline at end of file diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index c73a31e..3e599cc 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -41,7 +41,9 @@ import { JwtAuthGuard } from "src/common/guards/keycloak.guard"; import { Request, Response } from "express"; import { AllExceptionsFilter } from "src/common/filters/exception.filter"; import { APIID } from "src/common/utils/api-id.config"; -import { ForgotPasswordDto, ResetUserPasswordDto, SendPasswordResetLinkDto } from "./dto/passwordReset.dto"; +import { ForgotPasswordDto, ResetUserPasswordDto, SendPasswordResetLinkDto,learnerForgotPasswordDto } from "./dto/passwordReset.dto"; +import { PostgresUserService } from "src/adapters/postgres/user-adapter"; + export interface UserData { context: string; // tenantId: string; @@ -54,6 +56,7 @@ export interface UserData { export class UserController { constructor( private userAdapter: UserAdapter, + private postgresUserService: PostgresUserService, ) { } @UseFilters(new AllExceptionsFilter(APIID.USER_GET)) @@ -180,6 +183,17 @@ export class UserController { return await this.userAdapter.buildUserAdapter().forgotPassword(request, reqBody, response) } + @Post("/learner-forgot-password") + @ApiOkResponse({ description: 'Forgot password reset successfully.' }) + @UsePipes(new ValidationPipe({ transform: true })) + public async learnerforgotPassword( + @Req() request: Request, + @Res() response: Response, + @Body() reqBody: learnerForgotPasswordDto + ) { + return await this.postgresUserService.learnerforgotPassword(request, reqBody, response) + } + @UseFilters(new AllExceptionsFilter(APIID.USER_RESET_PASSWORD)) @Post("/reset-password") @UseGuards(JwtAuthGuard)