collabSupport/services/voicePin/resetVoicePortalPin.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

79 lines
2.7 KiB
JavaScript

// services/voicePin/resetVoicePortalPin.js
// GET passcodeRules + PUT /v1/telephony/config/locations/{id}/voicePortal passcode
import { logger } from '../../utils/logger.js';
import { generatePin } from './generatePin.js';
const LOG_SCOPE = 'resetvppin:service';
const MAX_PUT_ATTEMPTS = 5;
/**
* Reset a location's voice portal passcode.
*
* @param {string} locationId
* @param {object} [opts]
* @param {object} [opts.passcodeRules] cached rules from resolve step
* @param {object} [opts.voicePortal] cached portal snapshot from resolve step
* @param {object} [opts.webexClient] injectable client for tests
* @returns {Promise<string>} new PIN (never log this value)
*/
export async function resetVoicePortalPin(locationId, opts = {}) {
if (!locationId) {
throw new Error('locationId is required');
}
const client = opts.webexClient || (await import('../../integrations/webex/WebexClient.js')).default;
const passcodeRules = opts.passcodeRules
|| await client.request('GET', `telephony/config/locations/${locationId}/voicePortal/passcodeRules`);
let lastError = null;
for (let attempt = 0; attempt < MAX_PUT_ATTEMPTS; attempt += 1) {
const passcode = generatePin({
rules: passcodeRules,
extension: opts.voicePortal?.extension,
});
const body = {
passcode: {
newPasscode: passcode,
confirmPasscode: passcode,
},
};
try {
await client.request('PUT', `telephony/config/locations/${locationId}/voicePortal`, body);
logger(LOG_SCOPE, `Voice portal PIN reset succeeded for location ${locationId}`, 'info');
return passcode;
} catch (err) {
lastError = err;
const status = err?.response?.status;
if (status === 400 && attempt < MAX_PUT_ATTEMPTS - 1) {
logger(LOG_SCOPE, `Voice portal PIN rejected for ${locationId} (attempt ${attempt + 1}), retrying`, 'warn');
continue;
}
if (status === 400 || status === 422) {
const existing = opts.voicePortal?.raw
|| await client.request('GET', `telephony/config/locations/${locationId}/voicePortal`).catch(() => null);
if (existing) {
const merged = {
...existing,
passcode: body.passcode,
};
try {
await client.request('PUT', `telephony/config/locations/${locationId}/voicePortal`, merged);
logger(LOG_SCOPE, `Voice portal PIN reset succeeded (merged PUT) for location ${locationId}`, 'info');
return passcode;
} catch (mergeErr) {
lastError = mergeErr;
}
}
}
throw lastError;
}
}
throw lastError || new Error('Voice portal PIN reset failed');
}