userbin 1.1.0 → 1.1.1
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 +118 -28
- data/lib/userbin/client.rb +7 -2
- data/lib/userbin/configuration.rb +6 -0
- data/lib/userbin/request.rb +47 -24
- data/lib/userbin/session_token.rb +1 -1
- data/lib/userbin/utils.rb +1 -0
- 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: ffb023fc0244c53569a55984108c8f5e6a6d0376
|
4
|
+
data.tar.gz: 3edcb3bd8e9419fd0dbd4135044933b50c8888f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b81972686bf51fa8f71b613de9dfb7def38cb4c86d2106d1e1e3568529ec9f54787adf10085ca471802147afc3737989b06ac389d584075852bdafc1de93c943
|
7
|
+
data.tar.gz: dd8e6bc2f5c2d6b69ee1c32df5d1683d6aa99dfe40e0f1a4d34510b9f459c6b1124a2611376d86d51c696d77c430a572aeb4e8bd0fc50b31e3da4c41759a60ee
|
data/README.md
CHANGED
@@ -9,6 +9,10 @@
|
|
9
9
|
|
10
10
|
<!-- Your users can now easily activate two-factor authentication, configure the level of security in terms of monitoring and notifications and take action on suspicious behaviour. These settings are available as a per-user security settings page which is easily customized to fit your current layout. -->
|
11
11
|
|
12
|
+
### Using Devise?
|
13
|
+
|
14
|
+
If you're using [Devise](https://github.com/plataformatec/devise) for authentication, check out the **[Userbin extension for Devise](https://github.com/userbin/devise_userbin)** for an even easier integration.
|
15
|
+
|
12
16
|
## Getting started
|
13
17
|
|
14
18
|
Add the `userbin` gem to your `Gemfile`
|
@@ -23,66 +27,152 @@ Install the gem
|
|
23
27
|
bundle install
|
24
28
|
```
|
25
29
|
|
26
|
-
Load and configure the library with your Userbin API secret
|
30
|
+
Load and configure the library with your Userbin API secret in an initializer or similar
|
27
31
|
|
28
32
|
```ruby
|
29
33
|
require 'userbin'
|
30
34
|
Userbin.api_secret = "YOUR_API_SECRET"
|
31
35
|
```
|
32
36
|
|
33
|
-
|
34
|
-
|
35
|
-
```ruby
|
36
|
-
env['userbin'] = Userbin::Client.new(request)
|
37
|
-
```
|
38
|
-
|
37
|
+
## Monitor a user
|
39
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.
|
40
40
|
|
41
|
-
|
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.
|
42
42
|
|
43
|
-
|
43
|
+
### 1. Authorize the current user
|
44
44
|
|
45
45
|
```ruby
|
46
|
-
|
47
|
-
|
46
|
+
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
|
73
|
+
end
|
74
|
+
end
|
48
75
|
```
|
49
76
|
|
50
|
-
|
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
|
80
|
+
|
81
|
+
As a last step, you'll need to **end the Userbin session** when the user logs out from your application.
|
51
82
|
|
52
83
|
```ruby
|
53
|
-
|
54
|
-
|
84
|
+
def logout
|
85
|
+
# Your code for logging out a user
|
55
86
|
|
87
|
+
# End the Userbin session
|
88
|
+
env['userbin'].logout
|
89
|
+
end
|
90
|
+
```
|
56
91
|
|
57
|
-
|
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.
|
58
93
|
|
59
94
|
## Add a link to the user's security settings
|
60
95
|
|
61
|
-
Create a new route where you redirect the user to its
|
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.
|
62
97
|
|
63
98
|
```ruby
|
64
|
-
|
99
|
+
class UsersController < ApplicationController
|
100
|
+
def security_settings
|
101
|
+
redirect_to env['userbin'].security_settings_url
|
102
|
+
end
|
103
|
+
end
|
65
104
|
```
|
66
105
|
|
67
|
-
|
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.*
|
107
|
+
|
108
|
+
## Two-factor authentication
|
109
|
+
|
110
|
+
|
111
|
+
### 1. Protect routes
|
68
112
|
|
69
113
|
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.
|
70
114
|
|
71
115
|
```ruby
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
116
|
+
class UsersController < ApplicationController
|
117
|
+
before_filter :authenticate_with_userbin!
|
118
|
+
|
119
|
+
# Your controller code here
|
120
|
+
|
121
|
+
private
|
122
|
+
def authenticate_with_userbin!
|
123
|
+
begin
|
124
|
+
# Checks if two-factor authentication is needed. Returns nil if not.
|
125
|
+
factor = env['userbin'].two_factor_authenticate!
|
126
|
+
|
127
|
+
# Show form and message specific to the current factor
|
128
|
+
case factor
|
129
|
+
when :authenticator
|
130
|
+
redirect_to '/verify/authenticator'
|
131
|
+
when :sms
|
132
|
+
redirect_to '/verify/sms'
|
133
|
+
end
|
134
|
+
rescue Userbin::Error
|
135
|
+
# logged out from Userbin; clear your current_user and logout
|
136
|
+
end
|
137
|
+
end
|
77
138
|
end
|
78
139
|
```
|
79
140
|
|
80
|
-
|
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.
|
81
142
|
|
82
|
-
|
83
|
-
|
143
|
+
### 2. Show the two-factor authentication form to the user
|
144
|
+
|
145
|
+
```html
|
146
|
+
<p>
|
147
|
+
Open the two-factor authentication app on your device to view your
|
148
|
+
authentication code and verify your identity.
|
149
|
+
</p>
|
150
|
+
<form action="/users/handle_two_factor_response" method="post">
|
151
|
+
<label for="code">Authentication code</label>
|
152
|
+
<input id="code" name="code" type="text" />
|
153
|
+
<input type="submit" value="Verify code" />
|
154
|
+
</form>
|
84
155
|
```
|
85
156
|
|
86
|
-
|
157
|
+
### 3. Verify the code from the user
|
158
|
+
|
159
|
+
The user enters the authentication code in the form and posts it to your handler.
|
87
160
|
|
88
|
-
|
161
|
+
```ruby
|
162
|
+
def handle_two_factor_response
|
163
|
+
# Get the authentication code from the form
|
164
|
+
authentication_code = params[:code]
|
165
|
+
|
166
|
+
begin
|
167
|
+
env['userbin'].two_factor_verify(authentication_code)
|
168
|
+
rescue Userbin::UserUnauthorizedError
|
169
|
+
# invalid code, show the form again
|
170
|
+
rescue Userbin::ForbiddenError
|
171
|
+
# no tries remaining, log out
|
172
|
+
rescue Userbin::Error
|
173
|
+
# logged out from Userbin; clear your current_user and logout
|
174
|
+
end
|
175
|
+
|
176
|
+
# We made it through two-factor authentication!
|
177
|
+
end
|
178
|
+
```
|
data/lib/userbin/client.rb
CHANGED
@@ -42,7 +42,7 @@ module Userbin
|
|
42
42
|
|
43
43
|
@session_store.user_id = user_id
|
44
44
|
|
45
|
-
|
45
|
+
unless session_token
|
46
46
|
# Create a session, and implicitly a user with user_attrs
|
47
47
|
session = Userbin::Session.post(
|
48
48
|
"users/#{user_id}/sessions", user: user_attrs)
|
@@ -65,7 +65,8 @@ module Userbin
|
|
65
65
|
# Destroy the current session specified in the session token
|
66
66
|
begin
|
67
67
|
Userbin::Session.destroy_existing('current')
|
68
|
-
rescue Userbin::Error
|
68
|
+
rescue Userbin::Error # ignored
|
69
|
+
end
|
69
70
|
|
70
71
|
# Clear the session token
|
71
72
|
self.session_token = nil
|
@@ -112,5 +113,9 @@ module Userbin
|
|
112
113
|
return session_token.challenge_type
|
113
114
|
end
|
114
115
|
|
116
|
+
def authorized?
|
117
|
+
!!session_token
|
118
|
+
end
|
119
|
+
|
115
120
|
end
|
116
121
|
end
|
data/lib/userbin/request.rb
CHANGED
@@ -17,7 +17,7 @@ module Userbin
|
|
17
17
|
|
18
18
|
def self.get_uname
|
19
19
|
`uname -a 2>/dev/null`.strip if RUBY_PLATFORM =~ /linux|darwin/i
|
20
|
-
rescue Errno::ENOMEM
|
20
|
+
rescue Errno::ENOMEM # couldn't create subprocess
|
21
21
|
"uname lookup failed"
|
22
22
|
end
|
23
23
|
|
@@ -41,6 +41,21 @@ module Userbin
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
+
# Handle request errors
|
45
|
+
#
|
46
|
+
class RequestErrorHandler < Faraday::Middleware
|
47
|
+
def call(env)
|
48
|
+
env.request.timeout = Userbin.config.request_timeout
|
49
|
+
begin
|
50
|
+
@app.call(env)
|
51
|
+
rescue Faraday::ConnectionFailed
|
52
|
+
raise Userbin::RequestError, 'Could not connect to Userbin API'
|
53
|
+
rescue Faraday::TimeoutError
|
54
|
+
raise Userbin::RequestError, 'Userbin API timed out'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
44
59
|
# Adds details about current environment
|
45
60
|
#
|
46
61
|
class EnvironmentHeaders < Faraday::Middleware
|
@@ -48,7 +63,8 @@ module Userbin
|
|
48
63
|
begin
|
49
64
|
env[:request_headers]["X-Userbin-Client-User-Agent"] =
|
50
65
|
MultiJson.encode(Userbin::Request.client_user_agent)
|
51
|
-
rescue
|
66
|
+
rescue # ignored
|
67
|
+
end
|
52
68
|
|
53
69
|
env[:request_headers]["User-Agent"] =
|
54
70
|
"Userbin/v1 RubyBindings/#{Userbin::VERSION}"
|
@@ -98,36 +114,43 @@ module Userbin
|
|
98
114
|
end
|
99
115
|
end
|
100
116
|
|
101
|
-
class JSONParser <
|
102
|
-
# This method is triggered when the response has been received. It modifies
|
103
|
-
# the value of `env[:body]`.
|
104
|
-
#
|
105
|
-
# @param [Hash] env The response environment
|
106
|
-
# @private
|
117
|
+
class JSONParser < Faraday::Response::Middleware
|
107
118
|
def on_complete(env)
|
108
|
-
|
109
|
-
|
119
|
+
response = if env[:body].nil? || env[:body].empty?
|
120
|
+
{}
|
121
|
+
else
|
122
|
+
begin
|
123
|
+
MultiJson.load(env[:body], :symbolize_keys => true)
|
124
|
+
rescue MultiJson::LoadError
|
125
|
+
raise Userbin::ApiError, 'Invalid response from Userbin API'
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
case env[:status]
|
130
|
+
when 201..299
|
131
|
+
# OK
|
132
|
+
when 400
|
133
|
+
raise Userbin::BadRequestError, response[:message]
|
134
|
+
when 401
|
135
|
+
raise Userbin::UnauthorizedError, response[:message]
|
110
136
|
when 403
|
111
|
-
raise Userbin::ForbiddenError
|
112
|
-
MultiJson.decode(env[:body])['message'])
|
137
|
+
raise Userbin::ForbiddenError, response[:message]
|
113
138
|
when 404
|
114
|
-
raise Userbin::NotFoundError
|
115
|
-
MultiJson.decode(env[:body])['message'])
|
139
|
+
raise Userbin::NotFoundError, response[:message]
|
116
140
|
when 419
|
117
|
-
raise Userbin::UserUnauthorizedError
|
118
|
-
|
119
|
-
|
120
|
-
begin
|
121
|
-
message = MultiJson.decode(env[:body])['message']
|
122
|
-
raise Userbin::Error.new(message)
|
123
|
-
rescue MultiJson::ParseError
|
124
|
-
raise Userbin::ApiError.new
|
125
|
-
end
|
141
|
+
raise Userbin::UserUnauthorizedError, response[:message]
|
142
|
+
when 422
|
143
|
+
raise Userbin::InvalidParametersError, response[:message]
|
126
144
|
else
|
127
|
-
|
145
|
+
raise Userbin::ApiError, response[:message]
|
128
146
|
end
|
147
|
+
|
148
|
+
env[:body] = {
|
149
|
+
data: response
|
150
|
+
}
|
129
151
|
end
|
130
152
|
end
|
153
|
+
|
131
154
|
end
|
132
155
|
|
133
156
|
end
|
data/lib/userbin/utils.rb
CHANGED
@@ -9,6 +9,7 @@ module Userbin
|
|
9
9
|
|
10
10
|
Her::API.setup url: api_endpoint do |c|
|
11
11
|
c.use Userbin::Request::Middleware::BasicAuth, api_secret
|
12
|
+
c.use Userbin::Request::Middleware::RequestErrorHandler
|
12
13
|
c.use Userbin::Request::Middleware::EnvironmentHeaders
|
13
14
|
c.use Userbin::Request::Middleware::ContextHeaders
|
14
15
|
c.use Userbin::Request::Middleware::SessionToken
|
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.1.
|
4
|
+
version: 1.1.1
|
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-
|
11
|
+
date: 2014-07-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: her
|