uber_login 0.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 +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:
|