Discourse-ip-alert: بحاجة لمساعدة في الإضافة التي تم إنشاؤها

يهدف هذا المكون الإضافي إلى إرسال تنبيه عبر البريد الإلكتروني إلى جميع المسؤولين كلما قام مستخدم بتسجيل الدخول من عنوان IP مشبوه. يقوم بذلك عن طريق استخراج عنوان IP من أحدث رمز مصادقة (باستخدام حقل client_ip)، والتحقق من عنوان IP هذا مقابل قائمة حظر محددة في إعدادات الموقع (ip_alert_suspicious_ips)، وإذا تطابق عنوان IP مع أي نمط محظور، فإنه يرسل بريدًا إلكترونيًا تحذيريًا إلى جميع المستخدمين المسؤولين.

الخطأ الملاحظ:

على الرغم من أن كود المكون الإضافي يبدو صحيحًا ويتبع أنماط مكونات Discourse الإضافية القياسية، إلا أنني ما زلت أواجه الخطأ العام “عذرًا - واجه البرنامج الذي يشغل منتدى المناقشة هذا مشكلة غير متوقعة” عند تسجيل الدخول. لا تُظهر سجلات الأخطاء مؤشرات واضحة من رسائل السجل الخاصة بنا [DiscourseIpAlert]، ولا تحدد تتبع المكدس مشكلة مباشرة داخل كود المكون الإضافي الخاص بنا.

الوضع الحالي:

لقد قمت بمراجعة وتنقيح المكون الإضافي عدة مرات، بما في ذلك التحقق من إعدادات الموقع والتأكد من تسجيل الخطاف (on(:user_logged_in)) بشكل صحيح. يبدو أيضًا أن منطق استخراج عنوان IP وإرسال البريد الإلكتروني قد تم تنفيذه بشكل صحيح، ومع ذلك لا يزال خطأ الخادم الداخلي 500 مستمرًا دون تتبع مكدس واضح يربطه بالمكون الإضافي الخاص بنا.

هل هناك أي مساعدة؟

plugin.rb:

# frozen_string_literal: true

# name: discourse-ip-alert
# about: Sends an email alert when a user logs in from a suspicious IP address.
# version: 0.7
# authors: OrkoGrayskull (revised)
# 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'

  # Extracts the IP address from the latest authentication token
  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: Extracted IP from UserAuthToken: #{ip.inspect}")
    ip
  rescue => e
    Rails.logger.error("[DiscourseIpAlert] extract_ip error: #{e.message}")
    nil
  end

  # Checks if the given IP address is present in the blocklist
  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?: Blocked IPs: #{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?: Comparing #{ip_address} with wildcard regex #{regex.inspect}")
        ip_address.match?(regex)
      else
        begin
          result = IPAddr.new(blocked).include?(ip_address)
          Rails.logger.info("[DiscourseIpAlert] ip_blocked?: Comparing #{ip_address} with #{blocked}: #{result}")
          result
        rescue IPAddr::InvalidAddressError => e
          Rails.logger.error("[DiscourseIpAlert] ip_blocked?: Invalid IP in blocklist: #{blocked} (#{e.message})")
          false
        end
      end
    end
  rescue => e
    Rails.logger.error("[DiscourseIpAlert] ip_blocked? error: #{e.message}")
    false
  end

  # Sends a warning email to all admins
  def self.send_warning_email(user, ip_address)
    admin_emails = User.where(admin: true).pluck(:email)
    return if admin_emails.empty?
    subject = "⚠️ Warning: Login from a Suspicious IP!"
    body = <<~BODY
      Attention!

      The user #{user.username} (#{user.email}) has logged in from the IP address #{ip_address},
      which has been flagged as suspicious.

      Please review this account.
    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] Sending warning email to #{email}")
        mail.deliver!
      rescue => e
        Rails.logger.error("[DiscourseIpAlert] Error sending email to #{email}: #{e.message}")
      end
    end
  rescue => e
    Rails.logger.error("[DiscourseIpAlert] send_warning_email error: #{e.message}")
  end

  # Processes the user login: extracts the IP, checks the blocklist, and sends a warning email if necessary
  def self.process_user_login(user)
    Rails.logger.info("[DiscourseIpAlert] process_user_login for user: #{user.username}")
    ip_address = extract_ip(user)
    unless ip_address
      Rails.logger.warn("[DiscourseIpAlert] No IP address extracted – skipping further processing.")
      return
    end
    if ip_blocked?(ip_address)
      Rails.logger.warn("[DiscourseIpAlert] Suspicious IP detected: #{ip_address}")
      send_warning_email(user, ip_address)
    else
      Rails.logger.info("[DiscourseIpAlert] IP #{ip_address} is considered safe.")
    end
  rescue => e
    Rails.logger.error("[DiscourseIpAlert] process_user_login error: #{e.message}")
  end
end

after_initialize do
  Rails.logger.info("[DiscourseIpAlert] after_initialize: Plugin is being integrated into the login process.")

  # Register the hook that is called on every user login
  on(:user_logged_in) do |user|
    begin
      Rails.logger.info("[DiscourseIpAlert] on(:user_logged_in) hook called for user: #{user.username}")
      ::DiscourseIpAlert.process_user_login(user)
    rescue => e
      Rails.logger.error("[DiscourseIpAlert] Error in on(:user_logged_in) hook: #{e.message}")
    end
  end
end

config/settings.yml:

ip_alert_enabled:
  default: false
  client: true
  type: bool
  description: "Enable the IP alert"

ip_alert_suspicious_ips:
  default: "192.168.1.1,203.0.113.0/24"
  client: true
  type: string
  description: "List of suspicious IP addresses (comma-separated)"