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 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
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
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
@@ -0,0 +1,7 @@
1
+ module Entrance
2
+ MAJOR = 0
3
+ MINOR = 0
4
+ PATCH = 1
5
+
6
+ VERSION = [MAJOR, MINOR, PATCH].join('.')
7
+ 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: []