Skip to content

Commit e4608c8

Browse files
authored
Merge pull request #32 from vishnuvinay89/all-saas-0.2-dev
Task #235460 - Password change for learner API
2 parents d209d76 + 941b42b commit e4608c8

File tree

3 files changed

+112
-1
lines changed

3 files changed

+112
-1
lines changed

src/adapters/postgres/user-adapter.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,92 @@ export class PostgresUserService implements IServicelocator {
188188
}
189189
}
190190

191+
async learnerforgotPassword(request: any, body: any, response: Response<any, Record<string, any>>) {
192+
const apiId = APIID.USER_FORGOT_PASSWORD;
193+
const tenantId = request.headers.tenantid;
194+
const decoded: any = jwt_decode(request.headers.authorization);
195+
196+
try {
197+
// Check if User Exists
198+
const userDetail = await this.usersRepository.findOne({
199+
where: { username: body.userName }
200+
});
201+
if (!userDetail) {
202+
return APIResponse.error(
203+
response,
204+
apiId,
205+
API_RESPONSES.USERNAME_NOT_FOUND,
206+
'User not found.',
207+
HttpStatus.NOT_FOUND
208+
);
209+
}
210+
211+
// Validate Learner-Tenant & Admin-Tenant Mapping in a single function
212+
await this.validateUserandTenantMapping(userDetail.userId, tenantId);
213+
await this.validateUserandTenantMapping(decoded.sub, tenantId);
214+
215+
// Get Keycloak Admin Token
216+
const keycloakResponse = await getKeycloakAdminToken();
217+
const keyClocktoken = keycloakResponse.data.access_token;
218+
219+
// Check Role and Access
220+
const role = await this.getUserRoles(decoded.sub, tenantId);
221+
if (role.code === 'cohort_admin') {
222+
const cohortIds = await this.getCohortIdsForTenant(decoded.sub, tenantId);
223+
const learnercohorts = await this.cohortMemberRepository.find({
224+
where: { userId: userDetail.userId, cohortId: In(cohortIds) }
225+
});
226+
if (learnercohorts.length === 0) {
227+
return APIResponse.error(
228+
response,
229+
apiId,
230+
'You don\'t have access to update the password of this learner',
231+
'Unauthorized access attempt.',
232+
HttpStatus.UNAUTHORIZED
233+
);
234+
}
235+
}
236+
237+
// Validate new password
238+
if (!body.newPassword || body.newPassword.length < 8) {
239+
return APIResponse.error(
240+
response,
241+
apiId,
242+
'New password does not meet security requirements.',
243+
'Password too short.',
244+
HttpStatus.BAD_REQUEST
245+
);
246+
}
247+
248+
// Reset Keycloak Password
249+
await this.resetKeycloakPassword(
250+
request,
251+
userDetail,
252+
keyClocktoken,
253+
body.newPassword,
254+
userDetail.userId
255+
);
256+
257+
return APIResponse.success(
258+
response,
259+
apiId,
260+
{},
261+
HttpStatus.OK,
262+
API_RESPONSES.FORGOT_PASSWORD_SUCCESS
263+
);
264+
265+
} catch (error) {
266+
console.error('Error in learnerforgotPassword:', error);
267+
return APIResponse.error(
268+
response,
269+
apiId,
270+
API_RESPONSES.INTERNAL_SERVER_ERROR,
271+
`Error: ${error.message || error}`,
272+
error.status || HttpStatus.INTERNAL_SERVER_ERROR
273+
);
274+
}
275+
}
276+
191277
// Utility function to check user-tenant mapping
192278
async validateUserandTenantMapping(userId: string, tenantId: string) {
193279
let userTenantMapping = await this.userTenantMappingRepository.find({

src/user/dto/passwordReset.dto.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,15 @@ export class ForgotPasswordDto {
2424
@IsString()
2525
@IsNotEmpty()
2626
token: string;
27+
}
28+
29+
export class learnerForgotPasswordDto {
30+
31+
@IsString()
32+
@IsNotEmpty()
33+
newPassword: string;
34+
35+
@IsString()
36+
@IsNotEmpty()
37+
userName: string;
2738
}

src/user/user.controller.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ import { JwtAuthGuard } from "src/common/guards/keycloak.guard";
4141
import { Request, Response } from "express";
4242
import { AllExceptionsFilter } from "src/common/filters/exception.filter";
4343
import { APIID } from "src/common/utils/api-id.config";
44-
import { ForgotPasswordDto, ResetUserPasswordDto, SendPasswordResetLinkDto } from "./dto/passwordReset.dto";
44+
import { ForgotPasswordDto, ResetUserPasswordDto, SendPasswordResetLinkDto,learnerForgotPasswordDto } from "./dto/passwordReset.dto";
45+
import { PostgresUserService } from "src/adapters/postgres/user-adapter";
46+
4547
export interface UserData {
4648
context: string;
4749
// tenantId: string;
@@ -54,6 +56,7 @@ export interface UserData {
5456
export class UserController {
5557
constructor(
5658
private userAdapter: UserAdapter,
59+
private postgresUserService: PostgresUserService,
5760
) { }
5861

5962
@UseFilters(new AllExceptionsFilter(APIID.USER_GET))
@@ -180,6 +183,17 @@ export class UserController {
180183
return await this.userAdapter.buildUserAdapter().forgotPassword(request, reqBody, response)
181184
}
182185

186+
@Post("/learner-forgot-password")
187+
@ApiOkResponse({ description: 'Forgot password reset successfully.' })
188+
@UsePipes(new ValidationPipe({ transform: true }))
189+
public async learnerforgotPassword(
190+
@Req() request: Request,
191+
@Res() response: Response,
192+
@Body() reqBody: learnerForgotPasswordDto
193+
) {
194+
return await this.postgresUserService.learnerforgotPassword(request, reqBody, response)
195+
}
196+
183197
@UseFilters(new AllExceptionsFilter(APIID.USER_RESET_PASSWORD))
184198
@Post("/reset-password")
185199
@UseGuards(JwtAuthGuard)

0 commit comments

Comments
 (0)