diff --git a/enterprise/app/services/voice/call_status/manager.rb b/enterprise/app/services/voice/call_status/manager.rb index e7cc95f38..c45fdfd94 100644 --- a/enterprise/app/services/voice/call_status/manager.rb +++ b/enterprise/app/services/voice/call_status/manager.rb @@ -17,7 +17,10 @@ class Voice::CallStatus::Manager ts = timestamp || now_seconds if status == 'in_progress' - attrs[:started_at] = Time.zone.at(ts) + # Twilio can emit multiple in-progress updates (answered + in-progress, retries). + # Keep the earliest timestamp so duration_seconds doesn't shift forward. + started_at = Time.zone.at(ts) + attrs[:started_at] = started_at if call.started_at.nil? || started_at < call.started_at elsif Call::TERMINAL_STATUSES.include?(status) call.ended_at = ts attrs[:meta] = call.meta diff --git a/enterprise/app/services/voice/conference/manager.rb b/enterprise/app/services/voice/conference/manager.rb index c6115137b..9f34eec75 100644 --- a/enterprise/app/services/voice/conference/manager.rb +++ b/enterprise/app/services/voice/conference/manager.rb @@ -35,6 +35,17 @@ class Voice::Conference::Manager status_manager.process_status_update('in_progress', timestamp: now) end + # Parses agent user_id from participant_label. Only returns an id when the + # label's embedded account id matches the call's account — protects against + # a spoofed/cross-account label attaching a foreign user to the call. + def extract_user_id + match = participant_label.to_s.match(AGENT_LABEL_PATTERN) + return unless match + return unless match[2].to_i == call.account_id + + match[1].to_i + end + def handle_leave! case call.status when 'ringing' @@ -54,11 +65,6 @@ class Voice::Conference::Manager participant_label.to_s.start_with?('agent-') end - def extract_user_id - match = participant_label.to_s.match(AGENT_LABEL_PATTERN) - match && match[1].to_i - end - def now Time.zone.now.to_i end