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 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 LetMeIn::Session object that you can use to authenticate logins.
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
- Assuming the model you want to authenticate has fields: *email*, *password_hash* and *password_salt* all you need to add for that model is this:
9
-
10
- class User < ActiveRecord::Base
11
- letmein
12
- end
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
- class User < ActiveRecord::Base
17
- letmein :username, :encrypted_password, :salt
18
- end
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 user record you have access to *password* accessor.
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 LetMeIn::Session object. Example:
35
+ You authenticate using UserSession object. Example:
33
36
 
34
- >> session = LetMeIn::Session.new(:email => 'example@example.com', :password => 'letmein')
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 = LetMeIn::Session.new(:email => 'example@example.com', :password => 'bad_password')
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 = LetMeIn::Session.new(params)
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
- Copyright
68
- =========
69
- (c) 2011 Oleg Khabarov, released under the MIT license
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.5
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.5"
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-27}
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
- mattr_accessor :model, :identifier, :password, :salt
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
- class Error < StandardError
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
- end
31
- base.class_eval do
32
- class_eval %Q^
33
- def encrypt_password
34
- if password.present?
35
- self.send("#{LetMeIn.salt}=", BCrypt::Engine.generate_salt)
36
- self.send("#{LetMeIn.password}=", BCrypt::Engine.hash_secret(password, self.send(LetMeIn.salt)))
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 < ActiveRecord::Base
11
- # example values for password info:
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', LetMeIn.model
38
- assert_equal 'email', LetMeIn.identifier
39
- assert_equal 'password_hash', LetMeIn.password
40
- assert_equal 'password_salt', LetMeIn.salt
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 = LetMeIn::Session.new(:email => 'test@test.test', :password => 'test_pass')
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 = LetMeIn::Session.create(:email => User.first.email, :password => 'test')
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 = LetMeIn::Session.create(:email => User.first.email, :password => 'bad_pass')
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 = LetMeIn::Session.new(:email => User.first.email, :password => 'bad_pass')
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
- - 5
9
- version: 0.0.5
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-27 00:00:00 -04:00
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: 2972491954571850709
111
+ hash: 833678063283115753
112
112
  segments:
113
113
  - 0
114
114
  version: "0"