entrance 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|