letmein 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (9) hide show
  1. data/Gemfile +13 -0
  2. data/Gemfile.lock +35 -0
  3. data/LICENSE +20 -0
  4. data/README.md +72 -0
  5. data/Rakefile +38 -0
  6. data/VERSION +1 -0
  7. data/lib/letmein.rb +108 -0
  8. data/test/letmein_test.rb +101 -0
  9. metadata +130 -0
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'activerecord', '>=3.0.0'
4
+ gem 'bcrypt-ruby', :require => 'bcrypt'
5
+
6
+ group :development do
7
+ gem 'bundler', '~> 1.0.0'
8
+ gem 'jeweler', '~> 1.5.2'
9
+ end
10
+
11
+ group :test do
12
+ gem 'sqlite3'
13
+ end
@@ -0,0 +1,35 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activemodel (3.0.5)
5
+ activesupport (= 3.0.5)
6
+ builder (~> 2.1.2)
7
+ i18n (~> 0.4)
8
+ activerecord (3.0.5)
9
+ activemodel (= 3.0.5)
10
+ activesupport (= 3.0.5)
11
+ arel (~> 2.0.2)
12
+ tzinfo (~> 0.3.23)
13
+ activesupport (3.0.5)
14
+ arel (2.0.9)
15
+ bcrypt-ruby (2.1.4)
16
+ builder (2.1.2)
17
+ git (1.2.5)
18
+ i18n (0.5.0)
19
+ jeweler (1.5.2)
20
+ bundler (~> 1.0.0)
21
+ git (>= 1.2.5)
22
+ rake
23
+ rake (0.8.7)
24
+ sqlite3 (1.3.3)
25
+ tzinfo (0.3.25)
26
+
27
+ PLATFORMS
28
+ ruby
29
+
30
+ DEPENDENCIES
31
+ activerecord (>= 3.0.0)
32
+ bcrypt-ruby
33
+ bundler (~> 1.0.0)
34
+ jeweler (~> 1.5.2)
35
+ sqlite3
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Oleg Khabarov
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,72 @@
1
+ letmein
2
+ =======
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.
5
+
6
+ Setup
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 to password and salt columns to something else do this:
15
+
16
+ class User < ActiveRecord::Base
17
+ letmein :username, :encrypted_password, :salt
18
+ end
19
+
20
+ When creating/updating a user record you have access to *password* accessor.
21
+
22
+ >> user = User.new(:email => 'example@example.com', :password => 'letmein')
23
+ >> user.save!
24
+ >> user.password_hash
25
+ => $2a$10$0MeSaaE3I7.0FQ5ZDcKPJeD1.FzqkcOZfEKNZ/DNN.w8xOwuFdBCm
26
+ >> user.password_salt
27
+ => $2a$10$0MeSaaE3I7.0FQ5ZDcKPJe
28
+
29
+ Authentication
30
+ ==============
31
+
32
+ You authenticate using LetMeIn::Session object. Example:
33
+
34
+ >> session = LetMeIn::Session.new(:email => 'example@example.com', :password => 'letmein')
35
+ >> session.save
36
+ => true
37
+ >> session.user
38
+ => #<User id: 1, email: "example@example.com" ... >
39
+
40
+ When credentials are invalid:
41
+
42
+ >> session = LetMeIn::Session.new(:email => 'example@example.com', :password => 'bad_password')
43
+ >> session.save
44
+ => false
45
+ >> session.user
46
+ => nil
47
+
48
+ Usage
49
+ =====
50
+ 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
+
52
+ class SessionsController < ApplicationController
53
+ def create
54
+ @session = LetMeIn::Session.new(params)
55
+ @session.save!
56
+ session[:user_id] = @session.user.id
57
+ flash[:notice] = "Welcome back #{@session.user.name}!"
58
+ redirect_to '/'
59
+ rescue LetMeIn::Error
60
+ flash.now[:error] = 'Invalid Credentials'
61
+ render :action => :new
62
+ end
63
+ end
64
+
65
+ Upon successful login you have access to *session[:user_id]*. The rest is up to you.
66
+
67
+ Copyright
68
+ =========
69
+ (c) 2011 Oleg Khabarov, released under the MIT license
70
+
71
+
72
+
@@ -0,0 +1,38 @@
1
+ require 'rubygems'
2
+
3
+ require 'bundler'
4
+ begin
5
+ Bundler.setup(:default, :development)
6
+ rescue Bundler::BundlerError => e
7
+ $stderr.puts e.message
8
+ $stderr.puts "Run `bundle install` to install missing gems"
9
+ exit e.status_code
10
+ end
11
+
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = 'letmein'
18
+ gem.homepage = 'http://github.com/GBH/letmein'
19
+ gem.license = 'MIT'
20
+ gem.summary = 'minimalistic authentication'
21
+ gem.description = 'minimalistic authentication'
22
+ gem.email = "oleg@khabarov.ca"
23
+ gem.authors = ['Oleg Khabarov']
24
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
25
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
26
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
27
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
28
+ end
29
+ Jeweler::RubygemsDotOrgTasks.new
30
+
31
+ require 'rake/testtask'
32
+ Rake::TestTask.new(:test) do |test|
33
+ test.libs << 'lib' << 'test'
34
+ test.pattern = 'test/**/*_test.rb'
35
+ test.verbose = true
36
+ end
37
+
38
+ task :default => :test
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
@@ -0,0 +1,108 @@
1
+ require 'active_record'
2
+ require 'bcrypt'
3
+
4
+ module LetMeIn
5
+
6
+ class Error < StandardError
7
+ end
8
+
9
+ class Configuration
10
+ attr_accessor :model, :identifier, :password, :salt
11
+
12
+ def initialize
13
+ @model = nil
14
+ @identifier = 'email'
15
+ @password = 'password_hash'
16
+ @salt = 'password_salt'
17
+ end
18
+ end
19
+
20
+ class Session
21
+ include ActiveModel::Validations
22
+ attr_accessor :identifier, :password, :authenticated_object
23
+ validate :authenticate
24
+
25
+ def initialize(params = {})
26
+ self.identifier = params[:identifier] || params[LetMeIn.configuration.identifier.to_sym]
27
+ self.password = params[:password]
28
+ end
29
+
30
+ def save
31
+ self.valid?
32
+ end
33
+
34
+ def save!
35
+ save || raise(LetMeIn::Error, 'Failed to authenticate')
36
+ end
37
+
38
+ def self.create(params = {})
39
+ object = self.new(params)
40
+ object.save
41
+ object
42
+ end
43
+
44
+ def self.create!(params = {})
45
+ object = self.new(params)
46
+ object.save!
47
+ object
48
+ end
49
+
50
+ # Mapping to the identifier and authenticated object accessor
51
+ def method_missing(method_name, *args)
52
+ case method_name.to_s
53
+ when LetMeIn.configuration.identifier
54
+ self.identifier
55
+ when "#{LetMeIn.configuration.identifier}="
56
+ self.identifier = args[0]
57
+ when LetMeIn.configuration.model.underscore
58
+ self.authenticated_object
59
+ else
60
+ super
61
+ end
62
+ end
63
+
64
+ def authenticate
65
+ object = LetMeIn.configuration.model.constantize.send("find_by_#{LetMeIn.configuration.identifier}", self.identifier)
66
+ self.authenticated_object = if object && object.send(LetMeIn.configuration.password) == BCrypt::Engine.hash_secret(self.password, object.send(LetMeIn.configuration.salt))
67
+ object
68
+ else
69
+ errors.add(:base, 'Failed to authenticate')
70
+ nil
71
+ end
72
+ end
73
+ end
74
+
75
+ def self.configuration
76
+ @configuration ||= LetMeIn::Configuration.new
77
+ end
78
+
79
+ module Model
80
+ def self.included(base)
81
+ base.extend ClassMethods
82
+ end
83
+
84
+ module ClassMethods
85
+ def letmein(*args)
86
+ LetMeIn.configuration.model = self.to_s
87
+ LetMeIn.configuration.identifier = args[0].to_s if args[0]
88
+ LetMeIn.configuration.password = args[1].to_s if args[1]
89
+ LetMeIn.configuration.salt = args[2].to_s if args[2]
90
+
91
+ attr_accessor :password
92
+
93
+ before_save :encrypt_password
94
+
95
+ class_eval %Q^
96
+ def encrypt_password
97
+ if password.present?
98
+ self.send("#{LetMeIn.configuration.salt}=", BCrypt::Engine.generate_salt)
99
+ self.send("#{LetMeIn.configuration.password}=", BCrypt::Engine.hash_secret(password, self.send(LetMeIn.configuration.salt)))
100
+ end
101
+ end
102
+ ^
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ ActiveRecord::Base.send :include, LetMeIn::Model
@@ -0,0 +1,101 @@
1
+ require 'test/unit'
2
+ require 'active_record'
3
+ require 'letmein'
4
+ require 'sqlite3'
5
+
6
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
7
+ $stdout_orig = $stdout
8
+ $stdout = StringIO.new
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
+ letmein :username, :pass_crypt, :pass_salt
15
+ end
16
+
17
+ class LetMeInTest < Test::Unit::TestCase
18
+ def setup
19
+ ActiveRecord::Base.logger
20
+ ActiveRecord::Schema.define(:version => 1) do
21
+ create_table :users do |t|
22
+ t.column :username, :string
23
+ t.column :pass_crypt, :string
24
+ t.column :pass_salt, :string
25
+ end
26
+ end
27
+ end
28
+
29
+ def teardown
30
+ ActiveRecord::Base.connection.tables.each do |table|
31
+ ActiveRecord::Base.connection.drop_table(table)
32
+ end
33
+ end
34
+
35
+ def test_configuration_defaults
36
+ assert config = LetMeIn::Configuration.new
37
+ assert_equal nil, config.model
38
+ assert_equal 'email', config.identifier
39
+ assert_equal 'password_hash', config.password
40
+ assert_equal 'password_salt', config.salt
41
+ end
42
+
43
+ def test_configuration_initialization
44
+ conf = LetMeIn.configuration
45
+ assert_equal 'User', conf.model
46
+ assert_equal 'username', conf.identifier
47
+ assert_equal 'pass_crypt', conf.password
48
+ assert_equal 'pass_salt', conf.salt
49
+ end
50
+
51
+ def test_model_password_saving
52
+ user = User.new(:username => 'test', :password => 'test')
53
+ user.save!
54
+ user = User.find(user.id)
55
+ assert_equal nil, user.password
56
+ assert_match /.{60}/, user.pass_crypt
57
+ assert_match /.{29}/, user.pass_salt
58
+ end
59
+
60
+ def test_session_initialization
61
+ session = LetMeIn::Session.new(:username => 'test_user', :password => 'test_pass')
62
+ assert_equal 'test_user', session.identifier
63
+ assert_equal 'test_user', session.username
64
+ assert_equal 'test_pass', session.password
65
+
66
+ session.username = 'new_user'
67
+ assert_equal 'new_user', session.identifier
68
+ assert_equal 'new_user', session.username
69
+
70
+ assert_equal nil, session.authenticated_object
71
+ assert_equal nil, session.user
72
+ end
73
+
74
+ def test_session_authentication
75
+ user = User.create!(:username => 'test', :password => 'test')
76
+ session = LetMeIn::Session.create(:username => 'test', :password => 'test')
77
+ assert session.errors.blank?
78
+ assert_equal user, session.authenticated_object
79
+ assert_equal user, session.user
80
+ end
81
+
82
+ def test_session_authentication_failure
83
+ user = User.create!(:username => 'test', :password => 'test')
84
+ session = LetMeIn::Session.create(:username => 'test', :password => 'bad_pass')
85
+ assert session.errors.present?
86
+ assert_equal 'Failed to authenticate', session.errors[:base].first
87
+ assert_equal nil, session.authenticated_object
88
+ assert_equal nil, session.user
89
+ end
90
+
91
+ def test_session_authentication_exception
92
+ user = User.create!(:username => 'test', :password => 'test')
93
+ session = LetMeIn::Session.new(:username => 'test', :password => 'bad_pass')
94
+ begin
95
+ session.save!
96
+ rescue LetMeIn::Error => e
97
+ assert_equal 'Failed to authenticate', e.to_s
98
+ end
99
+ assert_equal nil, session.authenticated_object
100
+ end
101
+ end
metadata ADDED
@@ -0,0 +1,130 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: letmein
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 0
9
+ version: 0.0.0
10
+ platform: ruby
11
+ authors:
12
+ - Oleg Khabarov
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-03-24 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: activerecord
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 3
29
+ - 0
30
+ - 0
31
+ version: 3.0.0
32
+ type: :runtime
33
+ prerelease: false
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: bcrypt-ruby
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ segments:
43
+ - 0
44
+ version: "0"
45
+ type: :runtime
46
+ prerelease: false
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ name: bundler
50
+ requirement: &id003 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ segments:
56
+ - 1
57
+ - 0
58
+ - 0
59
+ version: 1.0.0
60
+ type: :development
61
+ prerelease: false
62
+ version_requirements: *id003
63
+ - !ruby/object:Gem::Dependency
64
+ name: jeweler
65
+ requirement: &id004 !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ~>
69
+ - !ruby/object:Gem::Version
70
+ segments:
71
+ - 1
72
+ - 5
73
+ - 2
74
+ version: 1.5.2
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: *id004
78
+ description: minimalistic authentication
79
+ email: oleg@khabarov.ca
80
+ executables: []
81
+
82
+ extensions: []
83
+
84
+ extra_rdoc_files:
85
+ - LICENSE
86
+ - README.md
87
+ files:
88
+ - Gemfile
89
+ - Gemfile.lock
90
+ - LICENSE
91
+ - README.md
92
+ - Rakefile
93
+ - VERSION
94
+ - lib/letmein.rb
95
+ - test/letmein_test.rb
96
+ has_rdoc: true
97
+ homepage: http://github.com/GBH/letmein
98
+ licenses:
99
+ - MIT
100
+ post_install_message:
101
+ rdoc_options: []
102
+
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ hash: -4484474238260419459
111
+ segments:
112
+ - 0
113
+ version: "0"
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ none: false
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ segments:
120
+ - 0
121
+ version: "0"
122
+ requirements: []
123
+
124
+ rubyforge_project:
125
+ rubygems_version: 1.3.7
126
+ signing_key:
127
+ specification_version: 3
128
+ summary: minimalistic authentication
129
+ test_files:
130
+ - test/letmein_test.rb