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 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"