userbin 1.6.1 → 1.7.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/README.md +17 -256
- data/lib/userbin.rb +1 -0
- data/lib/userbin/client.rb +6 -1
- data/lib/userbin/request.rb +4 -2
- data/lib/userbin/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8a4569c81702c9644066b6bdaa74246a068632ba
|
4
|
+
data.tar.gz: 17a9fd3f2c51969565ca485adf025601e74b4029
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79a9d2ac54fd2dafed09ca930e769f48b01d8b1b8896cde859f7ea98c2fa599b77529018409dac5ab41b902da43882878d43df8160079d324c4ffe78206b3b5b
|
7
|
+
data.tar.gz: b7024885c8ee6fca6566550b763ca2f81f7684fbda42bce710c90bf1e27378ade15597ab93a0b4db6b2384a3be06811f0b7e0bd166b1a526b2af0f4f08623d7e
|
data/README.md
CHANGED
@@ -5,286 +5,47 @@
|
|
5
5
|
[](https://gemnasium.com/userbin/userbin-ruby)
|
6
6
|
[](https://coveralls.io/r/userbin/userbin-ruby)
|
7
7
|
|
8
|
-
**[Userbin](https://userbin.com) adds
|
9
|
-
|
10
|
-
Your users **do not** need to be signed up or registered for Userbin before using the service and there's no need for them to download any proprietary apps. Also, Userbin requires **no modification of your current database schema** as it uses your local user IDs.
|
11
|
-
|
12
|
-
## Table of Contents
|
13
|
-
|
14
|
-
- [Installation](#installation)
|
15
|
-
- [Getting Started](#getting-started)
|
16
|
-
- [Active Sessions](#active-sessions)
|
17
|
-
- [Security Events](#security-events)
|
18
|
-
- [Two-factor Authentication](#two-factor-authentication)
|
19
|
-
- [Pairing with Google Authenticator](#pairing-with-google-authenticator)
|
20
|
-
- [Pairing with Phone Number (SMS)](#pairing-with-phone-number-sms)
|
21
|
-
- [Pairing with YubiKey](#pairing-with-yubikey)
|
22
|
-
- [Enabling and Disabling](#enabling-and-disabling)
|
23
|
-
- [Authenticating](#authenticating)
|
24
|
-
- [Backup Codes](#backup-codes)
|
25
|
-
- [List Pairings](#list-pairings)
|
8
|
+
**[Userbin](https://userbin.com) adds real-time monitoring of your authentication stack, instantly notifying you and your users on potential account hijacks.**
|
26
9
|
|
27
10
|
## Installation
|
28
11
|
|
29
12
|
Add the `userbin` gem to your `Gemfile`
|
30
13
|
|
31
14
|
```ruby
|
32
|
-
gem
|
15
|
+
gem 'userbin'
|
33
16
|
```
|
34
17
|
|
35
18
|
Load and configure the library with your Userbin API secret in an initializer or similar.
|
36
19
|
|
37
20
|
```ruby
|
38
|
-
Userbin.api_secret =
|
21
|
+
Userbin.api_secret = 'YOUR_API_SECRET'
|
39
22
|
```
|
40
23
|
|
41
|
-
|
42
|
-
|
43
|
-
### 1. Logging in and out
|
24
|
+
A Userbin client instance will automatically be made available as `userbin` in your Rails, Sinatra or Padrino controllers.
|
44
25
|
|
45
|
-
|
26
|
+
## Tracking security events
|
46
27
|
|
47
|
-
|
48
|
-
def your_after_login_hook
|
49
|
-
userbin.login(current_user.id, email: current_user.email)
|
50
|
-
end
|
51
|
-
```
|
28
|
+
`track` lets you record the security-related actions your users perform. The more actions you track, the more accurate Userbin is in identifying fraudsters.
|
52
29
|
|
53
|
-
When
|
30
|
+
When you have access to a logged in user, send along the same user identifier as when you initiated Userbin.js.
|
54
31
|
|
55
32
|
```ruby
|
56
|
-
|
57
|
-
|
58
|
-
|
33
|
+
userbin.track(
|
34
|
+
user_id: user.id,
|
35
|
+
name: 'login.succeeded')
|
59
36
|
```
|
60
37
|
|
61
|
-
|
62
|
-
|
63
|
-
### 2. Protecting routes
|
64
|
-
|
65
|
-
Call `authorize!` just before your `current_user` is being initialized. Usually you'll want to override your normal authentication filter, e.g. `authenticate_user!` if you're using Devise.
|
66
|
-
|
67
|
-
- `UserUnauthorizedError` will be raised if `login` has not yet been called, or if the session is no longer valid.
|
68
|
-
- `ChallengeRequiredError` will be raised when the user has enabled two-factor authentication and is logging in from an untrusted device.
|
38
|
+
If you don't have access to a logged in user just omit `user_id`, typically when tracking failed logins.
|
69
39
|
|
70
40
|
```ruby
|
71
|
-
|
72
|
-
rescue_from Userbin::UserUnauthorizedError, with: :user_unauthorized
|
73
|
-
rescue_from Userbin::ChallengeRequiredError, with: :challenge_required
|
74
|
-
|
75
|
-
# IMPLEMENT: Override the authentication method from your framework
|
76
|
-
def authenticate_user!
|
77
|
-
userbin.authorize!
|
78
|
-
super
|
79
|
-
end
|
80
|
-
|
81
|
-
# IMPLEMENT: Log out your user locally
|
82
|
-
def user_unauthorized
|
83
|
-
sign_out
|
84
|
-
redirect_to root_path
|
85
|
-
end
|
86
|
-
|
87
|
-
# IMPLEMENT: Redirect to two-factor authentication login
|
88
|
-
def challenge_required
|
89
|
-
redirect_to show_challenge_path
|
90
|
-
end
|
91
|
-
end
|
41
|
+
userbin.track(name: 'login.failed')
|
92
42
|
```
|
93
43
|
|
94
|
-
|
95
|
-
|
96
|
-
```ruby
|
97
|
-
class AccountController < ApplicationController
|
98
|
-
before_filter :authenticate_user!
|
99
|
-
end
|
100
|
-
```
|
101
|
-
|
102
|
-
## Active Sessions
|
103
|
-
|
104
|
-
Show a list of sessions currently signed to a user's account.
|
105
|
-
|
106
|
-
The *context* is from the last recorded [security event](#security-events) on a session.
|
107
|
-
|
108
|
-
```ruby
|
109
|
-
userbin.sessions.each do |session|
|
110
|
-
puts session.id # => 'yt9BkoHzcQoou4jqbQbJUqqMdxyxvCBr'
|
111
|
-
puts session.context.ip # => '88.12.129.1'
|
112
|
-
end
|
113
|
-
```
|
114
|
-
|
115
|
-
Destroy a session to revoke access and trigger a `UserUnauthorizedError` the next time `authorize!` refreshes the session token, which is within 5 minutes.
|
116
|
-
|
117
|
-
```ruby
|
118
|
-
userbin.sessions.destroy('yt9BkoHzcQoou4jqbQbJUqqMdxyxvCBr')
|
119
|
-
```
|
120
|
-
|
121
|
-
## Security Events
|
122
|
-
|
123
|
-
List a user's recent account activity, which include security events such as user logins and failed two-factor attempts. See the [Event API](https://api.userbin.com/#events) for a list of all the available events.
|
124
|
-
|
125
|
-
```ruby
|
126
|
-
userbin.events.each do |event|
|
127
|
-
puts event.name # => 'session.created'
|
128
|
-
puts event.context.ip # => '88.12.129.1'
|
129
|
-
puts event.context.location.country # => 'Sweden'
|
130
|
-
puts event.context.user_agent.browser # => 'Chrome'
|
131
|
-
end
|
132
|
-
```
|
133
|
-
|
134
|
-
## Two-factor Authentication
|
135
|
-
|
136
|
-
Using two-factor authentication involves two steps: **pairing** and **authenticating**.
|
137
|
-
|
138
|
-
### Pairing
|
139
|
-
|
140
|
-
Before your users can protect their account with two-factor authentication, they will need to pair their their preferred way of authenticating. The [Pairing API](https://api.userbin.com/#pairings) lets users add, verify, and remove authentication channels. Only *verified* pairings are valid for authentication.
|
141
|
-
|
142
|
-
#### Pairing with Google Authenticator
|
143
|
-
|
144
|
-
The user visits a page to add Google Authenticator to their account. First create a new Authenticator pairing to generate a QR code image.
|
145
|
-
|
146
|
-
```ruby
|
147
|
-
@authenticator = userbin.pairings.create(type: 'authenticator')
|
148
|
-
```
|
149
|
-
|
150
|
-
Render a page containing the QR code, which the user scans with Google Authenticator.
|
151
|
-
|
152
|
-
```erb
|
153
|
-
<img src="<%= @authenticator[:qr_url] %>">
|
154
|
-
```
|
155
|
-
|
156
|
-
After scanning the QR code, the user will enter the 6 digit token that Google Authenticator displays, and submit the form. Capture the response and verify the pairing.
|
157
|
-
|
158
|
-
```ruby
|
159
|
-
begin
|
160
|
-
userbin.pairings.verify(params[:pairing_id], response: params[:code])
|
161
|
-
rescue Userbin::InvalidParametersError
|
162
|
-
flash.notice = 'Wrong code, try again'
|
163
|
-
end
|
164
|
-
```
|
165
|
-
|
166
|
-
#### Pairing with Phone Number (SMS)
|
167
|
-
|
168
|
-
Create a new phone number pairing which will send out a verification SMS.
|
169
|
-
|
170
|
-
```ruby
|
171
|
-
@phone_number = userbin.pairings.create(
|
172
|
-
type: 'phone_number', number: '+1739855455')
|
173
|
-
```
|
174
|
-
|
175
|
-
Catch the code from the user to pair the phone number.
|
176
|
-
|
177
|
-
```ruby
|
178
|
-
begin
|
179
|
-
userbin.pairings.verify(params[:pairing_id], response: params[:code])
|
180
|
-
rescue Userbin::InvalidParametersError
|
181
|
-
flash.notice = 'Wrong code, try again'
|
182
|
-
end
|
183
|
-
```
|
184
|
-
|
185
|
-
#### Pairing with YubiKey
|
186
|
-
|
187
|
-
YubiKeys are immediately verified for two-factor authentication.
|
188
|
-
|
189
|
-
```ruby
|
190
|
-
begin
|
191
|
-
userbin.pairings.create(type: 'yubikey', otp: params[:code])
|
192
|
-
rescue Userbin::InvalidParametersError
|
193
|
-
flash.notice = 'Wrong code, try again'
|
194
|
-
end
|
195
|
-
```
|
196
|
-
|
197
|
-
#### Enabling and Disabling
|
198
|
-
|
199
|
-
For the sake of flexibility, two-factor authentication isn't enabled automatically when you add your first pairing.
|
200
|
-
|
201
|
-
```ruby
|
202
|
-
userbin.enable_mfa!
|
203
|
-
userbin.disable_mfa!
|
204
|
-
```
|
205
|
-
|
206
|
-
### Authenticating
|
207
|
-
|
208
|
-
If the user has enabled two-factor authentication, `authorize!` might raise `ChallengeRequiredError`, which means they'll have to verify a challenge to proceed.
|
209
|
-
|
210
|
-
Capture this error just as with UserUnauthorizedError and redirect the user to a path **not protected** by `authorize!`.
|
211
|
-
|
212
|
-
If the user tries to reach a path protected by `authorize!` after a challenge has been created but still not verified, the session will be destroyed and UserUnauthorizedError raised.
|
213
|
-
|
214
|
-
```ruby
|
215
|
-
class ApplicationController < ActionController::Base
|
216
|
-
rescue_from Userbin::ChallengeRequiredError do |exception|
|
217
|
-
redirect_to show_challenge_path
|
218
|
-
end
|
219
|
-
# ...
|
220
|
-
end
|
221
|
-
```
|
222
|
-
|
223
|
-
Create a challenge, which will send the user and SMS if this is the default pairing. After the challenge has been verified, `authorize!` will not throw any further exceptions until any suspicious behavior is detected.
|
224
|
-
|
225
|
-
When you call `trust_device`, the user will not be challenged for secondary authentication when they log in to your application from that device for a set period of time. You could add this to your form as a checkbox option.
|
226
|
-
|
227
|
-
```ruby
|
228
|
-
class ChallengeController < ApplicationController
|
229
|
-
def show
|
230
|
-
@challenge = userbin.challenges.create
|
231
|
-
end
|
232
|
-
|
233
|
-
def verify
|
234
|
-
challenge_id = params.require(:challenge_id)
|
235
|
-
code = params.require(:code)
|
236
|
-
|
237
|
-
userbin.challenges.verify(challenge_id, response: code)
|
238
|
-
|
239
|
-
# Avoid verification on next login for better experience
|
240
|
-
userbin.trust_device if params[:trust_device]
|
241
|
-
|
242
|
-
# Yay, the challenge was verified!
|
243
|
-
redirect_to root_url
|
244
|
-
|
245
|
-
rescue Userbin::ForbiddenError => e
|
246
|
-
sign_out # log out your user locally
|
247
|
-
|
248
|
-
flash.notice = 'Wrong code, bye!'
|
249
|
-
redirect_to root_path
|
250
|
-
end
|
251
|
-
end
|
252
|
-
```
|
253
|
-
|
254
|
-
### Backup Codes
|
255
|
-
|
256
|
-
List or generate new backup codes used for when the user didn't bring their authentication device.
|
257
|
-
|
258
|
-
```ruby
|
259
|
-
userbin.backup_codes
|
260
|
-
userbin.generate_backup_codes(count: 8)
|
261
|
-
```
|
262
|
-
|
263
|
-
### List Pairings
|
264
|
-
|
265
|
-
List all pairings.
|
266
|
-
|
267
|
-
```ruby
|
268
|
-
# List all pairings
|
269
|
-
userbin.pairings.each do |pairing|
|
270
|
-
puts pairing.id # => 'yt9BkoHzcQoou4jqbQbJUqqMdxyxvCBr'
|
271
|
-
puts pairing.type # => 'authenticator'
|
272
|
-
puts pairing.default # => true
|
273
|
-
end
|
274
|
-
```
|
275
|
-
|
276
|
-
Set a pairing as the default one.
|
277
|
-
|
278
|
-
```ruby
|
279
|
-
userbin.pairings.set_default!('yt9BkoHzcQoou4jqbQbJUqqMdxyxvCBr')
|
280
|
-
```
|
281
|
-
|
282
|
-
Remove a pairing. If you remove the default pairing, two-factor authentication will be disabled.
|
283
|
-
|
284
|
-
```ruby
|
285
|
-
userbin.pairings.destroy('yt9BkoHzcQoou4jqbQbJUqqMdxyxvCBr')
|
286
|
-
```
|
44
|
+
All the available events are:
|
287
45
|
|
46
|
+
- `login.succeeded`
|
47
|
+
- `login.failed`
|
48
|
+
- `logout.succeeded`
|
288
49
|
|
289
50
|
## Configuration
|
290
51
|
|
@@ -293,7 +54,7 @@ Userbin.configure do |config|
|
|
293
54
|
# Same as setting it through Userbin.api_secret
|
294
55
|
config.api_secret = 'secret'
|
295
56
|
|
296
|
-
# Userbin::RequestError is raised when timing out (default:
|
57
|
+
# Userbin::RequestError is raised when timing out (default: 30.0)
|
297
58
|
config.request_timeout = 2.0
|
298
59
|
end
|
299
60
|
```
|
data/lib/userbin.rb
CHANGED
data/lib/userbin/client.rb
CHANGED
@@ -32,7 +32,8 @@ module Userbin
|
|
32
32
|
|
33
33
|
@request_context = {
|
34
34
|
ip: request.ip,
|
35
|
-
user_agent: request.user_agent
|
35
|
+
user_agent: request.user_agent,
|
36
|
+
cookie_id: cookies['__cid']
|
36
37
|
}
|
37
38
|
end
|
38
39
|
|
@@ -126,5 +127,9 @@ module Userbin
|
|
126
127
|
def has_default_pairing?
|
127
128
|
@store.session_token ? @store.session_token.has_default_pairing? : false
|
128
129
|
end
|
130
|
+
|
131
|
+
def track(opts = {})
|
132
|
+
Userbin::Event.post('/v1/events', opts)
|
133
|
+
end
|
129
134
|
end
|
130
135
|
end
|
data/lib/userbin/request.rb
CHANGED
@@ -106,9 +106,11 @@ module Userbin
|
|
106
106
|
return @app.call(env) unless userbin
|
107
107
|
|
108
108
|
userbin.request_context.each do |key, value|
|
109
|
-
|
109
|
+
if value
|
110
|
+
header =
|
110
111
|
"X-Userbin-#{key.to_s.gsub('_', '-').gsub(/\w+/) {|m| m.capitalize}}"
|
111
|
-
|
112
|
+
env[:request_headers][header] = value
|
113
|
+
end
|
112
114
|
end
|
113
115
|
@app.call(env)
|
114
116
|
end
|
data/lib/userbin/version.rb
CHANGED
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.
|
4
|
+
version: 1.7.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-
|
11
|
+
date: 2014-12-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: her
|