SMS Reachability Check

Christopher Vollick created

On an inbound stanza it checks if the initiator is one of the
whitelisted numbers that can perform reachability checks. If not, which
virtually all received SMSs won't be, it just runs the code that was
already there.

If it is, then it does a further check to see if the reachability check
is ongoing for this user, and if not it runs the code that was already
there.

And finally, if the key exists it increments it and otherwise doesn't
process the message.

Change summary

config-schema.dhall      |  1 
config.dhall.sample      |  1 
lib/reachability_repo.rb | 67 ++++++++++++++++++++++++++++++++++++++++++
sgx_jmp.rb               |  9 +++++
4 files changed, 77 insertions(+), 1 deletion(-)

Detailed changes

config-schema.dhall 🔗

@@ -39,6 +39,7 @@
       , monthly_price : Natural
       , name : Text
       }
+, reachability_senders : List Text
 , rev_ai_token : Text
 , server : { host : Text, port : Natural }
 , sgx : Text

config.dhall.sample 🔗

@@ -87,6 +87,7 @@ in
 	approved_domains = toMap { `example.com` = Some "customer_id" },
 	keepgo = Some { api_key = "", access_token = "" },
 	simpleswap_api_key = "",
+	reachability_senders = [ "+14445556666" ],
 	support_link = \(customer_jid: Text) ->
 		"http://localhost:3002/app/accounts/2/contacts/custom_attributes/jid/${customer_jid}"
 }

lib/reachability_repo.rb 🔗

@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require "value_semantics/monkey_patched"
+
+class ReachabilityRepo
+	value_semantics do
+		redis Anything(), default: LazyObject.new { REDIS }
+		senders ArrayOf(String), default: CONFIG[:reachability_senders]
+	end
+
+	class SMS < self
+		def type
+			"sms"
+		end
+	end
+
+	class Voice < self
+		def type
+			"voice"
+		end
+	end
+
+	class NotTest
+		def filter
+			EMPromise.resolve(yield)
+		end
+	end
+
+	class Test
+		def initialize(redis, key)
+			@redis = redis
+			@key = key
+		end
+
+		def filter
+			@redis.incr(@key).then { nil }
+		end
+	end
+
+	# The customer is who is being contacted
+	# The initiator is the phone number trying to reach them
+	def find(customer, initiator)
+		return EMPromise.resolve(NotTest.new) unless potential?(initiator)
+
+		testing?(customer).then do |active|
+			active ? Test.new(redis, key(customer)) : NotTest.new
+		end
+	end
+
+protected
+
+	# This is basically an optimization to bail early without hitting a
+	# datastore for 99% of all messages
+	def potential?(tel)
+		senders.include?(tel)
+	end
+
+	# This checks if this particular customer has reachability turned on
+	def testing?(customer)
+		redis.exists(key(customer)).then { |v| v == 1 }
+	end
+
+	# This gets the particular key for this check
+	def key(customer)
+		"jmp_customer_reachability_#{type}-#{customer.customer_id}"
+	end
+end

sgx_jmp.rb 🔗

@@ -96,6 +96,7 @@ require_relative "lib/patches_for_sentry"
 require_relative "lib/payment_methods"
 require_relative "lib/paypal_done"
 require_relative "lib/postgres"
+require_relative "lib/reachability_repo"
 require_relative "lib/registration"
 require_relative "lib/transaction"
 require_relative "lib/tel_selections"
@@ -279,7 +280,13 @@ before nil, to: /\Acustomer_/, from: /(\A|@)#{CONFIG[:sgx]}(\/|\Z)/ do |s|
 	Sentry.get_current_scope.set_transaction_name("stanza_customer")
 	CustomerRepo.new(set_user: Sentry.method(:set_user)).find(
 		s.to.node.delete_prefix("customer_")
-	).then { |customer| customer.stanza_to(s) }
+	).then do |customer|
+		ReachabilityRepo::SMS.new.find(customer, s.from.node).then do |reach|
+			reach.filter do
+				customer.stanza_to(s)
+			end
+		end
+	end
 
 	halt
 end