smile-identity-core 1.2.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/release.yml +18 -0
- data/.github/workflows/test.yml +36 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +6 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +75 -30
- data/Gemfile +3 -1
- data/Gemfile.lock +35 -7
- data/README.md +33 -464
- data/Rakefile +5 -3
- data/bin/console +4 -3
- data/examples/biometric_kyc.rb +67 -0
- data/examples/business_verification.rb +45 -0
- data/examples/document_verification.rb +74 -0
- data/examples/enhanced_kyc.rb +39 -0
- data/examples/example-project/Gemfile +7 -0
- data/examples/example-project/Gemfile.lock +25 -0
- data/examples/example-project/README.md +14 -0
- data/examples/example-project/sample.env +4 -0
- data/examples/example-project/smart_bank.rb +216 -0
- data/examples/smart_selfie_authentication.rb +56 -0
- data/lib/smile-identity-core/business_verification.rb +106 -0
- data/lib/smile-identity-core/constants/env.rb +13 -0
- data/lib/smile-identity-core/constants/image_type.rb +14 -0
- data/lib/smile-identity-core/constants/job_type.rb +13 -0
- data/lib/smile-identity-core/id_api.rb +41 -82
- data/lib/smile-identity-core/signature.rb +15 -36
- data/lib/smile-identity-core/utilities.rb +28 -55
- data/lib/smile-identity-core/validations.rb +32 -0
- data/lib/smile-identity-core/version.rb +4 -1
- data/lib/smile-identity-core/web_api.rb +87 -136
- data/lib/smile-identity-core.rb +11 -5
- data/smile-identity-core.gemspec +26 -21
- metadata +82 -22
- data/.travis.yml +0 -11
@@ -1,82 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'validations'
|
4
|
+
|
1
5
|
module SmileIdentityCore
|
6
|
+
# Allows you to query the Identity Information for an individual using their ID number
|
2
7
|
class IDApi
|
8
|
+
include Validations
|
9
|
+
|
10
|
+
REQUIRED_ID_INFO_FIELD = %i[country id_type id_number].freeze
|
3
11
|
|
4
12
|
def initialize(partner_id, api_key, sid_server)
|
5
13
|
@partner_id = partner_id.to_s
|
6
14
|
@api_key = api_key
|
7
15
|
|
8
|
-
@
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
}
|
14
|
-
@url = sid_server_mapping[sid_server.to_i]
|
15
|
-
else
|
16
|
-
@url = sid_server
|
17
|
-
end
|
16
|
+
@url = if sid_server !~ URI::DEFAULT_PARSER.make_regexp
|
17
|
+
SmileIdentityCore::ENV::SID_SERVER_MAPPING[sid_server.to_s]
|
18
|
+
else
|
19
|
+
sid_server
|
20
|
+
end
|
18
21
|
end
|
19
22
|
|
20
23
|
def submit_job(partner_params, id_info, options = {})
|
21
|
-
|
22
|
-
|
23
|
-
@use_new_signature = symbolize_keys(options || {}).fetch(:signature, false)
|
24
|
+
@partner_params = validate_partner_params(symbolize_keys(partner_params))
|
25
|
+
@id_info = validate_id_info(symbolize_keys(id_info), REQUIRED_ID_INFO_FIELD)
|
24
26
|
|
25
|
-
|
26
|
-
raise ArgumentError, 'Please ensure that you are setting your job_type to 5 to query ID Api'
|
27
|
+
unless [JobType::ENHANCED_KYC, JobType::BUSINESS_VERIFICATION].include?(@partner_params[:job_type].to_i)
|
28
|
+
raise ArgumentError, 'Please ensure that you are setting your job_type to 5 or 7 to query ID Api'
|
27
29
|
end
|
28
30
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
if partner_params == nil
|
34
|
-
raise ArgumentError, 'Please ensure that you send through partner params'
|
31
|
+
if partner_params[:job_type] == JobType::BUSINESS_VERIFICATION
|
32
|
+
return SmileIdentityCore::BusinessVerification
|
33
|
+
.new(@partner_id, @api_key, @url)
|
34
|
+
.submit_job(partner_params, id_info)
|
35
35
|
end
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
end
|
37
|
+
options = symbolize_keys(options || {})
|
38
|
+
@use_async_endpoint = options.fetch(:async, false)
|
40
39
|
|
41
|
-
|
42
|
-
unless partner_params[key] && !partner_params[key].nil? && !(partner_params[key].empty? if partner_params[key].is_a?(String))
|
43
|
-
raise ArgumentError, "Please make sure that #{key} is included in the partner params"
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
@partner_params = partner_params
|
48
|
-
end
|
49
|
-
|
50
|
-
def id_info=(id_info)
|
51
|
-
|
52
|
-
updated_id_info = id_info
|
53
|
-
|
54
|
-
if updated_id_info.nil? || updated_id_info.keys.length == 0
|
55
|
-
raise ArgumentError, 'Please make sure that id_info not empty or nil'
|
56
|
-
end
|
57
|
-
|
58
|
-
[:country, :id_type, :id_number].each do |key|
|
59
|
-
unless updated_id_info[key] && !updated_id_info[key].nil? && !updated_id_info[key].empty?
|
60
|
-
raise ArgumentError, "Please make sure that #{key} is included in the id_info"
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
@id_info = updated_id_info
|
40
|
+
setup_requests
|
65
41
|
end
|
66
42
|
|
67
43
|
private
|
68
44
|
|
69
|
-
def symbolize_keys
|
70
|
-
|
45
|
+
def symbolize_keys(params)
|
46
|
+
params.is_a?(Hash) ? params.transform_keys(&:to_sym) : params
|
71
47
|
end
|
72
48
|
|
73
49
|
def setup_requests
|
74
|
-
url = "#{@url}/id_verification"
|
75
|
-
|
76
50
|
request = Typhoeus::Request.new(
|
77
|
-
url,
|
51
|
+
"#{@url}/#{endpoint}",
|
78
52
|
method: 'POST',
|
79
|
-
headers: {'Content-Type'=>
|
53
|
+
headers: { 'Content-Type' => 'application/json' },
|
80
54
|
body: configure_json
|
81
55
|
)
|
82
56
|
|
@@ -88,39 +62,24 @@ module SmileIdentityCore
|
|
88
62
|
request.run
|
89
63
|
end
|
90
64
|
|
65
|
+
def endpoint
|
66
|
+
@use_async_endpoint ? 'async_id_verification' : 'id_verification'
|
67
|
+
end
|
68
|
+
|
91
69
|
def configure_json
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
70
|
+
signature_generator.generate_signature(Time.now.to_s)
|
71
|
+
.merge(@id_info)
|
72
|
+
.merge(
|
73
|
+
partner_id: @partner_id,
|
74
|
+
partner_params: @partner_params,
|
75
|
+
source_sdk: SmileIdentityCore::SOURCE_SDK,
|
76
|
+
source_sdk_version: SmileIdentityCore::VERSION
|
77
|
+
)
|
78
|
+
.to_json
|
98
79
|
end
|
99
80
|
|
100
81
|
def signature_generator
|
101
82
|
SmileIdentityCore::Signature.new(@partner_id, @api_key)
|
102
83
|
end
|
103
|
-
|
104
|
-
def signature(timestamp: Time.now.to_s)
|
105
|
-
signature = signature_generator.generate_signature(timestamp)[:signature]
|
106
|
-
{
|
107
|
-
signature: signature,
|
108
|
-
timestamp: timestamp
|
109
|
-
}
|
110
|
-
end
|
111
|
-
|
112
|
-
def sec_key(timestamp: Time.now.to_s)
|
113
|
-
sec_key = signature_generator.generate_sec_key(timestamp)[:sec_key]
|
114
|
-
{
|
115
|
-
sec_key: sec_key,
|
116
|
-
timestamp: timestamp
|
117
|
-
}
|
118
|
-
end
|
119
|
-
|
120
|
-
def request_security(use_new_signature: true)
|
121
|
-
return signature if use_new_signature
|
122
|
-
|
123
|
-
sec_key
|
124
|
-
end
|
125
84
|
end
|
126
85
|
end
|
@@ -1,54 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SmileIdentityCore
|
4
|
+
# Contains handy methods to generate and confirm signature for authentication
|
2
5
|
class Signature
|
3
|
-
|
4
6
|
def initialize(partner_id, api_key)
|
5
7
|
@api_key = api_key
|
6
8
|
@partner_id = partner_id
|
7
9
|
end
|
8
10
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
hash_signature = Digest::SHA256.hexdigest([@partner_id.to_i, @timestamp].join(":"))
|
14
|
-
public_key = OpenSSL::PKey::RSA.new(Base64.decode64(@api_key))
|
15
|
-
@sec_key = [Base64.encode64(public_key.public_encrypt(hash_signature)), hash_signature].join('|')
|
16
|
-
|
17
|
-
{
|
18
|
-
sec_key: @sec_key,
|
19
|
-
timestamp: @timestamp
|
20
|
-
}
|
21
|
-
rescue => e
|
22
|
-
raise e
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def confirm_sec_key(timestamp, sec_key)
|
27
|
-
begin
|
28
|
-
hash_signature = Digest::SHA256.hexdigest([@partner_id.to_i, timestamp].join(":"))
|
29
|
-
encrypted = sec_key.split('|')[0]
|
30
|
-
|
31
|
-
public_key = OpenSSL::PKey::RSA.new(Base64.decode64(@api_key))
|
32
|
-
decrypted = public_key.public_decrypt(Base64.decode64(encrypted))
|
33
|
-
|
34
|
-
decrypted == hash_signature
|
35
|
-
rescue => e
|
36
|
-
raise e
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def generate_signature(timestamp=Time.now.to_s)
|
11
|
+
# Generates a signature based on the specified timestamp (uses the current time by default)
|
12
|
+
#
|
13
|
+
# @return [Hash] containing both the signature and related timestamp
|
14
|
+
def generate_signature(timestamp = Time.now.to_s)
|
41
15
|
hmac = OpenSSL::HMAC.new(@api_key, 'sha256')
|
42
16
|
hmac.update(timestamp.to_s)
|
43
17
|
hmac.update(@partner_id)
|
44
|
-
hmac.update(
|
45
|
-
signature = Base64.strict_encode64(hmac.digest
|
18
|
+
hmac.update('sid_request')
|
19
|
+
@signature = Base64.strict_encode64(hmac.digest)
|
46
20
|
{
|
47
|
-
signature: signature,
|
21
|
+
signature: @signature,
|
48
22
|
timestamp: timestamp.to_s
|
49
23
|
}
|
50
24
|
end
|
51
25
|
|
26
|
+
# Confirms the signature against a newly generated signature based on the same timestamp
|
27
|
+
#
|
28
|
+
# @param [String] timestamp the timestamp to generate the signature from
|
29
|
+
# @param [String] msg_signature a previously generated signature, to be confirmed
|
30
|
+
# @return [Boolean] TRUE or FALSE
|
52
31
|
def confirm_signature(timestamp, msg_signature)
|
53
32
|
generate_signature(timestamp)[:signature] == msg_signature
|
54
33
|
end
|
@@ -1,22 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SmileIdentityCore
|
4
|
+
# A utility class to query job status
|
2
5
|
class Utilities
|
3
|
-
|
4
6
|
def initialize(partner_id, api_key, sid_server)
|
5
7
|
@partner_id = partner_id.to_s
|
6
8
|
@api_key = api_key
|
7
9
|
|
8
|
-
if
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
@url = sid_server_mapping[sid_server.to_i]
|
14
|
-
else
|
15
|
-
@url = sid_server
|
16
|
-
end
|
10
|
+
@url = if sid_server !~ URI::DEFAULT_PARSER.make_regexp
|
11
|
+
SmileIdentityCore::ENV::SID_SERVER_MAPPING[sid_server.to_s]
|
12
|
+
else
|
13
|
+
sid_server
|
14
|
+
end
|
17
15
|
|
18
16
|
@signature_connection = SmileIdentityCore::Signature.new(@partner_id, @api_key)
|
19
|
-
|
20
17
|
end
|
21
18
|
|
22
19
|
def get_job_status(user_id, job_id, options = {})
|
@@ -24,77 +21,53 @@ module SmileIdentityCore
|
|
24
21
|
options[:return_history] ||= false
|
25
22
|
options[:return_image_links] ||= false
|
26
23
|
|
27
|
-
|
28
|
-
|
24
|
+
query_job_status(configure_job_query(user_id, job_id,
|
25
|
+
options).merge(@signature_connection.generate_signature(Time.now.to_s)))
|
29
26
|
end
|
30
27
|
|
31
28
|
private
|
32
29
|
|
33
|
-
def symbolize_keys
|
34
|
-
|
30
|
+
def symbolize_keys(params)
|
31
|
+
params.is_a?(Hash) ? params.transform_keys(&:to_sym) : params
|
35
32
|
end
|
36
33
|
|
37
34
|
def query_job_status(request_json_data)
|
38
|
-
url = "#{@url}/job_status"
|
39
|
-
|
40
35
|
request = Typhoeus::Request.new(
|
41
|
-
url,
|
42
|
-
headers: {'Content-Type': 'application/json', 'Accept': 'application/json'},
|
36
|
+
"#{@url}/job_status",
|
37
|
+
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
|
43
38
|
method: :post,
|
44
39
|
body: request_json_data.to_json
|
45
40
|
)
|
46
41
|
|
47
42
|
request.on_complete do |response|
|
48
|
-
|
49
|
-
body = JSON.parse(response.body)
|
43
|
+
body = JSON.parse(response.body)
|
50
44
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
valid = @signature_connection.confirm_sec_key(body['timestamp'], body['signature'])
|
58
|
-
else
|
59
|
-
valid = @signature_connection.confirm_signature(body['timestamp'], body['signature'])
|
60
|
-
end
|
45
|
+
# NB: we have to trust that the server will return the right kind of
|
46
|
+
# timestamp (integer or string) for the signature, and the right kind
|
47
|
+
# of signature in the "signature" field. The best way to know what
|
48
|
+
# kind of validation to perform is by remembering which kind of
|
49
|
+
# security we started with.
|
50
|
+
valid = @signature_connection.confirm_signature(body['timestamp'], body['signature'])
|
61
51
|
|
62
|
-
|
63
|
-
raise "Unable to confirm validity of the job_status response"
|
64
|
-
end
|
52
|
+
raise 'Unable to confirm validity of the job_status response' unless valid
|
65
53
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
end
|
54
|
+
return body
|
55
|
+
rescue StandardError => e
|
56
|
+
raise e
|
70
57
|
end
|
71
58
|
|
72
59
|
request.run
|
73
60
|
end
|
74
61
|
|
75
|
-
def request_security(use_new_signature: false)
|
76
|
-
if use_new_signature
|
77
|
-
@timestamp = Time.now.to_s
|
78
|
-
{
|
79
|
-
signature: @signature_connection.generate_signature(@timestamp)[:signature],
|
80
|
-
timestamp: @timestamp,
|
81
|
-
}
|
82
|
-
else
|
83
|
-
@timestamp = Time.now.to_i
|
84
|
-
{
|
85
|
-
sec_key: @signature_connection.generate_sec_key(@timestamp)[:sec_key],
|
86
|
-
timestamp: @timestamp,
|
87
|
-
}
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
62
|
def configure_job_query(user_id, job_id, options)
|
92
63
|
{
|
93
64
|
user_id: user_id,
|
94
65
|
job_id: job_id,
|
95
66
|
partner_id: @partner_id,
|
96
67
|
image_links: options[:return_image_links],
|
97
|
-
history:
|
68
|
+
history: options[:return_history],
|
69
|
+
source_sdk: SmileIdentityCore::SOURCE_SDK,
|
70
|
+
source_sdk_version: SmileIdentityCore::VERSION
|
98
71
|
}
|
99
72
|
end
|
100
73
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SmileIdentityCore
|
4
|
+
module Validations # :nodoc:
|
5
|
+
def validate_partner_params(partner_params)
|
6
|
+
raise ArgumentError, 'Please ensure that you send through partner params' if partner_params.nil?
|
7
|
+
|
8
|
+
raise ArgumentError, 'Partner params needs to be a hash' unless partner_params.is_a?(Hash)
|
9
|
+
|
10
|
+
%i[user_id job_id job_type].each do |key|
|
11
|
+
if partner_params[key].to_s.empty?
|
12
|
+
raise ArgumentError,
|
13
|
+
"Please make sure that #{key} is included in the partner params"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
partner_params
|
18
|
+
end
|
19
|
+
|
20
|
+
def validate_id_info(id_info, required_id_info_fields)
|
21
|
+
raise ArgumentError, 'Please make sure that id_info is not empty or nil' if id_info.nil? || id_info.empty?
|
22
|
+
|
23
|
+
raise ArgumentError, 'Id info needs to be a hash' unless id_info.is_a?(Hash)
|
24
|
+
|
25
|
+
required_id_info_fields.each do |key|
|
26
|
+
raise ArgumentError, "Please make sure that #{key} is included in the id_info" if id_info[key].to_s.empty?
|
27
|
+
end
|
28
|
+
|
29
|
+
id_info
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|