From 6cb9b2d0c72d39f579cc432419a3b448cf285fc6 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Sat, 7 Feb 2026 09:20:57 -0500 Subject: [PATCH] Block same body over 5 times in 120s --- lib/customer_usage.rb | 21 ++++++++++++++++++--- sgx_jmp.rb | 23 +++++++++++++++-------- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/lib/customer_usage.rb b/lib/customer_usage.rb index 84ff279ea95471caeae4b2b6f540ea5778aa6480..c67cee63bd096aa0a2eabec2148d9ed03729976d 100644 --- a/lib/customer_usage.rb +++ b/lib/customer_usage.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "digest" + require_relative "./usage_report" class CustomerUsage @@ -26,7 +28,7 @@ class CustomerUsage ) end - def incr_message_usage(amount=1) + def incr_message_usage(amount=1, body=nil) today = Time.now.utc.to_date EMPromise.all([ expire_message_usage, @@ -34,8 +36,21 @@ class CustomerUsage "jmp_customer_outbound_messages-#{@customer_id}", amount, today.strftime("%Y%m%d") - ) - ]) + ), + incr_body(amount, body) + ]).then { |result| { today: result[1], body: result[2] } } + end + + def incr_body(amount, body) + # Short message like "hello" we can't block dupes + return 0 if body.to_s.length < 30 + + hash = Digest::SHA2.hexdigest(body[0..100]) + + EMPromise.all([ + REDIS.incrby("jmp_outbound_body-#{hash}", amount), + REDIS.expire("jmp_outbound_body-#{hash}", 120) + ]).then(&:first) end def message_usage(range) diff --git a/sgx_jmp.rb b/sgx_jmp.rb index 9f28737ec2c80e9b502b2cbbdcc04a1cd6740a53..d4e0415eb8a8d8b124dc26782993fea60e3bdf59 100644 --- a/sgx_jmp.rb +++ b/sgx_jmp.rb @@ -321,6 +321,12 @@ def billable_message(m) b && !b.empty? || m.find("ns:x", ns: OOB.registered_ns).first end +def expired_guard(customer) + return unless !customer.plan_name || customer.active? + + raise CustomerExpired, "Your account is expired, please top up" +end + class OverLimit < StandardError def initialize(customer, usage) super("Please contact support") @@ -333,7 +339,8 @@ class OverLimit < StandardError BLATHER.join(CONFIG[:notify_admin], "sgx-jmp") BLATHER.say( CONFIG[:notify_admin], "#{@customer.customer_id} has used " \ - "#{@usage} messages today", :groupchat + "#{@usage[:today]} messages today (global #{@usage[:body]} this body)", + :groupchat ) end end @@ -392,7 +399,6 @@ end message do |m| StatsD.increment("message") - today = Time.now.utc.to_date find_from_and_to_customer(m.from, m.to).then { |(customer, target_customer)| if target_customer && customer.registered? m.from = "#{customer.registered?.phone}@sgx-jmp" @@ -401,17 +407,18 @@ message do |m| next customer.stanza_from(m) unless billable_message(m) - if customer.plan_name && !customer.active? - raise CustomerExpired, "Your account is expired, please top up" - end + expired_guard(customer) EMPromise.all([ TrustLevelRepo.new.find(customer), - customer.message_usage((today..today)) + customer.incr_message_usage(1, m.body) ]).then { |(tl, usage)| - raise OverLimit.new(customer, usage) unless tl.send_message?(usage) + next if tl.send_message?(usage[:today]) && usage[:body].to_i < 5 + + log.warn "OverLimit", m + raise OverLimit.new(customer, usage) }.then do - EMPromise.all([customer.incr_message_usage, customer.stanza_from(m)]) + customer.stanza_from(m) end }.catch_only(OverLimit) { |e| e.notify_admin