tozny-auth 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
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
  - - ">="