letmein 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +33 -20
- data/VERSION +1 -1
- data/letmein.gemspec +2 -2
- data/lib/letmein.rb +84 -76
- data/test/letmein_test.rb +52 -14
- metadata +4 -4
data/README.md
CHANGED
@@ -1,26 +1,29 @@
|
|
1
1
|
letmein
|
2
2
|
=======
|
3
3
|
|
4
|
-
**letmein** is a minimalistic authentication plugin for Rails applications. It doesn't have anything other than the
|
4
|
+
**letmein** is a minimalistic authentication plugin for Rails 3 applications. It doesn't have anything other than the UserSession (or WhateverSession) object that you can use to authenticate logins.
|
5
5
|
|
6
6
|
Setup
|
7
7
|
=====
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
If you want to use *username* instead of *email* and if maybe you prefer naming from password and salt columns to something else do this:
|
8
|
+
|
9
|
+
Plug the thing below into Gemfile and you know what to do after.
|
10
|
+
|
11
|
+
gem 'letmein'
|
12
|
+
|
13
|
+
If you want to authenticate *User* with database fields *email*, *password_hash* and *password_salt* you don't need to do anything. If you're authenticating something else, you want something like this in your initializers:
|
15
14
|
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
LetMeIn.initialize(
|
16
|
+
:model => 'Account',
|
17
|
+
:identifier => 'username',
|
18
|
+
:password => 'password_crypt',
|
19
|
+
:salt => 'salty_salt
|
20
|
+
)
|
19
21
|
|
20
|
-
When creating/updating a
|
22
|
+
When creating/updating a record you have access to *password* accessor.
|
21
23
|
|
22
24
|
>> user = User.new(:email => 'example@example.com', :password => 'letmein')
|
23
25
|
>> user.save!
|
26
|
+
=> true
|
24
27
|
>> user.password_hash
|
25
28
|
=> $2a$10$0MeSaaE3I7.0FQ5ZDcKPJeD1.FzqkcOZfEKNZ/DNN.w8xOwuFdBCm
|
26
29
|
>> user.password_salt
|
@@ -29,9 +32,9 @@ When creating/updating a user record you have access to *password* accessor.
|
|
29
32
|
Authentication
|
30
33
|
==============
|
31
34
|
|
32
|
-
You authenticate using
|
35
|
+
You authenticate using UserSession object. Example:
|
33
36
|
|
34
|
-
>> session =
|
37
|
+
>> session = UserSession.new(:email => 'example@example.com', :password => 'letmein')
|
35
38
|
>> session.save
|
36
39
|
=> true
|
37
40
|
>> session.user
|
@@ -39,7 +42,7 @@ You authenticate using LetMeIn::Session object. Example:
|
|
39
42
|
|
40
43
|
When credentials are invalid:
|
41
44
|
|
42
|
-
>> session =
|
45
|
+
>> session = UserSession.new(:email => 'example@example.com', :password => 'bad_password')
|
43
46
|
>> session.save
|
44
47
|
=> false
|
45
48
|
>> session.user
|
@@ -47,15 +50,17 @@ When credentials are invalid:
|
|
47
50
|
|
48
51
|
Usage
|
49
52
|
=====
|
53
|
+
|
50
54
|
There are no built-in routes/controllers/views/helpers or anything. I'm confident you can do those yourself, because you're awesome. But here's an example how you can implement the controller handling the login:
|
51
55
|
|
52
56
|
class SessionsController < ApplicationController
|
53
57
|
def create
|
54
|
-
@session =
|
58
|
+
@session = UserSession.new(params[:user_session])
|
55
59
|
@session.save!
|
56
60
|
session[:user_id] = @session.user.id
|
57
61
|
flash[:notice] = "Welcome back #{@session.user.name}!"
|
58
62
|
redirect_to '/'
|
63
|
+
|
59
64
|
rescue LetMeIn::Error
|
60
65
|
flash.now[:error] = 'Invalid Credentials'
|
61
66
|
render :action => :new
|
@@ -64,9 +69,17 @@ There are no built-in routes/controllers/views/helpers or anything. I'm confiden
|
|
64
69
|
|
65
70
|
Upon successful login you have access to *session[:user_id]*. The rest is up to you.
|
66
71
|
|
67
|
-
|
68
|
-
|
69
|
-
|
72
|
+
Authenticating Multiple Models
|
73
|
+
==============================
|
74
|
+
Yes, you can do that too. Let's assume you also want to authenticate admins that don't have email addresses, but have usernames.
|
70
75
|
|
76
|
+
LetMeIn.initialize(
|
77
|
+
:model => ['User', 'Admin'],
|
78
|
+
:identifier => ['email', 'username']
|
79
|
+
)
|
80
|
+
|
81
|
+
Bam! You're done. Now you have an AdminSession object that will use *username* and *password* to authenticate.
|
71
82
|
|
72
|
-
|
83
|
+
Copyright
|
84
|
+
=========
|
85
|
+
(c) 2011 Oleg Khabarov, released under the MIT license
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.6
|
data/letmein.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{letmein}
|
8
|
-
s.version = "0.0.
|
8
|
+
s.version = "0.0.6"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Oleg Khabarov"]
|
12
|
-
s.date = %q{2011-03-
|
12
|
+
s.date = %q{2011-03-28}
|
13
13
|
s.description = %q{minimalistic authentication}
|
14
14
|
s.email = %q{oleg@khabarov.ca}
|
15
15
|
s.extra_rdoc_files = [
|
data/lib/letmein.rb
CHANGED
@@ -3,15 +3,7 @@ require 'bcrypt'
|
|
3
3
|
|
4
4
|
module LetMeIn
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
def self.initialize(params = {})
|
9
|
-
@@model = params[:model] || 'User'
|
10
|
-
@@identifier = params[:identifier] || 'email'
|
11
|
-
@@password = params[:password] || 'password_hash'
|
12
|
-
@@salt = params[:salt] || 'password_salt'
|
13
|
-
@@model.constantize.send :include, LetMeIn::Model
|
14
|
-
end
|
6
|
+
Error = Class.new StandardError
|
15
7
|
|
16
8
|
class Railtie < Rails::Railtie
|
17
9
|
config.after_initialize do
|
@@ -19,7 +11,81 @@ module LetMeIn
|
|
19
11
|
end
|
20
12
|
end
|
21
13
|
|
22
|
-
|
14
|
+
mattr_accessor :models, :identifiers, :passwords, :salts
|
15
|
+
def self.initialize(params = {})
|
16
|
+
@@models = [params[:model] || 'User' ].flatten
|
17
|
+
@@identifiers = [params[:identifier] || 'email' ].flatten
|
18
|
+
@@passwords = [params[:password] || 'password_hash' ].flatten
|
19
|
+
@@salts = [params[:salt] || 'password_salt' ].flatten
|
20
|
+
|
21
|
+
def self.accessor(name, index = 0)
|
22
|
+
name = name.to_s.pluralize
|
23
|
+
self.send(name)[index] || self.send(name)[0]
|
24
|
+
end
|
25
|
+
|
26
|
+
@@models.each do |model|
|
27
|
+
|
28
|
+
model.constantize.send :include, LetMeIn::Model
|
29
|
+
|
30
|
+
Object.const_set("#{model.to_s.camelize}Session", Class.new do
|
31
|
+
include ActiveModel::Validations
|
32
|
+
attr_accessor :identifier, :password, :authenticated_object
|
33
|
+
validate :authenticate
|
34
|
+
|
35
|
+
def initialize(params = { })
|
36
|
+
unless params.blank?
|
37
|
+
i = LetMeIn.accessor(:identifier, LetMeIn.models.index(self.class.to_s.gsub('Session','')))
|
38
|
+
self.identifier = params[:identifier] || params[i.to_sym]
|
39
|
+
self.password = params[:password]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def save
|
44
|
+
self.valid?
|
45
|
+
end
|
46
|
+
|
47
|
+
def save!
|
48
|
+
save || raise(LetMeIn::Error, 'Failed to authenticate')
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.create(params = {})
|
52
|
+
object = self.new(params); object.save; object
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.create!(params = {})
|
56
|
+
object = self.new(params); object.save!; object
|
57
|
+
end
|
58
|
+
|
59
|
+
def method_missing(method_name, *args)
|
60
|
+
m = self.class.to_s.gsub('Session','')
|
61
|
+
i = LetMeIn.accessor(:identifier, LetMeIn.models.index(m))
|
62
|
+
case method_name.to_s
|
63
|
+
when i then self.identifier
|
64
|
+
when "#{i}=" then self.identifier = args[0]
|
65
|
+
when m.underscore then self.authenticated_object
|
66
|
+
else super
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def authenticate
|
71
|
+
m = self.class.to_s.gsub('Session','')
|
72
|
+
i = LetMeIn.accessor(:identifier, LetMeIn.models.index(m))
|
73
|
+
p = LetMeIn.accessor(:password, LetMeIn.models.index(m))
|
74
|
+
s = LetMeIn.accessor(:password, LetMeIn.models.index(m))
|
75
|
+
object = m.constantize.send("find_by_#{i}", self.identifier)
|
76
|
+
self.authenticated_object = if object && object.send(p) == BCrypt::Engine.hash_secret(self.password, object.send(s))
|
77
|
+
object
|
78
|
+
else
|
79
|
+
errors.add :base, 'Failed to authenticate'
|
80
|
+
nil
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_key
|
85
|
+
nil
|
86
|
+
end
|
87
|
+
end)
|
88
|
+
end
|
23
89
|
end
|
24
90
|
|
25
91
|
module Model
|
@@ -27,74 +93,16 @@ module LetMeIn
|
|
27
93
|
base.instance_eval do
|
28
94
|
attr_accessor :password
|
29
95
|
before_save :encrypt_password
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
end
|
96
|
+
|
97
|
+
define_method :encrypt_password do
|
98
|
+
if password.present?
|
99
|
+
p = LetMeIn.accessor(:password, LetMeIn.models.index(self.class.to_s))
|
100
|
+
s = LetMeIn.accessor(:salt, LetMeIn.models.index(self.class.to_s))
|
101
|
+
self.send("#{s}=", BCrypt::Engine.generate_salt)
|
102
|
+
self.send("#{p}=", BCrypt::Engine.hash_secret(password, self.send(s)))
|
38
103
|
end
|
39
|
-
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
class Session
|
45
|
-
include ActiveModel::Validations
|
46
|
-
attr_accessor :identifier, :password, :authenticated_object
|
47
|
-
validate :authenticate
|
48
|
-
|
49
|
-
def initialize(params = { })
|
50
|
-
unless params.blank?
|
51
|
-
self.identifier = params[:identifier] || params[LetMeIn.identifier.to_sym]
|
52
|
-
self.password = params[:password]
|
104
|
+
end
|
53
105
|
end
|
54
106
|
end
|
55
|
-
|
56
|
-
def save
|
57
|
-
self.valid?
|
58
|
-
end
|
59
|
-
|
60
|
-
def save!
|
61
|
-
save || raise(LetMeIn::Error, 'Failed to authenticate')
|
62
|
-
end
|
63
|
-
|
64
|
-
def self.create(params = {})
|
65
|
-
object = self.new(params)
|
66
|
-
object.save
|
67
|
-
object
|
68
|
-
end
|
69
|
-
|
70
|
-
def self.create!(params = {})
|
71
|
-
object = self.new(params)
|
72
|
-
object.save!
|
73
|
-
object
|
74
|
-
end
|
75
|
-
|
76
|
-
# Mapping to the identifier and authenticated object accessor
|
77
|
-
def method_missing(method_name, *args)
|
78
|
-
case method_name.to_s
|
79
|
-
when LetMeIn.identifier then self.identifier
|
80
|
-
when "#{LetMeIn.identifier}=" then self.identifier = args[0]
|
81
|
-
when LetMeIn.model.underscore then self.authenticated_object
|
82
|
-
else super
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
def authenticate
|
87
|
-
object = LetMeIn.model.constantize.send("find_by_#{LetMeIn.identifier}", self.identifier)
|
88
|
-
self.authenticated_object = if object && object.send(LetMeIn.password) == BCrypt::Engine.hash_secret(self.password, object.send(LetMeIn.salt))
|
89
|
-
object
|
90
|
-
else
|
91
|
-
errors.add(:base, 'Failed to authenticate')
|
92
|
-
nil
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
def to_key
|
97
|
-
nil
|
98
|
-
end
|
99
107
|
end
|
100
108
|
end
|
data/test/letmein_test.rb
CHANGED
@@ -7,11 +7,8 @@ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':me
|
|
7
7
|
$stdout_orig = $stdout
|
8
8
|
$stdout = StringIO.new
|
9
9
|
|
10
|
-
class User
|
11
|
-
|
12
|
-
# pass: $2a$10$0MeSaaE3I7.0FQ5ZDcKPJeD1.FzqkcOZfEKNZ/DNN.w8xOwuFdBCm
|
13
|
-
# salt: $2a$10$0MeSaaE3I7.0FQ5ZDcKPJe
|
14
|
-
end
|
10
|
+
class User < ActiveRecord::Base ; end
|
11
|
+
class Admin < ActiveRecord::Base ; end
|
15
12
|
|
16
13
|
class LetMeInTest < Test::Unit::TestCase
|
17
14
|
def setup
|
@@ -22,22 +19,35 @@ class LetMeInTest < Test::Unit::TestCase
|
|
22
19
|
t.column :password_hash, :string
|
23
20
|
t.column :password_salt, :string
|
24
21
|
end
|
22
|
+
create_table :admins do |t|
|
23
|
+
t.column :username, :string
|
24
|
+
t.column :pass_hash, :string
|
25
|
+
t.column :pass_salt, :string
|
26
|
+
end
|
25
27
|
end
|
26
|
-
LetMeIn.initialize
|
28
|
+
LetMeIn.initialize(
|
29
|
+
:model => ['User', 'Admin'],
|
30
|
+
:identifier => ['email', 'username'],
|
31
|
+
:password => ['password_hash', 'pass_hash'],
|
32
|
+
:salt => ['password_salt', 'pass_salt']
|
33
|
+
)
|
27
34
|
User.create!(:email => 'test@test.test', :password => 'test')
|
35
|
+
Admin.create!(:username => 'admin', :password => 'test')
|
28
36
|
end
|
29
37
|
|
30
38
|
def teardown
|
31
39
|
ActiveRecord::Base.connection.tables.each do |table|
|
32
40
|
ActiveRecord::Base.connection.drop_table(table)
|
33
41
|
end
|
42
|
+
Object.send(:remove_const, :UserSession)
|
43
|
+
Object.send(:remove_const, :AdminSession)
|
34
44
|
end
|
35
45
|
|
36
46
|
def test_configuration_initialization
|
37
|
-
assert_equal 'User',
|
38
|
-
assert_equal 'email',
|
39
|
-
assert_equal 'password_hash', LetMeIn.
|
40
|
-
assert_equal 'password_salt', LetMeIn.
|
47
|
+
assert_equal ['User', 'Admin'], LetMeIn.models
|
48
|
+
assert_equal ['email', 'username'], LetMeIn.identifiers
|
49
|
+
assert_equal ['password_hash', 'pass_hash'], LetMeIn.passwords
|
50
|
+
assert_equal ['password_salt', 'pass_salt'], LetMeIn.salts
|
41
51
|
end
|
42
52
|
|
43
53
|
def test_model_password_saving
|
@@ -47,8 +57,15 @@ class LetMeInTest < Test::Unit::TestCase
|
|
47
57
|
assert_match /.{29}/, user.password_salt
|
48
58
|
end
|
49
59
|
|
60
|
+
def test_model_password_saving_secondary
|
61
|
+
user = Admin.first
|
62
|
+
assert_equal nil, user.password
|
63
|
+
assert_match /.{60}/, user.pass_hash
|
64
|
+
assert_match /.{29}/, user.pass_salt
|
65
|
+
end
|
66
|
+
|
50
67
|
def test_session_initialization
|
51
|
-
session =
|
68
|
+
session = UserSession.new(:email => 'test@test.test', :password => 'test_pass')
|
52
69
|
assert_equal 'test@test.test', session.identifier
|
53
70
|
assert_equal 'test@test.test', session.email
|
54
71
|
assert_equal 'test_pass', session.password
|
@@ -61,15 +78,36 @@ class LetMeInTest < Test::Unit::TestCase
|
|
61
78
|
assert_equal nil, session.user
|
62
79
|
end
|
63
80
|
|
81
|
+
def test_session_initialization_secondary
|
82
|
+
session = AdminSession.new(:username => 'admin', :password => 'test_pass')
|
83
|
+
assert_equal 'admin', session.identifier
|
84
|
+
assert_equal 'admin', session.username
|
85
|
+
assert_equal 'test_pass', session.password
|
86
|
+
|
87
|
+
session.username = 'new_admin'
|
88
|
+
assert_equal 'new_admin', session.identifier
|
89
|
+
assert_equal 'new_admin', session.username
|
90
|
+
|
91
|
+
assert_equal nil, session.authenticated_object
|
92
|
+
assert_equal nil, session.admin
|
93
|
+
end
|
94
|
+
|
64
95
|
def test_session_authentication
|
65
|
-
session =
|
96
|
+
session = UserSession.create(:email => User.first.email, :password => 'test')
|
66
97
|
assert session.errors.blank?
|
67
98
|
assert_equal User.first, session.authenticated_object
|
68
99
|
assert_equal User.first, session.user
|
69
100
|
end
|
70
101
|
|
102
|
+
def test_session_authentication_secondary
|
103
|
+
session = AdminSession.create(:username => Admin.first.username, :password => 'test')
|
104
|
+
assert session.errors.blank?
|
105
|
+
assert_equal Admin.first, session.authenticated_object
|
106
|
+
assert_equal Admin.first, session.admin
|
107
|
+
end
|
108
|
+
|
71
109
|
def test_session_authentication_failure
|
72
|
-
session =
|
110
|
+
session = UserSession.create(:email => User.first.email, :password => 'bad_pass')
|
73
111
|
assert session.errors.present?
|
74
112
|
assert_equal 'Failed to authenticate', session.errors[:base].first
|
75
113
|
assert_equal nil, session.authenticated_object
|
@@ -77,7 +115,7 @@ class LetMeInTest < Test::Unit::TestCase
|
|
77
115
|
end
|
78
116
|
|
79
117
|
def test_session_authentication_exception
|
80
|
-
session =
|
118
|
+
session = UserSession.new(:email => User.first.email, :password => 'bad_pass')
|
81
119
|
begin
|
82
120
|
session.save!
|
83
121
|
rescue LetMeIn::Error => e
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 6
|
9
|
+
version: 0.0.6
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Oleg Khabarov
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2011-03-
|
17
|
+
date: 2011-03-28 00:00:00 -04:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -108,7 +108,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
108
108
|
requirements:
|
109
109
|
- - ">="
|
110
110
|
- !ruby/object:Gem::Version
|
111
|
-
hash:
|
111
|
+
hash: 833678063283115753
|
112
112
|
segments:
|
113
113
|
- 0
|
114
114
|
version: "0"
|