userbin 1.3.1 → 1.4.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 +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
|