smile-identity-core 1.1.0 → 1.2.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/.travis.yml +1 -0
- data/Gemfile.lock +1 -1
- data/README.md +58 -23
- data/lib/smile-identity-core/id_api.rb +7 -7
- data/lib/smile-identity-core/signature.rb +3 -3
- data/lib/smile-identity-core/version.rb +1 -1
- data/lib/smile-identity-core/web_api.rb +61 -22
- data/smile-identity-core.gemspec +2 -2
- metadata +8 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9614cb861ac00a8133527a647e8d6c6dcc91b00aca6c7fe643e32ddc986b3f7d
|
4
|
+
data.tar.gz: f1ac7dcb3413c1f08ac16c909e0f466091519dae0412519a1161cd0179143040
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: db1d5e0344e58bb11728af749ef1b37f051add89fdda7797347d0771d091b3221482d90beed6f566d082d4120c9c2d45d63e0dea828202ff9c087d58c498094e
|
7
|
+
data.tar.gz: f524bd93102a7ac6621314a950b5fba0b06a62437f1ad4328e26c092192ffa74ccc9ce5919d1806e126a5d2d9305b9ad204e913da13cf02f1c31079d5775dc70
|
data/.travis.yml
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -5,6 +5,7 @@ The official Smile Identity gem exposes four classes namely; the Web Api class,
|
|
5
5
|
The **Web Api Class** allows you as the Partner to validate a user’s identity against the relevant Identity Authorities/Third Party databases that Smile Identity has access to using ID information provided by your customer/user (including photo for compare). It has the following public methods:
|
6
6
|
- submit_job
|
7
7
|
- get_job_status
|
8
|
+
- get_web_token
|
8
9
|
|
9
10
|
The **ID Api Class** lets you performs basic KYC Services including verifying an ID number as well as retrieve a user's Personal Information. It has the following public methods:
|
10
11
|
- submit_job
|
@@ -39,7 +40,7 @@ require 'smile-identity-core'
|
|
39
40
|
|
40
41
|
Or install it to your system as:
|
41
42
|
|
42
|
-
```
|
43
|
+
```sh
|
43
44
|
$ gem install smile-identity-core
|
44
45
|
```
|
45
46
|
|
@@ -48,33 +49,33 @@ You now may use the classes as follows:
|
|
48
49
|
#### Web Api Class
|
49
50
|
|
50
51
|
##### submit_job method
|
51
|
-
```
|
52
|
-
|
52
|
+
```ruby
|
53
|
+
connection = SmileIdentityCore::WebApi.new(partner_id, default_callback, api_key, sid_server)
|
53
54
|
|
54
|
-
|
55
|
+
response = connection.submit_job(partner_params, images, id_info, options)
|
55
56
|
```
|
56
57
|
|
57
58
|
Please note that if you do not need to pass through id_info or options, you may omit calling those class and send through nil in submit_job, as follows:
|
58
59
|
|
59
|
-
```
|
60
|
-
|
60
|
+
```ruby
|
61
|
+
response = connection.submit_job(partner_params, images, nil, nil);
|
61
62
|
```
|
62
63
|
|
63
64
|
In the case of a Job Type 5 you can simply omit the the images and options keys. Remember that the response is immediate, so there is no need to query the job_status. There is also no enrollment so no images are required. The response for a job type 5 can be found in the response section below.
|
64
65
|
|
65
|
-
```
|
66
|
-
|
66
|
+
```ruby
|
67
|
+
response = connection.submit_job(partner_params, nil, id_info, nil);
|
67
68
|
```
|
68
69
|
|
69
70
|
**Response:**
|
70
71
|
|
71
72
|
Should you choose to *set return_job_status to false*, the response will be a JSON String containing:
|
72
|
-
```
|
73
|
+
```ruby
|
73
74
|
{success: true, smile_job_id: smile_job_id}
|
74
75
|
```
|
75
76
|
|
76
77
|
However, if you have *set return_job_status to true (with image_links and history)* then you will receive JSON Object response like below:
|
77
|
-
```
|
78
|
+
```json
|
78
79
|
{
|
79
80
|
"job_success":true,
|
80
81
|
"result":{
|
@@ -150,7 +151,7 @@ However, if you have *set return_job_status to true (with image_links and histor
|
|
150
151
|
```
|
151
152
|
|
152
153
|
You can also *view your response asynchronously at the callback* that you have set, it will look as follows:
|
153
|
-
```
|
154
|
+
```json
|
154
155
|
{
|
155
156
|
"job_success":true,
|
156
157
|
"result":{
|
@@ -226,7 +227,7 @@ You can also *view your response asynchronously at the callback* that you have s
|
|
226
227
|
```
|
227
228
|
|
228
229
|
If you have queried a job type 5, your response be a JSON String that will contain the following:
|
229
|
-
```
|
230
|
+
```json
|
230
231
|
{
|
231
232
|
"JSONVersion":"1.0.0",
|
232
233
|
"SmileJobID":"0000001105",
|
@@ -279,7 +280,7 @@ response = connection.get_job_status(partner_params, nil);
|
|
279
280
|
|
280
281
|
Your response will return a JSON Object below with image_links and history included:
|
281
282
|
|
282
|
-
```
|
283
|
+
```json
|
283
284
|
{
|
284
285
|
"job_success":true,
|
285
286
|
"result":{
|
@@ -354,20 +355,54 @@ Your response will return a JSON Object below with image_links and history inclu
|
|
354
355
|
}
|
355
356
|
```
|
356
357
|
|
358
|
+
##### get_web_token method
|
359
|
+
You may want to use our hosted web integration, and create a session. The `get_web_token` method enables this.
|
360
|
+
You have your Web Api class initialised as follows:
|
361
|
+
```ruby
|
362
|
+
connection = SmileIdentityCore::WebApi.new(partner_id, default_callback, api_key, sid_server)
|
363
|
+
|
364
|
+
```
|
365
|
+
|
366
|
+
Next, you'll need to create your request params. This should take the following
|
367
|
+
structure:
|
368
|
+
|
369
|
+
```ruby
|
370
|
+
{
|
371
|
+
user_id: 'user-1', # String: required
|
372
|
+
job_id: 'job-1', # String: required
|
373
|
+
product: 'authentication', # String: required one of 'authentication', 'identity_verification', 'smartselfie', 'ekyc_smartselfie', 'enhanced_kyc', 'document_verification'
|
374
|
+
callback_url: "https://smileidentity.com/callback" # String: required, optional if callback url was set during instantiation of the class
|
375
|
+
}
|
376
|
+
```
|
377
|
+
|
378
|
+
Thereafter, call `get_web_token` with the correct parameters:
|
379
|
+
```ruby
|
380
|
+
response = connection.get_web_token(request_params)
|
381
|
+
```
|
382
|
+
|
383
|
+
**Response**
|
384
|
+
|
385
|
+
Your response will return a hash that contains
|
386
|
+
```ruby
|
387
|
+
{
|
388
|
+
token: <token_string>
|
389
|
+
}
|
390
|
+
```
|
391
|
+
|
357
392
|
#### ID Api Class
|
358
393
|
|
359
394
|
|
360
395
|
##### submit_job method
|
361
|
-
```
|
362
|
-
|
396
|
+
```ruby
|
397
|
+
connection = SmileIdentityCore::IDApi.new(partner_id, api_key, sid_server)
|
363
398
|
|
364
|
-
|
399
|
+
response = connection.submit_job(partner_params, id_info)
|
365
400
|
```
|
366
401
|
|
367
402
|
**Response**
|
368
403
|
|
369
404
|
Your response will return a JSON String containing the below:
|
370
|
-
```
|
405
|
+
```json
|
371
406
|
{
|
372
407
|
"JSONVersion":"1.0.0",
|
373
408
|
"SmileJobID":"0000001105",
|
@@ -400,17 +435,17 @@ Your response will return a JSON String containing the below:
|
|
400
435
|
|
401
436
|
##### generate_sec_key method
|
402
437
|
|
403
|
-
```
|
404
|
-
|
438
|
+
```ruby
|
439
|
+
connection = SmileIdentityCore::Signature.new(partner_id, api_key)
|
405
440
|
|
406
|
-
|
441
|
+
sec_key = connection.generate_sec_key(timestamp)
|
407
442
|
where timestamp is optional
|
408
443
|
|
409
444
|
```
|
410
445
|
|
411
446
|
The response will be a hash:
|
412
447
|
|
413
|
-
```
|
448
|
+
```ruby
|
414
449
|
{
|
415
450
|
:sec_key=> "<the generated sec key>",
|
416
451
|
:timestamp=> 1563283420
|
@@ -422,8 +457,8 @@ The response will be a hash:
|
|
422
457
|
You can also confirm the signature that you receive when you interacting with our servers, simply use the confirm_sec_key method which returns a boolean:
|
423
458
|
|
424
459
|
```ruby
|
425
|
-
|
426
|
-
|
460
|
+
connection = SmileIdentityCore::Signature.new(partner_id, api_key)
|
461
|
+
sec_key = connection.confirm_sec_key(sec_key, timestamp)
|
427
462
|
```
|
428
463
|
|
429
464
|
#### Utilities Class
|
@@ -23,24 +23,24 @@ module SmileIdentityCore
|
|
23
23
|
@use_new_signature = symbolize_keys(options || {}).fetch(:signature, false)
|
24
24
|
|
25
25
|
if @partner_params[:job_type].to_i != 5
|
26
|
-
raise ArgumentError
|
26
|
+
raise ArgumentError, 'Please ensure that you are setting your job_type to 5 to query ID Api'
|
27
27
|
end
|
28
28
|
|
29
|
-
|
29
|
+
setup_requests
|
30
30
|
end
|
31
31
|
|
32
32
|
def partner_params=(partner_params)
|
33
33
|
if partner_params == nil
|
34
|
-
raise ArgumentError
|
34
|
+
raise ArgumentError, 'Please ensure that you send through partner params'
|
35
35
|
end
|
36
36
|
|
37
37
|
if !partner_params.is_a?(Hash)
|
38
|
-
raise ArgumentError
|
38
|
+
raise ArgumentError, 'Partner params needs to be a hash'
|
39
39
|
end
|
40
40
|
|
41
41
|
[:user_id, :job_id, :job_type].each do |key|
|
42
42
|
unless partner_params[key] && !partner_params[key].nil? && !(partner_params[key].empty? if partner_params[key].is_a?(String))
|
43
|
-
raise ArgumentError
|
43
|
+
raise ArgumentError, "Please make sure that #{key} is included in the partner params"
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
@@ -52,12 +52,12 @@ module SmileIdentityCore
|
|
52
52
|
updated_id_info = id_info
|
53
53
|
|
54
54
|
if updated_id_info.nil? || updated_id_info.keys.length == 0
|
55
|
-
raise ArgumentError
|
55
|
+
raise ArgumentError, 'Please make sure that id_info not empty or nil'
|
56
56
|
end
|
57
57
|
|
58
58
|
[:country, :id_type, :id_number].each do |key|
|
59
59
|
unless updated_id_info[key] && !updated_id_info[key].nil? && !updated_id_info[key].empty?
|
60
|
-
raise ArgumentError
|
60
|
+
raise ArgumentError, "Please make sure that #{key} is included in the id_info"
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
@@ -14,7 +14,7 @@ module SmileIdentityCore
|
|
14
14
|
public_key = OpenSSL::PKey::RSA.new(Base64.decode64(@api_key))
|
15
15
|
@sec_key = [Base64.encode64(public_key.public_encrypt(hash_signature)), hash_signature].join('|')
|
16
16
|
|
17
|
-
|
17
|
+
{
|
18
18
|
sec_key: @sec_key,
|
19
19
|
timestamp: @timestamp
|
20
20
|
}
|
@@ -31,7 +31,7 @@ module SmileIdentityCore
|
|
31
31
|
public_key = OpenSSL::PKey::RSA.new(Base64.decode64(@api_key))
|
32
32
|
decrypted = public_key.public_decrypt(Base64.decode64(encrypted))
|
33
33
|
|
34
|
-
|
34
|
+
decrypted == hash_signature
|
35
35
|
rescue => e
|
36
36
|
raise e
|
37
37
|
end
|
@@ -43,7 +43,7 @@ module SmileIdentityCore
|
|
43
43
|
hmac.update(@partner_id)
|
44
44
|
hmac.update("sid_request")
|
45
45
|
signature = Base64.strict_encode64(hmac.digest())
|
46
|
-
|
46
|
+
{
|
47
47
|
signature: signature,
|
48
48
|
timestamp: timestamp.to_s
|
49
49
|
}
|
@@ -48,7 +48,7 @@ module SmileIdentityCore
|
|
48
48
|
|
49
49
|
validate_return_data
|
50
50
|
|
51
|
-
|
51
|
+
setup_requests
|
52
52
|
end
|
53
53
|
|
54
54
|
def get_job_status(partner_params, options)
|
@@ -59,21 +59,21 @@ module SmileIdentityCore
|
|
59
59
|
job_id = partner_params[:job_id]
|
60
60
|
|
61
61
|
utilities = SmileIdentityCore::Utilities.new(@partner_id, @api_key, @sid_server)
|
62
|
-
|
62
|
+
utilities.get_job_status(user_id, job_id, options);
|
63
63
|
end
|
64
64
|
|
65
65
|
def partner_params=(partner_params)
|
66
66
|
if partner_params == nil
|
67
|
-
raise ArgumentError
|
67
|
+
raise ArgumentError, 'Please ensure that you send through partner params'
|
68
68
|
end
|
69
69
|
|
70
70
|
if !partner_params.is_a?(Hash)
|
71
|
-
raise ArgumentError
|
71
|
+
raise ArgumentError, 'Partner params needs to be a hash'
|
72
72
|
end
|
73
73
|
|
74
74
|
[:user_id, :job_id, :job_type].each do |key|
|
75
75
|
unless partner_params[key] && !partner_params[key].nil? && !(partner_params[key].empty? if partner_params[key].is_a?(String))
|
76
|
-
raise ArgumentError
|
76
|
+
raise ArgumentError, "Please make sure that #{key} is included in the partner params"
|
77
77
|
end
|
78
78
|
end
|
79
79
|
|
@@ -82,16 +82,16 @@ module SmileIdentityCore
|
|
82
82
|
|
83
83
|
def images=(images)
|
84
84
|
if images == nil
|
85
|
-
raise ArgumentError
|
85
|
+
raise ArgumentError, 'Please ensure that you send through image details'
|
86
86
|
end
|
87
87
|
|
88
88
|
if !images.is_a?(Array)
|
89
|
-
raise ArgumentError
|
89
|
+
raise ArgumentError, 'Image details needs to be an array'
|
90
90
|
end
|
91
91
|
|
92
92
|
# all job types require atleast a selfie
|
93
93
|
if images.length == 0 || images.none? {|h| h[:image_type_id] == 0 || h[:image_type_id] == 2 }
|
94
|
-
raise ArgumentError
|
94
|
+
raise ArgumentError, 'You need to send through at least one selfie image'
|
95
95
|
end
|
96
96
|
|
97
97
|
@images = images.map { |image| symbolize_keys image }
|
@@ -119,7 +119,7 @@ module SmileIdentityCore
|
|
119
119
|
if updated_id_info[:entered] && updated_id_info[:entered] == 'true'
|
120
120
|
[:country, :id_type, :id_number].each do |key|
|
121
121
|
unless id_info[key] && !id_info[key].nil? && !id_info[key].empty?
|
122
|
-
raise ArgumentError
|
122
|
+
raise ArgumentError, "Please make sure that #{key.to_s} is included in the id_info"
|
123
123
|
end
|
124
124
|
end
|
125
125
|
end
|
@@ -139,21 +139,51 @@ module SmileIdentityCore
|
|
139
139
|
@options = updated_options
|
140
140
|
end
|
141
141
|
|
142
|
+
def get_web_token(request_params)
|
143
|
+
raise ArgumentError, 'Please ensure that you send through request params' if request_params.nil?
|
144
|
+
raise ArgumentError, 'Request params needs to be an object' unless request_params.is_a?(Hash)
|
145
|
+
|
146
|
+
callback_url = request_params[:callback_url] || @callback_url
|
147
|
+
request_params[:callback_url] = callback_url
|
148
|
+
|
149
|
+
keys = %i[user_id job_id product callback_url]
|
150
|
+
blank_keys = get_blank_keys(keys, request_params)
|
151
|
+
error_message = "#{blank_keys.join(', ')} #{blank_keys.length > 1 ? 'are' : 'is'} required to get a web token"
|
152
|
+
raise ArgumentError, error_message unless blank_keys.empty?
|
153
|
+
|
154
|
+
request_web_token(request_params)
|
155
|
+
end
|
156
|
+
|
142
157
|
private
|
143
158
|
|
159
|
+
def request_web_token(request_params)
|
160
|
+
request_params.merge!({ partner_id: @partner_id }).merge!(request_security)
|
161
|
+
url = "#{@url}/token"
|
162
|
+
|
163
|
+
response = Typhoeus.post(
|
164
|
+
url,
|
165
|
+
headers: { 'Content-Type' => 'application/json' },
|
166
|
+
body: request_params.to_json
|
167
|
+
)
|
168
|
+
|
169
|
+
return response.body if response.code == 200
|
170
|
+
|
171
|
+
raise "#{response.code}: #{response.body}"
|
172
|
+
end
|
173
|
+
|
144
174
|
def symbolize_keys params
|
145
175
|
(params.is_a?(Hash)) ? Hash[params.map{ |k, v| [k.to_sym, v] }] : params
|
146
176
|
end
|
147
177
|
|
148
178
|
def validate_return_data
|
149
179
|
if (!@callback_url || @callback_url.empty?) && !@options[:return_job_status]
|
150
|
-
raise ArgumentError
|
180
|
+
raise ArgumentError, 'Please choose to either get your response via the callback or job status query'
|
151
181
|
end
|
152
182
|
end
|
153
183
|
|
154
184
|
def validate_enroll_with_id
|
155
185
|
if(((@images.none? {|h| h[:image_type_id] == 1 || h[:image_type_id] == 3 }) && @id_info[:entered] != 'true'))
|
156
|
-
raise ArgumentError
|
186
|
+
raise ArgumentError, 'You are attempting to complete a job type 1 without providing an id card image or id info'
|
157
187
|
end
|
158
188
|
end
|
159
189
|
|
@@ -163,21 +193,30 @@ module SmileIdentityCore
|
|
163
193
|
end
|
164
194
|
|
165
195
|
if !!obj[key] != obj[key]
|
166
|
-
raise ArgumentError
|
196
|
+
raise ArgumentError, "#{key} needs to be a boolean"
|
167
197
|
end
|
168
198
|
|
169
|
-
|
199
|
+
obj[key]
|
170
200
|
end
|
171
|
-
# zeKn:WFL7t2X/+4
|
172
201
|
|
173
202
|
def check_string(key, obj)
|
174
203
|
if (!obj || !obj[key])
|
175
|
-
|
204
|
+
''
|
176
205
|
else
|
177
|
-
|
206
|
+
obj[key]
|
178
207
|
end
|
179
208
|
end
|
180
209
|
|
210
|
+
def blank?(obj, key)
|
211
|
+
return obj[key].empty? if obj[key].respond_to?(:empty?)
|
212
|
+
|
213
|
+
obj[key].nil?
|
214
|
+
end
|
215
|
+
|
216
|
+
def get_blank_keys(keys, obj)
|
217
|
+
keys.select { |key| blank?(obj, key) }
|
218
|
+
end
|
219
|
+
|
181
220
|
def request_security(use_new_signature: true)
|
182
221
|
if use_new_signature
|
183
222
|
@timestamp = Time.now.to_s
|
@@ -230,13 +269,13 @@ module SmileIdentityCore
|
|
230
269
|
return file_upload_response
|
231
270
|
|
232
271
|
elsif response.timed_out?
|
233
|
-
raise "#{response.code
|
272
|
+
raise "#{response.code}: #{response.body}"
|
234
273
|
elsif response.code == 0
|
235
274
|
# Could not get an http response, something's wrong.
|
236
|
-
raise "#{response.code
|
275
|
+
raise "#{response.code}: #{response.body}"
|
237
276
|
else
|
238
277
|
# Received a non-successful http response.
|
239
|
-
raise "#{response.code
|
278
|
+
raise "#{response.code}: #{response.body}"
|
240
279
|
end
|
241
280
|
end
|
242
281
|
request.run
|
@@ -271,7 +310,7 @@ module SmileIdentityCore
|
|
271
310
|
"images": configure_image_payload,
|
272
311
|
"server_information": server_information
|
273
312
|
}
|
274
|
-
|
313
|
+
info
|
275
314
|
end
|
276
315
|
|
277
316
|
def configure_image_payload
|
@@ -357,9 +396,9 @@ module SmileIdentityCore
|
|
357
396
|
response = @utilies_connection.get_job_status(@partner_params[:user_id], @partner_params[:job_id], @options)
|
358
397
|
|
359
398
|
if response && (response['job_complete'] == true || counter == 20)
|
360
|
-
|
399
|
+
response
|
361
400
|
else
|
362
|
-
|
401
|
+
query_job_status(counter)
|
363
402
|
end
|
364
403
|
|
365
404
|
end
|
data/smile-identity-core.gemspec
CHANGED
@@ -5,8 +5,8 @@ require "smile-identity-core/version"
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = "smile-identity-core"
|
7
7
|
spec.version = SmileIdentityCore::VERSION
|
8
|
-
spec.authors = ["
|
9
|
-
spec.email = ["
|
8
|
+
spec.authors = ["Smile Identity"]
|
9
|
+
spec.email = ["support@smileidentity.com"]
|
10
10
|
|
11
11
|
spec.summary = "The Smile Identity Web API allows the user to access most of the features of the Smile Identity system through direct server to server queries."
|
12
12
|
spec.description = "The Official Smile Identity gem"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: smile-identity-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
8
|
-
autorequire:
|
7
|
+
- Smile Identity
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-10-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -108,7 +108,7 @@ dependencies:
|
|
108
108
|
version: 1.2.3
|
109
109
|
description: The Official Smile Identity gem
|
110
110
|
email:
|
111
|
-
-
|
111
|
+
- support@smileidentity.com
|
112
112
|
executables: []
|
113
113
|
extensions: []
|
114
114
|
extra_rdoc_files: []
|
@@ -138,7 +138,7 @@ metadata:
|
|
138
138
|
source_code_uri: https://github.com/smileidentity/smile-identity-core-ruby
|
139
139
|
documentation_uri: https://docs.smileidentity.com
|
140
140
|
changelog_uri: https://github.com/smileidentity/smile-identity-core/blob/master/CHANGELOG.md
|
141
|
-
post_install_message:
|
141
|
+
post_install_message:
|
142
142
|
rdoc_options: []
|
143
143
|
require_paths:
|
144
144
|
- lib
|
@@ -153,8 +153,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
153
153
|
- !ruby/object:Gem::Version
|
154
154
|
version: '0'
|
155
155
|
requirements: []
|
156
|
-
rubygems_version: 3.
|
157
|
-
signing_key:
|
156
|
+
rubygems_version: 3.0.3
|
157
|
+
signing_key:
|
158
158
|
specification_version: 4
|
159
159
|
summary: The Smile Identity Web API allows the user to access most of the features
|
160
160
|
of the Smile Identity system through direct server to server queries.
|