Skip to content

Commit 316e9c8

Browse files
committed
feat(user service): add Change Pass routes
1 parent 5cfaacf commit 316e9c8

File tree

6 files changed

+196
-3
lines changed

6 files changed

+196
-3
lines changed

src/user/dto/changePassword.dto.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export class ChangePasswordDTO {
2+
username: string;
3+
password: string;
4+
OTP: string;
5+
}

src/user/fusionauth/fusionauth.service.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import FusionAuthClient, {
1515

1616
import ClientResponse from '@fusionauth/typescript-client/build/src/ClientResponse';
1717
import { Injectable } from '@nestjs/common';
18+
import { response } from 'express';
1819

1920
export enum FAStatus {
2021
SUCCESS = 'SUCCESS',
@@ -33,6 +34,61 @@ export class FusionauthService {
3334
);
3435
}
3536

37+
getUser(
38+
username: string,
39+
): Promise<{ statusFA: FAStatus; userId: UUID; user: User }> {
40+
return this.fusionauthClient
41+
.retrieveUserByUsername(username)
42+
.then(
43+
(
44+
response: ClientResponse<UserResponse>,
45+
): { statusFA: FAStatus; userId: UUID; user: User } => {
46+
console.log('Found user');
47+
return {
48+
statusFA: FAStatus.USER_EXISTS,
49+
userId: response.response.user.id,
50+
user: response.response.user,
51+
};
52+
},
53+
)
54+
.catch((e): { statusFA: FAStatus; userId: UUID; user: User } => {
55+
console.log(
56+
`Could not fetch user with username ${username}`,
57+
JSON.stringify(e),
58+
);
59+
return {
60+
statusFA: FAStatus.ERROR,
61+
userId: null,
62+
user: null,
63+
};
64+
});
65+
}
66+
67+
updatePassword(
68+
userId: UUID,
69+
password: string,
70+
): Promise<{ statusFA: FAStatus; userId: UUID }> {
71+
return this.fusionauthClient
72+
.patchUser(userId, {
73+
user: {
74+
password: password,
75+
},
76+
})
77+
.then((response) => {
78+
return {
79+
statusFA: FAStatus.SUCCESS,
80+
userId: response.response.user.id,
81+
};
82+
})
83+
.catch((response) => {
84+
console.log(JSON.stringify(response));
85+
return {
86+
statusFA: FAStatus.ERROR,
87+
userId: null,
88+
};
89+
});
90+
}
91+
3692
delete(userId: UUID): Promise<any> {
3793
return this.fusionauthClient
3894
.deleteUser(userId)

src/user/user.controller.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Body, Controller, Get, Patch, Post, Query } from '@nestjs/common';
2+
import { ChangePasswordDTO } from './dto/changePassword.dto';
23

34
import { FusionauthService } from './fusionauth/fusionauth.service';
45
import { OtpService } from './otp/otp.service';
@@ -52,4 +53,20 @@ export class UserController {
5253
const status: SignupResponse = await this.userService.update(user);
5354
return status;
5455
}
56+
57+
@Post('/changePassword/sendOTP')
58+
async changePasswordOTP(@Body() data: any): Promise<SignupResponse> {
59+
const status: SignupResponse = await this.userService.changePasswordOTP(
60+
data.username,
61+
);
62+
return status;
63+
}
64+
65+
@Patch('/changePassword/update')
66+
async changePassword(
67+
@Body() data: ChangePasswordDTO,
68+
): Promise<SignupResponse> {
69+
const status: SignupResponse = await this.userService.changePassword(data);
70+
return status;
71+
}
5572
}

src/user/user.interface.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ export interface IGenericResponse {
4444
}
4545

4646
export interface SignupResult {
47-
responseMsg: string;
48-
accountStatus: AccountStatus;
47+
responseMsg?: string;
48+
accountStatus?: AccountStatus;
4949
data?: any;
5050
}
5151

src/user/user.service.spec.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,45 @@
11
import { Test, TestingModule } from '@nestjs/testing';
22

33
import { FusionauthService } from './fusionauth/fusionauth.service';
4+
import { GupshupService } from './sms/gupshup/gupshup.service';
5+
import { OtpService } from './otp/otp.service';
46
import { UserDBService } from './user-db/user-db.service';
57
import { UserService } from './user.service';
68

79
describe('UserService', () => {
810
let service: UserService;
911
let fusionauthService: FusionauthService;
1012
let userDBService: UserDBService;
13+
let otpService: OtpService;
14+
15+
const gupshupFactory = {
16+
provide: 'OtpService',
17+
useFactory: () => {
18+
return new GupshupService(
19+
process.env.GUPSHUP_USERNAME,
20+
process.env.GUPSHUP_PASSWORD,
21+
process.env.GUPSHUP_BASEURL,
22+
);
23+
},
24+
inject: [],
25+
};
26+
27+
const otpServiceFactory = {
28+
provide: OtpService,
29+
useFactory: () => {
30+
return new OtpService(gupshupFactory.useFactory());
31+
},
32+
inject: [],
33+
};
1134

1235
beforeEach(async () => {
1336
const module: TestingModule = await Test.createTestingModule({
14-
providers: [FusionauthService, UserDBService, UserService],
37+
providers: [
38+
FusionauthService,
39+
UserDBService,
40+
otpServiceFactory,
41+
UserService,
42+
],
1543
}).compile();
1644
fusionauthService = module.get<FusionauthService>(FusionauthService);
1745
userDBService = module.get<UserDBService>(UserDBService);

src/user/user.service.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ import Ajv, { ErrorObject } from 'ajv';
1212
import { FAStatus, FusionauthService } from './fusionauth/fusionauth.service';
1313
import { LoginResponse, UUID, User } from '@fusionauth/typescript-client';
1414

15+
import { ChangePasswordDTO } from './dto/changePassword.dto';
1516
import ClientResponse from '@fusionauth/typescript-client/build/src/ClientResponse';
1617
import { Injectable } from '@nestjs/common';
18+
import { OtpService } from './otp/otp.service';
19+
import { SMSResponseStatus } from './sms/sms.interface';
1720
import { UserDBService } from './user-db/user-db.service';
1821
import { v4 as uuidv4 } from 'uuid';
1922

@@ -39,6 +42,7 @@ export class UserService {
3942
constructor(
4043
private readonly userDBService: UserDBService,
4144
private readonly fusionAuthService: FusionauthService,
45+
private readonly otpService: OtpService,
4246
) {
4347
this.ajv = new Ajv({ strict: false });
4448
this.ajv.addSchema(addressSchema, 'address');
@@ -342,6 +346,89 @@ export class UserService {
342346
});
343347
}
344348

349+
async changePassword(data: ChangePasswordDTO): Promise<SignupResponse> {
350+
// Verify OTP
351+
const {
352+
statusFA,
353+
userId,
354+
user,
355+
}: { statusFA: FAStatus; userId: UUID; user: User } =
356+
await this.fusionAuthService.getUser(data.username);
357+
const response: SignupResponse = new SignupResponse().init(uuidv4());
358+
if (statusFA === FAStatus.USER_EXISTS) {
359+
const verifyOTPResult = await this.otpService.verifyOTP({
360+
phone: user.mobilePhone,
361+
otp: data.OTP,
362+
});
363+
364+
if (verifyOTPResult.status === SMSResponseStatus.success) {
365+
const result = await this.fusionAuthService.updatePassword(
366+
userId,
367+
data.password,
368+
);
369+
370+
if (result.statusFA == FAStatus.SUCCESS) {
371+
response.result = {
372+
responseMsg: 'Password updated successfully',
373+
};
374+
response.responseCode = ResponseCode.OK;
375+
response.params.status = ResponseStatus.success;
376+
} else {
377+
response.responseCode = ResponseCode.FAILURE;
378+
response.params.err = 'UNCAUGHT_EXCEPTION';
379+
response.params.errMsg = 'Server Error';
380+
response.params.status = ResponseStatus.failure;
381+
}
382+
} else {
383+
response.responseCode = ResponseCode.FAILURE;
384+
response.params.err = 'INVALID_OTP_USERNAME_PAIR';
385+
response.params.errMsg = 'OTP and Username did not match.';
386+
response.params.status = ResponseStatus.failure;
387+
}
388+
} else {
389+
response.responseCode = ResponseCode.FAILURE;
390+
response.params.err = 'INVALID_USERNAME';
391+
response.params.errMsg = 'No user with this Username exists';
392+
response.params.status = ResponseStatus.failure;
393+
}
394+
return response;
395+
}
396+
397+
async changePasswordOTP(username: string): Promise<SignupResponse> {
398+
// Get Phone No from username
399+
const {
400+
statusFA,
401+
userId,
402+
user,
403+
}: { statusFA: FAStatus; userId: UUID; user: User } =
404+
await this.fusionAuthService.getUser(username);
405+
const response: SignupResponse = new SignupResponse().init(uuidv4());
406+
// If phone number is valid => Send OTP
407+
if (statusFA === FAStatus.USER_EXISTS) {
408+
const re = /^[6-9]{1}[0-9]{9}$/;
409+
if (re.test(user.mobilePhone)) {
410+
const result = await this.otpService.sendOTP(user.mobilePhone);
411+
response.result = {
412+
data: result,
413+
responseMsg: `OTP has been sent to ${user.mobilePhone}.`,
414+
};
415+
response.responseCode = ResponseCode.OK;
416+
response.params.status = ResponseStatus.success;
417+
} else {
418+
response.responseCode = ResponseCode.FAILURE;
419+
response.params.err = 'INVALID_PHONE_NUMBER';
420+
response.params.errMsg = 'Invalid Phone number';
421+
response.params.status = ResponseStatus.failure;
422+
}
423+
} else {
424+
response.responseCode = ResponseCode.FAILURE;
425+
response.params.err = 'INVALID_USERNAME';
426+
response.params.errMsg = 'No user with this Username exists';
427+
response.params.status = ResponseStatus.failure;
428+
}
429+
return response;
430+
}
431+
345432
private isOldSchoolUser(fusionAuthUser: User) {
346433
return (
347434
fusionAuthUser.registrations[0].roles === undefined ||

0 commit comments

Comments
 (0)