Unverified Commit ee38540d authored by Abhishek Mishra's avatar Abhishek Mishra Committed by GitHub
Browse files

Merge pull request #49 from choxx/reset-password-generic-apis

Password reset APIs (OTP based)
No related merge requests found
Showing with 176 additions and 272 deletions
+176 -272
......@@ -20,6 +20,7 @@ import { FusionauthService } from './fusionauth/fusionauth.service';
import { OtpService } from './otp/otp.service';
import { SMSResponse } from './sms/sms.interface';
import { RefreshRequest } from '@fusionauth/typescript-client/build/src/FusionAuthClient';
import { ChangePasswordDTO } from '../user/dto/changePassword.dto';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const CryptoJS = require('crypto-js');
......@@ -266,4 +267,30 @@ export class ApiController {
authHeader,
);
}
@Post('/changePassword/sendOTP')
async changePasswordOTP(
@Headers('authorization') authHeader,
@Headers('x-application-id') applicationId,
@Body() data: any,
): Promise<SignupResponse> {
return await this.apiService.changePasswordOTP(
data.username,
applicationId,
authHeader,
);
}
@Patch('/changePassword/update')
async changePassword(
@Headers('authorization') authHeader,
@Headers('x-application-id') applicationId,
@Body() data: ChangePasswordDTO,
): Promise<SignupResponse> {
return await this.apiService.changePassword(
data,
applicationId,
authHeader,
);
}
}
......@@ -20,6 +20,9 @@ import { OtpService } from './otp/otp.service';
import { v4 as uuidv4 } from 'uuid';
import { ConfigResolverService } from './config.resolver.service';
import { RefreshRequest } from '@fusionauth/typescript-client/build/src/FusionAuthClient';
import { FAStatus } from '../user/fusionauth/fusionauth.service';
import { ChangePasswordDTO } from '../user/dto/changePassword.dto';
import { SMSResponseStatus } from '../user/sms/sms.interface';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const CryptoJS = require('crypto-js');
// eslint-disable-next-line @typescript-eslint/no-var-requires
......@@ -214,7 +217,7 @@ export class ApiService {
applicationId: string,
authHeader?: string,
): Promise<any> {
return this.fusionAuthService.upddatePasswordWithLoginId(
return this.fusionAuthService.updatePasswordWithLoginId(
data,
applicationId,
authHeader,
......@@ -408,4 +411,101 @@ export class ApiService {
response.result = userResponse.user;
return response;
}
async changePasswordOTP(
username: string,
applicationId: UUID,
authHeader: null | string,
): Promise<SignupResponse> {
// Get Phone No from username
const {
statusFA,
userId,
user,
}: { statusFA: FAStatus; userId: UUID; user: User } =
await this.fusionAuthService.getUser(username, applicationId, authHeader);
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;
}
async changePassword(
data: ChangePasswordDTO,
applicationId: UUID,
authHeader: null | string,
): Promise<SignupResponse> {
// Verify OTP
const {
statusFA,
userId,
user,
}: { statusFA: FAStatus; userId: UUID; user: User } =
await this.fusionAuthService.getUser(
data.username,
applicationId,
authHeader,
);
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,
applicationId,
authHeader,
);
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;
}
}
......@@ -3,12 +3,10 @@ import FusionAuthClient, {
LoginResponse,
RegistrationRequest,
RegistrationResponse,
SearchRequest,
SearchResponse,
Sort,
UUID,
User,
UserRegistration,
UserRequest,
UserResponse,
Error,
......@@ -50,10 +48,29 @@ export class FusionauthService {
return new FusionAuthClient(apiKey, host);
}
/**
* Returns the FA client for the given applicationId &/or authHeader
* @param applicationId
* @param authHeader
*/
getClientForApplicationId(
applicationId: UUID,
authHeader: null | string,
): FusionAuthClient {
let apiKey = this.configResolverService.getApiKey(applicationId);
if (authHeader != null) {
apiKey = authHeader;
}
const host = this.configResolverService.getHost(applicationId);
return this.getClient(apiKey, host);
}
getUser(
username: string,
applicationId: UUID,
authHeader: null | string,
): Promise<{ statusFA: FAStatus; userId: UUID; user: User }> {
return this.fusionauthClient
return this.getClientForApplicationId(applicationId, authHeader)
.retrieveUserByUsername(username)
.then(
(
......@@ -183,145 +200,6 @@ export class FusionauthService {
});
}
updatePasswordWithUserId(
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)
.then((response) => {
console.log(response);
})
.catch((e) => {
console.log(e);
});
}
persist(authObj: any): Promise<{ statusFA: FAStatus; userId: UUID }> {
console.log(authObj);
let resp;
const registrations: Array<UserRegistration> = [];
const currentRegistration: UserRegistration = {
username: authObj.username,
applicationId: process.env.FUSIONAUTH_APPLICATION_ID,
roles: authObj.role,
};
const currentRegistration_samarth_hp: UserRegistration = {
username: authObj.username,
applicationId: process.env.FUSIONAUTH_SAMARTH_HP_APPLICATION_ID,
roles: authObj.role,
};
registrations.push(currentRegistration);
const userRequest: RegistrationRequest = {
user: {
active: true,
data: {
school: authObj.school,
education: authObj.education,
address: authObj.address,
gender: authObj.gender,
dateOfRetirement: authObj.dateOfRetirement,
phoneVerified: false,
udise: authObj.udise,
},
email: authObj.email,
firstName: authObj.firstName,
lastName: authObj.lastName,
username: authObj.username,
password: authObj.password,
imageUrl: authObj.avatar,
mobilePhone: authObj.phone,
},
registration: currentRegistration,
};
const userRequest_samarth_hp: RegistrationRequest = {
registration: currentRegistration_samarth_hp,
};
// eslint-disable-next-line prefer-const
resp = this.fusionauthClient
.register(undefined, userRequest)
.then(
(
response: ClientResponse<RegistrationResponse>,
): { statusFA: FAStatus; userId: UUID } => {
this.fusionauthClient
.register(response.response.user.id, userRequest_samarth_hp)
.then((res: ClientResponse<RegistrationResponse>): any => {
console.log({ res });
})
.catch((e): Promise<{ statusFA: FAStatus; userId: UUID }> => {
console.log('Could not create a user in', JSON.stringify(e));
console.log('Trying to fetch an existing user in');
return this.fusionauthClient
.retrieveUserByUsername(authObj.username)
.then((response: ClientResponse<UserResponse>): any => {
console.log('Found user in');
})
.catch((e): any => {
console.log(
`Could not fetch user with username in ${authObj.username}`,
JSON.stringify(e),
);
});
});
return {
statusFA: FAStatus.SUCCESS,
userId: response.response.user.id,
};
},
)
.catch((e): Promise<{ statusFA: FAStatus; userId: UUID }> => {
console.log('Could not create a user', JSON.stringify(e));
console.log('Trying to fetch an existing user');
return this.fusionauthClient
.retrieveUserByUsername(authObj.username)
.then(
(
response: ClientResponse<UserResponse>,
): { statusFA: FAStatus; userId: UUID } => {
console.log('Found user');
return {
statusFA: FAStatus.USER_EXISTS,
userId: response.response.user.id,
};
},
)
.catch((e): { statusFA: FAStatus; userId: UUID } => {
console.log(
`Could not fetch user with username ${authObj.username}`,
JSON.stringify(e),
);
return {
statusFA: FAStatus.ERROR,
userId: null,
};
});
});
return resp;
}
login(
user: LoginRequest,
authHeader: string,
......@@ -345,134 +223,6 @@ export class FusionauthService {
});
}
update(
userID: UUID,
authObj: any,
isSimpleUpdate = false,
): Promise<{ statusFA: FAStatus; userId: UUID; fusionAuthUser: User }> {
let userRequest: UserRequest;
if (!isSimpleUpdate) {
const registrations: Array<UserRegistration> = [];
const currentRegistration: UserRegistration = {
username: authObj.username,
applicationId: process.env.FUSIONAUTH_APPLICATION_ID,
roles: authObj.role,
};
registrations.push(currentRegistration);
userRequest = {
user: {
active: true,
data: {
school: authObj.school,
education: authObj.education,
address: authObj.address,
gender: authObj.gender,
dateOfRetirement: authObj.dateOfRetirement,
phoneVerified: false,
udise: authObj.udise,
phone: authObj.phone,
accountName: authObj.firstName,
},
email: authObj.email,
firstName: authObj.firstName,
lastName: authObj.lastName,
fullName: authObj.fullName,
username: authObj.username,
password: authObj.password,
imageUrl: authObj.avatar,
mobilePhone: authObj.phone,
},
};
} else {
userRequest = {
user: authObj,
};
}
return this.fusionauthClient
.patchUser(userID, userRequest)
.then(
(
response: ClientResponse<UserResponse>,
): { statusFA: FAStatus; userId: UUID; fusionAuthUser: User } => {
console.log({ response });
return {
statusFA: FAStatus.SUCCESS,
userId: response.response.user.id,
fusionAuthUser: response.response.user,
};
},
)
.catch(
(e): { statusFA: FAStatus; userId: UUID; fusionAuthUser: User } => {
console.log('Unable to update user', JSON.stringify(e));
return {
statusFA: FAStatus.ERROR,
userId: null,
fusionAuthUser: null,
};
},
);
}
verifyUsernamePhoneCombination(): Promise<boolean> {
return Promise.resolve(true);
}
//One time Task
async updateAllEmptyRolesToSchool(): Promise<any> {
let allDone = false;
const searchRequest: SearchRequest = {
search: {
numberOfResults: 15,
startRow: 0,
sortFields: [
{
missing: '_first',
name: 'id',
order: Sort.asc,
},
],
query:
'{"bool":{"must":[{"nested":{"path":"registrations","query":{"bool":{"must":[{"match":{"registrations.applicationId":"f0ddb3f6-091b-45e4-8c0f-889f89d4f5da"}}],"must_not":[{"match":{"registrations.roles":"school"}}]}}}}]}}',
},
};
let iteration = 0;
let invalidUsersCount = 0;
while (!allDone) {
iteration += 1;
searchRequest.search.startRow = invalidUsersCount;
const resp: ClientResponse<SearchResponse> =
await this.fusionauthClient.searchUsersByQuery(searchRequest);
const total = resp.response.total;
console.log(iteration, total);
if (total === 0) allDone = true;
else {
const users: Array<User> = resp.response.users;
for (const user of users) {
if (user.registrations[0].roles === undefined) {
user.registrations[0].roles = ['school'];
console.log('Here', user);
await this.fusionauthClient
.updateRegistration(user.id, {
registration: user.registrations[0],
})
.then((resp) => {
console.log('response', JSON.stringify(resp));
})
.catch((e) => {
console.log('error', JSON.stringify(e));
});
} else {
console.log('Invalid User', user.id);
invalidUsersCount += 1;
}
}
}
}
}
async createAndRegisterUser(
user: RegistrationRequest,
applicationId: string,
......@@ -544,7 +294,7 @@ export class FusionauthService {
});
}
async upddatePasswordWithLoginId(
async updatePasswordWithLoginId(
data: { loginId: string; password: string },
applicationId: string,
authHeader?: string,
......@@ -735,4 +485,31 @@ export class FusionauthService {
};
});
}
updatePassword(
userId: UUID,
password: string,
applicationId,
authHeader?: null | string,
): Promise<{ statusFA: FAStatus; userId: UUID }> {
return this.getClientForApplicationId(applicationId, authHeader)
.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,
};
});
}
}
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