entrance 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/Rakefile +2 -0
- data/entrance.gemspec +24 -0
- data/lib/entrance/ciphers.rb +45 -0
- data/lib/entrance/controller.rb +129 -0
- data/lib/entrance/model.rb +103 -0
- data/lib/entrance/version.rb +7 -0
- data/lib/entrance.rb +83 -0
- metadata +79 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3320468261098d87b1536592f636be4d39b25403
|
4
|
+
data.tar.gz: f3b36768c497d6af747677461693920860a52c71
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e6169c56830fe1d3d430c854d2b0847c70d8776fc634bf0b72bfbf9124fedc0bb808a89cbc57d0bffdddcfe901697c474786703416c056d85c48985d36d9c06d
|
7
|
+
data.tar.gz: 3aa89b3005787082ae19173da9e8ba64135e9bc1521e49f1ed06ad5466d885b983c34a56d615906710b6d2e9c7d0a807f2b3adf60309c628c41e8f8a310a7872
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
.DS_Store
|
data/Rakefile
ADDED
data/entrance.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path("../lib/entrance/version", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "entrance"
|
6
|
+
s.version = Entrance::VERSION
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.authors = ['Tomás Pollak']
|
9
|
+
s.email = ['tomas@forkhq.com']
|
10
|
+
s.homepage = "https://github.com/tomas/entrance"
|
11
|
+
s.summary = "Lean authentication alternative for Rails and Sinatra."
|
12
|
+
s.description = "Doesn't fiddle with your controllers and routes."
|
13
|
+
|
14
|
+
s.required_rubygems_version = ">= 1.3.6"
|
15
|
+
s.rubyforge_project = "entrance"
|
16
|
+
|
17
|
+
s.add_runtime_dependency "bcrypt", "~> 3.0"
|
18
|
+
s.add_runtime_dependency "activesupport", "~> 3.0"
|
19
|
+
|
20
|
+
s.files = `git ls-files`.split("\n")
|
21
|
+
s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
|
22
|
+
s.require_path = 'lib'
|
23
|
+
# s.bindir = 'bin'
|
24
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
require 'bcrypt'
|
3
|
+
|
4
|
+
module Entrance
|
5
|
+
|
6
|
+
module Ciphers
|
7
|
+
|
8
|
+
module SHA1
|
9
|
+
|
10
|
+
JOIN_STRING = '--'
|
11
|
+
|
12
|
+
def self.read(password)
|
13
|
+
password
|
14
|
+
end
|
15
|
+
|
16
|
+
# same logic as restful authentication
|
17
|
+
def self.encrypt(password, salt)
|
18
|
+
digest = Entrance.config.secret
|
19
|
+
raise "Secret not set!" if digest.blank?
|
20
|
+
|
21
|
+
Entrance.config.stretches.times do
|
22
|
+
str = [digest, salt, password, Entrance.config.secret].join(JOIN_STRING)
|
23
|
+
digest = Digest::SHA1.hexdigest(str)
|
24
|
+
end
|
25
|
+
|
26
|
+
digest
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
module BCrypt
|
32
|
+
|
33
|
+
def self.read(password)
|
34
|
+
BCrypt::Password.new(password)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.encrypt(password, salt = nil)
|
38
|
+
BCrypt::Password.create(password)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module Entrance
|
2
|
+
|
3
|
+
module Controller
|
4
|
+
|
5
|
+
REMEMBER_ME_TOKEN = 'auth_token'.freeze
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
base.send(:helper_method, :current_user, :logged_in?, :logged_out?) if base.respond_to?(:helper_method)
|
9
|
+
end
|
10
|
+
|
11
|
+
def authenticate_and_login(username, password, remember_me = false)
|
12
|
+
if user = Entrance.config.model.constantize.authenticate(username, password)
|
13
|
+
login!(user, remember_me)
|
14
|
+
user
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def login!(user, remember_me = false)
|
19
|
+
self.current_user = user
|
20
|
+
remember_or_forget(remember_me)
|
21
|
+
end
|
22
|
+
|
23
|
+
def logout!
|
24
|
+
if logged_in?
|
25
|
+
current_user.forget_me!
|
26
|
+
self.current_user = nil
|
27
|
+
end
|
28
|
+
delete_remember_cookie
|
29
|
+
end
|
30
|
+
|
31
|
+
def login_required
|
32
|
+
logged_in? || access_denied
|
33
|
+
end
|
34
|
+
|
35
|
+
def current_user
|
36
|
+
@current_user ||= (login_from_session || login_from_cookie)
|
37
|
+
end
|
38
|
+
|
39
|
+
def logged_in?
|
40
|
+
!!current_user
|
41
|
+
end
|
42
|
+
|
43
|
+
def logged_out?
|
44
|
+
!logged_in?
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def current_user=(new_user)
|
50
|
+
raise "Invalid user: #{new_user}" unless new_user.nil? or new_user.is_a?(Entrance.config.model.constantize)
|
51
|
+
session[:user_id] = new_user ? new_user.id : nil
|
52
|
+
@current_user = new_user # should be nil when logging out
|
53
|
+
end
|
54
|
+
|
55
|
+
def remember_or_forget(remember_me)
|
56
|
+
if remember_me
|
57
|
+
current_user.remember_me!
|
58
|
+
set_remember_cookie
|
59
|
+
else
|
60
|
+
current_user.forget_me!
|
61
|
+
delete_remember_cookie
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def access_denied
|
66
|
+
store_location
|
67
|
+
if request.xhr?
|
68
|
+
render :nothing => true, :status => 401
|
69
|
+
else
|
70
|
+
flash[:notice] = I18n.t(Entrance.config.access_denied_message_key)
|
71
|
+
redirect_to Entrance.config.access_denied_redirect_to
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def login_from_session
|
76
|
+
self.current_user = User.find(session[:user_id]) if session[:user_id]
|
77
|
+
end
|
78
|
+
|
79
|
+
def login_from_cookie
|
80
|
+
return unless cookies[REMEMBER_ME_TOKEN]
|
81
|
+
|
82
|
+
query = {}
|
83
|
+
query[Entrance.config.remember_token_attr] = cookies[REMEMBER_ME_TOKEN]
|
84
|
+
if user = User.where(query).first \
|
85
|
+
and user.send(Entrance.config.remember_until_attr) > Time.now
|
86
|
+
self.current_user = user
|
87
|
+
# user.update_remember_token_expiration!
|
88
|
+
user
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def store_location
|
93
|
+
session[:return_to] = request.request_uri
|
94
|
+
end
|
95
|
+
|
96
|
+
def redirect_to_stored_or(default_path)
|
97
|
+
redirect_to(session[:return_to] || default_path)
|
98
|
+
session[:return_to] = nil
|
99
|
+
end
|
100
|
+
|
101
|
+
def redirect_to_back_or(default_path)
|
102
|
+
redirect_to(request.env['HTTP_REFERER'] || default_path)
|
103
|
+
end
|
104
|
+
|
105
|
+
def set_remember_cookie
|
106
|
+
values = {
|
107
|
+
:expires => Entrance.config.remember_for.from_now,
|
108
|
+
:httponly => Entrance.config.cookie_httponly,
|
109
|
+
:path => Entrance.config.cookie_path,
|
110
|
+
:secure => Entrance.config.cookie_secure,
|
111
|
+
:value => current_user.send(Entrance.config.remember_token_attr)
|
112
|
+
}
|
113
|
+
values[:domain] = Entrance.config.cookie_domain if Entrance.config.cookie_domain
|
114
|
+
|
115
|
+
cookies[REMEMBER_ME_TOKEN] = values
|
116
|
+
end
|
117
|
+
|
118
|
+
def delete_remember_cookie
|
119
|
+
cookies.delete(REMEMBER_ME_TOKEN)
|
120
|
+
# cookies.delete(REMEMBER_ME_TOKEN, :domain => AppConfig.cookie_domain)
|
121
|
+
end
|
122
|
+
|
123
|
+
# def cookies
|
124
|
+
# @cookies ||= @env['action_dispatch.cookies'] || Rack::Request.new(@env).cookies
|
125
|
+
# end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Model
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
# verify that username/password attributes are present
|
8
|
+
attrs = Entrance.config.model.constantize.columns.collect(&:name)
|
9
|
+
%w(username_attr password_attr).each do |key|
|
10
|
+
attr = Entrance.config.send(key)
|
11
|
+
raise "Couldn't find '#{attr}' in #{Entrance.config.model} model." unless attrs.include?(attr)
|
12
|
+
end
|
13
|
+
|
14
|
+
validates :password, :presence => true, :length => 6..32, :if => :password_required?
|
15
|
+
validates :password, :confirmation => true, :if => :password_required?
|
16
|
+
validates :password_confirmation, :presence => true, :if => :password_required?
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
|
21
|
+
def authenticate(username, password)
|
22
|
+
return if username.blank? or password.blank?
|
23
|
+
|
24
|
+
query = {}
|
25
|
+
query[Entrance.config.username_attr] = username.downcase.strip
|
26
|
+
if u = where(query).first
|
27
|
+
return u.authenticated?(password) ? u : nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def with_password_reset_token(token)
|
32
|
+
return if token.blank?
|
33
|
+
|
34
|
+
query = {}
|
35
|
+
query[Entrance.config.reset_token_attr] = token.strip
|
36
|
+
if u = where(query).first and u.send(Entrance.config.reset_until_attr) > Time.now
|
37
|
+
return u
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
def authenticated?(string)
|
44
|
+
password === encrypt_password(string)
|
45
|
+
end
|
46
|
+
|
47
|
+
def remember_me!(until_date = nil)
|
48
|
+
update_attribute(Entrance.config.remember_token_attr, Entrance.generate_token)
|
49
|
+
update_remember_token_expiration!(until_date)
|
50
|
+
end
|
51
|
+
|
52
|
+
def update_remember_token_expiration!(until_date = nil)
|
53
|
+
timestamp = until_date || Entrance.config.remember_for
|
54
|
+
update_attribute(Entrance.config.remember_until_attr, timestamp.from_now)
|
55
|
+
end
|
56
|
+
|
57
|
+
def forget_me!
|
58
|
+
update_attribute(Entrance.config.remember_token_attr, nil)
|
59
|
+
update_attribute(Entrance.config.remember_until_attr, nil)
|
60
|
+
end
|
61
|
+
|
62
|
+
def password
|
63
|
+
@password || Entrance.config.cipher.read(send(Entrance.config.password_attr))
|
64
|
+
end
|
65
|
+
|
66
|
+
def password=(new_password)
|
67
|
+
return if new_password.blank?
|
68
|
+
|
69
|
+
@password = new_password # for validation
|
70
|
+
@password_changed = true
|
71
|
+
|
72
|
+
# if we're using salt and it is empty, generate one
|
73
|
+
if Entrance.config.salt_attr \
|
74
|
+
and send(Entrance.config.salt_attr).blank?
|
75
|
+
self.send(Entrance.config.salt_attr + '=', Entrance.generate_token)
|
76
|
+
end
|
77
|
+
|
78
|
+
self.send(Entrance.config.password_attr + '=', encrypt_password(new_password))
|
79
|
+
end
|
80
|
+
|
81
|
+
def request_password_reset!
|
82
|
+
send(Entrance.config.reset_token_attr + '=', Entrance.generate_token)
|
83
|
+
update_attribute(Entrance.config.reset_until_attr, Entrance.config.reset_password_window.from_now)
|
84
|
+
if save(:validate => false)
|
85
|
+
Entrance.config.mailer_class.constantize.reset_password_request(self).deliver
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def get_salt
|
92
|
+
Entrance.config.salt_attr && send(Entrance.config.salt_attr)
|
93
|
+
end
|
94
|
+
|
95
|
+
def encrypt_password(string)
|
96
|
+
Entrance.config.cipher.encrypt(string, get_salt)
|
97
|
+
end
|
98
|
+
|
99
|
+
def password_required?
|
100
|
+
password.blank? or @password_changed
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
data/lib/entrance.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
####################################
|
2
|
+
# Entrance
|
3
|
+
#
|
4
|
+
# By Tomas Pollak
|
5
|
+
# Simple Ruby Authentication Library
|
6
|
+
###################################
|
7
|
+
|
8
|
+
=begin
|
9
|
+
|
10
|
+
In your controller:
|
11
|
+
include Entrance::Controller
|
12
|
+
|
13
|
+
- Provides authenticate_and_login, login!(user), logout! methods
|
14
|
+
- Provices login_required, logged_in? and logged_out? helpers
|
15
|
+
|
16
|
+
In your model:
|
17
|
+
|
18
|
+
include Entrance::Model
|
19
|
+
|
20
|
+
- Provides Model.authenticate(username, password)
|
21
|
+
- Provices Model#remember_me! and Model#forget_me!
|
22
|
+
- Provides Model#password getter and setter
|
23
|
+
- Provides Model#request_password_reset!
|
24
|
+
=end
|
25
|
+
|
26
|
+
require 'entrance/controller'
|
27
|
+
require 'entrance/model'
|
28
|
+
require 'entrance/ciphers'
|
29
|
+
|
30
|
+
require 'active_support/time'
|
31
|
+
|
32
|
+
module Entrance
|
33
|
+
|
34
|
+
REMEMBER_ME_TOKEN = 'auth_token'
|
35
|
+
|
36
|
+
def self.config
|
37
|
+
@config ||= Config.new
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.configure
|
41
|
+
yield config
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.generate_token(length = 40)
|
45
|
+
SecureRandom.hex(length/2).encode('UTF-8')
|
46
|
+
end
|
47
|
+
|
48
|
+
class Config
|
49
|
+
|
50
|
+
attr_accessor *%w(
|
51
|
+
model mailer_class cipher secret stretches
|
52
|
+
username_attr password_attr salt_attr
|
53
|
+
remember_token_attr remember_until_attr reset_token_attr reset_until_attr
|
54
|
+
access_denied_redirect_to access_denied_message_key reset_password_window remember_for
|
55
|
+
cookie_domain cookie_secure cookie_path cookie_httponly
|
56
|
+
)
|
57
|
+
|
58
|
+
def initialize
|
59
|
+
@model = 'User'
|
60
|
+
@mailer_class = 'UserMailer'
|
61
|
+
@cipher = Ciphers::SHA1
|
62
|
+
@secret = nil
|
63
|
+
@stretches = 1
|
64
|
+
@username_attr = 'email'
|
65
|
+
@password_attr = 'password_hash'
|
66
|
+
@salt_attr = nil
|
67
|
+
@remember_token_attr = 'remember_token'
|
68
|
+
@remember_until_attr = 'remember_token_expires_at'
|
69
|
+
@reset_token_attr = 'reset_token'
|
70
|
+
@reset_until_attr = 'reset_token_expires_at'
|
71
|
+
@access_denied_redirect_to = '/'
|
72
|
+
@access_denied_message_key = 'messages.access_denied'
|
73
|
+
@reset_password_window = 1.hour
|
74
|
+
@remember_for = 2.weeks
|
75
|
+
@cookie_domain = nil
|
76
|
+
@cookie_secure = true
|
77
|
+
@cookie_path = '/'
|
78
|
+
@cookie_httponly = false
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: entrance
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tomás Pollak
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-04-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bcrypt
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.0'
|
41
|
+
description: Doesn't fiddle with your controllers and routes.
|
42
|
+
email:
|
43
|
+
- tomas@forkhq.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".gitignore"
|
49
|
+
- Rakefile
|
50
|
+
- entrance.gemspec
|
51
|
+
- lib/entrance.rb
|
52
|
+
- lib/entrance/ciphers.rb
|
53
|
+
- lib/entrance/controller.rb
|
54
|
+
- lib/entrance/model.rb
|
55
|
+
- lib/entrance/version.rb
|
56
|
+
homepage: https://github.com/tomas/entrance
|
57
|
+
licenses: []
|
58
|
+
metadata: {}
|
59
|
+
post_install_message:
|
60
|
+
rdoc_options: []
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: 1.3.6
|
73
|
+
requirements: []
|
74
|
+
rubyforge_project: entrance
|
75
|
+
rubygems_version: 2.2.0
|
76
|
+
signing_key:
|
77
|
+
specification_version: 4
|
78
|
+
summary: Lean authentication alternative for Rails and Sinatra.
|
79
|
+
test_files: []
|