smile-identity-core 1.2.1 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|