Discourse-ip-alert: Preciso de ajuda com o plugin criado

Este plugin destina-se a enviar um alerta por e-mail a todos os administradores sempre que um usuário fizer login a partir de um endereço IP suspeito. Ele faz isso extraindo o IP do último token de autenticação (usando o campo client_ip), verificando este IP contra uma lista de bloqueio definida nas configurações do site (ip_alert_suspicious_ips) e, se o IP corresponder a algum padrão bloqueado, envia um e-mail de aviso a todos os usuários administradores.

Erro Observado:

Apesar do código do plugin parecer correto e seguir os padrões de plugins do Discourse, ainda encontro o erro genérico “Oops – O software que alimenta este fórum de discussão encontrou um problema inesperado” após o login. Os logs de erro não mostram indicações claras de nossas mensagens de log [DiscourseIpAlert], e o rastreamento da pilha não aponta um problema diretamente no código do nosso plugin.

Status Atual:

Revisei e revisei o plugin várias vezes, incluindo a verificação das configurações do site e a garantia de que o hook (on(:user_logged_in)) está corretamente registrado. A extração de IP e a lógica de envio de e-mail também parecem estar implementadas corretamente, mas o erro interno do servidor 500 persiste sem um rastreamento de pilha claro que o vincule ao nosso plugin.

Alguma ajuda?

plugin.rb:

# frozen_string_literal: true

# name: discourse-ip-alert
# about: Envia um alerta por e-mail quando um usuário faz login a partir de um endereço IP suspeito.
# version: 0.7
# authors: OrkoGrayskull (revisado)
# required_version: 3.5.0

enabled_site_setting :ip_alert_enabled
enabled_site_setting :ip_alert_suspicious_ips

module ::DiscourseIpAlert
  PLUGIN_NAME = "discourse-ip-alert"

  require 'ipaddr'
  require 'mail'

  # Extrai o endereço IP do último token de autenticação
  def self.extract_ip(user)
    token = UserAuthToken.where(user_id: user.id).order(created_at: :desc).first
    ip = token&.attributes["client_ip"]
    Rails.logger.info("[DiscourseIpAlert] extract_ip: IP extraído de UserAuthToken: #{ip.inspect}")
    ip
  rescue => e
    Rails.logger.error("[DiscourseIpAlert] erro extract_ip: #{e.message}")
    nil
  end

  # Verifica se o endereço IP fornecido está presente na lista de bloqueio
  def self.ip_blocked?(ip_address)
    return false if ip_address.blank?
    blocked_ips = SiteSetting.ip_alert_suspicious_ips.split(",").map(&:strip)
    Rails.logger.info("[DiscourseIpAlert] ip_blocked?: IPs bloqueados: #{blocked_ips.inspect}")
    blocked_ips.any? do |blocked|
      if blocked.include?('*')
        regex = Regexp.new("^" + Regexp.escape(blocked).gsub('\*', '\d{1,3}') + "$")
        Rails.logger.info("[DiscourseIpAlert] ip_blocked?: Comparando #{ip_address} com regex wildcard #{regex.inspect}")
        ip_address.match?(regex)
      else
        begin
          result = IPAddr.new(blocked).include?(ip_address)
          Rails.logger.info("[DiscourseIpAlert] ip_blocked?: Comparando #{ip_address} com #{blocked}: #{result}")
          result
        rescue IPAddr::InvalidAddressError => e
          Rails.logger.error("[DiscourseIpAlert] ip_blocked?: IP inválido na lista de bloqueio: #{blocked} (#{e.message})")
          false
        end
      end
    end
  rescue => e
    Rails.logger.error("[DiscourseIpAlert] erro ip_blocked?: #{e.message}")
    false
  end

  # Envia um e-mail de aviso a todos os administradores
  def self.send_warning_email(user, ip_address)
    admin_emails = User.where(admin: true).pluck(:email)
    return if admin_emails.empty?
    subject = "⚠️ Aviso: Login de um IP Suspeito!"
    body = <<~BODY
      Atenção!

      O usuário #{user.username} (#{user.email}) fez login a partir do endereço IP #{ip_address},
      que foi sinalizado como suspeito.

      Por favor, revise esta conta.
    BODY
    admin_emails.each do |email|
      begin
        mail = Mail.new do
          from    ENV['DISCOURSE_NOTIFICATION_EMAIL'] || 'no-reply@example.org'
          to      email
          subject subject
          body    body
        end
        Rails.logger.info("[DiscourseIpAlert] Enviando e-mail de aviso para #{email}")
        mail.deliver!
      rescue => e
        Rails.logger.error("[DiscourseIpAlert] Erro ao enviar e-mail para #{email}: #{e.message}")
      end
    end
  rescue => e
    Rails.logger.error("[DiscourseIpAlert] erro send_warning_email: #{e.message}")
  end

  # Processa o login do usuário: extrai o IP, verifica a lista de bloqueio e envia um e-mail de aviso, se necessário
  def self.process_user_login(user)
    Rails.logger.info("[DiscourseIpAlert] process_user_login para o usuário: #{user.username}")
    ip_address = extract_ip(user)
    unless ip_address
      Rails.logger.warn("[DiscourseIpAlert] Nenhum endereço IP extraído – pulando processamento adicional.")
      return
    end
    if ip_blocked?(ip_address)
      Rails.logger.warn("[DiscourseIpAlert] IP suspeito detectado: #{ip_address}")
      send_warning_email(user, ip_address)
    else
      Rails.logger.info("[DiscourseIpAlert] IP #{ip_address} é considerado seguro.")
    end
  rescue => e
    Rails.logger.error("[DiscourseIpAlert] erro process_user_login: #{e.message}")
  end
end

after_initialize do
  Rails.logger.info("[DiscourseIpAlert] after_initialize: Plugin está sendo integrado ao processo de login.")

  # Registra o hook que é chamado em cada login de usuário
  on(:user_logged_in) do |user|
    begin
      Rails.logger.info("[DiscourseIpAlert] hook on(:user_logged_in) chamado para o usuário: #{user.username}")
      ::DiscourseIpAlert.process_user_login(user)
    rescue => e
      Rails.logger.error("[DiscourseIpAlert] Erro no hook on(:user_logged_in): #{e.message}")
    end
  end
end

config/settings.yml:

ip_alert_enabled:
  default: false
  client: true
  type: bool
  description: "Habilita o alerta de IP"

ip_alert_suspicious_ips:
  default: "192.168.1.1,203.0.113.0/24"
  client: true
  type: string
  description: "Lista de endereços IP suspeitos (separados por vírgula)"