يهدف هذا المكون الإضافي إلى إرسال تنبيه عبر البريد الإلكتروني إلى جميع المسؤولين كلما قام مستخدم بتسجيل الدخول من عنوان 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)"