userbin 1.3.1 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +12 -4
- data/lib/userbin.rb +2 -0
- data/lib/userbin/client.rb +35 -5
- data/lib/userbin/models/trusted_device.rb +5 -0
- data/lib/userbin/models/user.rb +1 -0
- data/lib/userbin/request.rb +1 -1
- data/lib/userbin/session_token.rb +4 -0
- data/lib/userbin/trusted_token_store.rb +35 -0
- data/lib/userbin/version.rb +1 -1
- data/spec/jwt_spec.rb +2 -2
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0257d124a0048c84143386d8b283897aea15857b
|
4
|
+
data.tar.gz: dc8f8dfb8a4333dba7cd9f1852f54dadbe7a73bf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 45a474521b983d95e7feb3db328d2253f5e8ba690ac3848b4a78198ff25520f5b5138363ca518c0061a45a290fcab48bdefd1e66972d0911f36a94c70443d753
|
7
|
+
data.tar.gz: 811d1c0c867a4f02b4dcceab6c67696fae958d96e130fc7c87b8d337da9727468d42aeda9e67111708aa24935b8f377e782459554033c43eaebaa17249661df5
|
data/README.md
CHANGED
@@ -43,12 +43,12 @@ require 'userbin'
|
|
43
43
|
Userbin.api_secret = "YOUR_API_SECRET"
|
44
44
|
```
|
45
45
|
|
46
|
-
Add a reference to the Userbin client in your main controller so that it's globally accessible throughout a request. The initializer takes a Rack request as argument from which it extracts details such as IP address and user agent and sends it along all API requests.
|
46
|
+
Add a reference to the Userbin client in your main controller so that it's globally accessible throughout a request. The initializer takes a Rack **request** as argument from which it extracts details such as IP address and user agent and sends it along all API requests. The second argument is a reference to the **cookies** hash, used for storing the trusted device token.
|
47
47
|
|
48
48
|
```ruby
|
49
49
|
class ApplicationController < ActionController::Base
|
50
50
|
def userbin
|
51
|
-
@userbin ||= Userbin::Client.new(request)
|
51
|
+
@userbin ||= Userbin::Client.new(request, cookies)
|
52
52
|
end
|
53
53
|
# ...
|
54
54
|
end
|
@@ -209,7 +209,7 @@ userbin.disable_mfa
|
|
209
209
|
|
210
210
|
If the user has enabled two-factor authentication, `authorize!` might raise `ChallengeRequiredError`, which means they'll have to verify a challenge to proceed.
|
211
211
|
|
212
|
-
Capture this error just as with UserUnauthorizedError and redirect the user
|
212
|
+
Capture this error just as with UserUnauthorizedError and redirect the user to a path **not protected** by `authorize!`.
|
213
213
|
|
214
214
|
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.
|
215
215
|
|
@@ -224,6 +224,8 @@ end
|
|
224
224
|
|
225
225
|
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.
|
226
226
|
|
227
|
+
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.
|
228
|
+
|
227
229
|
```ruby
|
228
230
|
class ChallengeController < ApplicationController
|
229
231
|
def show
|
@@ -236,11 +238,17 @@ class ChallengeController < ApplicationController
|
|
236
238
|
|
237
239
|
userbin.challenges.verify(challenge_id, response: code)
|
238
240
|
|
241
|
+
# Avoid verification on next login for better experience
|
242
|
+
userbin.trust_device if params[:trust_device]
|
243
|
+
|
239
244
|
# Yay, the challenge was verified!
|
240
245
|
redirect_to root_url
|
241
246
|
|
242
|
-
rescue Userbin::ForbiddenError
|
247
|
+
rescue Userbin::ForbiddenError => e
|
248
|
+
sign_out # log out your user locally
|
249
|
+
|
243
250
|
flash.notice = 'Wrong code, bye!'
|
251
|
+
redirect_to root_path
|
244
252
|
end
|
245
253
|
end
|
246
254
|
```
|
data/lib/userbin.rb
CHANGED
@@ -13,6 +13,7 @@ require 'userbin/configuration'
|
|
13
13
|
require 'userbin/client'
|
14
14
|
require 'userbin/errors'
|
15
15
|
require 'userbin/session_store'
|
16
|
+
require 'userbin/trusted_token_store'
|
16
17
|
require 'userbin/jwt'
|
17
18
|
require 'userbin/utils'
|
18
19
|
require 'userbin/request'
|
@@ -30,4 +31,5 @@ require 'userbin/models/monitoring'
|
|
30
31
|
require 'userbin/models/pairing'
|
31
32
|
require 'userbin/models/backup_codes'
|
32
33
|
require 'userbin/models/session'
|
34
|
+
require 'userbin/models/trusted_device'
|
33
35
|
require 'userbin/models/user'
|
data/lib/userbin/client.rb
CHANGED
@@ -14,15 +14,16 @@ module Userbin
|
|
14
14
|
end
|
15
15
|
|
16
16
|
install_proxy_methods :challenges, :events, :sessions, :pairings,
|
17
|
-
:backup_codes, :generate_backup_codes, :
|
17
|
+
:backup_codes, :generate_backup_codes, :trusted_devices,
|
18
|
+
:enable_mfa, :disable_mfa
|
18
19
|
|
19
|
-
def initialize(request, opts = {})
|
20
|
+
def initialize(request, cookies, opts = {})
|
20
21
|
# Save a reference in the per-request store so that the request
|
21
22
|
# middleware in request.rb can access it
|
22
23
|
RequestStore.store[:userbin] = self
|
23
24
|
|
24
25
|
# By default the session token is persisted in the Rack store, which may
|
25
|
-
# in turn point to any source.
|
26
|
+
# in turn point to any source. This option give you an option to
|
26
27
|
# use any store, such as Redis or Memcached to store your Userbin tokens.
|
27
28
|
if opts[:session_store]
|
28
29
|
@session_store = opts[:session_store]
|
@@ -30,6 +31,8 @@ module Userbin
|
|
30
31
|
@session_store = Userbin::SessionStore::Rack.new(request.session)
|
31
32
|
end
|
32
33
|
|
34
|
+
@trusted_token_store = Userbin::TrustedTokenStore::Rack.new(cookies)
|
35
|
+
|
33
36
|
@request_context = {
|
34
37
|
ip: request.ip,
|
35
38
|
user_agent: request.user_agent
|
@@ -49,11 +52,24 @@ module Userbin
|
|
49
52
|
Userbin::SessionToken.new(token) if token
|
50
53
|
end
|
51
54
|
|
55
|
+
def trusted_device_token=(value)
|
56
|
+
if value && value != @trusted_token_store.read
|
57
|
+
@trusted_token_store.write(value)
|
58
|
+
elsif !value
|
59
|
+
@trusted_token_store.destroy
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def trusted_device_token
|
64
|
+
@trusted_token_store.read
|
65
|
+
end
|
66
|
+
|
52
67
|
def identify(user_id)
|
53
68
|
# The user identifier is used in API paths so it needs to be cleaned
|
54
69
|
user_id = URI.encode(user_id.to_s)
|
55
70
|
|
56
71
|
@session_store.user_id = user_id
|
72
|
+
@trusted_token_store.user_id = user_id
|
57
73
|
end
|
58
74
|
|
59
75
|
def authorize
|
@@ -82,7 +98,9 @@ module Userbin
|
|
82
98
|
'Logged out due to being unverified'
|
83
99
|
end
|
84
100
|
|
85
|
-
|
101
|
+
if mfa_required? && !device_trusted?
|
102
|
+
raise Userbin::ChallengeRequiredError
|
103
|
+
end
|
86
104
|
end
|
87
105
|
|
88
106
|
def login(user_id, user_attrs = {})
|
@@ -92,12 +110,20 @@ module Userbin
|
|
92
110
|
identify(user_id)
|
93
111
|
|
94
112
|
session = Userbin::Session.post(
|
95
|
-
"users/#{@session_store.user_id}/sessions", user: user_attrs
|
113
|
+
"users/#{@session_store.user_id}/sessions", user: user_attrs,
|
114
|
+
trusted_device_token: self.trusted_device_token)
|
96
115
|
|
97
116
|
# Set the session token for use in all subsequent requests
|
98
117
|
self.session_token = session.token
|
99
118
|
end
|
100
119
|
|
120
|
+
def trust_device(attrs = {})
|
121
|
+
trusted_device = trusted_devices.create(attrs)
|
122
|
+
|
123
|
+
# Set the session token for use in all subsequent requests
|
124
|
+
self.trusted_device_token = trusted_device.token
|
125
|
+
end
|
126
|
+
|
101
127
|
# This method ends the current monitoring session. It should be called
|
102
128
|
# whenever the user logs out from your system.
|
103
129
|
#
|
@@ -118,6 +144,10 @@ module Userbin
|
|
118
144
|
session_token ? session_token.mfa_enabled? : false
|
119
145
|
end
|
120
146
|
|
147
|
+
def device_trusted?
|
148
|
+
session_token ? session_token.device_trusted? : false
|
149
|
+
end
|
150
|
+
|
121
151
|
def mfa_in_progress?
|
122
152
|
session_token ? session_token.has_challenge? : false
|
123
153
|
end
|
data/lib/userbin/models/user.rb
CHANGED
data/lib/userbin/request.rb
CHANGED
@@ -0,0 +1,35 @@
|
|
1
|
+
module Userbin
|
2
|
+
class TrustedTokenStore
|
3
|
+
class Rack < TrustedTokenStore
|
4
|
+
def initialize(cookies)
|
5
|
+
@cookies = cookies
|
6
|
+
end
|
7
|
+
|
8
|
+
def user_id
|
9
|
+
@cookies['userbin.user_id']
|
10
|
+
end
|
11
|
+
|
12
|
+
def user_id=(value)
|
13
|
+
@cookies['userbin.user_id'] = value
|
14
|
+
end
|
15
|
+
|
16
|
+
def read
|
17
|
+
@cookies[key]
|
18
|
+
end
|
19
|
+
|
20
|
+
def write(value)
|
21
|
+
@cookies[key] = value
|
22
|
+
end
|
23
|
+
|
24
|
+
def destroy
|
25
|
+
@cookies.delete(key)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def key
|
31
|
+
"userbin.trusted_device_token.#{user_id}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/userbin/version.rb
CHANGED
data/spec/jwt_spec.rb
CHANGED
@@ -13,14 +13,14 @@ describe 'Userbin::JWT' do
|
|
13
13
|
it 'verifies that JWT has expired' do
|
14
14
|
new_time = Time.utc(2014, 4, 23, 8, 46, 44)
|
15
15
|
Timecop.freeze(new_time) do
|
16
|
-
Userbin::JWT.new(token).
|
16
|
+
Userbin::JWT.new(token).should be_expired
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
20
|
it 'verifies that JWT has not expired' do
|
21
21
|
new_time = Time.utc(2014, 4, 23, 8, 46, 43)
|
22
22
|
Timecop.freeze(new_time) do
|
23
|
-
Userbin::JWT.new(token).
|
23
|
+
Userbin::JWT.new(token).should_not be_expired
|
24
24
|
end
|
25
25
|
end
|
26
26
|
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.
|
4
|
+
version: 1.4.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-10-
|
11
|
+
date: 2014-10-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: her
|
@@ -185,10 +185,12 @@ files:
|
|
185
185
|
- lib/userbin/models/monitoring.rb
|
186
186
|
- lib/userbin/models/pairing.rb
|
187
187
|
- lib/userbin/models/session.rb
|
188
|
+
- lib/userbin/models/trusted_device.rb
|
188
189
|
- lib/userbin/models/user.rb
|
189
190
|
- lib/userbin/request.rb
|
190
191
|
- lib/userbin/session_store.rb
|
191
192
|
- lib/userbin/session_token.rb
|
193
|
+
- lib/userbin/trusted_token_store.rb
|
192
194
|
- lib/userbin/utils.rb
|
193
195
|
- lib/userbin/version.rb
|
194
196
|
- spec/fixtures/vcr_cassettes/challenge_create.yml
|