// src/services/voiceDiag/checks/outgoingPermission.js // // Info-only check for outgoing call permissions. Extends the // count-only surface currently in `/phonestatus` with per-rule // detail. When a call type is set to BLOCK (rather than ALLOW / // AUTH_CODE / TRANSFER), users on that line can't dial calls of that // class. Blocked domestic-toll or international are especially // common tickets ("we can't dial out from the store"), so we flag // those as a warning. // // Endpoint: GET /v1/people/{personId}/features/outgoingPermission // Scope: spark-admin:people_read // Response shape: // { // useCustomEnabled: boolean, // callingPermissions: [ // { callType: 'INTERNAL_CALL' | 'LOCAL' | 'TOLL_FREE' | 'TOLL' | // 'NATIONAL' | 'INTERNATIONAL' | 'OPERATOR_ASSISTED' | // 'CHARGEABLE_DIRECTORY_ASSISTED' | 'SPECIAL_SERVICES_I'|II | // 'PREMIUM_SERVICES_I'|II | 'CASUAL', // action: 'ALLOW' | 'BLOCK' | 'AUTH_CODE' | 'TRANSFER_NUMBER_1'..3, // transferEnabled: boolean } // ] // } // // When `useCustomEnabled=false` the account defers to the site's // default outgoing permission set — in that case there's nothing // user-scoped to report, so we return OK with a note. const ENDPOINT = (personId) => `people/${personId}/features/outgoingPermission`; // Call types that being BLOCKED tends to be a bug for a store line. // Others (premium/casual/operator) are almost always intentionally // blocked, so blocking them is fine. const HIGH_IMPACT_CALL_TYPES = new Set([ 'LOCAL', 'NATIONAL', 'TOLL_FREE', 'TOLL', 'INTERNATIONAL', ]); export const outgoingPermissionCheck = { id: 'outgoingPermission', label: 'Outgoing Call Permissions', requires: ['personId'], scope: 'spark-admin:people_read', async run(ctx) { const data = await ctx.webex.request('GET', ENDPOINT(ctx.personId)); const useCustomEnabled = !!data?.useCustomEnabled; const rules = Array.isArray(data?.callingPermissions) ? data.callingPermissions : []; if (!useCustomEnabled) { return { status: 'ok', message: 'Using the location default outgoing permission set (no user override).', details: { useCustomEnabled, rules }, remediation: null, }; } const blocked = rules .filter((r) => r?.action === 'BLOCK') .map((r) => r.callType); const highImpactBlocked = blocked.filter((t) => HIGH_IMPACT_CALL_TYPES.has(t)); if (highImpactBlocked.length > 0) { return { status: 'warn', message: `Custom outgoing permissions are set and the following high-impact call types ` + `are BLOCKED: ${highImpactBlocked.join(', ')}. Users on this line will hear a fast-busy for those calls.`, details: { useCustomEnabled, rules, blocked, highImpactBlocked }, remediation: null, }; } if (blocked.length > 0) { return { status: 'ok', message: `Custom outgoing permissions with ${blocked.length} blocked call type(s): ${blocked.join(', ')}. None are high-impact.`, details: { useCustomEnabled, rules, blocked, highImpactBlocked: [] }, remediation: null, }; } return { status: 'ok', message: `Custom outgoing permissions (${rules.length} rules) — nothing is blocked.`, details: { useCustomEnabled, rules, blocked: [], highImpactBlocked: [] }, remediation: null, }; }, };