userbin 1.1.4 → 1.2.0

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: 1fff5de3f63cad930c9197795fb5f73bb3136fa9
4
- data.tar.gz: 9736769ebafb7cb7766c4328feb1fc4084e2790f
3
+ metadata.gz: 744a85b12224637026c348146ce9e1baba43796a
4
+ data.tar.gz: 52e72314536f2f65636313f0efafd9b7901fb473
5
5
  SHA512:
6
- metadata.gz: 8f6f88837d08d9a2dbc785c63f28933a84021f7361cf802af5f85df876bd46c3a9acbafb3fbd9a3abc8625550cb62b5e997b86fcd8f0b34e6254bd5379ea11ce
7
- data.tar.gz: 21259c54e483df37fcfa39526f7f7247444b0ab1c536ecb5d88c343bca5622d4bd33c7786dc03b7c7372c581c18b859b180e8386f2e40dc4cb21005754a5e085
6
+ metadata.gz: 11c9a493bc34d29161da8657a397ea57c3a310a73758b8b2138075bf94ca9bcb52b55fc94c01622e8d90a73b6904d4d2563f68b438911dfd1ab02a2e09f645db
7
+ data.tar.gz: 6a29b61d8966dbb116f55711000a0e73cdef214cbca9faf6a3e366dcd7303eecca8d9d4def5b9d58e970cd82443c623d5d245886838e016f60d15aa684296c47
data/README.md CHANGED
@@ -27,88 +27,123 @@ Install the gem
27
27
  bundle install
28
28
  ```
29
29
 
30
- Load and configure the library with your Userbin API secret in an initializer or similar
30
+ Load and configure the library with your Userbin API secret in an initializer or similar.
31
31
 
32
32
  ```ruby
33
33
  require 'userbin'
34
34
  Userbin.api_secret = "YOUR_API_SECRET"
35
35
  ```
36
36
 
37
- ## Monitor a user
37
+ ## The basics
38
38
 
39
- First you'll need to **initialize a Userbin client** for every incoming HTTP request and add it to the environment so that it's accessible during the request lifetime.
39
+ First you'll need to initialize a Userbin client for every incoming HTTP request and preferrably add it to the environment so that it's accessible during the request lifetime.
40
40
 
41
- To **monitor a logged in user**, simply call `authorize!` on the Userbin object. You need to pass the user id, and optionally a hash of user properties, preferrable including at least `email`. This call only result in an HTTP request once every 5 minutes.
41
+ ```ruby
42
+ env['userbin'] = Userbin::Client.new(request)
43
+ ```
42
44
 
43
- ### 1. Authorize the current user
45
+ At any time, a call to Userbin might result in an exception, maybe because the user has been logged out. You should catch these errors in one place and take action. Just catch and display all Userbin errors for now.
44
46
 
45
47
  ```ruby
46
48
  class ApplicationController < ActionController::Base
47
- # Define a before filter which is run on all requests
48
- before_filter :initialize_userbin
49
-
50
- # Your controller code here
51
-
52
- private
53
- def initialize_userbin
54
- # Initialize Userbin and add it to the request environment
55
- env['userbin'] = Userbin::Client.new(request)
56
-
57
- if current_user
58
- # Optional details for text messages, emails and your dashboard
59
- user_properties = {
60
- email: current_user.email, # recommended
61
- # Add `name`, `username` and `image` for improved experience
62
- }
63
-
64
- begin
65
- # This checks against Userbin once every 5 minutes under the hood.
66
- # The `id` MUST be unique across all your users and roles
67
- env['userbin'].authorize!(current_user.id, user_properties)
68
- rescue Userbin::Error
69
- # Logged out from Userbin; clear your current_user and logout
70
- # TODO: implement!
71
- end
72
- end
49
+ rescue_from Userbin::Error do |e|
50
+ redirect_to root_url, alert: e.message
73
51
  end
74
52
  end
75
53
  ```
76
54
 
77
- > **Verify that it works:** Log in to your Ruby application with an existing user, and [watch a user appear](https://dashboard.userbin.com/users) in your Userbin dashboard.
78
-
79
- ### 2. Log out
55
+ ## Tracking user sessions
80
56
 
81
- As a last step, you'll need to **end the Userbin session** when the user logs out from your application.
57
+ You should call `login` as soon as the user has logged in to your application. Pass a unique user identifier, and an optional hash of user properties. This starts the Userbin session.
82
58
 
83
59
  ```ruby
84
- def logout
85
- # Your code for logging out a user
60
+ def after_login_hook
61
+ env['userbin'].login(current_user.id, email: current_user.email)
62
+ end
63
+ ```
64
+
65
+ And call `logout` just after the user has logged out from your application. This ends the Userbin session.
86
66
 
87
- # End the Userbin session
67
+ ```ruby
68
+ def after_logout_hook
88
69
  env['userbin'].logout
89
70
  end
90
71
  ```
91
72
 
92
- > **Verify that it works:** Log out of your Ruby application and watch the number of sessions for the user in your Userbin dashboard return to zero.
73
+ The session created by login expires typically every 5 minutes and needs to be refreshed with new metadata. This is done by calling authorize. Makes sure that the session hasn't been revoked or locked.
74
+
75
+ ```ruby
76
+ before_filter do
77
+ env['userbin'].authorize
78
+ end
79
+ ```
80
+
81
+ > **Verify that it works:** Log in to your Ruby application and watch a user appear in the [Userbin dashboard](https://dashboard.userbin.com).
93
82
 
94
- ## Add a link to the user's security settings
95
83
 
96
- Create a new route where you redirect the user to its security settings page, where they can configure two-factor authentication, revoke suspicious sessions and set up notifications.
84
+ ## Configuring two-factor authentication
85
+
86
+ ### Pairing
87
+
88
+ #### Google Authenticator
89
+
90
+ Create a new Authenticator pairing to get hold of the QR code image to show to the user.
97
91
 
98
92
  ```ruby
99
- class UsersController < ApplicationController
100
- def security_settings
101
- redirect_to env['userbin'].security_settings_url
102
- end
93
+ authenticator = env['userbin'].pairings.create(type: 'authenticator')
94
+
95
+ puts authenticator.qr_url # => "http://..."
96
+ ```
97
+
98
+ Catch the code from the user to pair the Authenticator app.
99
+
100
+ ```ruby
101
+ authenticator = env['userbin'].pairings.build(id: params[:pairing_id])
102
+
103
+ begin
104
+ authenticator.verify(response: params[:code])
105
+ rescue
106
+ flash.notice = 'Wrong code, try again'
103
107
  end
104
108
  ```
105
109
 
106
- > **Verify that it works:** Log in to your Ruby application and visit your new route. This should redirect to https://security.userbin.com where you'll see that you have one active session. *Don't enable two-factor authentication just yet.*
110
+ #### YubiKey
107
111
 
108
- ## Two-factor authentication
112
+ YubiKeys are immediately verified for two-factor authentication.
109
113
 
114
+ ```ruby
115
+ begin
116
+ env['userbin'].pairings.create(type: 'yubikey', otp: code)
117
+ rescue
118
+ flash.notice = 'Wrong code, try again'
119
+ end
120
+ ```
110
121
 
111
- ### 1. Protect routes
122
+ #### SMS
123
+
124
+ Create a new phone number pairing which will send out a verification SMS.
125
+
126
+ ```ruby
127
+ phone_number = env['userbin'].pairings.create(
128
+ type: 'phone_number', number: '+1739855455')
129
+ ```
130
+
131
+ Catch the code from the user to pair the phone number.
132
+
133
+ ```ruby
134
+ phone_number = env['userbin'].pairings.build(id: params[:pairing_id])
135
+
136
+ begin
137
+ phone_number.verify(response: params[:code])
138
+ rescue
139
+ flash.notice = 'Wrong code, try again'
140
+ end
141
+ ```
142
+
143
+
144
+ ### Usage
145
+
146
+ #### 1. Protect routes
112
147
 
113
148
  If the user has enabled two-factor authentication, `two_factor_authenticate!` will return the second factor that is used to authenticate. If SMS is used, this call will also send out an SMS to the user's registered phone number.
114
149
 
@@ -138,9 +173,7 @@ class UsersController < ApplicationController
138
173
  end
139
174
  ```
140
175
 
141
- > **Verify that it works:** Enable two-factor on your security settings page, followed by a logout and and login to your Ruby application. You should now be redirected to one of the routes in the case statement.
142
-
143
- ### 2. Show the two-factor authentication form to the user
176
+ #### 2. Show the two-factor authentication form to the user
144
177
 
145
178
  ```html
146
179
  <p>
@@ -154,7 +187,7 @@ end
154
187
  </form>
155
188
  ```
156
189
 
157
- ### 3. Verify the code from the user
190
+ #### 3. Verify the code from the user
158
191
 
159
192
  The user enters the authentication code in the form and posts it to your handler.
160
193
 
@@ -23,9 +23,11 @@ end
23
23
 
24
24
  # These need to be required after setting up Her
25
25
  require 'userbin/models/model'
26
+ require 'userbin/models/event'
26
27
  require 'userbin/models/challenge'
27
28
  require 'userbin/models/channel'
28
- require 'userbin/models/token'
29
+ require 'userbin/models/monitoring'
30
+ require 'userbin/models/pairing'
31
+ require 'userbin/models/recovery_code'
29
32
  require 'userbin/models/session'
30
33
  require 'userbin/models/user'
31
- require 'userbin/models/monitoring'
@@ -36,23 +36,31 @@ module Userbin
36
36
  Userbin::SessionToken.new(token) if token
37
37
  end
38
38
 
39
- def authorize!(user_id, user_attrs = {})
39
+ def identify(user_id)
40
40
  # The user identifier is used in API paths so it needs to be cleaned
41
41
  user_id = URI.encode(user_id.to_s)
42
42
 
43
43
  @session_store.user_id = user_id
44
+ end
45
+
46
+ def login(user_id, user_attrs = {})
47
+ # Clear the session token if any
48
+ self.session_token = nil
44
49
 
45
- unless session_token
46
- # Create a session, and implicitly a user with user_attrs
47
- session = Userbin::Session.post(
48
- "users/#{user_id}/sessions", user: user_attrs)
50
+ identify(user_id)
49
51
 
50
- # Set the session token for use in all subsequent requests
51
- self.session_token = session.token
52
- else
53
- if session_token.expired?
54
- Userbin::Monitoring.heartbeat
55
- end
52
+ session = Userbin::Session.post(
53
+ "users/#{@session_store.user_id}/sessions", user: user_attrs)
54
+
55
+ # Set the session token for use in all subsequent requests
56
+ self.session_token = session.token
57
+ end
58
+
59
+ def authorize
60
+ return unless session_token
61
+
62
+ if session_token.expired?
63
+ Userbin::Monitoring.heartbeat
56
64
  end
57
65
  end
58
66
 
@@ -122,5 +130,41 @@ module Userbin
122
130
  session_token.has_challenge?
123
131
  end
124
132
 
133
+ def two_factor_enabled?
134
+ session_token.mfa_enabled?
135
+ end
136
+
137
+ def two_factor_required?
138
+ session_token.needs_challenge?
139
+ end
140
+
141
+ def events
142
+ Userbin::User.new('current').events
143
+ end
144
+
145
+ def sessions
146
+ Userbin::User.new('current').sessions
147
+ end
148
+
149
+ def pairings
150
+ Userbin::User.new('current').pairings
151
+ end
152
+
153
+ def channels
154
+ Userbin::User.new('current').channels
155
+ end
156
+
157
+ def recovery_codes
158
+ Userbin::User.new('current').recovery_codes
159
+ end
160
+
161
+ def enable_mfa
162
+ Userbin::User.new('current').enable_mfa
163
+ end
164
+
165
+ def disable_mfa
166
+ Userbin::User.new('current').disable_mfa
167
+ end
168
+
125
169
  end
126
170
  end
@@ -6,7 +6,7 @@ class Userbin::ConfigurationError < Userbin::Error; end
6
6
 
7
7
  class Userbin::ApiError < Userbin::Error; end
8
8
 
9
- class Userbin::BadRequest < Userbin::ApiError; end
9
+ class Userbin::BadRequestError < Userbin::ApiError; end
10
10
  class Userbin::UnauthorizedError < Userbin::ApiError; end
11
11
  class Userbin::ForbiddenError < Userbin::ApiError; end
12
12
  class Userbin::NotFoundError < Userbin::ApiError; end
@@ -1,5 +1,6 @@
1
1
  module Userbin
2
2
  class Channel < Model
3
- has_one :token
3
+ collection_path "users/:user_id/channels"
4
+ belongs_to :user
4
5
  end
5
6
  end
@@ -0,0 +1,6 @@
1
+ module Userbin
2
+ class Event < Model
3
+ collection_path "users/:user_id/events"
4
+ belongs_to :user
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ module Userbin
2
+ class Pairing < Model
3
+ collection_path "users/:user_id/pairings"
4
+ instance_post :verify
5
+ belongs_to :user
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ module Userbin
2
+ class RecoveryCode < Model
3
+ end
4
+ end
@@ -3,7 +3,12 @@ module Userbin
3
3
  custom_post :import
4
4
  instance_post :lock
5
5
  instance_post :unlock
6
+ instance_post :enable_mfa
7
+ instance_post :disable_mfa
6
8
 
9
+ has_many :channels
10
+ has_many :events
11
+ has_many :pairings
7
12
  has_many :sessions
8
13
  end
9
14
  end
@@ -138,6 +138,9 @@ module Userbin
138
138
  when 404
139
139
  raise Userbin::NotFoundError, response[:message]
140
140
  when 419
141
+ # session token is invalid so clear it
142
+ RequestStore.store[:userbin].session_token = nil
143
+
141
144
  raise Userbin::UserUnauthorizedError, response[:message]
142
145
  when 422
143
146
  raise Userbin::InvalidParametersError, response[:message]
@@ -24,6 +24,10 @@ module Userbin
24
24
  !!@jwt.payload['chg']
25
25
  end
26
26
 
27
+ def mfa_enabled?
28
+ @jwt.payload['mfa'] == 1
29
+ end
30
+
27
31
  def challenge_type
28
32
  @jwt.payload['chg']['typ'].to_sym if has_challenge?
29
33
  end
@@ -1,3 +1,3 @@
1
1
  module Userbin
2
- VERSION = "1.1.4"
2
+ VERSION = "1.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: userbin
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.4
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Johan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-20 00:00:00.000000000 Z
11
+ date: 2014-10-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: her
@@ -179,10 +179,12 @@ files:
179
179
  - lib/userbin/jwt.rb
180
180
  - lib/userbin/models/challenge.rb
181
181
  - lib/userbin/models/channel.rb
182
+ - lib/userbin/models/event.rb
182
183
  - lib/userbin/models/model.rb
183
184
  - lib/userbin/models/monitoring.rb
185
+ - lib/userbin/models/pairing.rb
186
+ - lib/userbin/models/recovery_code.rb
184
187
  - lib/userbin/models/session.rb
185
- - lib/userbin/models/token.rb
186
188
  - lib/userbin/models/user.rb
187
189
  - lib/userbin/request.rb
188
190
  - lib/userbin/session_store.rb
@@ -1,4 +0,0 @@
1
- module Userbin
2
- class Token < Model
3
- end
4
- end