Merge branch 'message-limits'

Stephen Paul Weber created

* message-limits:
  Block outgoing messages when expired
  Contacting support is not billable
  Use TrustLevel to determine daily quota
  Refactor message limits, change to 500/day

Change summary

config-schema.dhall |  1 
config.dhall.sample |  1 
lib/trust_level.rb  | 16 +++++++++++
sgx_jmp.rb          | 64 ++++++++++++++++++++++++++++++----------------
4 files changed, 59 insertions(+), 23 deletions(-)

Detailed changes

config-schema.dhall 🔗

@@ -39,6 +39,7 @@
 , sgx : Text
 , sip : { app : Text, realm : Text }
 , sip_host : Text
+, unbilled_targets : List Text
 , upstream_domain : Text
 , web : < Inet : { interface : Text, port : Natural } | Unix : Text >
 , web_register : { from : Text, to : Text }

config.dhall.sample 🔗

@@ -76,6 +76,7 @@ in
 	payable = "",
 	notify_from = "+15551234567@example.net",
 	admins = ["test\\40example.com@example.net"],
+	unbilled_targets = ["+14169938000"],
 	upstream_domain = "example.net",
 	approved_domains = toMap { `example.com` = Some "customer_id" }
 }

lib/trust_level.rb 🔗

@@ -27,6 +27,10 @@ module TrustLevel
 		def support_call?(*)
 			false
 		end
+
+		def send_message?(*)
+			false
+		end
 	end
 
 	class Basement
@@ -37,6 +41,10 @@ module TrustLevel
 		def support_call?(rate, concurrency)
 			rate <= 0.02 && concurrency < 1
 		end
+
+		def send_message?(messages_today)
+			messages_today < 200
+		end
 	end
 
 	class Paragon
@@ -47,6 +55,10 @@ module TrustLevel
 		def support_call?(_, concurrency)
 			concurrency < 10
 		end
+
+		def send_message?(messages_today)
+			messages_today < 700
+		end
 	end
 
 	class Customer
@@ -70,5 +82,9 @@ module TrustLevel
 		def support_call?(rate, concurrency)
 			rate <= @max_rate && concurrency < 4
 		end
+
+		def send_message?(messages_today)
+			messages_today < 500
+		end
 	end
 end

sgx_jmp.rb 🔗

@@ -307,42 +307,60 @@ end
 # Especially if we have the component join MUC for notifications
 message(type: :groupchat) { true }
 
+UNBILLED_TARGETS = Set.new(CONFIG[:unbilled_targets])
 def billable_message(m)
-	(m.body && !m.body.empty?) || m.find("ns:x", ns: OOB.registered_ns).first
+	b = m.body
+	!UNBILLED_TARGETS.member?(m.to.node) && \
+		(b && !b.empty? || m.find("ns:x", ns: OOB.registered_ns).first)
 end
 
-def notify_admin_of_usage(customer, usage, today)
-	ExpiringLock.new("jmp_usage_notify-#{customer.customer_id}").with do
-		BLATHER.join(CONFIG[:notify_admin], "sgx-jmp")
-		BLATHER.say(
-			CONFIG[:notify_admin], "#{customer.customer_id} has used " \
-			"#{usage} messages since #{today - 30}", :groupchat
-		)
+class OverLimit < StandardError
+	def initialize(customer, usage)
+		super("Please contact support")
+		@customer = customer
+		@usage = usage
+	end
+
+	def notify_admin
+		ExpiringLock.new("jmp_usage_notify-#{@customer.customer_id}").with do
+			BLATHER.join(CONFIG[:notify_admin], "sgx-jmp")
+			BLATHER.say(
+				CONFIG[:notify_admin], "#{@customer.customer_id} has used " \
+				"#{@usage} messages today", :groupchat
+			)
+		end
 	end
 end
 
+class CustomerExpired < StandardError; end
+
 message do |m|
 	StatsD.increment("message")
 
 	sentry_hub = new_sentry_hub(m, name: "message")
 	today = Time.now.utc.to_date
-	CustomerRepo
-		.new(set_user: sentry_hub.current_scope.method(:set_user))
+	CustomerRepo.new(set_user: sentry_hub.current_scope.method(:set_user))
 		.find_by_jid(m.from.stripped).then { |customer|
+			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
+
 			EMPromise.all([
-				(customer.incr_message_usage if billable_message(m)),
-				customer.message_usage((today..(today - 30))).then do |usage|
-					if usage < 4500
-						customer.stanza_from(m)
-					else
-						BLATHER << m.as_error(
-							"policy-violation", :wait, "Please contact support"
-						)
-					end
-					notify_admin_of_usage(customer, usage, today) if usage > 900
-				end
-			])
-		}.catch_only(CustomerRepo::NotFound) { |e|
+				TrustLevelRepo.new.find(customer),
+				customer.message_usage((today..today))
+			]).then { |(tl, usage)|
+				raise OverLimit.new(customer, usage) unless tl.send_message?(usage)
+			}.then do
+				EMPromise.all([
+					customer.incr_message_usage, customer.stanza_from(m)
+				])
+			end
+		}.catch_only(OverLimit) { |e|
+			e.notify_admin
+			BLATHER << m.as_error("policy-violation", :wait, e.message)
+		}.catch_only(CustomerRepo::NotFound, CustomerExpired) { |e|
 			BLATHER << m.as_error("forbidden", :auth, e.message)
 		}.catch { |e| panic(e, sentry_hub) }
 end