~singpolyma/sgx-jmp

c3f3220de79b9473e2a43aeb1443a818769e62ca — Stephen Paul Weber a month ago 4b5310c + 5dfee34
Merge branch 'message-limits'

* 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
4 files changed, 59 insertions(+), 23 deletions(-)

M config-schema.dhall
M config.dhall.sample
M lib/trust_level.rb
M sgx_jmp.rb
M config-schema.dhall => config-schema.dhall +1 -0
@@ 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 }

M config.dhall.sample => config.dhall.sample +1 -0
@@ 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" }
}

M lib/trust_level.rb => lib/trust_level.rb +16 -0
@@ 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

M sgx_jmp.rb => sgx_jmp.rb +41 -23
@@ 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