Skip to content

Commit

Permalink
Js ofmcc 2 provider login3 (#21)
Browse files Browse the repository at this point in the history
* Pushh to brach for purpose of debugging code with wkubo. Not to be merged with main.

* Branch update only, not for main.

* My branch update for wkubo review of issues. Not for merge.

* Tweaked menu and env header.

* Latest updates for wkubo to review re: issues with TheHeader.vue state.

* Header and router cleanup.

* Removed comment.

* Menu updates.

* Sprint 1 updates: fixed getUserInfo to only call once, added conditions for unauth routing.

* Header layout updates and cleanup.

* Button fixes.

* Updates to address code review. 1 outstanding issue remaining: hasUserRoles getter.

* Fixed getter.

* Confirmed getter for userHasRoles works, removed related/redundant setter code.

* Updated unit tests.

* Fixed image loading issues in prod build.

* Fixed src.

---------

Co-authored-by: weskubo-cgi <[email protected]>
  • Loading branch information
jgstorey and weskubo-cgi authored Oct 19, 2023
1 parent 3a332cc commit e91c2e1
Show file tree
Hide file tree
Showing 26 changed files with 749 additions and 1,311 deletions.
4 changes: 2 additions & 2 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

248 changes: 75 additions & 173 deletions backend/src/components/user.js
Original file line number Diff line number Diff line change
@@ -1,236 +1,138 @@
'use strict';
const {getSessionUser, getHttpHeader, minify, getUserGuid, getUserName, getLabelFromValue, postOperation, isIdirUser, getOperation} = require('./utils');
const config = require('../config/index');
const ApiError = require('./error');
const axios = require('axios');
const HttpStatus = require('http-status-codes');
const log = require('../components/logger');
const { APPLICATION_STATUS_CODES, CCFRI_STATUS_CODES, ECEWE_STATUS_CODES, CCOF_STATUS_CODES, CCOF_APPLICATION_TYPES, ORGANIZATION_PROVIDER_TYPES, CHANGE_REQUEST_TYPES} = require('../util/constants');
const { UserProfileFacilityMappings, UserProfileOrganizationMappings, UserProfileBaseFundingMappings, UserProfileApplicationMappings, UserProfileCCFRIMappings, UserProfileECEWEMappings /* lint error: , UserProfileChangeRequestNewFacilityMappings*/} = require('../util/mapping/Mappings');

const { MappableObjectForFront } = require('../util/mapping/MappableObject');
const _ = require ('lodash');

'use strict'
const { getSessionUser, getUserName, getHttpHeader, minify, getUserGuid, isIdirUser } = require('./utils')
const config = require('../config/index')
const ApiError = require('./error')
const axios = require('axios')
const HttpStatus = require('http-status-codes')
const log = require('../components/logger')
// TODO... const { ORGANIZATION_PROVIDER_TYPES} = require('../util/constants')
const { UserProfileMappings, UserProfileOrganizationMappings, UserProfileFacilityPermissionMappings, UserProfileFacilityMappings } = require('../util/mapping/Mappings')

const { MappableObjectForFront } = require('../util/mapping/MappableObject')
const _ = require('lodash')

async function getUserInfo(req, res) {

const userInfo = getSessionUser(req);
const userInfo = getSessionUser(req)
if (!userInfo || !userInfo.jwt || !userInfo._json) {
return res.status(HttpStatus.UNAUTHORIZED).json({
message: 'No session data'
});
message: 'No session data',
})
}
const isIdir = isIdirUser(req);
const queryUserName = req.params?.queryUserName;
const userName = getUserName(req);
const isIdir = isIdirUser(req)
const queryUserName = req.params?.queryUserName
const userName = getUserName(req)

// if is idir user (ministry user), make sure they are a user in dynamics
// TODO commented out until we focus on IDIR login and weather this code is relevant
if (isIdir) {
let response = await getDynamicsUserByEmail(req);
let response = await getDynamicsUserByEmail(req)
if (response.value?.length > 0 && response.value[0].systemuserid) {
log.verbose(`Ministry user: [${req.session.passport.user._json.display_name}] logged in.`);
log.verbose(`Ministry user: [${req.session.passport.user._json.display_name}] logged in.`)
} else {
log.info(`Ministry user: [${req.session.passport.user._json.display_name}] attempted to log in but is not part of Dynamics.`);
log.info(`Ministry user: [${req.session.passport.user._json.display_name}] attempted to log in but is not part of Dynamics.`)
return res.status(HttpStatus.UNAUTHORIZED).json({
message: 'Not Authorized'
});
message: 'Not Authorized',
})
}
}
let resData = {
displayName: (queryUserName)? userName + '-' + queryUserName : userName,
// TODO i thing this has to do with impersonate... displayName: (queryUserName)? userName + '-' + queryUserName : userName,
userName: userName,
email: req.session.passport.user._json.email,
isMinistryUser: isIdir,
serverTime: new Date(),
//TODO: unreadMessages is hardcoded. Remove this with API values when built out!
unreadMessages: false,
};
let userResponse = undefined;
}
let userResponse = undefined
if (isIdir) {
if (queryUserName) {
try {
log.info(`Ministry user [${userName}] is impersonating with username: [${queryUserName}].`);
log.info(`Ministry user [${userName}] is impersonating with username: [${queryUserName}].`)
// dynamics api requires a userID. if userID not found then it wil use the query name
// put a random userID so that we only search by queryname
userResponse = await getUserProfile(null, queryUserName);
userResponse = await getUserProfile(null, queryUserName)
if (userResponse === null) {
return res.status(HttpStatus.NOT_FOUND).json({message: 'No user found with that BCeID UserName'});
return res.status(HttpStatus.NOT_FOUND).json({ message: 'No user found with that BCeID UserName' })
}
} catch (e) {
log.error('getUserProfile Error', e.response ? e.response.status : e.message);
throw new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, {message: 'API Get error'}, e);
log.error('getUserProfile Error', e.response ? e.response.status : e.message)
throw new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, { message: 'API Get error' }, e)
}
} else {
//If not looking for a username, return from here since ministry staff should not have an account
return res.status(HttpStatus.OK).json(resData);
return res.status(HttpStatus.OK).json(resData)
}
} else {
//Not an idir user, so just get the guid from the header
const userGuid = getUserGuid(req);
log.verbose('User Guid is: ', userGuid);
userResponse = await getUserProfile(userGuid, userName );
const userGuid = getUserGuid(req)
log.verbose('User Guid is: ', userGuid)
userResponse = await getUserProfile(userGuid)
}

if (log.isVerboseEnabled) {
log.verbose('getUserProfile response:',minify(userResponse));
log.verbose('getUserProfile response:', minify(userResponse))
}

if (userResponse === null) {
creatUser(req);
return res.status(HttpStatus.OK).json(resData);
}
if (userResponse === {}){
// If no data back, then no associated Organization/Facilities, return empty orgination data
return res.status(HttpStatus.OK).json(resData);
// If no data back, then no associated User Roles/Organization/Facilities/
return res.status(HttpStatus.UNAUTHORIZED).json(resData)
}

let organization = new MappableObjectForFront(userResponse, UserProfileOrganizationMappings).data;
let user = new MappableObjectForFront(userResponse, UserProfileMappings).data
let organization = new MappableObjectForFront(userResponse.organization, UserProfileOrganizationMappings).data
resData.facilityPermission = parseFacilityPermissions(userResponse)

let application = new MappableObjectForFront(userResponse.application, UserProfileApplicationMappings).data;
application.organizationProviderType = getLabelFromValue(application.organizationProviderType, ORGANIZATION_PROVIDER_TYPES);
application.applicationStatus = getLabelFromValue(application.applicationStatus, APPLICATION_STATUS_CODES, 'NEW');
application.applicationType = getLabelFromValue(application.applicationType, CCOF_APPLICATION_TYPES);
application.ccofProgramYearId = userResponse.application?.ccof_ProgramYear?.ccof_program_yearid;
application.ccofProgramYearName = userResponse.application?.ccof_ProgramYear?.ccof_name;
application.ccofApplicationStatus = getLabelFromValue(application.ccofStatus, CCOF_STATUS_CODES, 'NEW');


resData.facilityList = parseFacilityData(userResponse);
let results = {
...resData,
...user,
...organization,
...application,
};
return res.status(HttpStatus.OK).json(results);
}
log.verbose('getUserInfo response:', results)
return res.status(HttpStatus.OK).json(results)
}

async function getUserProfile(userGuid, userName) {
async function getUserProfile(userGuid) {
try {
let url = undefined;
let url = undefined
if (userGuid) {
url = config.get('dynamicsApi:apiEndpoint') + `/api/ProviderProfile?userId=${userGuid}&userName=${userName}`;
} else {
url = config.get('dynamicsApi:apiEndpoint') + `/api/ProviderProfile?userName=${userName}`;
url = config.get('dynamicsApi:apiEndpoint') + `/api/ProviderProfile?userId=${userGuid}`
}

log.verbose('UserProfile Url is', url);
const response = await axios.get(url, getHttpHeader());
return response.data;
log.verbose('UserProfile Url is', url)
let response = undefined
response = await axios.get(url, getHttpHeader())
log.verbose('getUserProfile response:', response.data)
return response.data
} catch (e) {
if (e.response?.status == '404') {
log.verbose('response ', e.response.data);
log.verbose('response ', e.response.data)
if (e.response?.data?.startsWith('User not found')) {
return null;
return null
}
return {};
return {}
}
log.error('getUserProfile Error', e.response ? e.response.status : e.message);
throw e;
log.error('getUserProfile Error', e.response ? e.response.status : e.message)
throw e
}
}

function updateFacilityWithChangeRequestDetails(changeRequestList, returnValue, facilityId) {
for (const changeRequest of changeRequestList) {
//todo -mk check statuscode
let changeActionNewFacilityList = changeRequest?.ccof_change_action_change_request?.filter(item =>item.ccof_changetype === CHANGE_REQUEST_TYPES.NEW_FACILITY);
for (const changeActionNewFacility of changeActionNewFacilityList) {
let result = changeActionNewFacility?.ccof_change_request_new_facility_change_act.find(item => item['_ccof_facility_value'] === facilityId);
if (result) {
returnValue.changeRequestId = changeRequest?.ccof_change_requestid;
returnValue.unlockCcfri = result?.ccof_unlock_ccfri;
returnValue.unlockNmf = result?.ccof_unlock_nmf_rfi;
returnValue.unlockRfi = result?.ccof_unlock_rfi;

function parseFacilityPermissions(userResponse) {
const facilityList = Object.entries(userResponse.facility_permission)
.map(([key, value]) => {
// Only add facilities that have portal access
if (value.ofm_portal_access === true) {
const facilityPermission = new MappableObjectForFront(value, UserProfileFacilityPermissionMappings).data
const facility = new MappableObjectForFront(value.facility, UserProfileFacilityMappings).data
const combinedData = { ...facilityPermission, ...facility }
if (!_.isEmpty(combinedData)) {
return combinedData
}
}
}
}
}

function parseFacilityData(userResponse) {
let facilityMap = new Map(userResponse.facilities?.map((m) => [m['accountid'], new MappableObjectForFront(m, UserProfileFacilityMappings).data]));

if (userResponse.application) {
facilityMap.forEach((value, key, map) => {
let ccfriInfo = userResponse.application.ccof_applicationccfri_Application_ccof_ap?.find(item => item['_ccof_facility_value'] === key);
ccfriInfo = new MappableObjectForFront(ccfriInfo, UserProfileCCFRIMappings).data;
let eceweInfo = userResponse.application.ccof_ccof_application_ccof_applicationecewe_application?.find(item => item['_ccof_facility_value'] === key);
eceweInfo = new MappableObjectForFront(eceweInfo, UserProfileECEWEMappings).data;
let baseFunding = userResponse.application.ccof_application_basefunding_Application?.find(item => item['_ccof_facility_value'] === key);
baseFunding = new MappableObjectForFront(baseFunding, UserProfileBaseFundingMappings).data;
let changeRequestList = userResponse.application.ccof_ccof_change_request_Application_ccof_appl;
let returnValue = {
...value,
...ccfriInfo,
...eceweInfo,
...baseFunding,
};
updateFacilityWithChangeRequestDetails(changeRequestList, returnValue, key);
map.set(key, returnValue);
});
}
let facilityList = [];
facilityMap.forEach((facility) => {
if (!_.isEmpty(facility)) {
facility.ccofBaseFundingStatus = getLabelFromValue(facility.ccofBaseFundingStatus, CCOF_STATUS_CODES);
facility.ccfriStatus = getLabelFromValue(facility.ccfriStatus, CCFRI_STATUS_CODES, 'NOT STARTED');
facility.eceweStatus = getLabelFromValue(facility.eceweStatus, ECEWE_STATUS_CODES, 'NOT STARTED');
facilityList.push(facility);
}
});
return facilityList;
}

async function getDynamicsUserByEmail(req) {
let email = req.session.passport.user._json.email;
if (!email) {
//If for some reason, an email is not associated with the IDIR, just use [email protected]
email = `${req.session.passport.user._json.idir_username}@gov.bc.ca`;
}
// eslint-disable-next-line quotes,
email.includes("'") ? email = email.replace("'", "''") : email;
try {
let response = await getOperation(`systemusers?$select=firstname,domainname,lastname&$filter=internalemailaddress eq '${email}'`);
return response;
} catch (e) {
log.error('getDynamicsUserByEmail Error', e.response ? e.response.status : e.message);
throw new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, {message: 'API Get error'}, e);
}
}

async function creatUser(req) {
log.info('No user found, creating BCeID User: ', getUserName(req));
let given_name = req.session.passport.user._json.given_name;
let family_name = req.session.passport.user._json.family_name;
let firstname = undefined;
let lastname = undefined;
try {
if (!family_name && given_name && given_name.split(' ').length > 1) {
//If for some reason we don't have a last name from SSO, see if firstname has 2 words
firstname = given_name.split(' ').slice(0, -1).join(' ');
lastname = given_name.split(' ').slice(-1).join(' ');
} else if (!given_name && family_name && family_name.split(' ').length > 1) {
//If for some reason we don't have a firstname name from SSO, see if lastname has 2 words
firstname = family_name.split(' ').slice(0, -1).join(' ');
lastname = family_name.split(' ').slice(-1).join(' ');
} else {
firstname = given_name;
lastname = family_name;
}

let payload = {
ccof_userid: getUserGuid(req),
firstname: firstname,
lastname: lastname,
emailaddress1: req.session.passport.user._json.email,
ccof_username: getUserName(req)
};
postOperation('contacts', payload);
} catch (e) {
log.error('Error when creating user: ', e);
throw new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, {message: 'Error while creating a new BCeID User'}, e);
}
return null
})
.filter((facility) => facility !== null)
return facilityList
}

module.exports = {
getUserInfo,
};
}
65 changes: 35 additions & 30 deletions backend/src/util/mapping/Mappings.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,39 @@
const OrganizationMappings = [
{ back: 'ccof_facilitystartdate', front: 'yearBeganOperation' },
{ back: 'name', front: 'legalName' },
{ back: 'address1_name', front: 'address1' }, //Address
{ back: 'address1_city', front: 'city1' },
{ back: 'address1_postalcode', front: 'postalCode1' },
{ back: 'address2_name', front: 'address2' }, //Mailing Address
{ back: 'address2_city', front: 'city2' },
{ back: 'address2_postalcode', front: 'postalCode2' },
{ back: 'address1_primarycontactname', front: 'contactName' },
{ back: 'ccof_position', front: 'position' },
{ back: 'telephone1', front: 'phone' },
const UserProfileMappings = [
{ back: 'contactid', front: 'contactId' },
{ back: 'ccof_userid', front: 'userId' },
{ back: 'ccof_username', front: 'username' },
{ back: 'emailaddress1', front: 'email' },
{ back: 'ccof_instructionnumber', front: 'incNumber' },//incorporation number
{ back: 'ccof_typeoforganization', front: 'organizationType' },
{ back: 'ccof_formcomplete', front: 'isOrganizationComplete' },
{ back: 'ccof_is_mailing_address_same', front: 'isSameAsMailing'}
];
{ back: 'ofm_first_name', front: 'firstName' },
{ back: 'ofm_last_name', front: 'lastName' },
{ back: 'ofm_portal_role', front: 'roles' },
]

const ProgramYearMappings = [
{ back: 'ccof_program_yearid', front: 'programYearId' },
{ back: 'ccof_name', front: 'name' },
{ back: 'statuscode', front: 'status' },
{ back: 'ccof_programyearnumber', front: 'order' },
{ back: '_ccof_previousyear_value', front: 'previousYearId' },
{ back: 'ccof_intakeperiodstart', front: 'intakeStart' },
{ back: 'ccof_intakeperiodend', front: 'intakeEnd' },
{ back: 'ccof_declarationbstart', front: 'declarationbStart' },
];
const UserProfileOrganizationMappings = [
{ back: 'accountid', front: 'organizationId' },
{ back: 'accountnumber', front: 'organizationAccountNumber' },
{ back: 'ccof_accounttype', front: 'organizationAccountType' },
{ back: 'name', front: 'organizationName' },
{ back: 'statecode', front: 'organizationStateCode' },
{ back: 'statuscode', front: 'organizationStatus' },
]

const UserProfileFacilityPermissionMappings = [
{ back: 'statecode', front: 'stateCode' },
{ back: 'statuscode', front: 'statusCode' },
]

const UserProfileFacilityMappings = [
{ back: 'accountid', front: 'facilityId' },
{ back: 'accountnumber', front: 'facilityAccountNumber' },
{ back: 'name', front: 'facilityName' },
{ back: 'ccof_accounttype', front: 'facilityType' },
{ back: 'statecode', front: 'facilityStateCode' },
{ back: 'statuscode', front: 'facilityStatusCode' },
]

module.exports = {
OrganizationMappings,
ProgramYearMappings
};
UserProfileMappings,
UserProfileOrganizationMappings,
UserProfileFacilityPermissionMappings,
UserProfileFacilityMappings,
}
Loading

0 comments on commit e91c2e1

Please sign in to comment.