letmein 0.0.5 → 0.0.6
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.
- 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"
|