M Gemfile => Gemfile +1 -1
@@ 18,7 18,7 @@ gem "multibases"
gem "multihashes"
gem "ougai"
gem "roda"
-gem "ruby-bandwidth-iris"
+gem "ruby-bandwidth-iris", git: "https://github.com/singpolyma/ruby-bandwidth-iris", branch: "sip_credential"
gem "sentry-ruby", "<= 4.3.1"
gem "slim"
gem "statsd-instrument", git: "https://github.com/singpolyma/statsd-instrument.git", branch: "graphite"
M config-schema.dhall => config-schema.dhall +1 -1
@@ 1,7 1,6 @@
{ activation_amount : Natural
, admins : List Text
, adr : Text
-, bandwidth_app : Text
, bandwidth_peer : Text
, bandwidth_site : Text
, braintree :
@@ 44,6 43,7 @@
}
, server : { host : Text, port : Natural }
, sgx : Text
+, sip : { app : Text, realm : Text }
, sip_host : Text
, upstream_domain : Text
, web : < Inet : { interface : Text, port : Natural } | Unix : Text >
M config.dhall.sample => config.dhall.sample +4 -1
@@ 33,7 33,6 @@ in
},
bandwidth_site = "",
bandwidth_peer = "",
- bandwidth_app = "", -- This can be any voice app
braintree = {
environment = "sandbox",
merchant_id = "",
@@ 47,6 46,10 @@ in
xep0157 = [
{ var = "support-addresses", value = "xmpp:+14169938000@cheogram.com", label = "Support" }
],
+ sip = {
+ realm = "",
+ app = ""
+ },
notify_admin = "muc@example.com",
sip_host = "sip.jmp.chat",
plans = [
M lib/backend_sgx.rb => lib/backend_sgx.rb +7 -1
@@ 24,7 24,9 @@ class BackendSgx
ibr.username = creds[:username]
ibr.password = creds[:password]
ibr.phone = tel
- IQ_MANAGER.write(ibr)
+ IQ_MANAGER.write(ibr).then do
+ with(registered?: irb)
+ end
end
def stanza(s)
@@ 34,6 36,10 @@ class BackendSgx
end
end
+ def set_fwd(uri)
+ REDIS.set("catapult_fwd-#{registered?.phone}", uri)
+ end
+
def set_fwd_timeout(timeout)
REDIS.set("catapult_fwd_timeout-#{from_jid}", timeout)
end
M lib/customer.rb => lib/customer.rb +2 -4
@@ 23,7 23,7 @@ class Customer
def_delegators :@plan, :active?, :activate_plan_starting_now, :bill_plan,
:currency, :merchant_account, :plan_name, :auto_top_up_amount
def_delegators :@sgx, :register!, :registered?, :set_ogm_url,
- :set_fwd_timeout, :fwd, :transcription_enabled
+ :set_fwd, :fwd, :transcription_enabled
def_delegators :@usage, :usage_report, :message_usage, :incr_message_usage
def initialize(
@@ 92,9 92,7 @@ class Customer
end
def reset_sip_account
- SipAccount::New.new(username: customer_id).put.catch do
- sip_account.then { |acct| acct.with_random_password.put }
- end
+ sip_account.with_random_password.put
end
def btc_addresses
M lib/registration.rb => lib/registration.rb +3 -3
@@ 459,11 459,11 @@ class Registration
end
def customer_active_tel_purchased
- @customer.register!(@tel).catch(&method(:raise_setup_error)).then {
+ @customer.register!(@tel).catch(&method(:raise_setup_error)).then { |sgx|
EMPromise.all([
REDIS.del("pending_tel_for-#{@customer.jid}"),
- REDIS.set("catapult_fwd-#{@tel}", cheogram_sip_addr),
- @customer.set_fwd_timeout(25) # ~5 seconds / ring, 5 rings
+ sgx.set_fwd(cheogram_sip_addr),
+ sgx.set_fwd_timeout(25) # ~5 seconds / ring, 5 rings
])
}.then do
Command.finish("Your JMP account has been activated as #{@tel}")
M lib/sip_account.rb => lib/sip_account.rb +49 -61
@@ 1,44 1,38 @@
# frozen_string_literal: true
-require "em-synchrony/em-http" # For aget vs get
+require "digest"
require "securerandom"
require "value_semantics/monkey_patched"
-require_relative "./catapult"
-require_relative "./mn_words"
+require_relative "mn_words"
class SipAccount
def self.find(name)
- CATAPULT.endpoint_find(name).then do |found|
- next New.new(username: name) unless found
-
- new(username: found["name"], url: found["url"])
- end
+ new(BandwidthIris::SipCredential.get(name))
+ rescue BandwidthIris::Errors::GenericError # 404
+ New.new(BandwidthIris::SipCredential.new(
+ user_name: name,
+ realm: CONFIG[:sip][:realm],
+ http_voice_v2_app_id: CONFIG[:sip][:app]
+ ))
end
- module Common
- def with_random_password
- with(password: MN_WORDS.sample(3).join(" "))
- end
-
- protected
-
- def create
- CATAPULT.create_endpoint(
- name: username,
- credentials: { password: password }
- ).then do |url|
- with(url: url)
- end
- end
+ def initialize(api_object, password: nil)
+ @api_object = api_object
+ @password = password
end
- include Common
+ def with(password:)
+ self.class.new(@api_object.class.new(@api_object.to_data.merge(
+ hash1: Digest::MD5.hexdigest("#{username}:#{server}:#{password}"),
+ hash1b: Digest::MD5.hexdigest(
+ "#{username}:#{server}:#{server}:#{password}"
+ )
+ )), password: password)
+ end
- value_semantics do
- url String
- username String
- password Either(String, nil), default: nil
+ def with_random_password
+ with(password: MN_WORDS.sample(3).join(" "))
end
def form
@@ 47,56 41,50 @@ class SipAccount
form.instructions = "These are your new SIP credentials"
form.fields = [
- { var: "username", value: username, label: "Username" },
- { var: "password", value: password, label: "Password" },
- { var: "server", value: server, label: "Server" }
+ { var: "username", value: username, label: "Username", type: :fixed },
+ { var: "password", value: @password, label: "Password", type: :fixed },
+ { var: "server", value: server, label: "Server", type: :fixed }
]
form
end
def put
- delete.then { create }
+ @api_object.update(
+ hash1: @api_object.hash1,
+ hash1b: @api_object.hash1b,
+ realm: server,
+ http_voice_v2_app_id: @api_object.http_voice_v2_app_id
+ )
+ self
end
def delete
- CATAPULT.delete(url).then do |http|
- unless http.response_header.status == 200
- raise "Delete old SIP account failed"
- end
-
- self
- end
+ @api_object.delete
end
-protected
-
- protected :url, :username, :password
+ def username
+ @api_object.user_name.to_s
+ end
def server
- CATAPULT.sip_host
+ @api_object.realm
end
- class New
- include Common
-
- value_semantics do
- username String
- password String, default_generator: -> { MN_WORDS.sample(3).join(" ") }
- end
+ def uri
+ "sip:#{username}@#{server}"
+ end
+ class New < SipAccount
def put
- create
- end
-
- def with(**kwargs)
- if kwargs.key?(:url)
- SipAccount.new(internal_to_h.merge(kwargs))
- else
- super
- end
+ BandwidthIris::SipCredential.create(
+ user_name: username,
+ hash1: @api_object.hash1,
+ hash1b: @api_object.hash1b,
+ realm: server,
+ http_voice_v2_app_id: @api_object.http_voice_v2_app_id
+ )
+ self
end
-
- protected :username, :password
end
end
M sgx_jmp.rb => sgx_jmp.rb +27 -5
@@ 475,7 475,7 @@ Command.new(
CONFIG[:creds][:account],
body: customer.fwd.create_call_request do |cc|
cc.from = customer.registered?.phone
- cc.application_id = CONFIG[:bandwidth_app]
+ cc.application_id = CONFIG[:sip][:app]
cc.answer_url = "#{CONFIG[:web_root]}/ogm/start?" \
"customer_id=#{customer.customer_id}"
end
@@ 599,11 599,33 @@ Command.new(
Command.new(
"reset sip account",
- "Create or Reset SIP Account"
+ "Create or Reset SIP Account",
+ customer_repo: CustomerRepo.new(sgx_repo: Bwmsgsv2Repo.new)
) {
- Command.customer.then(&:reset_sip_account).then do |sip_account|
- Command.finish do |reply|
- reply.command << sip_account.form
+ Command.customer.then do |customer|
+ sip_account = customer.reset_sip_account
+ Command.reply { |reply|
+ reply.allowed_actions = [:next]
+ form = sip_account.form
+ form.type = :form
+ form.fields += [{
+ type: :boolean, var: "change_fwd",
+ label: "Should inbound calls forward to this SIP account?"
+ }]
+ reply.command << form
+ }.then do |fwd|
+ if ["1", "true"].include?(fwd.form.field("change_fwd")&.value.to_s)
+ # Migrate location if needed
+ BandwidthIris::SipPeer.new(
+ site_id: CONFIG[:bandwidth_site],
+ id: CONFIG[:bandwidth_peer]
+ ).move_tns([customer.registered?.phone])
+ customer.set_fwd(sip_account.uri).then do
+ Command.finish("Inbound calls will now forward to SIP.")
+ end
+ else
+ Command.finish
+ end
end
end
}.register(self).then(&CommandList.method(:register))
D test/data/catapult_create_sip.json => test/data/catapult_create_sip.json +0 -1
@@ 1,1 0,0 @@
-{"applicationId":"catapult_app","name":"12345","credentials":{"password":"old password"}}
M test/test_customer.rb => test/test_customer.rb +14 -42
@@ 13,14 13,6 @@ CustomerPlan::DB = Minitest::Mock.new
CustomerUsage::REDIS = Minitest::Mock.new
CustomerUsage::DB = Minitest::Mock.new
-class SipAccount
- public :username, :url
-
- class New
- public :username
- end
-end
-
class CustomerTest < Minitest::Test
def test_bill_plan_activate
CustomerPlan::DB.expect(:transaction, nil) do |&block|
@@ 152,14 144,13 @@ class CustomerTest < Minitest::Test
def test_sip_account_new
req = stub_request(
:get,
- "https://api.catapult.inetwork.com/v1/users/" \
- "catapult_user/domains/catapult_domain/endpoints?page=0&size=1000"
+ "https://dashboard.bandwidth.com/v1.0/accounts//sipcredentials/test"
).with(
headers: {
- "Authorization" => "Basic Y2F0YXB1bHRfdG9rZW46Y2F0YXB1bHRfc2VjcmV0"
+ "Authorization" => "Basic Og=="
}
).to_return(status: 404)
- sip = customer.sip_account.sync
+ sip = customer.sip_account
assert_kind_of SipAccount::New, sip
assert_equal "test", sip.username
assert_requested req
@@ 169,52 160,33 @@ class CustomerTest < Minitest::Test
def test_sip_account_existing
req1 = stub_request(
:get,
- "https://api.catapult.inetwork.com/v1/users/" \
- "catapult_user/domains/catapult_domain/endpoints?page=0&size=1000"
+ "https://dashboard.bandwidth.com/v1.0/accounts//sipcredentials/test"
).with(
headers: {
- "Authorization" => "Basic Y2F0YXB1bHRfdG9rZW46Y2F0YXB1bHRfc2VjcmV0"
+ "Authorization" => "Basic Og=="
}
- ).to_return(status: 200, body: [
- { name: "NOTtest", domainId: "domain", id: "endpoint" }
- ].to_json)
-
- req2 = stub_request(
- :get,
- "https://api.catapult.inetwork.com/v1/users/" \
- "catapult_user/domains/catapult_domain/endpoints?page=1&size=1000"
- ).with(
- headers: {
- "Authorization" => "Basic Y2F0YXB1bHRfdG9rZW46Y2F0YXB1bHRfc2VjcmV0"
+ ).to_return(status: 200, body: {
+ SipCredential: {
+ UserName: "test",
+ Realm: "sip.example.com"
}
- ).to_return(status: 200, body: [
- { name: "test", domainId: "domain", id: "endpoint" }
- ].to_json)
+ }.to_xml)
- sip = customer.sip_account.sync
+ sip = customer.sip_account
assert_kind_of SipAccount, sip
assert_equal "test", sip.username
- assert_equal(
- "https://api.catapult.inetwork.com/v1/users/" \
- "catapult_user/domains/domain/endpoints/endpoint",
- sip.url
- )
assert_requested req1
- assert_requested req2
end
em :test_sip_account_existing
def test_sip_account_error
stub_request(
:get,
- "https://api.catapult.inetwork.com/v1/users/" \
- "catapult_user/domains/catapult_domain/endpoints?page=0&size=1000"
- ).to_return(status: 400)
+ "https://dashboard.bandwidth.com/v1.0/accounts//sipcredentials/test"
+ ).to_return(status: 404)
- assert_raises(RuntimeError) do
- customer.sip_account.sync
- end
+ assert_equal "test", customer.sip_account.username
end
em :test_sip_account_error
M test/test_helper.rb => test/test_helper.rb +4 -0
@@ 94,6 94,10 @@ CONFIG = {
USD: "merchant_usd"
}
},
+ sip: {
+ realm: "sip.example.com",
+ app: "sipappid"
+ },
credit_card_url: ->(*) { "http://creditcard.example.com" },
electrum_notify_url: ->(*) { "http://notify.example.com" },
upstream_domain: "example.net"
M test/test_registration.rb => test/test_registration.rb +8 -6
@@ 564,6 564,11 @@ class RegistrationTest < Minitest::Test
}
).to_return(status: 201)
Registration::Finish::REDIS.expect(
+ :del,
+ nil,
+ ["pending_tel_for-test@example.net"]
+ )
+ BackendSgx::REDIS.expect(
:set,
nil,
[
@@ 571,11 576,6 @@ class RegistrationTest < Minitest::Test
"sip:test%40example.net@sip.cheogram.com"
]
)
- Registration::Finish::REDIS.expect(
- :del,
- nil,
- ["pending_tel_for-test@example.net"]
- )
BackendSgx::REDIS.expect(
:set,
nil,
@@ 597,7 597,9 @@ class RegistrationTest < Minitest::Test
execute_command(blather: blather) do
@sgx.expect(
:register!,
- EMPromise.resolve(OpenStruct.new(error?: false)),
+ EMPromise.resolve(@sgx.with(registered?: IBR.new.tap do |ibr|
+ ibr.phone = "+15555550000"
+ end)),
["+15555550000"]
)
M test/test_sip_account.rb => test/test_sip_account.rb +45 -57
@@ 4,21 4,18 @@ require "test_helper"
require "sip_account"
class SipAccount
- public :password, :url
-
- class New
- public :password
- end
+ attr_reader :password
end
class SipAccountTest < Minitest::Test
def setup
@sip = SipAccount.new(
- url: "https://api.catapult.inetwork.com/v1/" \
- "users/catapult_user/domains/catapult_domain/endpoints/test",
- username: "12345",
- password: "old password"
- )
+ BandwidthIris::SipCredential.new(
+ user_name: "12345",
+ realm: "sip.example.com",
+ http_voice_v2_app_id: "sipappid"
+ )
+ ).with(password: "old password")
end
def test_with_random_password
@@ 32,61 29,52 @@ class SipAccountTest < Minitest::Test
form = @sip.form
assert_equal "12345", form.field("username").value
assert_equal "old password", form.field("password").value
- assert_equal "host.bwapp.io.example.com", form.field("server").value
+ assert_equal "sip.example.com", form.field("server").value
end
def test_put
- delete = stub_request(:delete, @sip.url).with(
- headers: {
- "Authorization" => "Basic Y2F0YXB1bHRfdG9rZW46Y2F0YXB1bHRfc2VjcmV0"
- }
- ).to_return(status: 200)
-
- post = stub_request(
- :post,
- "https://api.catapult.inetwork.com/v1/users/" \
- "catapult_user/domains/catapult_domain/endpoints"
+ put = stub_request(
+ :put,
+ "https://dashboard.bandwidth.com/v1.0/accounts//sipcredentials/12345"
).with(
- body: open(__dir__ + "/data/catapult_create_sip.json").read.chomp,
+ body: {
+ Hash1: "73b05bcaf9096438c978aecff5f7cc45",
+ Hash1b: "2b7fe68f6337ef4db29e752684a18db4",
+ Realm: "sip.example.com",
+ HttpVoiceV2AppId: "sipappid"
+ }.to_xml(indent: 0, root: "SipCredential"),
headers: {
- "Authorization" => "Basic Y2F0YXB1bHRfdG9rZW46Y2F0YXB1bHRfc2VjcmV0",
- "Content-Type" => "application/json"
+ "Authorization" => "Basic Og=="
}
).to_return(
status: 201,
headers: { "Location" => "http://example.com/endpoint" }
)
- new_sip = @sip.put.sync
- assert_equal "http://example.com/endpoint", new_sip.url
- assert_requested delete
- assert_requested post
+ new_sip = @sip.put
+ assert_equal "12345", new_sip.username
+ assert_requested put
end
em :test_put
- def test_put_delete_fail
- stub_request(:delete, @sip.url).to_return(status: 400)
- assert_raises(RuntimeError) { @sip.put.sync }
- end
- em :test_put_delete_fail
-
- def test_put_post_fail
- stub_request(:delete, @sip.url).to_return(status: 200)
+ def test_put_fail
stub_request(
- :post,
- "https://api.catapult.inetwork.com/v1/users/" \
- "catapult_user/domains/catapult_domain/endpoints"
+ :put,
+ "https://dashboard.bandwidth.com/v1.0/accounts//sipcredentials/12345"
).to_return(status: 400)
- assert_raises(RuntimeError) { @sip.put.sync }
+ assert_raises(BandwidthIris::Errors::GenericError) { @sip.put }
end
- em :test_put_post_fail
+ em :test_put_fail
class NewTest < Minitest::Test
def setup
- @sip = SipAccount::New.new(
- username: "12345",
- password: "old password"
- )
+ @sip = SipAccount.new(
+ BandwidthIris::SipCredential.new(
+ user_name: "12345",
+ realm: "sip.example.com",
+ http_voice_v2_app_id: "sipappid"
+ )
+ ).with(password: "old password")
end
def test_with_random_password
@@ 98,22 86,22 @@ class SipAccountTest < Minitest::Test
def test_put
post = stub_request(
- :post,
- "https://api.catapult.inetwork.com/v1/users/" \
- "catapult_user/domains/catapult_domain/endpoints"
+ :put,
+ "https://dashboard.bandwidth.com/v1.0/accounts//sipcredentials/12345"
).with(
- body: open(__dir__ + "/data/catapult_create_sip.json").read.chomp,
+ body: {
+ Hash1: "73b05bcaf9096438c978aecff5f7cc45",
+ Hash1b: "2b7fe68f6337ef4db29e752684a18db4",
+ Realm: "sip.example.com",
+ HttpVoiceV2AppId: "sipappid"
+ }.to_xml(indent: 0, root: "SipCredential"),
headers: {
- "Authorization" => "Basic Y2F0YXB1bHRfdG9rZW46Y2F0YXB1bHRfc2VjcmV0",
- "Content-Type" => "application/json"
+ "Authorization" => "Basic Og=="
}
- ).to_return(
- status: 201,
- headers: { "Location" => "http://example.com/endpoint" }
- )
+ ).to_return(status: 201)
- new_sip = @sip.put.sync
- assert_equal "http://example.com/endpoint", new_sip.url
+ new_sip = @sip.put
+ assert_equal "12345", new_sip.username
assert_requested post
end
em :test_put