collabSupport/commands/resetVmPin.js
jmcqueen 2ec2b7a486 Add /resetvmpin and /resetvppin with confirmation cards for Webex PIN resets.
Chat-only commands reset voicemail mailbox and voice portal passcodes via telephony_config_write, with extension lookup and unit tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-31 16:34:09 -04:00

196 lines
5.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// commands/resetVmPin.js
//
// /resetvmpin <target> — reset a user's voicemail mailbox PIN with confirmation card.
//
// Targets: email, 24 digit store number, or 5-digit extension.
import { logger } from '../utils/logger.js';
import { pendingVmPinResets } from '../utils/pendingVmPinResets.js';
import { extractRequester, describeRequester } from '../utils/requester.js';
import {
parseVmPinTarget,
resolveVmPinTarget,
} from '../services/voicePin/resolveVmPinTarget.js';
import { resetVoicemailPin } from '../services/voicePin/resetVoicemailPin.js';
function matchSourceLabel(source) {
switch (source) {
case 'email': return 'Email lookup';
case 'store': return 'Store user';
case 'extension': return 'Extension lookup';
default: return source || '—';
}
}
function buildConfirmationCard(cardId, resolved) {
const facts = [
{ title: 'User', value: resolved.displayName },
{ title: 'Email', value: resolved.email },
{ title: 'Person ID', value: resolved.personId },
{ title: 'Match source', value: matchSourceLabel(resolved.matchSource) },
];
if (resolved.storeNum) {
facts.push({ title: 'Store', value: resolved.storeNum });
}
if (resolved.locationName) {
facts.push({ title: 'Location', value: resolved.locationName });
}
if (resolved.mainNumber) {
facts.push({ title: 'Main number', value: resolved.mainNumber });
}
if (resolved.extension) {
facts.push({ title: 'Extension', value: resolved.extension });
}
facts.push({
title: 'Voicemail enabled',
value: resolved.voicemailEnabled == null ? 'Unknown' : (resolved.voicemailEnabled ? 'Yes' : 'No'),
});
return {
type: 'AdaptiveCard',
version: '1.3',
body: [
{
type: 'TextBlock',
text: '🔐 RESET VOICEMAIL PIN',
weight: 'Bolder',
size: 'Large',
color: 'Attention',
},
{
type: 'TextBlock',
text:
'Confirming will generate a new **6-digit voicemail PIN** and apply it immediately. ' +
'The new PIN will be posted in this space after confirmation.',
wrap: true,
spacing: 'Small',
},
{
type: 'FactSet',
spacing: 'Medium',
facts,
},
],
actions: [
{
type: 'Action.Submit',
title: '✅ Confirm Reset',
data: { action: 'confirm_reset_vmpin', cardId },
},
{
type: 'Action.Submit',
title: '❌ Cancel',
data: { action: 'cancel_reset_vmpin', cardId },
},
],
};
}
export async function applyResetVmPinConfirmation(bot, data, _roomId, requester) {
logger(
'resetvmpin:audit',
`CONFIRMED voicemail PIN reset for ${data.email} (person=${data.personId}) by ${describeRequester(requester)}`,
);
try {
const newPin = await resetVoicemailPin(data.personId);
await bot.say(
'markdown',
`✅ Voicemail PIN reset for **${data.displayName}** (\`${data.email}\`).\n\n` +
`**New voicemail PIN:** \`${newPin}\`\n\n` +
'_Share this PIN only in trusted spaces._',
);
logger(
'resetvmpin:audit',
`COMPLETED voicemail PIN reset for ${data.email} (person=${data.personId}) by ${describeRequester(requester)}`,
);
} catch (err) {
const apiMsg = err?.response?.data?.message || err.message;
logger(
'resetvmpin:audit',
`FAILED voicemail PIN reset for ${data.email}: ${apiMsg}`,
'error',
);
await bot.say(
'markdown',
`❌ Voicemail PIN reset failed for **${data.displayName}**: ${apiMsg}`,
);
}
}
export async function cancelResetVmPinCard(bot, data, _roomId, requester) {
await bot.say(
'markdown',
`❌ Voicemail PIN reset cancelled for **${data.displayName}** (\`${data.email}\`). No changes were made.`,
);
logger(
'resetvmpin:audit',
`CANCELLED voicemail PIN reset for ${data.email} by ${describeRequester(requester)}`,
);
}
export async function handleResetVmPin(bot, trigger) {
const args = trigger.args || [];
const query = trigger.query || {};
const target = (args[0] || query.target || query.email || query.store || query.extension || '').trim();
if (!target) {
await bot.say(
'markdown',
'**Usage:** `/resetvmpin <email|store|extension>`\n\n' +
'• Email: `user@ae.com`\n' +
'• Store: `782` (24 digits)\n' +
'• Extension: `12345` (5 digits)',
);
return;
}
const parsed = parseVmPinTarget(target);
if (!parsed) {
await bot.say(
'markdown',
`❌ Invalid target \`${target}\`. Use email, 24 digit store number, or 5-digit extension.`,
);
return;
}
const requester = extractRequester(trigger);
logger(
'resetvmpin:audit',
`REQUESTED voicemail PIN reset card for ${target} by ${describeRequester(requester)}`,
);
try {
const resolved = await resolveVmPinTarget(parsed);
if (!trigger.person) {
await bot.say(
'markdown',
`Resolved **${resolved.displayName}** (\`${resolved.email}\`). ` +
'Confirmation cards are only available in Webex chat.',
);
return;
}
const cardId = `vmpin-${Date.now()}`;
pendingVmPinResets.set(cardId, {
...resolved,
roomId: trigger.roomId || trigger.message?.roomId,
requester,
});
const adaptiveCard = buildConfirmationCard(cardId, resolved);
await bot.say({
markdown:
`Review voicemail PIN reset for **${resolved.displayName}** (\`${resolved.email}\`):`,
attachments: [{
contentType: 'application/vnd.microsoft.card.adaptive',
content: adaptiveCard,
}],
});
} catch (err) {
logger('resetvmpin', `Resolve failed for ${target}: ${err.message}`, 'warn');
await bot.say('markdown', `${err.message}`);
}
}