smile-identity-core 1.2.1 → 2.0.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 +71 -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 +37 -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 +109 -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 +35 -58
- 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 +92 -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,26 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SmileIdentityCore
|
4
|
+
# Allows you to query the Identity Information for an individual using their ID number
|
2
5
|
class IDApi
|
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
|
-
@
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
}
|
14
|
-
@url = sid_server_mapping[sid_server.to_i]
|
15
|
-
else
|
16
|
-
@url = sid_server
|
17
|
-
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
|
18
15
|
end
|
19
16
|
|
20
17
|
def submit_job(partner_params, id_info, options = {})
|
21
18
|
self.partner_params = symbolize_keys partner_params
|
22
19
|
self.id_info = symbolize_keys id_info
|
23
|
-
|
20
|
+
options = symbolize_keys(options || {})
|
21
|
+
@use_async_endpoint = options.fetch(:async, false)
|
24
22
|
|
25
23
|
if @partner_params[:job_type].to_i != 5
|
26
24
|
raise ArgumentError, 'Please ensure that you are setting your job_type to 5 to query ID Api'
|
@@ -30,17 +28,14 @@ module SmileIdentityCore
|
|
30
28
|
end
|
31
29
|
|
32
30
|
def partner_params=(partner_params)
|
33
|
-
if partner_params
|
34
|
-
raise ArgumentError, 'Please ensure that you send through partner params'
|
35
|
-
end
|
31
|
+
raise ArgumentError, 'Please ensure that you send through partner params' if partner_params.nil?
|
36
32
|
|
37
|
-
|
38
|
-
raise ArgumentError, 'Partner params needs to be a hash'
|
39
|
-
end
|
33
|
+
raise ArgumentError, 'Partner params needs to be a hash' unless partner_params.is_a?(Hash)
|
40
34
|
|
41
|
-
[
|
42
|
-
|
43
|
-
raise ArgumentError,
|
35
|
+
%i[user_id job_id job_type].each do |key|
|
36
|
+
if partner_params[key].to_s.empty?
|
37
|
+
raise ArgumentError,
|
38
|
+
"Please make sure that #{key} is included in the partner params"
|
44
39
|
end
|
45
40
|
end
|
46
41
|
|
@@ -48,14 +43,13 @@ module SmileIdentityCore
|
|
48
43
|
end
|
49
44
|
|
50
45
|
def id_info=(id_info)
|
51
|
-
|
52
46
|
updated_id_info = id_info
|
53
47
|
|
54
|
-
if updated_id_info.nil? ||
|
48
|
+
if updated_id_info.nil? || updated_id_info.keys.length.zero?
|
55
49
|
raise ArgumentError, 'Please make sure that id_info not empty or nil'
|
56
50
|
end
|
57
51
|
|
58
|
-
[
|
52
|
+
%i[country id_type id_number].each do |key|
|
59
53
|
unless updated_id_info[key] && !updated_id_info[key].nil? && !updated_id_info[key].empty?
|
60
54
|
raise ArgumentError, "Please make sure that #{key} is included in the id_info"
|
61
55
|
end
|
@@ -66,17 +60,15 @@ module SmileIdentityCore
|
|
66
60
|
|
67
61
|
private
|
68
62
|
|
69
|
-
def symbolize_keys
|
70
|
-
|
63
|
+
def symbolize_keys(params)
|
64
|
+
params.is_a?(Hash) ? params.transform_keys(&:to_sym) : params
|
71
65
|
end
|
72
66
|
|
73
67
|
def setup_requests
|
74
|
-
url = "#{@url}/id_verification"
|
75
|
-
|
76
68
|
request = Typhoeus::Request.new(
|
77
|
-
url,
|
69
|
+
"#{@url}/#{endpoint}",
|
78
70
|
method: 'POST',
|
79
|
-
headers: {'Content-Type'=>
|
71
|
+
headers: { 'Content-Type' => 'application/json' },
|
80
72
|
body: configure_json
|
81
73
|
)
|
82
74
|
|
@@ -88,39 +80,24 @@ module SmileIdentityCore
|
|
88
80
|
request.run
|
89
81
|
end
|
90
82
|
|
83
|
+
def endpoint
|
84
|
+
@use_async_endpoint ? 'async_id_verification' : 'id_verification'
|
85
|
+
end
|
86
|
+
|
91
87
|
def configure_json
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
88
|
+
signature_generator.generate_signature(Time.now.to_s)
|
89
|
+
.merge(@id_info)
|
90
|
+
.merge(
|
91
|
+
partner_id: @partner_id,
|
92
|
+
partner_params: @partner_params,
|
93
|
+
source_sdk: SmileIdentityCore::SOURCE_SDK,
|
94
|
+
source_sdk_version: SmileIdentityCore::VERSION
|
95
|
+
)
|
96
|
+
.to_json
|
98
97
|
end
|
99
98
|
|
100
99
|
def signature_generator
|
101
100
|
SmileIdentityCore::Signature.new(@partner_id, @api_key)
|
102
101
|
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
102
|
end
|
126
103
|
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 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
|