tozny-auth 0.1.4 → 0.1.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f88e975adf4c7b8ca5dfc1bdc17dbfdbd4491815
4
- data.tar.gz: 695c46c5741214b12eef87f2187602ebb7c47c50
3
+ metadata.gz: 2eb729af3e457366a65763b14a64592dad16f316
4
+ data.tar.gz: e623a5995a6f6a59227d30f9e68a1b3ee5f881ae
5
5
  SHA512:
6
- metadata.gz: 8a7544b849a0a10306e46bb9b58554b75aaacd627328df4b26285349664104d3a3e7f35053a91b3d73afacb5f54d4d5a0bf9dfc6625b9d6449755d4154cc92a9
7
- data.tar.gz: d34e998e76655590c97d8e9267675bbcef6571ab9b7bb8f408764d627efa2339673c5e7167221bae88b4a0fd41441e6c9bb5066e6135a4f8d5f262924adf0fb3
6
+ metadata.gz: e2fc60e32ed36a9543a24a1550e8acff4fb424f4d2c6399f412c7dc0dc9467c362346bde8c5b14679a27a6a46725c0d41f63cfb116defb599d664a80e8169389
7
+ data.tar.gz: f2fb6af758d2b234a850fe6b924c5e7c395776b7191c08637d70bd879f1cabb49035fa1ca9001fd276a757b088f1723535f66c88e2085f77c75ebc0108630f7e
data/.gitignore CHANGED
@@ -9,4 +9,5 @@
9
9
  /tmp/
10
10
  /.idea
11
11
  .rakeTasks
12
- *.gem
12
+ *.gem
13
+ *.bak
data/.rubocop.yml ADDED
@@ -0,0 +1,8 @@
1
+ Metrics/LineLength:
2
+ Enabled: false
3
+ Metrics/MethodLength:
4
+ Enabled: false
5
+ Rails:
6
+ Enabled: false
7
+ Style/SpaceInsideHashLiteralBraces:
8
+ Enabled: false
data/README.md CHANGED
@@ -16,7 +16,7 @@ Or install it yourself as:
16
16
 
17
17
  $ gem install tozny-auth
18
18
 
19
- ## Usage
19
+ ## Usage (Authentication)
20
20
  In your template, include jQuery and the Tozny jQuery library:
21
21
  ```html
22
22
  <script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
@@ -45,6 +45,40 @@ if params[:tozny_action] == 'tozny_login'
45
45
  end
46
46
  ```
47
47
 
48
+ ## Usage (SMS OTP / 2FA)
49
+
50
+ To send a one-time-password (via SMS)
51
+ ```ruby
52
+ require 'tozny/auth'
53
+ realm_key_id = 'sid_123456789'
54
+ realm_secret = '6f75.....190a8dbc7'
55
+ tozny = Tozny::Realm.new(realm_key_id, realm_secret)
56
+
57
+ tozny.otp_challenge('sms-otp-6', '8005551234', nil, {foo: 'bar'})
58
+ #or alternatively (for a 6 digit OTP -- you cannot do an 8 digit OTP using the following method)
59
+ tozny.sms_otp('8005551234', {foo: 'bar'})
60
+ #or, if you don't need custom data, and you have unauthenticated OTP enabled in your realm's admin console:
61
+ tozny.user_api.otp_challenge('sms-otp-6', '8005551234')
62
+ #finally, if you already have an otp 'presence' you can use that instead of the type and destination:
63
+ tozny.otp_challenge(presence='237fa....af794')
64
+ ```
65
+
66
+ To verify the OTP the end-user enters based on the session
67
+ ```ruby
68
+ require 'tozny/auth'
69
+ realm_key_id = 'sid_123456789'
70
+ tozny_user = Tozny::User.new(realm_key_id) # Note: Tozny::Realm#user_api is an instance of Tozny::User pre-set to the realm
71
+
72
+ session_id = '2392e...134' # this should be the session_id you got back from otp_challenge
73
+ otp = '123456' # this should be the OTP entered by the user
74
+
75
+ if tozny_user.otp_result(session_id, otp).key?(:signed_data)
76
+ # the OTP was correct
77
+ else
78
+ # you can try another OTP until the session expires
79
+ end
80
+ ```
81
+
48
82
  ## Contributing
49
83
 
50
84
  Bug reports and pull requests are welcome on GitHub at https://github.com/tozny/sdk-ruby
data/Rakefile CHANGED
@@ -1,10 +1,14 @@
1
- require "bundler/gem_tasks"
2
- require "rake/testtask"
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+ require 'rubocop/rake_task'
3
4
 
4
5
  Rake::TestTask.new(:test) do |t|
5
- t.libs << "test"
6
- t.libs << "lib"
6
+ t.libs << 'test'
7
+ t.libs << 'lib'
7
8
  t.test_files = FileList['test/**/*_test.rb']
8
9
  end
9
10
 
10
11
  task :default => :test
12
+
13
+ RuboCop::RakeTask.new
14
+
@@ -12,9 +12,9 @@ module Tozny
12
12
  # @return [String] the base64url-encoded string
13
13
  def self.base64url_encode(str)
14
14
  Base64::strict_encode64(
15
- str #str to decode
16
- ) #remove padding
17
- .tr('+/', '-_') #replace + with - and / with _
15
+ str
16
+ )
17
+ .tr('+/', '-_')
18
18
  .tr('=', '')
19
19
  end
20
20
 
@@ -22,15 +22,18 @@ module Tozny
22
22
  # @param [String] str the base64url-encoded string
23
23
  # @return [String] the decoded plaintext string
24
24
  def self.base64url_decode(str)
25
- Base64::strict_decode64(str.tr('-_', '+/') #replace - with + and _ with /
26
- .ljust(str.length+(str.length % 4), '=')) #add padding
25
+ Base64::strict_decode64(
26
+ str.tr('-_', '+/')
27
+ .ljust(str.length+(str.length % 4), '='))
28
+ # replace - with + and _ with /
29
+ # then add padding
27
30
  end
28
31
 
29
32
  # checks the HMAC/SHA256 signature of a string
30
33
  # @param [String] signature the signature to check against
31
34
  # @param [String] str the signed data to check the signature against
32
35
  # @param [String] secret the secret to check the signature against
33
- # @return [Boolean] whether the signature matched
36
+ # @return [TrueClass, FalseClass] whether the signature matched
34
37
  def self.check_signature(signature, str, secret)
35
38
  expected_sig = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), secret, str)
36
39
  expected_sig == signature
@@ -44,11 +47,11 @@ module Tozny
44
47
  # @return [Hash] a hash including the signed_data and a signature
45
48
  def self.encode_and_sign(data, secret)
46
49
  encoded_data = base64url_encode(data)
47
- sig=OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), secret, encoded_data)
50
+ sig = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), secret, encoded_data)
48
51
  encoded_sig = base64url_encode(sig)
49
- return { #behold, the rare return statement
50
- :signed_data => encoded_data,
51
- :signature => encoded_sig
52
+ {
53
+ signed_data: encoded_data,
54
+ signature: encoded_sig
52
55
  }
53
56
  end
54
57
 
@@ -58,4 +61,4 @@ module Tozny
58
61
  OpenSSL::Digest::SHA256.hexdigest SecureRandom.random_bytes(8)
59
62
  end
60
63
  end
61
- end
64
+ end
@@ -1,5 +1,5 @@
1
1
  module Tozny
2
2
  module Auth
3
- VERSION = '0.1.4'
3
+ VERSION = '0.1.5'
4
4
  end
5
5
  end
data/lib/tozny/realm.rb CHANGED
@@ -3,27 +3,26 @@ require 'json'
3
3
  require 'net/http'
4
4
  require 'uri'
5
5
 
6
+ # rubocop:disable Metrics/PerceivedComplexity
7
+
6
8
  module Tozny
7
9
  class Realm
8
10
  attr_accessor :realm_key_id, :realm_secret, :api_url, :user_api
9
11
 
10
12
  def initialize(realm_key_id, realm_secret, api_url = nil)
11
- #self.realm_key_id = realm_key_id
12
- #self.realm_secret = realm_secret
13
-
14
- #set the API URL
13
+ # set the API URL
15
14
  if !api_url.nil?
16
15
  self.api_url = api_url
17
16
  elsif !(ENV['API_URL'].nil?)
18
- self.api_url=ENV['API_URL']
17
+ self.api_url = ENV['API_URL']
19
18
  else
20
- self.api_url='https://api.tozny.com/index.php'
19
+ self.api_url = 'https://api.tozny.com/index.php'
21
20
  end
22
- unless self.api_url.is_a? URI #don't try to parse a URI instance into a URI, as this will break
21
+ unless self.api_url.is_a? URI # don't try to parse a URI instance into a URI, as this will break
23
22
  self.api_url = URI.parse(self.api_url)
24
23
  end
25
24
 
26
- self.set_new_realm(realm_key_id, realm_secret)
25
+ set_new_realm(realm_key_id, realm_secret)
27
26
 
28
27
  end
29
28
 
@@ -31,11 +30,11 @@ module Tozny
31
30
  # @param [String] realm_key_id
32
31
  # @param [String] realm_secret
33
32
  # @return [TrueClass] will always return true
34
- def set_new_realm (realm_key_id, realm_secret)
33
+ def set_new_realm(realm_key_id, realm_secret)
35
34
  self.realm_key_id = realm_key_id
36
35
  self.realm_secret = realm_secret
37
- if self.user_api.is_a? ::Tozny::User
38
- self.user_api.set_new_realm(realm_key_id)
36
+ if user_api.is_a? ::Tozny::User
37
+ user_api.set_new_realm(realm_key_id)
39
38
  else
40
39
  self.user_api = ::Tozny::User.new(realm_key_id, api_url)
41
40
  end
@@ -60,12 +59,12 @@ module Tozny
60
59
  # @param [String] user_id the user_id of the login to check
61
60
  # @param [String] session_id the session_id of the login to check
62
61
  # @return [Hash] the return from the API
63
- def check_login_via_api(user_id, session_id) #NOTE: this only returns true/false. You need to parse the data locally. See Tozny::Core.base64url_decode
64
- raw_call({
65
- :method => 'realm.check_valid_login',
66
- :user_id => user_id,
67
- :session_id => session_id
68
- })[:return] == 'true'
62
+ def check_login_via_api(user_id, session_id) # NOTE: this only returns true/false. You need to parse the data locally. See Tozny::Core.base64url_decode
63
+ raw_call(
64
+ method: 'realm.check_valid_login',
65
+ user_id: user_id,
66
+ session_id: session_id
67
+ )[:return] == 'true'
69
68
  end
70
69
 
71
70
  # Add a user to a closed realm
@@ -74,17 +73,15 @@ module Tozny
74
73
  # @param [String, OpenSSL::PKey::RSA] pub_key the public key of the user to be added. Only necessary
75
74
  # @return [Hash, FalseClass] the user in its current (incomplete if defer is 'true' state)
76
75
  # @raise ArgumentError if there is no pubkey when there should be one
77
- def user_add(defer = 'false', meta, pub_key)
76
+ def user_add(defer = 'false', meta = nil, pub_key)
78
77
  unless pub_key.nil?
79
- if pub_key.is_a? String
80
- pub_key = OpenSSL::PKey::RSA.new pub_key
81
- end
78
+ pub_key = OpenSSL::PKey::RSA.new pub_key if pub_key.is_a? String
82
79
  pub_key = pub_key.public_key if pub_key.private?
83
80
  end
84
81
 
85
82
  request_obj = {
86
- :method => 'realm.user_add',
87
- :defer => defer
83
+ method: 'realm.user_add',
84
+ defer: defer
88
85
  }
89
86
  if defer == 'false'
90
87
  raise ArgumentError, 'Must provide a public key if not using deferred enrollment' if pub_key.nil?
@@ -106,31 +103,31 @@ module Tozny
106
103
  # @param [Hash{Symbol,String=>Object}] meta the metadata fields to update, along with their new values
107
104
  # @return [Hash] the updated user
108
105
  def user_update(user_id, meta)
109
- raw_call({
110
- :method => 'realm.user_update',
111
- :user_id => user_id,
112
- :extra_fields => Tozny::Core::base64url_encode(meta.to_json)
113
- })
106
+ raw_call(
107
+ method: 'realm.user_update',
108
+ user_id: user_id,
109
+ extra_fields: Tozny::Core.base64url_encode(meta.to_json)
110
+ )
114
111
  end
115
112
 
116
113
  # @param [String] user_id
117
114
  # @return [Hash] the result of the request to the API
118
115
  def user_delete(user_id)
119
- raw_call({
120
- :method => 'realm.user_delete',
121
- :user_id => user_id
122
- })
116
+ raw_call(
117
+ method: 'realm.user_delete',
118
+ user_id: user_id
119
+ )
123
120
  end
124
121
 
125
122
  # retrieve a user's information
126
123
  # * Note: all meta fields are stored as strings
127
124
  # @param [String] user_id the id or email (if is_id = false) of the user to get
128
- # @param [Boolean] is_id true if looking up the user by id, false if looking up by email. defaults to true
125
+ # @param [TrueClass, FalseClass] is_id true if looking up the user by id, false if looking up by email. defaults to true
129
126
  # @return [Hash] the user's information
130
127
  # @raise ArgumentError on failed lookup
131
128
  def user_get(user_id, is_id = true)
132
129
  request_obj = {
133
- :method => 'realm.user_get'
130
+ method: 'realm.user_get'
134
131
  }
135
132
  if is_id
136
133
  request_obj[:user_id] = user_id
@@ -139,8 +136,8 @@ module Tozny
139
136
  end
140
137
 
141
138
  user = raw_call(request_obj)
142
- if user.nil? or user[:results].nil?
143
- raise ArgumentError, ('No user was found for '+(is_id ? 'id' : 'email')+': '+user_id+'.')
139
+ if user.nil? || user[:results].nil?
140
+ raise ArgumentError, ('No user was found for ' + (is_id ? 'id' : 'email') + ': ' + user_id + '.')
144
141
  end
145
142
  user[:results]
146
143
  end
@@ -149,10 +146,10 @@ module Tozny
149
146
  # @param [String] user_id
150
147
  # @return [Hash] the result of the call: keys include :user_id, :temp_key, :secret_enrollment_url, and :key_id
151
148
  def user_device_add(user_id)
152
- raw_call({
153
- :method=>'realm.user_device_add',
154
- :user_id=>user_id
155
- })
149
+ raw_call(
150
+ method: 'realm.user_device_add',
151
+ user_id: user_id
152
+ )
156
153
  end
157
154
 
158
155
  # create an OOB challenge question session
@@ -163,53 +160,115 @@ module Tozny
163
160
  # @param [String] success_url The URL the user's browser should be redirected to after successful authentication if not specified in the question object
164
161
  # @param [String] error_url The URL the user's browser should be redirected to after unsuccessful authentication if not specified in the question object
165
162
  # @param [String] user_id optional. The user who should answer the question.
163
+ # @raise ArgumentError on invalid question type
166
164
  # TODO: support URI objects instead of strings for success and error
167
165
  # @return [Hash] the result of the API call
168
166
  def question_challenge(question, success_url, error_url, user_id)
169
- raise ArgumentError, 'question must either be a string or an options hash as specified' unless question.is_a?String or question.is_a?Hash
170
- final_question = nil #scope final_question and prevent linting errors
167
+ raise ArgumentError, 'question must either be a string or an options hash as specified' unless (question.is_a?String) || (question.is_a?Hash)
168
+ final_question = nil # scope final_question and prevent linting errors
171
169
  if question.is_a?Hash
172
170
  final_question = question
173
171
  final_question[:type] = 'callback'
172
+ elsif (success_url.is_a?String) || (error_url.is_a?String)
173
+ final_question = {
174
+ type: 'callback',
175
+ question: question
176
+ }
177
+ final_question[:success] = success_url if success_url.is_a?String
178
+ final_question[:error] = error_url if error_url.is_a?String
174
179
  else
175
- if success_url.is_a?String or error_url.is_a?String
176
- final_question = {
177
- :type => 'callback',
178
- :question => question
179
- }
180
- final_question[:success] = success_url if success_url.is_a?String
181
- final_question[:error] = error_url if error_url.is_a?String
182
- else
183
- final_question = {
184
- :type => 'question',
185
- :question => question
186
- }
187
- end
180
+ final_question = {
181
+ type: 'question',
182
+ question: question
183
+ }
188
184
  end
189
185
  request_obj = {
190
- :method => 'realm.question_challenge',
191
- :question => final_question
186
+ method: 'realm.question_challenge',
187
+ question: final_question
192
188
  }
193
189
  request_obj[:user_id] = user_id if user_id.is_a?String
194
190
  raw_call request_obj
195
191
  end
196
192
 
193
+ # Create an OTP challenge session
194
+ # @return [Hash] a hash [session_id, presence] containing an OTP session id and an OTP presence (an alias for a type-destination combination)
195
+ # @param [String] type one of 'sms-otp-6', 'sms-otp-8': the type of the OTP to send
196
+ # @param [String] destination the destination for the OTP. For an SMS OTP, this should be a phone number
197
+ # @param [String] presence can be used instead of 'type' and 'destination': an OTP presence provided by the TOZNY API
198
+ # @param [Object] data optional passthru data to be added to the signed response on a successful request
199
+ # @raise ArgumentError when not enough information to submit an OTP request
200
+ # @raise ArgumentError on invalid request type
201
+ def otp_challenge(type = nil, destination = nil, presence = nil, data = nil)
202
+ raise ArgumentError, 'must provide either a presence or a type and destination' if (type.nil? || destination.nil?) && presence.nil?
203
+ request_obj = {
204
+ method: 'realm.otp_challenge'
205
+ }
206
+ unless data.nil?
207
+ data = data.to_json unless data.is_a?String
208
+ request_obj[:data] = data
209
+ end
210
+
211
+ if presence.nil?
212
+ raise ArgumentError, "request type must one of 'sms-otp-6' or 'sms-otp-8'" unless %w(sms-otp-6 sms-otp-8).include? type
213
+ request_obj[:type] = type
214
+ # TODO: consider validating that 'destination' is a valid phone number when 'type' is sms-otp-*
215
+ request_obj[:destination] = destination
216
+ else
217
+ request_obj[:presence] = presence
218
+ end
219
+ raw_call request_obj
220
+ end
221
+
222
+ # Shorthand method to send an 6-digit OTP via SMS without a presence token
223
+ # @param destination @see(otp_challenge)
224
+ # @param data @see(otp_challenge)
225
+ # @return @see(otp_challenge)
226
+ def sms_otp(destination, data = nil)
227
+ # TODO: validate that 'destination' is a phone number
228
+ otp_challenge('sms-otp-6', destination, nil, data)
229
+ end
230
+
231
+ # push to a user's device based off of their id, email, or username
232
+ # @raise ArgumentError when not enough information is provided to find the user
233
+ # @param [String] session_id optional a valid Tozny session_id
234
+ # @param [String] user_id optional a Tozny user_id
235
+ # @param [String] email optional an email for a field associated with tozny_email
236
+ # @param [String] username optional a username for a field associated with tozny_username
237
+ # @return [Hash {Symbol => String, Integer, Array<TrueClass, FalseClass>}] the result of the push attempt. Will be true if any device owned by the user is successfully sent a push.
238
+ def user_push(session_id = nil, user_id = nil, email = nil, username = nil)
239
+ raise ArgumentError, 'must provide either a Tozny user id, a tozny_email, or a tozny_username in order to find the user to push to' if
240
+ user_id.nil? && email.nil? && username.nil?
241
+ session_id ||= user_api.login_challenge[:session_id]
242
+ request_obj = {
243
+ method: 'realm.user_push',
244
+ session_id: session_id
245
+ }
246
+ if !user_id.nil?
247
+ request_obj[:user_id] = user_id
248
+ elsif !email.nil?
249
+ request_obj[:tozny_email] = email
250
+ elsif !username.nil?
251
+ request_obj[:tozny_username] = username
252
+ end
253
+ raw_call request_obj
254
+ end
255
+
197
256
  # perform a raw(ish) API call
198
257
  # @param [Hash{Symbol, String => Object}] request_obj The request to conduct. Should include a :method at the least. Prefer symbol keys to string keys
199
258
  # @return [Object] The parsed result of the request. NOTE: most types will be stringified for most requests
200
259
  def raw_call(request_obj)
201
- request_obj[:nonce] = Tozny::Core.generate_nonce #generate the nonce
202
- request_obj[:expires_at] = Time.now.to_i + 5*60 # UNIX timestamp for now +5 min TODO: does this work with check_login_via_api, or should it default to a passed in expires_at?
203
- unless request_obj.key?('realm_key_id') || request_obj.key?(:realm_key_id) #check for both string and symbol
204
- #TODO: how should we handle conflicts of symbol and string keys?
260
+ request_obj[:nonce] = Tozny::Core.generate_nonce # generate the nonce
261
+ request_obj[:expires_at] = Time.now.to_i + 5 * 60 # UNIX timestamp for now +5 min TODO: does this work with check_login_via_api, or should it default to a passed in expires_at?
262
+ unless request_obj.key?('realm_key_id') || request_obj.key?(:realm_key_id) # check for both string and symbol
263
+ # TODO: how should we handle conflicts of symbol and string keys?
205
264
  request_obj[:realm_key_id] = realm_key_id
206
265
  end
207
- encoded_params = Tozny::Core.encode_and_sign(request_obj.to_json, realm_secret) #make a proper request of it.
208
- request_url = api_url #copy the URL to a local variable so that we can add the query params
209
- request_url.query = URI.encode_www_form encoded_params #encode signed_data and signature as query params
210
- #p request_url
266
+ encoded_params = Tozny::Core.encode_and_sign(request_obj.to_json, realm_secret) # make a proper request of it.
267
+ request_url = api_url # copy the URL to a local variable so that we can add the query params
268
+ request_url.query = URI.encode_www_form encoded_params # encode signed_data and signature as query params
269
+ # p request_url
211
270
  http_result = Net::HTTP.get(request_url)
212
- JSON.parse(http_result, {:symbolize_names => true}) #TODO: handle errors
271
+ JSON.parse(http_result, symbolize_names: true) # TODO: handle errors
213
272
  end
214
273
  end
215
- end
274
+ end
data/lib/tozny/user.rb CHANGED
@@ -4,49 +4,90 @@ module Tozny
4
4
  class User
5
5
  attr_accessor :realm_key_id, :api_url
6
6
  def initialize(realm_key_id, api_url = nil)
7
-
8
7
  if !api_url.nil?
9
8
  self.api_url = api_url
10
- elsif !(ENV['API_URL'].nil?)
11
- self.api_url=ENV['API_URL']
9
+ elsif !(ENV['API_URL'].nil?) # rubocop:disable all
10
+ self.api_url = ENV['API_URL']
12
11
  else
13
- self.api_url='https://api.tozny.com/index.php'
12
+ self.api_url = 'https://api.tozny.com/index.php'
14
13
  end
15
14
 
16
- unless self.api_url.is_a? URI
17
- self.api_url = URI.parse(self.api_url)
18
- end
15
+ self.api_url = URI.parse(self.api_url) unless self.api_url.is_a? URI
19
16
 
20
- self.set_new_realm(realm_key_id)
17
+ set_new_realm(realm_key_id)
21
18
  end
22
19
 
20
+ # check the status of a session
21
+ # @param [String] session_id the session to check
22
+ # @return [TrueClass, FalseClass] 'true' if the use has logged in with the session, false otherwise
23
23
  def check_session_status(session_id)
24
- raw_call({
25
- :method => 'user.check_session_status',
26
- :session_id => session_id
27
- }).has_key?(:signed_data)
24
+ raw_call(
25
+ method: 'user.check_session_status',
26
+ session_id: session_id
27
+ ).key?(:signed_data)
28
28
  end
29
29
 
30
30
  # use a new realm_key_id
31
31
  # @param [String] realm_key_id
32
32
  # @return [TrueClass] will always return true
33
- def set_new_realm (realm_key_id)
33
+ def set_new_realm(realm_key_id) # rubocop:disable Style/AccessorMethodName
34
34
  self.realm_key_id = realm_key_id
35
35
  true
36
36
  end
37
37
 
38
- # perform a raw(ish) API call
38
+ # Generate a new login challenge session
39
+ # @param [TrueClass, FalseClass] user_add optional: whether or not to create an enrollment challenge rather than an authentication challenge
40
+ # @return [Hash] a challenge session (:challenge, :realm_key_id, :session_id, :qr_url, :mobile_url, :created_at, :presence = "")
41
+ def login_challenge(user_add = nil)
42
+ request_obj = {
43
+ method: 'user.login_challenge'
44
+ }
45
+ request_obj[:user_add] = user_add if user_add
46
+ raw_call request_obj
47
+ end
48
+
49
+ # Create an OTP challenge session
50
+ # @return [Hash] a hash [session_id, presence] containing an OTP session id and an OTP presence (an alias for a type-destination combination)
51
+ # @param [String] type one of 'sms-otp-6', 'sms-otp-8': the type of the OTP to send
52
+ # @param [String] destination the destination for the OTP. For an SMS OTP, this should be a phone number
53
+ # @param [String] presence can be used instead of 'type' and 'destination': an OTP presence provided by the TOZNY API
54
+ # @raise ArgumentError when not enough information to submit an OTP request
55
+ # @raise ArgumentError on invalid request type
56
+ def otp_challenge(type = nil, destination = nil, presence = nil)
57
+ raise ArgumentError, 'must provide either a presence or a type and destination' if (type.nil? || destination.nil?) && presence.nil?
58
+ request_obj = {
59
+ method: 'user.otp_challenge'
60
+ }
61
+ if presence.nil?
62
+ raise ArgumentError, "request type must one of 'sms-otp-6' or 'sms-otp-8'" unless %w(sms-otp-6 sms-otp-8).include? type
63
+ request_obj[:type] = type
64
+ # TODO: consider validating that 'destination' is a valid phone number when 'type' is sms-otp-*
65
+ request_obj[:destination] = destination
66
+ else
67
+ request_obj[:presence] = presence
68
+ end
69
+ raw_call request_obj
70
+ end
71
+
72
+ # Check an OTP against an OTP session
73
+ # @param [String] session_id the OTP session to validate
74
+ # @param [String, Integer] otp the OTP to check
75
+ # @return [Hash] The signed_data and signature containing a session ID and metadata, if any, on success. Otherwise, error[s].
76
+ def otp_result(session_id, otp)
77
+ raw_call(method: 'user.otp_result', session_id: session_id, otp: otp)
78
+ end
79
+
80
+ # Perform a raw(ish) API call
39
81
  # @param [Hash{Symbol, String => Object}] request_obj The request to conduct. Should include a :method at the least. Prefer symbol keys to string keys
40
82
  # @return [Object] The parsed result of the request. NOTE: most types will be stringified for most requests
41
83
  def raw_call(request_obj)
42
- unless request_obj.key?('realm_key_id') || request_obj.key?(:realm_key_id) #check for both string and symbol
43
- #TODO: how should we handle conflicts of symbol and string keys?
84
+ unless request_obj.key?('realm_key_id') || request_obj.key?(:realm_key_id) # check for both string and symbol
85
+ # TODO: how should we handle conflicts of symbol and string keys?
44
86
  request_obj[:realm_key_id] = realm_key_id
45
87
  end
46
- request_url = api_url #copy the URL to a local variable so that we can add the query params
47
- request_url.query = URI.encode_www_form request_obj #encode request as query params
48
- #p request_url
49
- JSON.parse(Net::HTTP.get(request_url), {:symbolize_names => true})
88
+ request_url = api_url # copy the URL to a local variable so that we can add the query params
89
+ request_url.query = URI.encode_www_form request_obj # encode request as query params
90
+ JSON.parse(Net::HTTP.get(request_url), symbolize_names: true)
50
91
  end
51
92
  end
52
- end
93
+ end
data/tozny-auth.gemspec CHANGED
@@ -30,7 +30,8 @@ Gem::Specification.new do |spec|
30
30
  spec.add_development_dependency 'bundler', '~> 1.12'
31
31
  spec.add_development_dependency 'rake', '~> 10.0'
32
32
  spec.add_development_dependency 'minitest', '~> 5.0'
33
+ spec.add_development_dependency 'rubocop', '~> 0.41.1'
33
34
 
34
35
  # Only with ruby 2.0.x
35
- spec.required_ruby_version = '~> 2.0'
36
+ spec.required_ruby_version = '~> 2'
36
37
  end
data/tozny-auth.iml CHANGED
@@ -19,9 +19,17 @@
19
19
  <excludeFolder url="file://$MODULE_DIR$/.bundle" />
20
20
  <excludeFolder url="file://$MODULE_DIR$/vendor/bundle" />
21
21
  </content>
22
- <orderEntry type="jdk" jdkName="ruby-2.1.5-p273" jdkType="RUBY_SDK" />
22
+ <orderEntry type="jdk" jdkName="ruby-2.2.4-p230" jdkType="RUBY_SDK" />
23
23
  <orderEntry type="sourceFolder" forTests="false" />
24
- <orderEntry type="library" scope="PROVIDED" name="bundler (v1.12.2, ruby-2.1.5-p273) [gem]" level="application" />
25
- <orderEntry type="library" scope="PROVIDED" name="minitest (v5.8.4, ruby-2.1.5-p273) [gem]" level="application" />
24
+ <orderEntry type="library" scope="PROVIDED" name="ast (v2.3.0, ruby-2.2.4-p230) [gem]" level="application" />
25
+ <orderEntry type="library" scope="PROVIDED" name="bundler (v1.12.5, ruby-2.2.4-p230) [gem]" level="application" />
26
+ <orderEntry type="library" scope="PROVIDED" name="minitest (v5.9.0, ruby-2.2.4-p230) [gem]" level="application" />
27
+ <orderEntry type="library" scope="PROVIDED" name="parser (v2.3.1.2, ruby-2.2.4-p230) [gem]" level="application" />
28
+ <orderEntry type="library" scope="PROVIDED" name="powerpack (v0.1.1, ruby-2.2.4-p230) [gem]" level="application" />
29
+ <orderEntry type="library" scope="PROVIDED" name="rainbow (v2.1.0, ruby-2.2.4-p230) [gem]" level="application" />
30
+ <orderEntry type="library" scope="PROVIDED" name="rake (v10.1.0, ruby-2.2.4-p230) [gem]" level="application" />
31
+ <orderEntry type="library" scope="PROVIDED" name="rubocop (v0.41.1, ruby-2.2.4-p230) [gem]" level="application" />
32
+ <orderEntry type="library" scope="PROVIDED" name="ruby-progressbar (v1.8.1, ruby-2.2.4-p230) [gem]" level="application" />
33
+ <orderEntry type="library" scope="PROVIDED" name="unicode-display_width (v1.1.0, ruby-2.2.4-p230) [gem]" level="application" />
26
34
  </component>
27
35
  </module>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tozny-auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ethan Bell / emanb29
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-06-27 00:00:00.000000000 Z
11
+ date: 2016-06-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.41.1
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.41.1
55
69
  description: A set of methods to more conveniently access the Tozny authentication
56
70
  API as a RP of Tozny from Ruby
57
71
  email:
@@ -61,6 +75,7 @@ extensions: []
61
75
  extra_rdoc_files: []
62
76
  files:
63
77
  - ".gitignore"
78
+ - ".rubocop.yml"
64
79
  - ".travis.yml"
65
80
  - Gemfile
66
81
  - LICENSE
@@ -87,7 +102,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
87
102
  requirements:
88
103
  - - "~>"
89
104
  - !ruby/object:Gem::Version
90
- version: '2.0'
105
+ version: '2'
91
106
  required_rubygems_version: !ruby/object:Gem::Requirement
92
107
  requirements:
93
108
  - - ">="