userbin 0.3.5 → 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +76 -26
- data/lib/userbin.rb +0 -2
- data/lib/userbin/authentication.rb +14 -30
- data/lib/userbin/basic_auth.rb +1 -3
- data/lib/userbin/userbin.rb +17 -33
- data/lib/userbin/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eae191f76ab1ba6ce4587ba1b815e3d8fabeab8e
|
4
|
+
data.tar.gz: f98042c6d8858878062c45851ca71489010c9738
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6cbc7e71ac93e2f9eb8061e206451c144f378e4ec82ced8b3fda8ec94bb30bf020f6d9b5540d1c4e4c986927f14ab1735d66c331197ca99064ebc4429bb29e3f
|
7
|
+
data.tar.gz: c0b1103e93988a1a11ce39087be9ecda328824530403f77bf1fd20e68d2d343edd918aa4f7d6e883ea4f7cd959cf55d7175f03e88bff69e58755779b0c08f58b
|
data/README.md
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
[![Build Status](https://travis-ci.org/userbin/userbin-ruby.png)](https://travis-ci.org/userbin/userbin-ruby)
|
2
|
+
[![Gem Version](https://badge.fury.io/rb/userbin.png)](http://badge.fury.io/rb/userbin)
|
3
|
+
[![Dependency Status](https://gemnasium.com/userbin/userbin-ruby.png)](https://gemnasium.com/userbin/userbin-ruby)
|
2
4
|
|
3
5
|
Userbin for Ruby
|
4
6
|
================
|
5
7
|
|
6
|
-
Userbin for Ruby adds user authentication, login flows and user management to your **Rails**, **Sinatra** or **Rack** app.
|
8
|
+
Userbin for Ruby adds user authentication, login flows and user management to your **Rails**, **Sinatra** or **Rack** app, with users stored in your own database.
|
7
9
|
|
8
|
-
[Userbin](https://userbin.com) provides a set of login, signup, and password reset forms that drop right into your application without any need of styling or writing markup. Connect your users via traditional logins or third party social networks.
|
10
|
+
[Userbin](https://userbin.com) provides a set of login, signup, and password reset forms that drop right into your application without any need of styling or writing markup. Connect your users via traditional logins or third party social networks. It takes care of linking accounts across networks, resetting passwords, and keeping everything safe and secure.
|
9
11
|
|
10
12
|
[Create a free account](https://userbin.com) at Userbin to start accepting users in your application.
|
11
13
|
|
@@ -24,51 +26,76 @@ Installation
|
|
24
26
|
bundle install
|
25
27
|
```
|
26
28
|
|
27
|
-
|
29
|
+
1. Generate configuration
|
28
30
|
|
29
|
-
|
30
|
-
|
31
|
-
```ruby
|
32
|
-
Userbin.configure do |config|
|
33
|
-
config.app_id = "YOUR_APP_ID"
|
34
|
-
config.api_secret = "YOUR_API_SECRET"
|
35
|
-
end
|
31
|
+
```shell
|
32
|
+
curl https://lib.userbin.com/install.sh | sh YOUR_APP_ID YOUR_API_SECRET
|
36
33
|
```
|
37
34
|
|
38
|
-
If you don't configure the
|
35
|
+
If you don't configure the App ID and API Secret, the Userbin module will read the `USERBIN_APP_ID` and `USERBIN_API_SECRET` environment variables. This may come in handy on Heroku.
|
39
36
|
|
40
|
-
|
37
|
+
1. **Rack/Sinatra apps only**: Load and activate the Userbin Rack middleware
|
41
38
|
|
42
39
|
```ruby
|
40
|
+
require 'userbin'
|
41
|
+
|
43
42
|
use Userbin::Authentication
|
44
43
|
```
|
45
44
|
|
46
45
|
|
47
|
-
|
48
|
-
|
46
|
+
Configuration
|
47
|
+
-------------
|
49
48
|
|
50
|
-
###
|
49
|
+
### Authenticating
|
51
50
|
|
52
|
-
|
51
|
+
You need to configure Userbin in order to provide the `current_user` model:
|
53
52
|
|
54
|
-
|
53
|
+
``` ruby
|
54
|
+
Userbin.configure do
|
55
|
+
current_user do |profile|
|
56
|
+
User.find(profile.uid).first_or_initialize(
|
57
|
+
email: profile.email,
|
58
|
+
image: profile.image
|
59
|
+
)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
```
|
55
63
|
|
56
|
-
|
64
|
+
This code is run in the context of your application so you have access to your models, session or routes helpers. However, since this code is not run in the context of your application's ApplicationController it doesn't have access
|
65
|
+
to the methods defined over there.
|
57
66
|
|
58
|
-
```html
|
59
|
-
<a class="ub-login">Log in</a>
|
60
|
-
```
|
61
67
|
|
62
|
-
|
63
|
-
|
68
|
+
### Protecting routes
|
69
|
+
|
70
|
+
Use `authorize` to control access in controllers:
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
class ArticlesController < ApplicationController
|
74
|
+
before_filter :authorize
|
75
|
+
|
76
|
+
def index
|
77
|
+
current_user.articles
|
78
|
+
end
|
79
|
+
end
|
64
80
|
```
|
65
81
|
|
66
|
-
|
82
|
+
Or, you can authorize users in `config/routes.rb`:
|
67
83
|
|
68
|
-
```
|
69
|
-
|
84
|
+
```ruby
|
85
|
+
Blog::Application.routes.draw do
|
86
|
+
constraints Userbin::Protect.new { |user| user.admin? } do
|
87
|
+
root to: 'admin'
|
88
|
+
end
|
89
|
+
|
90
|
+
constraints Userbin::Protect.new do
|
91
|
+
mount MagicalWorker
|
92
|
+
end
|
93
|
+
end
|
70
94
|
```
|
71
95
|
|
96
|
+
Usage
|
97
|
+
-----
|
98
|
+
|
72
99
|
### The current user
|
73
100
|
|
74
101
|
Userbin keeps track of the currently logged in user which can be accessed through the `current_user` property. This automatically taps into libraries such as the authorization solution [CanCan](https://github.com/ryanb/cancan).
|
@@ -85,8 +112,31 @@ To check if a user is logged in, use `user_logged_in?` (or its alias `user_signe
|
|
85
112
|
<% end %>
|
86
113
|
```
|
87
114
|
|
115
|
+
|
88
116
|
**Rack/Sinatra apps only**: Since above helpers aren't available outside Rails, instead use `Userbin.current_user` and `Userbin.user_logged_in?`.
|
89
117
|
|
118
|
+
### Forms
|
119
|
+
|
120
|
+
An easy way to integrate Userbin is via the [Widget](https://userbin.com/docs/javascript#widget), which will take care of building forms, validating input and provides a drop-in design that adapts nicely to all devices.
|
121
|
+
|
122
|
+
The Widget is fairly high level, so remember that you can still use Userbin with your [own forms](https://userbin.com) if it doesn't fit your use-case.
|
123
|
+
|
124
|
+
Use `current_user` and `logged_in?` in controllers, views, and helpers:
|
125
|
+
|
126
|
+
```haml
|
127
|
+
- if signed_in?
|
128
|
+
= current_user.email
|
129
|
+
= link_to 'Log out', '/', class: 'ub-logout'
|
130
|
+
- else
|
131
|
+
= link_to 'Sign up', '/dashboard', class: 'ub-signup'
|
132
|
+
= link_to 'Log in', '/dashboard', class: 'ub-login'
|
133
|
+
```
|
134
|
+
|
135
|
+
### Protecting resources
|
136
|
+
|
137
|
+
...
|
138
|
+
|
139
|
+
|
90
140
|
Configuration
|
91
141
|
-------------
|
92
142
|
|
data/lib/userbin.rb
CHANGED
@@ -16,9 +16,7 @@ api_endpoint = ENV.fetch('USERBIN_API_ENDPOINT') {
|
|
16
16
|
c.use Userbin::BasicAuth
|
17
17
|
c.use Faraday::Request::UrlEncoded
|
18
18
|
c.use Her::Middleware::DefaultParseJSON
|
19
|
-
#c.use Userbin::ParseSignedJSON
|
20
19
|
c.use Faraday::Adapter::NetHttp
|
21
|
-
#c.use Userbin::VerifySignature
|
22
20
|
end
|
23
21
|
|
24
22
|
require "userbin/configuration"
|
@@ -12,41 +12,29 @@ module Userbin
|
|
12
12
|
raise ConfigurationError, "app_id and api_secret must be present"
|
13
13
|
end
|
14
14
|
|
15
|
+
Thread.current[:userbin] = nil
|
16
|
+
|
15
17
|
request = Rack::Request.new(env)
|
16
18
|
|
17
19
|
begin
|
18
|
-
|
19
|
-
env["REQUEST_METHOD"] == "POST"
|
20
|
-
signature, data = Userbin.authenticate_events!(request)
|
21
|
-
|
22
|
-
MultiJson.decode(data)['events'].each do |event|
|
23
|
-
Userbin::Events.trigger(event)
|
24
|
-
end
|
25
|
-
|
26
|
-
[ 200, { 'Content-Type' => 'text/html',
|
27
|
-
'Content-Length' => '2' }, ['OK'] ]
|
28
|
-
else
|
29
|
-
signature, data = Userbin.authenticate!(request)
|
30
|
-
|
31
|
-
if !Userbin.authenticated? && Userbin.config.protected_path &&
|
32
|
-
env["PATH_INFO"].start_with?(Userbin.config.protected_path)
|
20
|
+
jwt = Userbin.authenticate!(request)
|
33
21
|
|
34
|
-
|
35
|
-
|
22
|
+
if !Userbin.authenticated? && Userbin.config.protected_path &&
|
23
|
+
env["PATH_INFO"].start_with?(Userbin.config.protected_path)
|
36
24
|
|
37
|
-
|
25
|
+
return render_gateway(env["PATH_INFO"])
|
38
26
|
end
|
27
|
+
|
28
|
+
generate_response(env, jwt)
|
39
29
|
rescue Userbin::SecurityError
|
40
30
|
message =
|
41
|
-
'Userbin::SecurityError: Invalid
|
31
|
+
'Userbin::SecurityError: Invalid session. Refresh to try again.'
|
42
32
|
headers = {
|
43
33
|
'Content-Type' => 'text/text'
|
44
34
|
}
|
45
35
|
|
46
36
|
Rack::Utils.delete_cookie_header!(
|
47
|
-
headers, '
|
48
|
-
Rack::Utils.delete_cookie_header!(
|
49
|
-
headers, '_ubd', value = {})
|
37
|
+
headers, '_ubt', value = {})
|
50
38
|
|
51
39
|
[ 400, headers, [message] ]
|
52
40
|
end
|
@@ -99,7 +87,7 @@ module Userbin
|
|
99
87
|
[403, headers, [login_page]]
|
100
88
|
end
|
101
89
|
|
102
|
-
def generate_response(env,
|
90
|
+
def generate_response(env, jwt)
|
103
91
|
status, headers, response = @app.call(env)
|
104
92
|
|
105
93
|
if headers['Content-Type'] && headers['Content-Type']['text/html']
|
@@ -123,16 +111,12 @@ module Userbin
|
|
123
111
|
end
|
124
112
|
end
|
125
113
|
|
126
|
-
if
|
114
|
+
if jwt
|
127
115
|
Rack::Utils.set_cookie_header!(
|
128
|
-
headers, '
|
129
|
-
Rack::Utils.set_cookie_header!(
|
130
|
-
headers, '_ubd', value: data, path: '/')
|
116
|
+
headers, '_ubt', value: jwt, path: '/')
|
131
117
|
else
|
132
118
|
Rack::Utils.delete_cookie_header!(
|
133
|
-
headers, '
|
134
|
-
Rack::Utils.delete_cookie_header!(
|
135
|
-
headers, '_ubd', value = {})
|
119
|
+
headers, '_ubt', value = {})
|
136
120
|
end
|
137
121
|
|
138
122
|
[status, headers, response]
|
data/lib/userbin/basic_auth.rb
CHANGED
@@ -11,9 +11,7 @@ module Userbin
|
|
11
11
|
class VerifySignature < Faraday::Response::Middleware
|
12
12
|
def call(env)
|
13
13
|
@app.call(env).on_complete do
|
14
|
-
|
15
|
-
data = env[:body]
|
16
|
-
Userbin.valid_signature?(signature, data)
|
14
|
+
Userbin.decode_jwt(env[:body])
|
17
15
|
end
|
18
16
|
end
|
19
17
|
end
|
data/lib/userbin/userbin.rb
CHANGED
@@ -1,42 +1,37 @@
|
|
1
1
|
module Userbin
|
2
|
-
def self.
|
3
|
-
|
4
|
-
request.params.values_at('signature', 'data')
|
5
|
-
|
6
|
-
valid_signature?(signature, data)
|
7
|
-
|
8
|
-
[signature, data]
|
2
|
+
def self.decode_jwt(jwt)
|
3
|
+
JWT.decode(jwt, Userbin.config.api_secret)
|
9
4
|
end
|
10
5
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
signature, data =
|
15
|
-
request.cookies.values_at('_ubs', '_ubd')
|
6
|
+
def self.authenticate!(request)
|
7
|
+
jwt = request.cookies['_ubt']
|
8
|
+
return unless jwt
|
16
9
|
|
17
|
-
|
10
|
+
decoded = Userbin.decode_jwt(jwt)
|
18
11
|
|
19
|
-
|
12
|
+
if Time.now > Time.at(decoded['expires_at'] / 1000)
|
13
|
+
jwt = refresh_session(decoded['id'])
|
14
|
+
return unless jwt
|
20
15
|
|
21
|
-
|
22
|
-
|
16
|
+
decoded = Userbin.decode_jwt(jwt)
|
17
|
+
|
18
|
+
if Time.now > Time.at(decoded['expires_at'] / 1000)
|
19
|
+
raise Userbin::SecurityError
|
23
20
|
end
|
24
21
|
end
|
25
22
|
|
26
|
-
|
27
|
-
|
28
|
-
self.current = Userbin::Session.new(tmp)
|
23
|
+
self.current = Userbin::Session.new(decoded)
|
29
24
|
|
30
|
-
|
25
|
+
return jwt
|
31
26
|
end
|
32
27
|
|
33
28
|
def self.refresh_session(session_id)
|
34
29
|
api_endpoint = ENV["USERBIN_API_ENDPOINT"] || 'https://api.userbin.com'
|
35
|
-
uri = URI("#{api_endpoint}/sessions/#{session_id}/refresh")
|
30
|
+
uri = URI("#{api_endpoint}/sessions/#{session_id}/refresh.jwt")
|
36
31
|
uri.user = config.app_id
|
37
32
|
uri.password = config.api_secret
|
38
33
|
net = Net::HTTP.post_form(uri, {})
|
39
|
-
|
34
|
+
net.body
|
40
35
|
end
|
41
36
|
|
42
37
|
def self.current
|
@@ -107,15 +102,4 @@ module Userbin
|
|
107
102
|
def self.user
|
108
103
|
current_user
|
109
104
|
end
|
110
|
-
|
111
|
-
private
|
112
|
-
|
113
|
-
# Checks signature against secret and returns boolean
|
114
|
-
#
|
115
|
-
def self.valid_signature?(signature, data)
|
116
|
-
digest = OpenSSL::Digest::SHA256.new
|
117
|
-
valid = signature == OpenSSL::HMAC.hexdigest(digest, config.api_secret, data)
|
118
|
-
raise SecurityError, "Invalid signature" unless valid
|
119
|
-
valid
|
120
|
-
end
|
121
105
|
end
|
data/lib/userbin/version.rb
CHANGED