Detailed changes
@@ -0,0 +1,28 @@
+form!
+title "Reachability"
+
+field(
+ var: "tel",
+ datatype: "html:tel",
+ required: true,
+ label: "Number to test"
+)
+
+field(
+ var: "reachability_tel",
+ type: "list-single",
+ options: CONFIG[:reachability_senders]
+ .map { |tel| { label: tel, value: tel } },
+ label: "Number to send test prompt to",
+ desc: "leave blank to not send a prompt"
+)
+
+field(
+ var: "type",
+ type: "list-single",
+ options: [
+ { label: "SMS", value: "sms" },
+ { label: "Voice", value: "voice" }
+ ],
+ label: "Type of test to perform"
+)
@@ -0,0 +1,8 @@
+result!
+title "Reachability Result"
+
+field(
+ var: "count",
+ value: @count,
+ label: "Received requests so far"
+)
@@ -40,7 +40,7 @@ class BackendSgx
domain: jid.domain,
node: jid.node || stanza.to.node
)
- stanza.from = from_jid.with(resource: stanza.from.resource)
+ stanza.from = from_jid.with(resource: stanza.from&.resource)
end
end
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require "value_semantics/monkey_patched"
+
+require_relative "customer"
+require_relative "form_template"
+require_relative "form_to_h"
+require_relative "reachability_repo"
+
+class ReachabilityForm
+ using FormToH
+
+ REPOS = {
+ "sms" => ReachabilityRepo::SMS,
+ "voice" => ReachabilityRepo::Voice
+ }.freeze
+
+ class Result
+ value_semantics do
+ repo ReachabilityRepo
+ target Customer
+ sender Either(String, nil)
+ end
+
+ def prompt
+ return unless sender
+
+ Blather::Stanza::Message.new.tap do |m|
+ m.body = "/#{repo.type}"
+ # stanza_from will fix domain
+ m.to = Blather::JID.new("#{sender}@sgx-jmp")
+ end
+ end
+ end
+
+ def initialize(customer_repo)
+ @customer_repo = customer_repo
+ end
+
+ def render
+ FormTemplate.render("reachability")
+ end
+
+ def render_result(count)
+ FormTemplate.render("reachability_result", count: count)
+ end
+
+ def parse(form)
+ params = form.to_h
+ tel = cleanup_tel(params["tel"])
+
+ sender = cleanup_sender(params["reachability_tel"])
+
+ repo = REPOS[params["type"]]
+ raise "Type is invalid" unless repo
+
+ find_target(tel).then { |target|
+ Result.new(target: target, repo: repo.new, sender: sender)
+ }
+ end
+
+protected
+
+ def find_target(tel)
+ @customer_repo.find_by_tel(tel)
+ end
+
+ def cleanup_sender(str)
+ return nil unless str
+
+ str = str.strip
+ return nil if str.empty?
+
+ unless CONFIG[:reachability_senders].include?(str)
+ raise "Sender not in whitelist"
+ end
+
+ str
+ end
+
+ def cleanup_tel(str)
+ "+1#{str.gsub(/\A\+?1?/, '')}"
+ end
+end
@@ -8,6 +8,8 @@ class ReachabilityRepo
senders ArrayOf(String), default: CONFIG[:reachability_senders]
end
+ EXPIRY = 60 * 60 # 1 hr
+
class SMS < self
def type
"sms"
@@ -47,6 +49,13 @@ class ReachabilityRepo
end
end
+ # This creates the keys if they don't exist, and returns the value
+ def get_or_create(customer)
+ redis.set(key(customer), 0, "NX", "EX", EXPIRY).then {
+ redis.get(key(customer))
+ }
+ end
+
protected
# This is basically an optimization to bail early without hitting a
@@ -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_form"
require_relative "lib/reachability_repo"
require_relative "lib/registration"
require_relative "lib/transaction"
@@ -824,6 +825,35 @@ Command.new(
end
}.register(self).then(&CommandList.method(:register))
+Command.new(
+ "reachability",
+ "Test Reachability",
+ list_for: ->(customer: nil, **) { customer&.admin? }
+) {
+ Command.customer.then do |customer|
+ raise AuthError, "You are not an admin" unless customer&.admin?
+
+ form = ReachabilityForm.new(CustomerRepo.new)
+
+ Command.reply { |reply|
+ reply.allowed_actions = [:next]
+ reply.command << form.render
+ }.then { |response|
+ form.parse(response.form)
+ }.then { |result|
+ result.repo.get_or_create(result.target).then { |v|
+ result.target.stanza_from(result.prompt) if result.prompt
+
+ Command.finish { |reply|
+ reply.command << form.render_result(v)
+ }
+ }
+ }.catch_only(RuntimeError) { |e|
+ Command.finish(e, type: :error)
+ }
+ end
+}.register(self).then(&CommandList.method(:register))
+
Command.new(
"snikket",
"Launch Snikket Instance",