Skip to content

Commit

Permalink
Network API initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
superchilled committed Jul 24, 2024
1 parent 2c04bc4 commit 53fb8b3
Show file tree
Hide file tree
Showing 10 changed files with 291 additions and 3 deletions.
4 changes: 3 additions & 1 deletion lib/vonage/keys.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ def camelcase(hash)
'max_duration',
'partial_captions',
'status_callback_url',
'audio_rate'
'audio_rate',
'phone_number',
'max_age'
]
hash.transform_keys do |k|
if exceptions.include?(k.to_s)
Expand Down
2 changes: 1 addition & 1 deletion lib/vonage/namespace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def build_request(path:, type: Get, params: {})
end

# Set BasicAuth if neeeded
authentication.update(uri)
# authentication.update(uri)

# instantiate request
request = type.new(uri)
Expand Down
22 changes: 22 additions & 0 deletions lib/vonage/network_authentication.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# typed: true
# frozen_string_literal: true

module Vonage
class NetworkAuthentication < AbstractAuthentication
def update(object, data)
return unless object.is_a?(Net::HTTPRequest)

token = self.public_send(data[:auth_flow]).token(data).access_token

object['Authorization'] = 'Bearer ' + token
end

def client
@client ||= Client.new(@config)
end

def server
@server ||= Server.new(@config)
end
end
end
41 changes: 41 additions & 0 deletions lib/vonage/network_authentication/client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# typed: true
# frozen_string_literal: true

module Vonage
class NetworkAuthentication::Client < Namespace
extend T::Sig

# include NetworkAuthentication::TokenRequest

self.authentication = BearerToken

self.host = :vonage_host

self.request_headers['Content-Type'] = 'application/x-www-form-urlencoded'

def token(**params)
request(
'/oauth2/token',
params: {
grant_type: 'authorization_code',
code: params[:oidc_auth_code],
redirect_uri: params[:redirect_uri]
},
type: Post
)
end

def self.generate_oidc_uri(purpose:, api_scope:, login_hint:, redirect_uri:, state: nil)
scope = "openid+dpv:#{purpose}##{api_scope}"
uri = "https://oidc.idp.vonage.com/oauth2/auth?" +
"client_id=#{@config.application_id}" +
"&response_type=code" +
"&scope=#{scope}" +
"&login_hint=#{login_hint}" +
"&redirect_uri=#{redirect_uri}"

uri += "&state=#{state}" if state
uri
end
end
end
37 changes: 37 additions & 0 deletions lib/vonage/network_authentication/server.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# typed: true
# frozen_string_literal: true

module Vonage
class NetworkAuthentication::Server < Namespace
extend T::Sig

include TokenRequest

self.authentication = BearerToken

self.host = :vonage_host

self.request_headers['Content-Type'] = 'application/x-www-form-urlencoded'

def generate_token(**params)
auth_request_id = bc_authorize(**params).auth_request_id

token(
grant_type: 'urn:openid:params:grant-type:ciba',
auth_req_id: auth_request_id
).access_token
end

def bc_authorize(purpose:, api_scope:, login_hint:)
scope = "openid+dpv:#{purpose}##{api_scope}"
request(
"/oauth2/bc-authorize",
params: {
scope: scope,
login_hint: login_hint
},
type: Post
)
end
end
end
19 changes: 19 additions & 0 deletions lib/vonage/network_authentication/token_request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# typed: true
# frozen_string_literal: true

module Vonage
module NetworkAuthentication::TokenRequest
extend T::Sig

# Request an authentication token for use with Network APIs.
#
# @see https://developer.vonage.com/en/api/camara/auth#token
def token(**params)
request(
'/oauth2/token',
params: params,
type: Post
)
end
end
end
53 changes: 53 additions & 0 deletions lib/vonage/network_number_verification.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# typed: strict
# frozen_string_literal: true
require 'phonelib'

module Vonage
class NetworkNumberVerification < Namespace
extend T::Sig
include Keys

self.authentication = NetworkAuthentication

self.host = :vonage_host

self.request_body = JSON

# Make fraud check requests with a phone number by looking up fraud score and/or by checking sim swap status.
#
# @example
# response = client.number_insight_2.fraud_check(type: 'phone', phone: '447900000000', insights: ['fraud_score'])
#
# @param [required, String] :type The type of number to check.
# Accepted value is “phone” when a phone number is provided.
#
# @param [required, String] :phone A single phone number that you need insight about in the E.164 format.
#
# @param [required, Array] :insights An array of strings indicating the fraud check insights required for the number.
# Must be least one of: `fraud_score`, `sim_swap`
#
# @return [Response]
#
# @see https://developer.vonage.com/en/api/number-insight.v2#fraud_check
#
sig { params(phone_number: String, auth_data: Hash, hashed: T::Boolean).returns(Vonage::Response) }
def verify(phone_number:, auth_data:, hashed: false)
raise ArgumentError.new("`phone_number` must be in E.164 format") unless unless Phonelib.parse(phone_number).valid? || hashed == true
raise ArgumentError.new("`phone_number` must be prepended with a `+`") unless unless phone_number.start_with?('+') || hashed == true

params = {phone_number: phone_number}
params[:hashed_phone_number] = params.delete(:phone_number) if hashed == true

request(
'/camara/number-verification/v031/verify',
params: camelcase(params),
type: Post,
auth_data: {
oidc_auth_code: auth_data[:oidc_auth_code],
redirect_uri: auth_data[:redirect_uri],
auth_flow: :client
}
)
end
end
end
78 changes: 78 additions & 0 deletions lib/vonage/network_sim_swap.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# typed: strict
# frozen_string_literal: true
require 'phonelib'

module Vonage
class NetworkSIMSwap < Namespace
extend T::Sig
include Keys

self.authentication = NetworkAuthentication

self.host = :vonage_host

self.request_body = JSON

# Make fraud check requests with a phone number by looking up fraud score and/or by checking sim swap status.
#
# @example
# response = client.number_insight_2.fraud_check(type: 'phone', phone: '447900000000', insights: ['fraud_score'])
#
# @param [required, String] :type The type of number to check.
# Accepted value is “phone” when a phone number is provided.
#
# @param [required, String] :phone A single phone number that you need insight about in the E.164 format.
#
# @param [required, Array] :insights An array of strings indicating the fraud check insights required for the number.
# Must be least one of: `fraud_score`, `sim_swap`
#
# @return [Response]
#
# @see https://developer.vonage.com/en/api/number-insight.v2#fraud_check
#
sig { params(phone_number: String, max_age: T.nilable(Integer)).returns(Vonage::Response) }
def check(phone_number:, max_age: nil)
raise ArgumentError.new("`phone_number` must be in E.164 format") unless unless Phonelib.parse(phone_number).valid?
raise ArgumentError.new("`phone_number` must be prepended with a `+`") unless unless phone_number.start_with?('+')
if max_age
raise ArgumentError.new("`max_age` must be an integer") unless max_age.is_a?(Integer)
raise ArgumentError.new("`max_age` must between 1 and 2400") unless max_age.between?(1, 2400)
end

params = {phone_number: phone_number}
params[:max_age] = max_age if max_age

request(
'/camara/sim-swap/v040/check',
params: camelcase(params),
type: Post,
auth_data: {
login_hint: phone_number,
purpose: 'FraudPreventionAndDetection',
api_scope: 'check-sim-swap',
auth_flow: :server
}
)
end

sig { params(phone_number: String).returns(Vonage::Response) }
def retrieve_date(phone_number:)
raise ArgumentError.new("`phone_number` must be in E.164 format") unless unless Phonelib.parse(phone_number).valid?
raise ArgumentError.new("`phone_number` must be prepended with a `+`") unless unless phone_number.start_with?('+')

params = {phone_number: phone_number}

request(
'/camara/sim-swap/v040/retrieve-date',
params: camelcase(params),
type: Post,
auth_data: {
login_hint: phone_number,
purpose: 'FraudPreventionAndDetection',
api_scope: 'retrieve-sim-swap-date',
auth_flow: :server
}
)
end
end
end
34 changes: 34 additions & 0 deletions test/vonage/network_authentication/client_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# typed: false
require_relative '../test'

class Vonage::NetworkAuthentication::ClientTest < Vonage::Test
def client
Vonage::NetworkAuthentication::Client.new(config)
end

def token_uri
"https://api-eu.vonage.com/oauth2/token"
end

def test_token_method
grant_type = 'authorization_code'
code = '0dadaeb4-7c79-4d39-b4b0-5a6cc08bf537'
redirect_uri = 'https://example.com/callback'

request_params = {
grant_type: grant_type,
code: code,
redirect_uri: redirect_uri
}

stub_request(:post, token_uri).with(request(body: request_params, headers: headers)).to_return(response)

response = client.token(
grant_type: grant_type,
oidc_auth_code: code,
redirect_uri: redirect_uri
)

assert_kind_of Vonage::Response, response
end
end
4 changes: 3 additions & 1 deletion test/vonage/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,9 @@ def vonage_host

def request(body: nil, query: nil, headers: {}, auth: nil)
headers['Authorization'] = auth || authorization
headers['Content-Type'] = 'application/json' if body
if body
headers['Content-Type'] = 'application/json' unless headers.has_key?('Content-Type')
end

{ headers: headers, body: body, query: query }.compact
end
Expand Down

0 comments on commit 53fb8b3

Please sign in to comment.