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.
- data/Gemfile +13 -0
- data/Gemfile.lock +35 -0
- data/LICENSE +20 -0
- data/README.md +72 -0
- data/Rakefile +38 -0
- data/VERSION +1 -0
- data/lib/letmein.rb +108 -0
- data/test/letmein_test.rb +101 -0
- metadata +130 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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
|
+
|
data/Rakefile
ADDED
@@ -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
|
data/lib/letmein.rb
ADDED
@@ -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
|