Signal fixes

This commit is contained in:
Darren Clarke 2025-11-13 11:18:08 +01:00
parent 0e8c9be247
commit 457a86ebcd
7 changed files with 91 additions and 70 deletions

View file

@ -8,8 +8,8 @@ class CdrSignalChannelsController < ApplicationController
{
id: channel.id,
phone_number: channel.options['phone_number'],
bot_token: channel.options['bot_token'],
bot_endpoint: channel.options['bot_endpoint']
# bot_token intentionally excluded - bridge-worker should look it up from cdr database
}
end

View file

@ -115,7 +115,11 @@ class ChannelsCdrSignalController < ApplicationController
channel = channel_for_token(token)
return render json: {}, status: 401 if !channel || !channel.active
return render json: {}, status: 401 if channel.options[:token] != token
# Use constant-time comparison to prevent timing attacks
return render json: {}, status: 401 unless ActiveSupport::SecurityUtils.secure_compare(
channel.options[:token].to_s,
token.to_s
)
# Handle group creation events
if params[:event] == 'group_created'
@ -218,38 +222,13 @@ class ChannelsCdrSignalController < ApplicationController
Rails.logger.info "Channel ID: #{channel.id}"
begin
# For group messages, search all tickets regardless of customer
# since users may have duplicate phone numbers
all_tickets = Ticket.where.not(state_id: state_ids)
.order(updated_at: :desc)
Rails.logger.info "Found #{all_tickets.count} active tickets (searching all customers for group match)"
ticket = all_tickets.find do |t|
begin
has_preferences = t.preferences.is_a?(Hash)
has_cdr_signal = has_preferences && t.preferences['cdr_signal'].is_a?(Hash)
has_channel_id = has_preferences && t.preferences['channel_id'] == channel.id
if has_cdr_signal && has_channel_id
stored_chat_id = t.preferences['cdr_signal']['chat_id']
Rails.logger.info " - stored_chat_id: #{stored_chat_id}"
Rails.logger.info " - incoming_group_id: #{receiver_phone_number}"
matches = receiver_phone_number == stored_chat_id
Rails.logger.info " - MATCH: #{matches}"
matches
else
Rails.logger.info "Ticket ##{t.number} has no cdr_signal preferences or wrong channel"
false
end
rescue => e
Rails.logger.error "Error checking ticket #{t.id}: #{e.message}"
false
end
end
# Use PostgreSQL JSONB queries to efficiently search preferences without loading all tickets into memory
# This prevents DoS attacks from memory exhaustion
ticket = Ticket.where.not(state_id: state_ids)
.where("preferences->>'channel_id' = ?", channel.id.to_s)
.where("preferences->'cdr_signal'->>'chat_id' = ?", receiver_phone_number)
.order(updated_at: :desc)
.first
if ticket
Rails.logger.info "=== FOUND MATCHING TICKET BY GROUP ID: ##{ticket.number} ==="
@ -441,14 +420,13 @@ class ChannelsCdrSignalController < ApplicationController
end
# Find ticket(s) with this group_id in preferences
# Search all active tickets for matching group
# Use PostgreSQL JSONB queries for efficient lookup (prevents DoS from loading all tickets)
state_ids = Ticket::State.where(name: %w[closed merged removed]).pluck(:id)
ticket = Ticket.where.not(state_id: state_ids).find do |t|
t.preferences.is_a?(Hash) &&
t.preferences['cdr_signal'].is_a?(Hash) &&
t.preferences['cdr_signal']['chat_id'] == params[:group_id]
end
ticket = Ticket.where.not(state_id: state_ids)
.where("preferences->'cdr_signal'->>'chat_id' = ?", params[:group_id])
.order(updated_at: :desc)
.first
unless ticket
Rails.logger.warn "Signal group member joined: Ticket not found for group_id #{params[:group_id]}"
@ -456,11 +434,22 @@ class ChannelsCdrSignalController < ApplicationController
return
end
# Check if the member who joined matches the original recipient
original_recipient = ticket.preferences.dig('cdr_signal', 'original_recipient')
member_phone = params[:member_phone]
# Idempotency check: if already marked as joined, skip update and return success
# This prevents unnecessary database writes when the cron job sends duplicate notifications
if ticket.preferences.dig('cdr_signal', 'group_joined') == true
Rails.logger.debug "Signal group member #{params[:member_phone]} already marked as joined for group #{params[:group_id]} ticket #{ticket.id}, skipping update"
render json: {
success: true,
ticket_id: ticket.id,
ticket_number: ticket.number,
group_joined: true,
already_joined: true
}, status: :ok
return
end
# Update group_joined flag
member_phone = params[:member_phone]
ticket.preferences[:cdr_signal][:group_joined] = true
ticket.preferences[:cdr_signal][:group_joined_at] = params[:timestamp] if params[:timestamp].present?
ticket.preferences[:cdr_signal][:group_joined_by] = member_phone