Commit 316e9c8d authored by ChakshuGautam's avatar ChakshuGautam
Browse files

feat(user service): add Change Pass routes

Showing with 196 additions and 3 deletions
+196 -3
export class ChangePasswordDTO {
username: string;
password: string;
OTP: string;
}
......@@ -15,6 +15,7 @@ import FusionAuthClient, {
import ClientResponse from '@fusionauth/typescript-client/build/src/ClientResponse';
import { Injectable } from '@nestjs/common';
import { response } from 'express';
export enum FAStatus {
SUCCESS = 'SUCCESS',
......@@ -33,6 +34,61 @@ export class FusionauthService {
);
}
getUser(
username: string,
): Promise<{ statusFA: FAStatus; userId: UUID; user: User }> {
return this.fusionauthClient
.retrieveUserByUsername(username)
.then(
(
response: ClientResponse<UserResponse>,
): { statusFA: FAStatus; userId: UUID; user: User } => {
console.log('Found user');
return {
statusFA: FAStatus.USER_EXISTS,
userId: response.response.user.id,
user: response.response.user,
};
},
)
.catch((e): { statusFA: FAStatus; userId: UUID; user: User } => {
console.log(
`Could not fetch user with username ${username}`,
JSON.stringify(e),
);
return {
statusFA: FAStatus.ERROR,
userId: null,
user: null,
};
});
}
updatePassword(
userId: UUID,
password: string,
): Promise<{ statusFA: FAStatus; userId: UUID }> {
return this.fusionauthClient
.patchUser(userId, {
user: {
password: password,
},
})
.then((response) => {
return {
statusFA: FAStatus.SUCCESS,
userId: response.response.user.id,
};
})
.catch((response) => {
console.log(JSON.stringify(response));
return {
statusFA: FAStatus.ERROR,
userId: null,
};
});
}
delete(userId: UUID): Promise<any> {
return this.fusionauthClient
.deleteUser(userId)
......
import { Body, Controller, Get, Patch, Post, Query } from '@nestjs/common';
import { ChangePasswordDTO } from './dto/changePassword.dto';
import { FusionauthService } from './fusionauth/fusionauth.service';
import { OtpService } from './otp/otp.service';
......@@ -52,4 +53,20 @@ export class UserController {
const status: SignupResponse = await this.userService.update(user);
return status;
}
@Post('/changePassword/sendOTP')
async changePasswordOTP(@Body() data: any): Promise<SignupResponse> {
const status: SignupResponse = await this.userService.changePasswordOTP(
data.username,
);
return status;
}
@Patch('/changePassword/update')
async changePassword(
@Body() data: ChangePasswordDTO,
): Promise<SignupResponse> {
const status: SignupResponse = await this.userService.changePassword(data);
return status;
}
}
......@@ -44,8 +44,8 @@ export interface IGenericResponse {
}
export interface SignupResult {
responseMsg: string;
accountStatus: AccountStatus;
responseMsg?: string;
accountStatus?: AccountStatus;
data?: any;
}
......
import { Test, TestingModule } from '@nestjs/testing';
import { FusionauthService } from './fusionauth/fusionauth.service';
import { GupshupService } from './sms/gupshup/gupshup.service';
import { OtpService } from './otp/otp.service';
import { UserDBService } from './user-db/user-db.service';
import { UserService } from './user.service';
......@@ -8,10 +10,36 @@ describe('UserService', () => {
let service: UserService;
let fusionauthService: FusionauthService;
let userDBService: UserDBService;
let otpService: OtpService;
const gupshupFactory = {
provide: 'OtpService',
useFactory: () => {
return new GupshupService(
process.env.GUPSHUP_USERNAME,
process.env.GUPSHUP_PASSWORD,
process.env.GUPSHUP_BASEURL,
);
},
inject: [],
};
const otpServiceFactory = {
provide: OtpService,
useFactory: () => {
return new OtpService(gupshupFactory.useFactory());
},
inject: [],
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [FusionauthService, UserDBService, UserService],
providers: [
FusionauthService,
UserDBService,
otpServiceFactory,
UserService,
],
}).compile();
fusionauthService = module.get<FusionauthService>(FusionauthService);
userDBService = module.get<UserDBService>(UserDBService);
......
......@@ -12,8 +12,11 @@ import Ajv, { ErrorObject } from 'ajv';
import { FAStatus, FusionauthService } from './fusionauth/fusionauth.service';
import { LoginResponse, UUID, User } from '@fusionauth/typescript-client';
import { ChangePasswordDTO } from './dto/changePassword.dto';
import ClientResponse from '@fusionauth/typescript-client/build/src/ClientResponse';
import { Injectable } from '@nestjs/common';
import { OtpService } from './otp/otp.service';
import { SMSResponseStatus } from './sms/sms.interface';
import { UserDBService } from './user-db/user-db.service';
import { v4 as uuidv4 } from 'uuid';
......@@ -39,6 +42,7 @@ export class UserService {
constructor(
private readonly userDBService: UserDBService,
private readonly fusionAuthService: FusionauthService,
private readonly otpService: OtpService,
) {
this.ajv = new Ajv({ strict: false });
this.ajv.addSchema(addressSchema, 'address');
......@@ -342,6 +346,89 @@ export class UserService {
});
}
async changePassword(data: ChangePasswordDTO): Promise<SignupResponse> {
// Verify OTP
const {
statusFA,
userId,
user,
}: { statusFA: FAStatus; userId: UUID; user: User } =
await this.fusionAuthService.getUser(data.username);
const response: SignupResponse = new SignupResponse().init(uuidv4());
if (statusFA === FAStatus.USER_EXISTS) {
const verifyOTPResult = await this.otpService.verifyOTP({
phone: user.mobilePhone,
otp: data.OTP,
});
if (verifyOTPResult.status === SMSResponseStatus.success) {
const result = await this.fusionAuthService.updatePassword(
userId,
data.password,
);
if (result.statusFA == FAStatus.SUCCESS) {
response.result = {
responseMsg: 'Password updated successfully',
};
response.responseCode = ResponseCode.OK;
response.params.status = ResponseStatus.success;
} else {
response.responseCode = ResponseCode.FAILURE;
response.params.err = 'UNCAUGHT_EXCEPTION';
response.params.errMsg = 'Server Error';
response.params.status = ResponseStatus.failure;
}
} else {
response.responseCode = ResponseCode.FAILURE;
response.params.err = 'INVALID_OTP_USERNAME_PAIR';
response.params.errMsg = 'OTP and Username did not match.';
response.params.status = ResponseStatus.failure;
}
} else {
response.responseCode = ResponseCode.FAILURE;
response.params.err = 'INVALID_USERNAME';
response.params.errMsg = 'No user with this Username exists';
response.params.status = ResponseStatus.failure;
}
return response;
}
async changePasswordOTP(username: string): Promise<SignupResponse> {
// Get Phone No from username
const {
statusFA,
userId,
user,
}: { statusFA: FAStatus; userId: UUID; user: User } =
await this.fusionAuthService.getUser(username);
const response: SignupResponse = new SignupResponse().init(uuidv4());
// If phone number is valid => Send OTP
if (statusFA === FAStatus.USER_EXISTS) {
const re = /^[6-9]{1}[0-9]{9}$/;
if (re.test(user.mobilePhone)) {
const result = await this.otpService.sendOTP(user.mobilePhone);
response.result = {
data: result,
responseMsg: `OTP has been sent to ${user.mobilePhone}.`,
};
response.responseCode = ResponseCode.OK;
response.params.status = ResponseStatus.success;
} else {
response.responseCode = ResponseCode.FAILURE;
response.params.err = 'INVALID_PHONE_NUMBER';
response.params.errMsg = 'Invalid Phone number';
response.params.status = ResponseStatus.failure;
}
} else {
response.responseCode = ResponseCode.FAILURE;
response.params.err = 'INVALID_USERNAME';
response.params.errMsg = 'No user with this Username exists';
response.params.status = ResponseStatus.failure;
}
return response;
}
private isOldSchoolUser(fusionAuthUser: User) {
return (
fusionAuthUser.registrations[0].roles === undefined ||
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment