userbin 0.1.3
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 +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
|