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>
196 lines
5.7 KiB
JavaScript
196 lines
5.7 KiB
JavaScript
// commands/resetVmPin.js
|
||
//
|
||
// /resetvmpin <target> — reset a user's voicemail mailbox PIN with confirmation card.
|
||
//
|
||
// Targets: email, 2–4 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` (2–4 digits)\n' +
|
||
'• Extension: `12345` (5 digits)',
|
||
);
|
||
return;
|
||
}
|
||
|
||
const parsed = parseVmPinTarget(target);
|
||
if (!parsed) {
|
||
await bot.say(
|
||
'markdown',
|
||
`❌ Invalid target \`${target}\`. Use email, 2–4 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}`);
|
||
}
|
||
}
|