letmein 0.0.0

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