userbin 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +4 -0
- data/lib/userbin/authentication.rb +140 -0
- data/lib/userbin/basic_auth.rb +33 -0
- data/lib/userbin/current.rb +17 -0
- data/lib/userbin/session.rb +31 -0
- data/lib/userbin/userbin.rb +123 -0
- data/lib/userbin.rb +28 -0
- data/spec/session_spec.rb +38 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/userbin_spec.rb +100 -0
- metadata +127 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4283d8611ee035f2141284b2c339d5244f2830fa
|
4
|
+
data.tar.gz: 0200a7e005412dc60e56fee0693fcea5a1f826bd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2980f02fb4669639ac88c121141959499e6e783dc23ca58f4dc41156838a3639f626e55537ec0a04b4d8f4016857400cee9e8cc69f7a10bb5184fa938a55a4c4
|
7
|
+
data.tar.gz: 4735bd77af93832204838b1776c0779c77056af02f31e19778e7b4bc92933f07a0fa1214dd2f592f86c49084d79d2e6fe767b56e22ae7b3959a684916148f6d4
|
data/README.md
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
module Userbin
|
2
|
+
class Authentication
|
3
|
+
|
4
|
+
CLOSING_HEAD_TAG = %r{</head>}
|
5
|
+
CLOSING_BODY_TAG = %r{</body>}
|
6
|
+
|
7
|
+
def initialize(app, options = {})
|
8
|
+
@app = app
|
9
|
+
@restricted_path = options[:restricted_path]
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(env)
|
13
|
+
request = Rack::Request.new(env)
|
14
|
+
|
15
|
+
begin
|
16
|
+
if env["REQUEST_PATH"] == "/userbin" &&
|
17
|
+
env["REQUEST_METHOD"] == "POST"
|
18
|
+
signature, data = Userbin.authenticate_events!(request)
|
19
|
+
|
20
|
+
MultiJson.decode(data)['events'].each do |event|
|
21
|
+
Userbin.trigger(event)
|
22
|
+
end
|
23
|
+
|
24
|
+
[ 200, { 'Content-Type' => 'text/html',
|
25
|
+
'Content-Length' => '2' }, ['OK'] ]
|
26
|
+
else
|
27
|
+
signature, data = Userbin.authenticate!(request)
|
28
|
+
|
29
|
+
if restrict && env["REQUEST_PATH"].start_with?(restrict) &&
|
30
|
+
!Userbin.authenticated?
|
31
|
+
return render_gateway(env["REQUEST_PATH"])
|
32
|
+
end
|
33
|
+
|
34
|
+
generate_response(env, signature, data)
|
35
|
+
end
|
36
|
+
rescue Userbin::SecurityError
|
37
|
+
message =
|
38
|
+
'Userbin::SecurityError: Invalid signature. Refresh to try again.'
|
39
|
+
headers = {
|
40
|
+
'Content-Type' => 'text/text',
|
41
|
+
'Content-Length' => message.length.to_s
|
42
|
+
}
|
43
|
+
|
44
|
+
Rack::Utils.delete_cookie_header!(
|
45
|
+
headers, 'userbin_signature', value = {})
|
46
|
+
Rack::Utils.delete_cookie_header!(
|
47
|
+
headers, 'userbin_data', value = {})
|
48
|
+
|
49
|
+
[ 400, headers, [message] ]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def restrict
|
54
|
+
Userbin.restricted_path || @restricted_path
|
55
|
+
end
|
56
|
+
|
57
|
+
def link_tags(login_path)
|
58
|
+
<<-LINK_TAGS
|
59
|
+
<link rel="userbin:root" href="/">
|
60
|
+
<link rel="userbin:login" href="#{login_path}">
|
61
|
+
LINK_TAGS
|
62
|
+
end
|
63
|
+
|
64
|
+
def meta_tag
|
65
|
+
<<-META_TAG
|
66
|
+
<meta property="userbin:events" content="/userbin">
|
67
|
+
META_TAG
|
68
|
+
end
|
69
|
+
|
70
|
+
def script_tag
|
71
|
+
script_url = ENV.fetch('USERBIN_SCRIPT_URL') {
|
72
|
+
"https://userbin.com/js/v0"
|
73
|
+
}
|
74
|
+
str = <<-SCRIPT_TAG
|
75
|
+
<script src="#{script_url}?#{Userbin.app_id}"></script>
|
76
|
+
SCRIPT_TAG
|
77
|
+
end
|
78
|
+
|
79
|
+
def render_gateway(current_path)
|
80
|
+
login_page = <<-LOGIN_PAGE
|
81
|
+
<html>
|
82
|
+
<head>
|
83
|
+
<title>Log in</title>
|
84
|
+
</head>
|
85
|
+
<body style="background-color: #DBDBDB;">
|
86
|
+
<a class="ub-login-form"></a>
|
87
|
+
</body>
|
88
|
+
</html>
|
89
|
+
LOGIN_PAGE
|
90
|
+
login_page = inject_tags(login_page, current_path)
|
91
|
+
[ 200,
|
92
|
+
{ 'Content-Type' => 'text/html',
|
93
|
+
'Content-Length' => login_page.length.to_s },
|
94
|
+
[login_page]
|
95
|
+
]
|
96
|
+
end
|
97
|
+
|
98
|
+
def inject_tags(body, login_path = restrict)
|
99
|
+
if body[CLOSING_HEAD_TAG]
|
100
|
+
body = body.gsub(CLOSING_HEAD_TAG, link_tags(login_path) + '\\0')
|
101
|
+
body = body.gsub(CLOSING_HEAD_TAG, meta_tag + '\\0')
|
102
|
+
end
|
103
|
+
if body[CLOSING_BODY_TAG]
|
104
|
+
body = body.gsub(CLOSING_BODY_TAG, script_tag + '\\0')
|
105
|
+
end
|
106
|
+
body
|
107
|
+
end
|
108
|
+
|
109
|
+
def generate_response(env, signature, data)
|
110
|
+
status, headers, response = @app.call(env)
|
111
|
+
if headers['Content-Type'] && headers['Content-Type']['text/html']
|
112
|
+
body = response.each.map do |chunk|
|
113
|
+
inject_tags(chunk)
|
114
|
+
end
|
115
|
+
|
116
|
+
if response.class.name.end_with?('::Response')
|
117
|
+
response.body = body
|
118
|
+
else
|
119
|
+
response = body
|
120
|
+
end
|
121
|
+
|
122
|
+
headers['Content-Length'] = body.join.length.to_s
|
123
|
+
end
|
124
|
+
|
125
|
+
if signature && data
|
126
|
+
Rack::Utils.set_cookie_header!(
|
127
|
+
headers, 'userbin_signature', value: signature, path: '/')
|
128
|
+
Rack::Utils.set_cookie_header!(
|
129
|
+
headers, 'userbin_data', value: data, path: '/')
|
130
|
+
else
|
131
|
+
Rack::Utils.delete_cookie_header!(
|
132
|
+
headers, 'userbin_signature', value = {})
|
133
|
+
Rack::Utils.delete_cookie_header!(
|
134
|
+
headers, 'userbin_data', value = {})
|
135
|
+
end
|
136
|
+
|
137
|
+
[status, headers, response]
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Userbin
|
2
|
+
class BasicAuth < Faraday::Middleware
|
3
|
+
def call(env)
|
4
|
+
value = Base64.encode64([Userbin.app_id, Userbin.api_secret].join(':'))
|
5
|
+
value.gsub!("\n", '')
|
6
|
+
env[:request_headers]["Authorization"] = "Basic #{value}"
|
7
|
+
@app.call(env)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class VerifySignature < Faraday::Response::Middleware
|
12
|
+
def call(env)
|
13
|
+
@app.call(env).on_complete do
|
14
|
+
signature = env[:response_headers]['x-userbin-signature']
|
15
|
+
data = env[:body]
|
16
|
+
Userbin.valid_signature?(signature, data)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class ParseSignedJSON < Faraday::Response::Middleware
|
22
|
+
def on_complete(env)
|
23
|
+
json = MultiJson.load(env[:body], symbolize_keys: true)
|
24
|
+
signature = env[:response_headers]['x-userbin-signature']
|
25
|
+
json[:signature] = signature if signature
|
26
|
+
env[:body] = {
|
27
|
+
data: json,
|
28
|
+
errors: [],
|
29
|
+
metadata: {}
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Userbin
|
2
|
+
class Current
|
3
|
+
attr_accessor :token, :expires_at, :user
|
4
|
+
|
5
|
+
def initialize(data)
|
6
|
+
if data
|
7
|
+
@token = data['id']
|
8
|
+
@expires_at = data['expires_at']
|
9
|
+
@user = Userbin::User.new(data['user'])
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def authenticated?
|
14
|
+
!@user.nil?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'her'
|
2
|
+
|
3
|
+
module Userbin
|
4
|
+
class Model
|
5
|
+
include Her::Model
|
6
|
+
end
|
7
|
+
|
8
|
+
class User < Model
|
9
|
+
def update_local_id(local_id)
|
10
|
+
self.local_id = local_id
|
11
|
+
save
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Session < Model
|
16
|
+
has_one :user
|
17
|
+
|
18
|
+
# Hack to avoid loading a remote user
|
19
|
+
def user
|
20
|
+
return self['user'] if self['user'].is_a?(User)
|
21
|
+
User.new(self['user']) if self['user']
|
22
|
+
end
|
23
|
+
|
24
|
+
def authenticated?
|
25
|
+
!user.id.nil? rescue false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Event < Model
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module Userbin
|
2
|
+
|
3
|
+
Callback = Struct.new(:pattern, :block) do; end
|
4
|
+
|
5
|
+
class << self
|
6
|
+
attr_accessor :app_id, :api_secret, :restricted_path
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.authenticate_events!(request, now = Time.now)
|
10
|
+
signature, data =
|
11
|
+
request.params.values_at('userbin_signature', 'userbin_data')
|
12
|
+
|
13
|
+
valid_signature?(signature, data)
|
14
|
+
|
15
|
+
[signature, data]
|
16
|
+
end
|
17
|
+
|
18
|
+
# Provide either a Rack::Request or a Hash containing :signature and :data.
|
19
|
+
#
|
20
|
+
def self.authenticate!(request, now = Time.now)
|
21
|
+
signature, data =
|
22
|
+
request.cookies.values_at('userbin_signature', 'userbin_data')
|
23
|
+
|
24
|
+
if signature && data && valid_signature?(signature, data)
|
25
|
+
|
26
|
+
current = Userbin::Session.new(MultiJson.decode(data))
|
27
|
+
|
28
|
+
if current.authenticated?
|
29
|
+
# FIXME: NoMethodError (undefined method `/' for nil:NilClass):
|
30
|
+
if now > Time.at(current.expires_at / 1000)
|
31
|
+
signature, data = refresh_session(current.user.id)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
tmp = MultiJson.decode(data) if data
|
37
|
+
|
38
|
+
self.current = Userbin::Session.new(tmp)
|
39
|
+
|
40
|
+
[signature, data]
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.refresh_session(user_id)
|
44
|
+
api_endpoint = ENV["USERBIN_API_ENDPOINT"] || 'https://userbin.com/api/v0'
|
45
|
+
uri = URI("#{api_endpoint}/users/#{user_id}/sessions")
|
46
|
+
uri.user = app_id
|
47
|
+
uri.password = api_secret
|
48
|
+
net = Net::HTTP.post_form(uri, {})
|
49
|
+
[net['X-Userbin-Signature'], net.body]
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.current
|
53
|
+
Thread.current[:userbin]
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.current=(value)
|
57
|
+
Thread.current[:userbin] = value
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.authenticated?
|
61
|
+
current.authenticated? rescue false
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.current_user
|
65
|
+
current.user if current
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.user
|
69
|
+
current_user
|
70
|
+
end
|
71
|
+
|
72
|
+
# Event handling
|
73
|
+
#
|
74
|
+
class << self
|
75
|
+
def on(*names, &block)
|
76
|
+
pattern = Regexp.union(names.empty? ? TYPE_LIST.to_a : names)
|
77
|
+
callbacks.each do |callback|
|
78
|
+
if pattern == callback.pattern
|
79
|
+
callbacks.delete(callback)
|
80
|
+
callbacks << Userbin::Callback.new(pattern, block)
|
81
|
+
return
|
82
|
+
end
|
83
|
+
end
|
84
|
+
callbacks << Userbin::Callback.new(pattern, block)
|
85
|
+
end
|
86
|
+
|
87
|
+
def trigger(raw_event)
|
88
|
+
event = Userbin::Event.new(raw_event)
|
89
|
+
callbacks.each do |callback|
|
90
|
+
if event.type =~ callback.pattern
|
91
|
+
object = case event['type']
|
92
|
+
when /^user\./
|
93
|
+
Userbin::User.new(event.object)
|
94
|
+
else
|
95
|
+
event.object
|
96
|
+
end
|
97
|
+
model = event.instance_exec object, &callback.block
|
98
|
+
|
99
|
+
if event.type =~ /user\.created/ && model.respond_to?(:id)
|
100
|
+
object.update_local_id(model.id)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def callbacks
|
109
|
+
@callbacks ||= []
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
# Checks signature against secret and returns boolean
|
116
|
+
#
|
117
|
+
def self.valid_signature?(signature, data)
|
118
|
+
digest = OpenSSL::Digest::SHA256.new
|
119
|
+
valid = signature == OpenSSL::HMAC.hexdigest(digest, api_secret, data)
|
120
|
+
raise SecurityError, "Invalid signature" unless valid
|
121
|
+
valid
|
122
|
+
end
|
123
|
+
end
|
data/lib/userbin.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'her'
|
2
|
+
require 'multi_json'
|
3
|
+
require 'openssl'
|
4
|
+
require 'net/http'
|
5
|
+
|
6
|
+
require "userbin/userbin"
|
7
|
+
require "userbin/basic_auth"
|
8
|
+
|
9
|
+
api_endpoint = ENV.fetch('USERBIN_API_ENDPOINT') {
|
10
|
+
"https://userbin.com/api/v0"
|
11
|
+
}
|
12
|
+
|
13
|
+
@api = Her::API.setup url: api_endpoint do |c|
|
14
|
+
c.use Userbin::BasicAuth
|
15
|
+
c.use Faraday::Request::UrlEncoded
|
16
|
+
c.use Userbin::ParseSignedJSON
|
17
|
+
c.use Faraday::Adapter::NetHttp
|
18
|
+
c.use Userbin::VerifySignature
|
19
|
+
end
|
20
|
+
|
21
|
+
require "userbin/current"
|
22
|
+
require "userbin/session"
|
23
|
+
require "userbin/authentication"
|
24
|
+
|
25
|
+
class Userbin::Error < Exception; end
|
26
|
+
class Userbin::SecurityError < Userbin::Error; end
|
27
|
+
|
28
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'multi_json'
|
3
|
+
|
4
|
+
describe 'Userbin::Session' do
|
5
|
+
before do
|
6
|
+
Userbin.app_id = '100000000000000'
|
7
|
+
Userbin.api_secret = 'test'
|
8
|
+
end
|
9
|
+
|
10
|
+
before do
|
11
|
+
data = {
|
12
|
+
id: "Prars5v7xz2xwWvF5LEqfEUHCoNNsV7V",
|
13
|
+
created_at: 1378978281000,
|
14
|
+
expires_at: 1378981881000,
|
15
|
+
user: {
|
16
|
+
confirmed_at: nil,
|
17
|
+
created_at: 1378978280000,
|
18
|
+
email: "johan@userbin.com",
|
19
|
+
id: "TF15JEy7HRxDYx6U435zzEwydKJcptUr",
|
20
|
+
last_sign_in_at: nil,
|
21
|
+
local_id: nil
|
22
|
+
}
|
23
|
+
}
|
24
|
+
stub_request(:post, /\/users\/.*\/sessions/).to_return(
|
25
|
+
status: 200,
|
26
|
+
headers: {'X-Userbin-Signature' => 'abcd'},
|
27
|
+
body: MultiJson.encode(data)
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
xit 'creates a session' do
|
32
|
+
allow(OpenSSL::HMAC).to receive(:hexdigest) { 'abcd' }
|
33
|
+
user = Userbin::User.new(id: 'guid1')
|
34
|
+
session = user.sessions.create
|
35
|
+
session.user.email.should == 'johan@userbin.com'
|
36
|
+
session.signature.should == 'abcd'
|
37
|
+
end
|
38
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'cgi'
|
3
|
+
|
4
|
+
describe Userbin do
|
5
|
+
before do
|
6
|
+
Userbin.app_id = '1000'
|
7
|
+
Userbin.api_secret = '1234'
|
8
|
+
end
|
9
|
+
|
10
|
+
let (:args) do
|
11
|
+
{
|
12
|
+
"HTTP_COOKIE" => "userbin_signature=abcd; userbin_data=#{CGI.escape(MultiJson.encode(session))} "
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
let (:session) do
|
17
|
+
{
|
18
|
+
"id" => 'xyz',
|
19
|
+
"expires_at" => 1478981881000,
|
20
|
+
"user" => {
|
21
|
+
"id" => 'abc'
|
22
|
+
}
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
let (:request) do
|
27
|
+
Rack::Request.new({
|
28
|
+
'HTTP_X_USERBIN_SIGNATURE' => 'abcd',
|
29
|
+
'CONTENT_TYPE' => 'application/json',
|
30
|
+
'rack.input' => StringIO.new()
|
31
|
+
}.merge(args))
|
32
|
+
end
|
33
|
+
|
34
|
+
let (:response) do
|
35
|
+
Rack::Response.new("", 200, {})
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'when session is created' do
|
39
|
+
|
40
|
+
it 'authenticates with class methods' do
|
41
|
+
allow(OpenSSL::HMAC).to receive(:hexdigest) { 'abcd' }
|
42
|
+
Userbin.authenticate!(request)
|
43
|
+
Userbin.should be_authenticated
|
44
|
+
Userbin.current_user.id.should == "abc"
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'renews' do
|
48
|
+
stub_request(:post, /.*userbin\.com.*/).to_return(:status => 200, :body => "{\"id\":\"Prars5v7xz2xwWvF5LEqfEUHCoNNsV7V\",\"created_at\":1378978281000,\"expires_at\":1378981881000,\"user\":{\"confirmed_at\":null,\"created_at\":1378978280000,\"email\":\"admin@getapp6133.com\",\"id\":\"TF15JEy7HRxDYx6U435zzEwydKJcptUr\",\"last_sign_in_at\":null,\"local_id\":null}}", :headers => {'X-Userbin-Signature' => 'abcd'})
|
49
|
+
allow(OpenSSL::HMAC).to receive(:hexdigest) { 'abcd' }
|
50
|
+
Userbin.authenticate!(request, Time.at(1478981882)) # expired 1s
|
51
|
+
Userbin.current.id.should == 'Prars5v7xz2xwWvF5LEqfEUHCoNNsV7V'
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'does not renew' do
|
55
|
+
stub_request(:post, /.*userbin\.com.*/).to_return(:status => 404)
|
56
|
+
allow(OpenSSL::HMAC).to receive(:hexdigest) { 'abcd' }
|
57
|
+
Userbin.authenticate!(request, Time.at(1478981882)) # expired 1s
|
58
|
+
end
|
59
|
+
|
60
|
+
xit 'authenticate with correct signature' do
|
61
|
+
expect {
|
62
|
+
Userbin.authenticate!(request)
|
63
|
+
}.to raise_error { Userbin::SecurityError }
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'does not authenticate incorrect signature' do
|
67
|
+
expect {
|
68
|
+
Userbin.authenticate!(request)
|
69
|
+
}.to raise_error { Userbin::SecurityError }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'when session is deleted' do
|
74
|
+
let (:args) do
|
75
|
+
{
|
76
|
+
"HTTP_COOKIE" => "userbin_signature=abcd;"
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'does not authenticate' do
|
81
|
+
allow(OpenSSL::HMAC).to receive(:hexdigest) { 'abcd' }
|
82
|
+
Userbin.authenticate!(request)
|
83
|
+
Userbin.should_not be_authenticated
|
84
|
+
Userbin.user.should be_nil
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'when params are present' do
|
89
|
+
let (:args) do
|
90
|
+
{
|
91
|
+
"QUERY_STRING" => "userbin_signature=abcd&userbin_data=#{MultiJson.encode(session)}"
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'authenticates with class methods' do
|
96
|
+
allow(OpenSSL::HMAC).to receive(:hexdigest) { 'abcd' }
|
97
|
+
Userbin.authenticate_events!(request)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
metadata
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: userbin
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Johan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-09-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: her
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.6.8
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.6.8
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: multi_json
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rack
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: webmock
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Plug n’ play user accounts. The simplest way to integrate a full authentication
|
84
|
+
and user management stack into your web application.
|
85
|
+
email: johan@userbin.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- lib/userbin/authentication.rb
|
91
|
+
- lib/userbin/basic_auth.rb
|
92
|
+
- lib/userbin/current.rb
|
93
|
+
- lib/userbin/session.rb
|
94
|
+
- lib/userbin/userbin.rb
|
95
|
+
- lib/userbin.rb
|
96
|
+
- README.md
|
97
|
+
- spec/session_spec.rb
|
98
|
+
- spec/spec_helper.rb
|
99
|
+
- spec/userbin_spec.rb
|
100
|
+
homepage: https://userbin.com
|
101
|
+
licenses:
|
102
|
+
- MIT
|
103
|
+
metadata: {}
|
104
|
+
post_install_message:
|
105
|
+
rdoc_options: []
|
106
|
+
require_paths:
|
107
|
+
- lib
|
108
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - '>='
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
requirements: []
|
119
|
+
rubyforge_project:
|
120
|
+
rubygems_version: 2.0.3
|
121
|
+
signing_key:
|
122
|
+
specification_version: 4
|
123
|
+
summary: Userbin
|
124
|
+
test_files:
|
125
|
+
- spec/session_spec.rb
|
126
|
+
- spec/spec_helper.rb
|
127
|
+
- spec/userbin_spec.rb
|