cctsa-wxccdemobot-commons
Version:
Common routines for building demo/reference bots with Webex Contact Center and Google Dialogflow ES using the codingforconvos NPM library.
517 lines (463 loc) • 20 kB
JavaScript
/**
* Copyright 2022 Justin Randall, Cisco Systems Inc. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU
* General Public License as published by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not,
* see <https://www.gnu.org/licenses/>.
*/
const { Intent, Sequence } = require("codingforconvos");
const { RedmineConnector } = require("../connectors/redmine");
const { WebexConnectConnector } = require("../connectors/webexconnect");
const { injectJdsEvent,createRedmineIssue } = require("../common");
const { DialogContext } = require("codingforconvos/src/contexts");
// Define Sequence Name Constants.
const SEQ_PWRESET_NAME = 'passwordreset';
const CHARS_NUMBERS = "0123456789";
const CHARS_UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const CHARS_LOWER = "abcdefghijklmnopqrstuvwxyz";
const CHARS_SPECIAL = ".@()-_";
/**
* Utility function to generate a basic password with upper/lower case letters,
* numbers, and special characters to satify Redmine's password policy. This
* function is not suitable for production/secure passwords.
*
* @param {number} passwordLength The length of the password.
* @returns the generated password.
*/
function generatePassword(passwordLength=8) {
let allChars = CHARS_NUMBERS + CHARS_UPPER + CHARS_LOWER + CHARS_SPECIAL;
let randPasswordArray = Array(passwordLength);
randPasswordArray[0] = CHARS_NUMBERS;
randPasswordArray[1] = CHARS_UPPER;
randPasswordArray[2] = CHARS_LOWER;
randPasswordArray[3] = CHARS_SPECIAL;
randPasswordArray = randPasswordArray.fill(allChars, 4);
return shuffleArray(randPasswordArray.map(function(x) { return x[Math.floor(Math.random() * x.length)]; })).join('');
}
// Shuffle an array.
// array - The array.
function shuffleArray(array) {
for (let x = array.length - 1; x > 0; x--) {
let y = Math.floor(Math.random() * (x + 1));
let temp = array[x];
array[x] = array[y];
array[y] = temp;
}
return array;
}
///////////////////////////////////////////////////////////
// Register Password Reset Sequence and Intent Handlers. //
///////////////////////////////////////////////////////////
/**
* Reset and send a new password based on secondary channel configuration.
*
* @param {DialogContext} dialogContext The dialog context.
*/
async function resetAndSendPassword(dialogContext) {
if (dialogContext.params.secondChannel === 'sms') {
resetAndSendPasswordBySms(dialogContext);
} else {
resetAndSendPasswordByEmail(dialogContext);
}
}
/**
* Reset and send a new password via SMS.
*
* @param {DialogContext} dialogContext The dialog context.
*/
async function resetAndSendPasswordBySms(dialogContext) {
const tempPw = generatePassword();
dialogContext.connectorManager.get(RedmineConnector.name()).resetRedmineUserPassword(dialogContext.params.redmineUserId,tempPw);
dialogContext.connectorManager.get(WebexConnectConnector.name()).sendPwresetLinkBySms(
dialogContext.params.sessionId,
dialogContext.params.smsNumber,
dialogContext.params.customerFirstName,
dialogContext.params.companyName,
dialogContext.params.interactionSource,
tempPw
);
}
/**
* Reset and send a new password via Email.
*
* @param {DialogContext} dialogContext The dialog context.
*/
async function resetAndSendPasswordByEmail(dialogContext) {
const tempPw = generatePassword();
dialogContext.connectorManager.get(RedmineConnector.name()).resetRedmineUserPassword(dialogContext.params.redmineUserId,tempPw);
dialogContext.connectorManager.get(WebexConnectConnector.name()).sendPwresetLinkByEmail(
dialogContext.params.sessionId,
dialogContext.params.mail,
dialogContext.params.customerFirstName,
dialogContext.params.companyName,
dialogContext.params.interactionSource,
tempPw
);
}
/**
* Inject a JDS tape event representing a password reset success.
*
* @param {DialogContext} dialogContext The dialog context.
*/
async function injectPasswdResetSuccessEvent(dialogContext) {
const redmineNewIssue = await createRedmineIssue(dialogContext);
injectJdsEvent(dialogContext, 'Password Change Success', {
caseUrl: 'http://cctsa-redmine.outofservice.org/issues/'+redmineNewIssue.id,
caseReason: 'Record of success'
});
}
/**
* Inject a JDS tape event representing a password reset failure.
*
* @param {DialogContext} dialogContext The dialog context.
*/
async function injectPasswdResetFailureEvent(dialogContext) {
const redmineNewIssue = await createRedmineIssue(dialogContext);
injectJdsEvent(dialogContext, 'Password Change Failure', {
caseUrl: 'http://cctsa-redmine.outofservice.org/issues/'+redmineNewIssue.id,
caseReason: 'Escalate failed password change'
});
}
/**
* Registers the sequences and intents for the reset password module.
*
* @param {ConvoClient} convoClient The convo client.
*/
function registerModuleResetPassword(convoClient) {
// Register Sequence.
convoClient.registerSequence(new Sequence({
name: SEQ_PWRESET_NAME, // Sequence name, also used for Dialogflow context name.
activity: 'resetting your password', // Activity description, used in course correction.
identityRequired: false,
authRequired: true,
params: {
executeStatus: '-1',
passwordLinkSent: '0',
passwordLinkReceived: '0',
passwordLinkNotReceived: '0',
confirmedWorking: '0',
confirmedNotWorking: '0',
notifiedSuccess: '0',
notifiedFailure: '0'
},
navigate: (dialogContext) => { // Navigate the sequence forward.
let context = dialogContext.getOrCreateCtx(SEQ_PWRESET_NAME);
if (context.parameters.notifiedSuccess === '1') {
console.log('Calling popSequenceAndNavigate');
dialogContext.popSequenceAndNavigate(SEQ_PWRESET_NAME);
return;
}
if (context.parameters.notifiedFailure === '1') {
if (dialogContext.params.offeredAgent === '0') {
let event = dialogContext.respondWithEvent('TicketTransfer', dialogContext.params.lastFulfillmentText);
return;
}
if (dialogContext.params.offeredAgentAccepted === '1') {
let event = dialogContext.respondWithEvent('EscalateToAgent', dialogContext.params.lastFulfillmentText);
return;
}
if (dialogContext.params.offeredAgentDeclined === '1') {
dialogContext.resetOfferedAgentFlags();
console.log('Calling popSequenceAndNavigate');
dialogContext.popSequenceAndNavigate(SEQ_PWRESET_NAME);
return;
}
}
if (context.parameters.passwordLinkSent === '0') {
if (dialogContext.params.secondChannel === 'sms') {
console.log('Responding with event PasswordResetSms');
let event = dialogContext.respondWithEvent('PasswordResetSms', dialogContext.params.lastFulfillmentText);
return;
} else {
console.log('Responding with event PasswordResetEmail');
let event = dialogContext.respondWithEvent('PasswordResetEmail', dialogContext.params.lastFulfillmentText);
return;
}
}
if (context.parameters.passwordLinkReceived === '1' && context.parameters.confirmedWorking === '0' && context.parameters.confirmedNotWorking === '0') {
let event = dialogContext.respondWithEvent('ResetPasswordLoginSuccess', dialogContext.params.lastFulfillmentText);
return;
}
if (context.parameters.confirmedWorking === '1' && context.parameters.notifiedSuccess === '0') {
let event = dialogContext.respondWithEvent('ResetPasswordSuccess', dialogContext.params.lastFulfillmentText);
return;
}
if ((context.parameters.passwordLinkNotReceived === '1' || context.parameters.confirmedNotWorking === '1') && context.parameters.notifiedFailure === '0') {
let event = dialogContext.respondWithEvent('ResetPasswordFailure', dialogContext.params.lastFulfillmentText);
return;
}
// Uncomment once ready to implement here.
//completeAndNavigate(agent, ctxSessionProps, CTX_AUTH);
dialogContext.setFulfillmentText();
console.log('action: '+dialogContext.currentAction+', lastFulfillmentText: '+dialogContext.params.lastFulfillmentText);
dialogContext.respondWithText();
return;
}
}));
// Register a case creation template for the sequence.
RedmineConnector.registerCaseTemplate(SEQ_PWRESET_NAME, (dialogContext) => {
let context = dialogContext.getOrCreateCtx(SEQ_PWRESET_NAME);
let subject = 'Password Reset ';
if (context.parameters.executeStatus === '-1') {
subject = subject + 'Attempt Incomplete';
}
if (context.parameters.executeStatus === '0') {
subject = subject + 'Success';
}
if (context.parameters.executeStatus === '1') {
subject = subject + 'Failure';
}
subject = subject + ' for '+dialogContext.params.customerFirstName+' '+dialogContext.params.customerLastName;
let description = dialogContext.params.customerFirstName+' attempted a password reset.';
if (context.parameters.passwordLinkSent === '1') {
description = description + ' I sent the SMS password reset link.';
}
if (context.parameters.passwordLinkReceived === '1') {
description = description + ' '+dialogContext.params.customerFirstName+' confirmed receiveing the reset link,';
}
if (context.parameters.passwordLinkNotReceived === '1') {
description = description + ' '+dialogContext.params.customerFirstName+' never received the reset link.';
}
if (context.parameters.confirmedWorking === '1') {
description = description + ' and was able to login successfully.';
}
if (context.parameters.passwordLinkNotReceived === '0' && context.parameters.confirmedNotWorking === '1') {
description = description + ' but was unable to login successfully.';
}
let newCase = {
subject: subject,
description: description,
note: 'Case created.'
};
return newCase;
});
// Register Intent Handlers.
convoClient.registerIntent(new Intent({
action: 'skill.resetpassword.loginsuccess',
sequenceName: SEQ_PWRESET_NAME,
waitForReply: true,
handler: (dialogContext) => {
dialogContext.appendFulfillmentText();
return;
}
}));
convoClient.registerIntent(new Intent({
action: 'skill.resetpassword.loginsuccess.fallback',
sequenceName: SEQ_PWRESET_NAME,
waitForReply: true,
handler: (dialogContext) => {
dialogContext.appendFulfillmentText();
return;
}
}));
convoClient.registerIntent(new Intent({
action: 'skill.resetpassword.sms',
sequenceName: SEQ_PWRESET_NAME,
waitForReply: true,
handler: (dialogContext) => {
dialogContext.appendFulfillmentText();
dialogContext.setCurrentParam('passwordLinkSent', '1');
resetAndSendPassword(dialogContext);
return;
}
}));
convoClient.registerIntent(new Intent({
action: 'skill.resetpassword.sms.fallback',
sequenceName: SEQ_PWRESET_NAME,
waitForReply: true,
handler: (dialogContext) => {
dialogContext.appendFulfillmentText();
return;
}
}));
convoClient.registerIntents({
actions: [
'skill.resetpassword.sms.confirmation.yes',
'skill.resetpassword.sms.confirmation.received'
],
sequenceName: SEQ_PWRESET_NAME,
waitForReply: false,
handler: (dialogContext) => {
dialogContext.setFulfillmentText();
dialogContext.setCurrentParam('passwordLinkReceived', '1');
dialogContext.deleteCtx('skillresetpasswordsms-followup');
return;
}
});
convoClient.registerIntents({
actions: [
'skill.resetpassword.sms.confirmation.no',
'skill.resetpassword.sms.confirmation.notreceived',
'skill.resetpassword.sms.confirmation.notworking'
],
sequenceName: SEQ_PWRESET_NAME,
waitForReply: false,
handler: (dialogContext) => {
dialogContext.setFulfillmentText();
dialogContext.setCurrentParams({
'passwordLinkNotReceived': '1',
'confirmedNotWorking': '1',
'executeStatus': '1'
});
dialogContext.deleteCtx('skillresetpasswordsms-followup');
return;
}
});
convoClient.registerIntents({
actions: [
'skill.resetpassword.sms.confirmation.working'
],
sequenceName: SEQ_PWRESET_NAME,
waitForReply: false,
handler: (dialogContext) => {
dialogContext.setFulfillmentText();
dialogContext.setCurrentParams({
'passwordLinkReceived': '1',
'confirmedWorking': '1',
'executeStatus': '0'
});
dialogContext.deleteCtx('skillresetpasswordsms-followup');
return;
}
});
convoClient.registerIntent(new Intent({
action: 'skill.resetpassword.email',
sequenceName: SEQ_PWRESET_NAME,
waitForReply: true,
handler: (dialogContext) => {
dialogContext.appendFulfillmentText();
dialogContext.setCurrentParam('passwordLinkSent', '1');
resetAndSendPassword(dialogContext);
return;
}
}));
convoClient.registerIntent(new Intent({
action: 'skill.resetpassword.email.fallback',
sequenceName: SEQ_PWRESET_NAME,
waitForReply: true,
handler: (dialogContext) => {
dialogContext.appendFulfillmentText();
return;
}
}));
convoClient.registerIntents({
actions: [
'skill.resetpassword.email.confirmation.yes',
'skill.resetpassword.email.confirmation.received'
],
sequenceName: SEQ_PWRESET_NAME,
waitForReply: false,
handler: (dialogContext) => {
dialogContext.setFulfillmentText();
dialogContext.setCurrentParam('passwordLinkReceived', '1');
dialogContext.deleteCtx('skillresetpasswordemail-followup');
return;
}
});
convoClient.registerIntents({
actions: [
'skill.resetpassword.email.confirmation.no',
'skill.resetpassword.email.confirmation.notreceived',
'skill.resetpassword.email.confirmation.notworking'
],
sequenceName: SEQ_PWRESET_NAME,
waitForReply: false,
handler: (dialogContext) => {
dialogContext.setFulfillmentText();
dialogContext.setCurrentParams({
'passwordLinkNotReceived': '1',
'confirmedNotWorking': '1',
'executeStatus': '1'
});
dialogContext.deleteCtx('skillresetpasswordemail-followup');
return;
}
});
convoClient.registerIntents({
actions: [
'skill.resetpassword.email.confirmation.working'
],
sequenceName: SEQ_PWRESET_NAME,
waitForReply: false,
handler: (dialogContext) => {
dialogContext.setFulfillmentText();
dialogContext.setCurrentParams({
'passwordLinkReceived': '1',
'confirmedWorking': '1',
'executeStatus': '0'
});
dialogContext.deleteCtx('skillresetpasswordemail-followup');
return;
}
});
convoClient.registerIntents({
actions: [
'skill.resetpassword.loginsuccess.confirmation.yes',
'skill.resetpassword.loginsuccess.confirmation.received',
'skill.resetpassword.loginsuccess.confirmation.able',
'skill.resetpassword.loginsuccess.confirmation.working'
],
sequenceName: SEQ_PWRESET_NAME,
waitForReply: false,
handler: (dialogContext) => {
dialogContext.setFulfillmentText();
dialogContext.setCurrentParams({
'confirmedWorking': '1',
'executeStatus': '0'
});
dialogContext.deleteCtx('skillresetpasswordloginsuccess-followup');
return;
}
});
convoClient.registerIntents({
actions: [
'skill.resetpassword.loginsuccess.confirmation.no',
'skill.resetpassword.loginsuccess.confirmation.notreceived',
'skill.resetpassword.loginsuccess.confirmation.notable',
'skill.resetpassword.loginsuccess.confirmation.notworking'
],
sequenceName: SEQ_PWRESET_NAME,
waitForReply: false,
handler: (dialogContext) => {
dialogContext.setFulfillmentText();
dialogContext.setCurrentParams({
'confirmedNotWorking': '1',
'executeStatus': '1'
});
dialogContext.deleteCtx('skillresetpasswordloginsuccess-followup');
return;
}
});
convoClient.registerIntent(new Intent({
action: 'skill.resetpassword.success',
sequenceName: SEQ_PWRESET_NAME,
waitForReply: false,
handler: async (dialogContext) => {
await injectPasswdResetSuccessEvent(dialogContext);
dialogContext.appendFulfillmentText();
dialogContext.setCurrentParam('notifiedSuccess', '1');
let helpCounter = parseInt(dialogContext.params.helpCounter) + 1;
dialogContext.setSessionParam('helpCounter', helpCounter.toString());
return;
}
}));
convoClient.registerIntent(new Intent({
action: 'skill.resetpassword.failure',
sequenceName: SEQ_PWRESET_NAME,
waitForReply: false,
handler: async (dialogContext) => {
await injectPasswdResetFailureEvent(dialogContext);
dialogContext.appendFulfillmentText();
dialogContext.setCurrentParam('notifiedFailure', '1');
return;
}
}));
}
module.exports = {registerModuleResetPassword};