uber_login 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/lib/uber_login.rb +135 -0
- data/lib/uber_login/configuration.rb +24 -0
- data/lib/uber_login/cookie_manager.rb +66 -0
- data/lib/uber_login/version.rb +3 -0
- data/spec/cookie_manager_spec.rb +115 -0
- data/spec/spec_helper.rb +83 -0
- data/spec/uber_login_spec.rb +203 -0
- metadata +71 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
OGZjMzFmM2JhZGY0ZmJlZGE5MWY5ODc0ZWY4OTE1MDI4MjdkMTc0OA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MWQ3YTdhYzI1MzU2YTFlZjQxMjFhYzk4NjI2NTgyZDA5Y2E4MDVkNw==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MmIyNGE2MzhkYmE3NmI1OTk5MWU0OTM2Y2RmYTI3NWYzNDI2YzUxNDNmNTQy
|
10
|
+
MmUxZTdhNjRiMDU0NmJjNDY5NjMxZGZlZWVkNTRmZDQwN2RjNGZmMWE5Mzcz
|
11
|
+
YzU2NGRkZGQ0NGI5MWVmNTUzM2RjOGFkNzFiYWQzOGFjNmExYWI=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MWEyNzkzODc4ZGFiMGE3MDlkYTQyMWUxMDFjNTNhNTYwZWQyNTM0ODY5YzNk
|
14
|
+
ZTNhNjkxZDc3YmJmNGI2NTkxZDRmYTk1MTVmNmNlYmMwNzZlNzNmM2E2MjA1
|
15
|
+
MmE5MTQ4OWZlZDgxNDk1Njc0NzVmMjJlZTAwN2IwZTBhZTI0OTE=
|
data/lib/uber_login.rb
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'uber_login/version'
|
2
|
+
require 'uber_login/cookie_manager'
|
3
|
+
require 'uber_login/configuration'
|
4
|
+
require 'securerandom'
|
5
|
+
require 'bcrypt'
|
6
|
+
require 'user_agent'
|
7
|
+
|
8
|
+
module UberLogin
|
9
|
+
##
|
10
|
+
# Returns the logged in user.
|
11
|
+
# If session[+:uid+] is set it returns that user.
|
12
|
+
# If session[+:uid+] is NOT set but cookies[+:uid+] and cookies[+:ulogin+] ARE:
|
13
|
+
# * It dissects +:ulogin+ into Sequence and Token
|
14
|
+
# * Looks for a LoginToken from UID and Sequence
|
15
|
+
# * Test Token against the stored and strong hashed one
|
16
|
+
# * If they match, session[+:uid+] is set and it returns the +User+
|
17
|
+
# If none of the previous cases, +nil+ is returned.
|
18
|
+
# If the cookie did not match, they are cleared from the user browser.
|
19
|
+
#
|
20
|
+
# All the checks are runt only once and the result is cached
|
21
|
+
def current_user
|
22
|
+
@current_user ||= current_user_uncached
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# Logs in the given +user+
|
27
|
+
# If +remember+ is true all the needed cookies are set.
|
28
|
+
# session[+:uid+] is set to user.id
|
29
|
+
def login(user, remember = false)
|
30
|
+
logout_all unless UberLogin.configuration.allow_multiple_login
|
31
|
+
|
32
|
+
session[:uid] = user.id
|
33
|
+
generate_and_set_cookies(user.id) if remember
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Clears session[:uid]
|
38
|
+
# If remember cookies were set they're cleared and the token is
|
39
|
+
# removed from the database.
|
40
|
+
def logout
|
41
|
+
session.delete(:uid)
|
42
|
+
delete_from_database if cookies[:uid]
|
43
|
+
cookie_manager.clear
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Deletes all "remember me" session for this user from whatever device
|
48
|
+
# he/she has ever used to login.
|
49
|
+
def logout_all
|
50
|
+
LoginToken.find_by(uid: session[:uid]).destroy
|
51
|
+
session.delete :uid
|
52
|
+
cookie_manager.clear
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
def cookie_manager
|
57
|
+
@cookie_manager ||= CookieManager.new(cookies, request)
|
58
|
+
end
|
59
|
+
|
60
|
+
# See +current_user+
|
61
|
+
def current_user_uncached
|
62
|
+
sid = session[:uid]
|
63
|
+
sid = login_from_cookies if cookies[:uid] and !sid
|
64
|
+
User.find(sid) if sid
|
65
|
+
end
|
66
|
+
|
67
|
+
##
|
68
|
+
# Attempts a login from the +:uid+ and +:ulogin+ cookies.
|
69
|
+
def login_from_cookies
|
70
|
+
if cookie_manager.valid?
|
71
|
+
session[:uid] = cookies[:uid]
|
72
|
+
generate_new_token
|
73
|
+
session[:uid]
|
74
|
+
else
|
75
|
+
cookie_manager.clear
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
##
|
81
|
+
# Deletes the current token from the database and creates a new one in place.
|
82
|
+
# Sets the user cookies to match the new values
|
83
|
+
def generate_new_token
|
84
|
+
delete_from_database
|
85
|
+
generate_and_set_cookies(cookies[:uid])
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# Creates a pair of cookies.
|
90
|
+
# +:uid+ is set to the user id
|
91
|
+
# +:ulogin+ is a composite field made of +:sequence+ and +:token+
|
92
|
+
#
|
93
|
+
# +:sequence+ is used to choose between all possible user login tokens
|
94
|
+
# +:token+ is stored +bcrypt+ed in the database and then compared on login
|
95
|
+
def generate_and_set_cookies(uid)
|
96
|
+
sequence, token = generate_sequence_and_token
|
97
|
+
cookie_manager.persistent_login(uid, sequence, token)
|
98
|
+
save_to_database
|
99
|
+
end
|
100
|
+
|
101
|
+
##
|
102
|
+
# Creates a LoginToken based on the +uid+, +sequence+ and hashed +token+
|
103
|
+
def save_to_database
|
104
|
+
token_row = LoginToken.new(
|
105
|
+
uid: cookies[:uid],
|
106
|
+
sequence: cookie_manager.sequence,
|
107
|
+
token: cookie_manager.hashed_token
|
108
|
+
)
|
109
|
+
|
110
|
+
set_user_data token_row
|
111
|
+
|
112
|
+
token_row.save!
|
113
|
+
end
|
114
|
+
|
115
|
+
##
|
116
|
+
# Removes a LoginToken with +uid+ and +sequence+ taken from the cookies
|
117
|
+
def delete_from_database
|
118
|
+
sequence = cookie_manager.sequence
|
119
|
+
token = LoginToken.find_by(uid: cookies[:uid], sequence: sequence)
|
120
|
+
token.destroy
|
121
|
+
end
|
122
|
+
|
123
|
+
def generate_sequence_and_token
|
124
|
+
# 9 and 21 are both multiple of 3, so we do not get base64 padding (==)
|
125
|
+
[ SecureRandom.base64(9), SecureRandom.base64(21) ]
|
126
|
+
end
|
127
|
+
|
128
|
+
def set_user_data(row)
|
129
|
+
user_agent = UserAgent.parse(request.user_agent)
|
130
|
+
|
131
|
+
row.ip_address = request.remote_ip if row.respond_to? :ip_address=
|
132
|
+
row.os = user_agent.os if row.respond_to? :os=
|
133
|
+
row.browser = user_agent.browser + ' ' + user_agent.version if row.respond_to? :browser=
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
##
|
2
|
+
# Use this class in app/config/initializers to change configuration parameters.
|
3
|
+
module UberLogin
|
4
|
+
class Configuration
|
5
|
+
attr_accessor :allow_multiple_login
|
6
|
+
attr_accessor :token_expiration
|
7
|
+
attr_accessor :tie_tokens_to_ip
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
self.allow_multiple_login = true
|
11
|
+
self.token_expiration = nil
|
12
|
+
self.tie_tokens_to_ip = false
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.configure
|
17
|
+
yield(configuration) if block_given?
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def self.configuration
|
22
|
+
@configuration ||= Configuration.new
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
##
|
2
|
+
# This class handles the +:uid+ and +:ulogin+ cookies
|
3
|
+
# It builds and sets the cookies, clears them, checks for their validity.
|
4
|
+
class CookieManager
|
5
|
+
def initialize(cookies, request)
|
6
|
+
@cookies = cookies
|
7
|
+
@request = request
|
8
|
+
@validity_checks = [ :token_match ]
|
9
|
+
|
10
|
+
@validity_checks << :ip_equality if UberLogin.configuration.tie_tokens_to_ip
|
11
|
+
@validity_checks << :expiration if UberLogin.configuration.token_expiration
|
12
|
+
end
|
13
|
+
|
14
|
+
##
|
15
|
+
# Clears +:uid+ and +:ulogin+ cookies
|
16
|
+
def clear
|
17
|
+
@cookies.delete :uid
|
18
|
+
@cookies.delete :ulogin
|
19
|
+
end
|
20
|
+
|
21
|
+
def valid?
|
22
|
+
token_row = LoginToken.find_by(uid: @cookies[:uid], sequence: sequence)
|
23
|
+
@validity_checks.all? { |check| send(check, token_row) }
|
24
|
+
rescue
|
25
|
+
false
|
26
|
+
end
|
27
|
+
|
28
|
+
def hashed_token
|
29
|
+
BCrypt::Password.create(token).to_s
|
30
|
+
end
|
31
|
+
|
32
|
+
def persistent_login(uid, sequence, token)
|
33
|
+
@cookies.permanent[:uid] = uid
|
34
|
+
@cookies.permanent[:ulogin] = ulogin_cookie(sequence, token)
|
35
|
+
end
|
36
|
+
|
37
|
+
def ulogin_cookie(sequence, token)
|
38
|
+
sequence + ':' + token
|
39
|
+
end
|
40
|
+
|
41
|
+
def sequence_and_token
|
42
|
+
@cookies[:ulogin].split(':')
|
43
|
+
end
|
44
|
+
|
45
|
+
def sequence
|
46
|
+
sequence_and_token[0]
|
47
|
+
end
|
48
|
+
|
49
|
+
def token
|
50
|
+
sequence_and_token[1]
|
51
|
+
end
|
52
|
+
|
53
|
+
# Validity checks
|
54
|
+
|
55
|
+
def token_match(row)
|
56
|
+
BCrypt::Password.new(row.token) == token
|
57
|
+
end
|
58
|
+
|
59
|
+
def ip_equality(row)
|
60
|
+
row.ip_address == @request.remote_ip
|
61
|
+
end
|
62
|
+
|
63
|
+
def expiration(row)
|
64
|
+
row.updated_at >= Time.now - UberLogin.configuration.token_expiration
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CookieManager do
|
4
|
+
let(:cookie_manager) { CookieManager.new({ uid: 100, ulogin: "dead:beef" }, FakeRequest.new) }
|
5
|
+
|
6
|
+
describe '#valid?' do
|
7
|
+
context 'User id and sequence combination is found' do
|
8
|
+
let(:fake_token) {
|
9
|
+
LoginToken.new(
|
10
|
+
token: BCrypt::Password.create("beef"),
|
11
|
+
ip_address: '192.168.1.1'
|
12
|
+
)
|
13
|
+
}
|
14
|
+
|
15
|
+
before { LoginToken.stub(:find_by).and_return fake_token }
|
16
|
+
|
17
|
+
it 'always executes token_match' do
|
18
|
+
expect(cookie_manager).to receive(:token_match)
|
19
|
+
cookie_manager.valid?
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'does not run ip_equality by default' do
|
23
|
+
expect(cookie_manager).to_not receive(:ip_equality)
|
24
|
+
cookie_manager.valid?
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'if IP are tied to Tokens' do
|
28
|
+
before { UberLogin.configuration.tie_tokens_to_ip = true }
|
29
|
+
|
30
|
+
it 'executes ip_equality' do
|
31
|
+
expect(cookie_manager).to receive(:ip_equality)
|
32
|
+
cookie_manager.valid?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'does not run expiration by default' do
|
37
|
+
expect(cookie_manager).to_not receive(:expiration)
|
38
|
+
cookie_manager.valid?
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'if Tokens do expire' do
|
42
|
+
before { UberLogin.configuration.token_expiration = 86400 }
|
43
|
+
|
44
|
+
it 'executes expiration' do
|
45
|
+
expect(cookie_manager).to receive(:expiration)
|
46
|
+
cookie_manager.valid?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'all checks return true' do
|
51
|
+
it 'returns true' do
|
52
|
+
expect(cookie_manager.valid?).to be_true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'any check fails' do
|
57
|
+
before { Array.any_instance.stub(:all?).and_return false }
|
58
|
+
|
59
|
+
it 'returns false' do
|
60
|
+
expect(cookie_manager.valid?).to be_false
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'User id and sequence combination is not found' do
|
66
|
+
before { LoginToken.stub(:find_by).and_return nil }
|
67
|
+
|
68
|
+
it 'returns false' do
|
69
|
+
expect(cookie_manager.valid?).to be_false
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe '#token_match' do
|
75
|
+
before { cookie_manager.stub(:token).and_return 'secret' }
|
76
|
+
|
77
|
+
it 'returns true if tokens are matched' do
|
78
|
+
row = double(token: BCrypt::Password.create('secret', cost: 1))
|
79
|
+
expect(cookie_manager.token_match(row)).to be_true
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'returns false if tokens are not matched' do
|
83
|
+
row = double(token: BCrypt::Password.create('s3cr3t', cost: 1))
|
84
|
+
expect(cookie_manager.token_match(row)).to be_false
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe '#ip_equality' do
|
89
|
+
before { FakeRequest.any_instance.stub(:remote_ip).and_return '10.10.10.10' }
|
90
|
+
|
91
|
+
it 'returns true if IPs are equal' do
|
92
|
+
row = double(ip_address: '10.10.10.10')
|
93
|
+
expect(cookie_manager.ip_equality(row)).to be_true
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'returns false if IPs are different' do
|
97
|
+
row = double(ip_address: '192.168.1.1')
|
98
|
+
expect(cookie_manager.ip_equality(row)).to be_false
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe '#expiration' do
|
103
|
+
before { UberLogin.configuration.token_expiration = 86400 }
|
104
|
+
|
105
|
+
it 'returns true if less than token_expiration seconds are past' do
|
106
|
+
row = double(updated_at: Time.now - 100)
|
107
|
+
expect(cookie_manager.expiration(row)).to be_true
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'returns false if more than token_expiration seconds are past' do
|
111
|
+
row = double(updated_at: Time.now - 86401)
|
112
|
+
expect(cookie_manager.expiration(row)).to be_false
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
|
2
|
+
|
3
|
+
require 'uber_login'
|
4
|
+
|
5
|
+
class FakeCookieJar < Hash
|
6
|
+
def permanent
|
7
|
+
self
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class FakeRequest
|
12
|
+
def remote_ip
|
13
|
+
'192.168.1.1'
|
14
|
+
end
|
15
|
+
|
16
|
+
def user_agent
|
17
|
+
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.36"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class ApplicationController
|
22
|
+
include UberLogin
|
23
|
+
|
24
|
+
attr_accessor :session
|
25
|
+
attr_accessor :cookies
|
26
|
+
attr_accessor :request
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
@session = {}
|
30
|
+
@cookies = FakeCookieJar.new
|
31
|
+
@request = FakeRequest.new
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# This is required to be an ActiveRecord like class
|
36
|
+
# Mongoid and MongoMapper should be just fine and probably others too.
|
37
|
+
class LoginToken
|
38
|
+
attr_accessor :uid, :sequence, :token
|
39
|
+
attr_accessor :ip_address, :os, :browser
|
40
|
+
|
41
|
+
@@count = 0
|
42
|
+
|
43
|
+
def initialize(attributes = {})
|
44
|
+
attributes.each do |k, v|
|
45
|
+
send("#{k}=", v)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def save!
|
50
|
+
true
|
51
|
+
@@count += 1
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.count
|
55
|
+
@@count
|
56
|
+
end
|
57
|
+
|
58
|
+
def destroy
|
59
|
+
@@count -= 1
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.find_by(hash)
|
63
|
+
new
|
64
|
+
end
|
65
|
+
|
66
|
+
def updated_at
|
67
|
+
Time.now - 100
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class User
|
72
|
+
attr_accessor :id
|
73
|
+
|
74
|
+
def initialize(attributes = {})
|
75
|
+
attributes.each do |k, v|
|
76
|
+
send("#{k}=", v)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.find(id)
|
81
|
+
User.new(id: id)
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe UberLogin do
|
4
|
+
let(:user) { double(id: 100) }
|
5
|
+
let(:controller) { ApplicationController.new }
|
6
|
+
let(:session) { controller.session }
|
7
|
+
let(:cookies) { controller.cookies }
|
8
|
+
|
9
|
+
describe '#login' do
|
10
|
+
context 'remember is false' do
|
11
|
+
it 'sets session[:uid]' do
|
12
|
+
controller.login(user)
|
13
|
+
expect(session[:uid]).to eq 100
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'remember is true' do
|
18
|
+
it 'sets session[:uid]' do
|
19
|
+
controller.login(user, true)
|
20
|
+
expect(session[:uid]).to eq 100
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'sets the uid cookie' do
|
24
|
+
controller.login(user, true)
|
25
|
+
expect(cookies[:uid]).to eq 100
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'sets the ulogin cookie' do
|
29
|
+
controller.login(user, true)
|
30
|
+
expect(cookies[:ulogin]).to match(/[a-z0-9+\/]+:[a-z0-9+\/]+/i)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'sets both cookies as persistent' do
|
34
|
+
expect(cookies).to receive(:permanent).twice.and_return cookies
|
35
|
+
controller.login(user, true)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'only one session is allowed per user' do
|
40
|
+
before { UberLogin::Configuration.any_instance.stub(:allow_multiple_login).and_return false }
|
41
|
+
|
42
|
+
it 'clears all the other tokens' do
|
43
|
+
expect_any_instance_of(LoginToken).to receive :destroy
|
44
|
+
controller.login(user)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '#logout' do
|
50
|
+
it 'deletes session[:uid]' do
|
51
|
+
controller.login(user)
|
52
|
+
controller.logout
|
53
|
+
expect(session[:uid]).to be_nil
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'persistent login was made' do
|
57
|
+
before { controller.login(user, true) }
|
58
|
+
|
59
|
+
it 'deletes session[:uid]' do
|
60
|
+
controller.logout
|
61
|
+
expect(session[:uid]).to be_nil
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'deletes login cookies' do
|
65
|
+
controller.logout
|
66
|
+
expect(cookies[:uid]).to be_nil
|
67
|
+
expect(cookies[:ulogin]).to be_nil
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'deletes a LoginToken row' do
|
71
|
+
expect {
|
72
|
+
controller.logout
|
73
|
+
}.to change{ LoginToken.count }.by -1
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe '#logout_all' do
|
79
|
+
it 'deletes session[:uid]' do
|
80
|
+
controller.login(user)
|
81
|
+
controller.logout_all
|
82
|
+
expect(session[:uid]).to be_nil
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'deletes session[:uid]' do
|
86
|
+
controller.logout_all
|
87
|
+
expect(session[:uid]).to be_nil
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'deletes login cookies' do
|
91
|
+
controller.logout_all
|
92
|
+
expect(cookies[:uid]).to be_nil
|
93
|
+
expect(cookies[:ulogin]).to be_nil
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'deletes any token associated with the user' do
|
97
|
+
expect_any_instance_of(LoginToken).to receive :destroy
|
98
|
+
controller.logout_all
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe '#save_to_database' do
|
103
|
+
before {
|
104
|
+
cookies[:uid] = "100"
|
105
|
+
cookies[:ulogin] = "dead:beef"
|
106
|
+
}
|
107
|
+
|
108
|
+
it 'saves the triplet to the database' do
|
109
|
+
expect_any_instance_of(LoginToken).to receive(:save!)
|
110
|
+
controller.send('save_to_database')
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe '#set_user_data' do
|
115
|
+
let(:row) { LoginToken.new }
|
116
|
+
|
117
|
+
context 'the token table has an "ip_address" field' do
|
118
|
+
it 'sets the field to the client IP' do
|
119
|
+
expect(row).to receive(:ip_address=).with('192.168.1.1')
|
120
|
+
controller.send('set_user_data', row)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context 'the token table has an "os" field' do
|
125
|
+
it 'sets the field to the client Operating System' do
|
126
|
+
expect(row).to receive(:os=).with('Linux x86_64')
|
127
|
+
controller.send('set_user_data', row)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
context 'the token table has a "browser" field' do
|
132
|
+
it 'sets the field to the client Browser and version' do
|
133
|
+
expect(row).to receive(:browser=).with('Chrome 32.0.1667.0')
|
134
|
+
controller.send('set_user_data', row)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe '#current_user_uncached' do
|
140
|
+
context 'session[:uid] is set' do
|
141
|
+
before { session[:uid] = 100 }
|
142
|
+
|
143
|
+
it 'returns an user object with that uid' do
|
144
|
+
expect(controller.send(:current_user_uncached).id).to eq 100
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
context 'session[:uid] is nil' do
|
149
|
+
before { session[:uid] = nil }
|
150
|
+
|
151
|
+
context 'cookies[:uid] and cookies[:ulogin] are set' do
|
152
|
+
before {
|
153
|
+
cookies[:uid] = "100"
|
154
|
+
cookies[:ulogin] = "whatever:beef"
|
155
|
+
}
|
156
|
+
|
157
|
+
context 'the cookies are valid' do
|
158
|
+
before { CookieManager.any_instance.stub(:valid?).and_return true }
|
159
|
+
|
160
|
+
it 'returns an user object with that uid' do
|
161
|
+
expect(controller.send(:current_user_uncached).id).to eq "100"
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'deletes the token from the database' do
|
165
|
+
expect_any_instance_of(LoginToken).to receive(:destroy)
|
166
|
+
controller.send(:current_user_uncached)
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'creates a new token for the next login' do
|
170
|
+
expect_any_instance_of(LoginToken).to receive(:save!)
|
171
|
+
controller.send(:current_user_uncached)
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'refreshes the cookie' do
|
175
|
+
controller.send(:current_user_uncached)
|
176
|
+
expect(cookies[:uid]).to eq "100"
|
177
|
+
expect(cookies[:ulogin]).to_not eq "whatever:beef"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
context 'the cookies are not valid' do
|
182
|
+
before { CookieManager.any_instance.stub(:valid?).and_return false }
|
183
|
+
|
184
|
+
it 'returns nil' do
|
185
|
+
expect(controller.send(:current_user_uncached)).to be_nil
|
186
|
+
end
|
187
|
+
|
188
|
+
it 'clears the cookies for this user' do
|
189
|
+
controller.send(:current_user_uncached)
|
190
|
+
expect(cookies[:uid]).to be_nil
|
191
|
+
expect(cookies[:ulogin]).to be_nil
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
context 'cookies are not set' do
|
197
|
+
it 'returns nil' do
|
198
|
+
expect(controller.send(:current_user_uncached)).to be_nil
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: uber_login
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Francesco Boffa
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-12-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: useragent
|
15
|
+
type: :runtime
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ! '>='
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 0.10.0
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.10.0
|
27
|
+
description: Login and logout management with secure "remember me" capabilities
|
28
|
+
email: fra.boffa@gmail.com
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- lib/uber_login.rb
|
34
|
+
- lib/uber_login/cookie_manager.rb
|
35
|
+
- lib/uber_login/version.rb
|
36
|
+
- lib/uber_login/configuration.rb
|
37
|
+
- spec/spec_helper.rb
|
38
|
+
- spec/cookie_manager_spec.rb
|
39
|
+
- spec/uber_login_spec.rb
|
40
|
+
homepage: https://github.com/AlfaOmega08/uber_login
|
41
|
+
licenses:
|
42
|
+
- MIT
|
43
|
+
metadata: {}
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ! '>='
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ! '>='
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
requirements: []
|
59
|
+
rubyforge_project:
|
60
|
+
rubygems_version: 2.1.10
|
61
|
+
signing_key:
|
62
|
+
specification_version: 4
|
63
|
+
summary: Tired of rewriting the login, logout and current_user methods for the millionth
|
64
|
+
time? Scared of all the security concerns of writing your own authentication methods?
|
65
|
+
This gem will solve all of this problems and still leave you the control over your
|
66
|
+
application.
|
67
|
+
test_files:
|
68
|
+
- spec/spec_helper.rb
|
69
|
+
- spec/cookie_manager_spec.rb
|
70
|
+
- spec/uber_login_spec.rb
|
71
|
+
has_rdoc:
|