authenticate 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +21 -0
- data/Gemfile.lock +154 -0
- data/LICENSE +20 -0
- data/README.md +240 -0
- data/Rakefile +6 -0
- data/app/assets/config/authenticate_manifest.js +0 -0
- data/app/assets/images/authenticate/.keep +0 -0
- data/app/assets/javascripts/authenticate/.keep +0 -0
- data/app/assets/stylesheets/authenticate/.keep +0 -0
- data/app/controllers/.keep +0 -0
- data/app/helpers/.keep +0 -0
- data/app/mailers/.keep +0 -0
- data/app/models/.keep +0 -0
- data/app/views/.keep +0 -0
- data/authenticate.gemspec +38 -0
- data/bin/rails +12 -0
- data/config/routes.rb +2 -0
- data/lib/authenticate.rb +12 -0
- data/lib/authenticate/callbacks/authenticatable.rb +4 -0
- data/lib/authenticate/callbacks/brute_force.rb +31 -0
- data/lib/authenticate/callbacks/lifetimed.rb +5 -0
- data/lib/authenticate/callbacks/timeoutable.rb +15 -0
- data/lib/authenticate/callbacks/trackable.rb +8 -0
- data/lib/authenticate/configuration.rb +144 -0
- data/lib/authenticate/controller.rb +110 -0
- data/lib/authenticate/crypto/bcrypt.rb +30 -0
- data/lib/authenticate/debug.rb +10 -0
- data/lib/authenticate/engine.rb +21 -0
- data/lib/authenticate/lifecycle.rb +120 -0
- data/lib/authenticate/login_status.rb +27 -0
- data/lib/authenticate/model/brute_force.rb +51 -0
- data/lib/authenticate/model/db_password.rb +71 -0
- data/lib/authenticate/model/email.rb +76 -0
- data/lib/authenticate/model/lifetimed.rb +48 -0
- data/lib/authenticate/model/timeoutable.rb +47 -0
- data/lib/authenticate/model/trackable.rb +43 -0
- data/lib/authenticate/model/username.rb +45 -0
- data/lib/authenticate/modules.rb +61 -0
- data/lib/authenticate/session.rb +123 -0
- data/lib/authenticate/token.rb +7 -0
- data/lib/authenticate/user.rb +50 -0
- data/lib/authenticate/version.rb +3 -0
- data/lib/tasks/authenticate_tasks.rake +4 -0
- data/spec/configuration_spec.rb +60 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/images/.keep +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.keep +0 -0
- data/spec/dummy/app/models/.keep +0 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/models/user.rb +3 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +29 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +26 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +41 -0
- data/spec/dummy/config/environments/production.rb +79 -0
- data/spec/dummy/config/environments/test.rb +42 -0
- data/spec/dummy/config/initializers/assets.rb +11 -0
- data/spec/dummy/config/initializers/authenticate.rb +7 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +56 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20160120003910_create_users.rb +18 -0
- data/spec/dummy/db/schema.rb +31 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/factories/users.rb +23 -0
- data/spec/model/session_spec.rb +86 -0
- data/spec/model/token_spec.rb +11 -0
- data/spec/model/user_spec.rb +12 -0
- data/spec/orm/active_record.rb +17 -0
- data/spec/spec_helper.rb +148 -0
- metadata +255 -0
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'authenticate/callbacks/trackable'
|
2
|
+
|
3
|
+
module Authenticate
|
4
|
+
module Model
|
5
|
+
|
6
|
+
# Track information about your user sign ins. This module is always enabled.
|
7
|
+
#
|
8
|
+
# == Columns
|
9
|
+
# This module expects and tracks the following columns on your user model:
|
10
|
+
# - sign_in_count - increase every time a sign in is made
|
11
|
+
# - current_sign_in_at - a timestamp updated at each sign in
|
12
|
+
# - last_sign_in_at - a timestamp of the previous sign in
|
13
|
+
# - current_sign_in_ip - the remote ip address of the user at sign in
|
14
|
+
# - previous_sign_in_ip - the remote ip address of the previous sign in
|
15
|
+
#
|
16
|
+
module Trackable
|
17
|
+
extend ActiveSupport::Concern
|
18
|
+
|
19
|
+
def self.required_fields(klass)
|
20
|
+
[:current_sign_in_at, :current_sign_in_ip, :last_sign_in_at, :last_sign_in_ip, :sign_in_count]
|
21
|
+
end
|
22
|
+
|
23
|
+
def update_tracked_fields(request)
|
24
|
+
old_current, new_current = self.current_sign_in_at, Time.now.utc
|
25
|
+
self.last_sign_in_at = old_current || new_current
|
26
|
+
self.current_sign_in_at = new_current
|
27
|
+
|
28
|
+
old_current, new_current = self.current_sign_in_ip, request.remote_ip
|
29
|
+
self.last_sign_in_ip = old_current || new_current
|
30
|
+
self.current_sign_in_ip = new_current
|
31
|
+
|
32
|
+
self.sign_in_count ||= 0
|
33
|
+
self.sign_in_count += 1
|
34
|
+
end
|
35
|
+
|
36
|
+
def update_tracked_fields!(request)
|
37
|
+
update_tracked_fields(request)
|
38
|
+
save(validate: false)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Authenticate
|
2
|
+
module Model
|
3
|
+
|
4
|
+
module Username
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
def self.required_fields(klass)
|
8
|
+
[:username]
|
9
|
+
end
|
10
|
+
|
11
|
+
included do
|
12
|
+
before_validation :normalize_username
|
13
|
+
validates :username,
|
14
|
+
presence: true,
|
15
|
+
uniqueness: { allow_blank: true }
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
def credentials(params)
|
20
|
+
[params[:session][:username], params[:session][:password]]
|
21
|
+
end
|
22
|
+
|
23
|
+
def authenticate(credentials)
|
24
|
+
user = find_by_credentials(credentials)
|
25
|
+
user && user.password_match?(credentials[1]) ? user : nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def find_by_credentials(credentials)
|
29
|
+
username = credentials[0]
|
30
|
+
find_by_username normalize_username(username)
|
31
|
+
end
|
32
|
+
|
33
|
+
def normalize_username(username)
|
34
|
+
username.to_s.downcase.gsub(/\s+/, '')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def normalize_username
|
39
|
+
self.username = self.class.normalize_username(username)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Authenticate
|
2
|
+
module Modules
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
# Methods to help your user model load Authenticate modules
|
6
|
+
module ClassMethods
|
7
|
+
|
8
|
+
def load_modules
|
9
|
+
constants = []
|
10
|
+
Authenticate.configuration.modules.each do |mod|
|
11
|
+
puts "load_modules about to load #{mod.to_s}"
|
12
|
+
require "authenticate/model/#{mod.to_s}" if mod.is_a?(Symbol)
|
13
|
+
mod = load_constant(mod) if mod.is_a?(Symbol)
|
14
|
+
constants << mod
|
15
|
+
end
|
16
|
+
check_fields constants
|
17
|
+
constants.each { |mod|
|
18
|
+
include mod
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def load_constant module_symbol
|
25
|
+
Authenticate::Model.const_get(module_symbol.to_s.classify)
|
26
|
+
end
|
27
|
+
|
28
|
+
# For each module, look at the fields it requires. Ensure the User
|
29
|
+
# model including the module has the required fields. If it does not
|
30
|
+
# have all required fields, huck an exception.
|
31
|
+
def check_fields modules
|
32
|
+
failed_attributes = []
|
33
|
+
instance = self.new
|
34
|
+
modules.each do |mod|
|
35
|
+
if mod.respond_to?(:required_fields)
|
36
|
+
mod.required_fields(self).each do |field|
|
37
|
+
failed_attributes << field unless instance.respond_to?(field)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
if failed_attributes.any?
|
43
|
+
fail MissingAttribute.new(failed_attributes)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
class MissingAttribute < StandardError
|
50
|
+
def initialize(attributes)
|
51
|
+
@attributes = attributes
|
52
|
+
end
|
53
|
+
|
54
|
+
def message
|
55
|
+
"The following attribute(s) is (are) missing on your model: #{@attributes.join(", ")}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'authenticate/login_status'
|
2
|
+
require 'authenticate/debug'
|
3
|
+
|
4
|
+
module Authenticate
|
5
|
+
class Session
|
6
|
+
include Debug
|
7
|
+
|
8
|
+
attr_accessor :request
|
9
|
+
|
10
|
+
def initialize(request, cookies)
|
11
|
+
@request = request # trackable module accesses request
|
12
|
+
@cookies = cookies
|
13
|
+
@session_token = @cookies[cookie_name]
|
14
|
+
d 'SESSION initialize: @session_token: ' + @session_token.inspect
|
15
|
+
end
|
16
|
+
|
17
|
+
# consecutive_failed_logins_limit
|
18
|
+
# timeout - time elapsed since last thingy. last_access_at column
|
19
|
+
# max session lifetime
|
20
|
+
# confirmation / awaiting confirmation
|
21
|
+
# reset password
|
22
|
+
# change password
|
23
|
+
# trackable - sign_in_count, last_sign_in_at, last_sign_in_ip
|
24
|
+
|
25
|
+
|
26
|
+
# Finish user login process, *after* the user has been authenticated.
|
27
|
+
# Called when user creates an account or signs back into the app.
|
28
|
+
#
|
29
|
+
# @return [User]
|
30
|
+
def login(user, &block)
|
31
|
+
d 'session.login()'
|
32
|
+
@current_user = user
|
33
|
+
d "session.login @current_user: #{@current_user.inspect}"
|
34
|
+
# todo extract token gen to two different strategies
|
35
|
+
@current_user.generate_session_token if user.present?
|
36
|
+
|
37
|
+
message = catch(:failure) do
|
38
|
+
Authenticate.lifecycle.run_callbacks(:after_set_user, @current_user, self, { event: :authentication })
|
39
|
+
Authenticate.lifecycle.run_callbacks(:after_authentication, @current_user, self, { event: :authentication })
|
40
|
+
end
|
41
|
+
|
42
|
+
d "session.login after lifecycle callbacks, message: #{message}"
|
43
|
+
status = message.present? ? Failure.new(message) : Success.new
|
44
|
+
if status.success?
|
45
|
+
@current_user.save
|
46
|
+
write_cookie if @current_user.session_token
|
47
|
+
else
|
48
|
+
@current_user = nil
|
49
|
+
end
|
50
|
+
|
51
|
+
if block_given?
|
52
|
+
block.call(status)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
# Get the user represented by this session.
|
58
|
+
#
|
59
|
+
# @return [User]
|
60
|
+
def current_user
|
61
|
+
d 'session.current_user'
|
62
|
+
if @session_token.present?
|
63
|
+
@current_user ||= load_user
|
64
|
+
end
|
65
|
+
@current_user
|
66
|
+
end
|
67
|
+
|
68
|
+
# Has this session successfully authenticated?
|
69
|
+
#
|
70
|
+
# @return [Boolean]
|
71
|
+
def authenticated?
|
72
|
+
d 'session.authenticated?'
|
73
|
+
current_user.present?
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
# Invalidate the session token, unset the current user and remove the cookie.
|
78
|
+
#
|
79
|
+
# @return [void]
|
80
|
+
def deauthenticate
|
81
|
+
# nuke session_token in db
|
82
|
+
if current_user.present?
|
83
|
+
current_user.reset_session_token!
|
84
|
+
end
|
85
|
+
|
86
|
+
# nuke notion of current_user
|
87
|
+
@current_user = nil
|
88
|
+
|
89
|
+
# # nuke cookie
|
90
|
+
@cookies.delete cookie_name
|
91
|
+
end
|
92
|
+
|
93
|
+
protected
|
94
|
+
|
95
|
+
def user_loaded?
|
96
|
+
!@current_user.present?
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def write_cookie
|
102
|
+
cookie_hash = {
|
103
|
+
httponly: true,
|
104
|
+
value: @current_user.session_token,
|
105
|
+
expires: Authenticate.configuration.cookie_expiration.call
|
106
|
+
}
|
107
|
+
cookie_hash[:domain] = Authenticate.configuration.cookie_domain if Authenticate.configuration.cookie_domain
|
108
|
+
# @cookies.signed[cookie_name] = cookie_hash
|
109
|
+
@cookies[cookie_name] = cookie_hash
|
110
|
+
d 'session.write_cookie WROTE COOKIE I HOPE. Cookie guts:' + @cookies[cookie_name].inspect
|
111
|
+
end
|
112
|
+
|
113
|
+
def cookie_name
|
114
|
+
Authenticate.configuration.cookie_name.freeze.to_sym
|
115
|
+
end
|
116
|
+
|
117
|
+
def load_user
|
118
|
+
Authenticate.configuration.user_model_class.where(session_token: @session_token).first
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'authenticate/configuration'
|
2
|
+
require 'authenticate/token'
|
3
|
+
require 'authenticate/callbacks/authenticatable'
|
4
|
+
|
5
|
+
module Authenticate
|
6
|
+
|
7
|
+
# Required to be included in your configued user class, which is `User` by
|
8
|
+
# default, but can be changed with {Configuration#user_model=}.
|
9
|
+
#
|
10
|
+
# class User
|
11
|
+
# include Authenticate::User
|
12
|
+
# # ...
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# To change the user class from the default User, assign it :
|
16
|
+
#
|
17
|
+
# Authenticate.configure do |config|
|
18
|
+
# config.user_model = 'MyPackage::Gundan'
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# The fields and methods included by Authenticate::User will depend on what modules you have included in your
|
22
|
+
# configuration. When your user class is loaded, User will load any modules at that time. If you have another
|
23
|
+
# initializer that loads User before Authenticate's initializer has run, this may cause interfere with the
|
24
|
+
# configuration of your user.
|
25
|
+
#
|
26
|
+
# Every user will have two methods to manage session tokens:
|
27
|
+
# - generate_session_token - generates and sets the Authenticate session token
|
28
|
+
# - reset_session_token! - calls generate_session_token and save! immediately
|
29
|
+
#
|
30
|
+
module User
|
31
|
+
extend ActiveSupport::Concern
|
32
|
+
|
33
|
+
included do
|
34
|
+
include Modules
|
35
|
+
load_modules
|
36
|
+
end
|
37
|
+
|
38
|
+
def generate_session_token
|
39
|
+
self.session_token = Authenticate::Token.new
|
40
|
+
# puts 'User.generate_session_token session_token:' + self.session_token.to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
def reset_session_token!
|
44
|
+
generate_session_token
|
45
|
+
save validate: false
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Authenticate::Configuration do
|
4
|
+
after {restore_default_configuration}
|
5
|
+
|
6
|
+
context 'when no user_model_name is specified' do
|
7
|
+
before do
|
8
|
+
Authenticate.configure do |config|
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'defaults to User' do
|
13
|
+
expect(Authenticate.configuration.user_model).to eq '::User'
|
14
|
+
expect(Authenticate.configuration.user_model_class).to eq ::User
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'with a customer user_model' do
|
19
|
+
before do
|
20
|
+
MyUser = Class.new
|
21
|
+
Authenticate.configure do |config|
|
22
|
+
config.user_model = 'MyUser'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'is used instead of User' do
|
27
|
+
expect(Authenticate.configuration.user_model_class).to eq MyUser
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#authentication_strategy' do
|
32
|
+
context 'with no strategy set' do
|
33
|
+
it 'defaults to email' do
|
34
|
+
expect(Authenticate.configuration.authentication_strategy).to eq :email
|
35
|
+
end
|
36
|
+
it 'includes email in modules' do
|
37
|
+
expect(Authenticate.configuration.modules).to include :email
|
38
|
+
end
|
39
|
+
it 'does not include username in modules' do
|
40
|
+
expect(Authenticate.configuration.modules).to_not include :username
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'with strategy set to username' do
|
45
|
+
before do
|
46
|
+
Authenticate.configure do |config|
|
47
|
+
config.authentication_strategy = :username
|
48
|
+
end
|
49
|
+
end
|
50
|
+
it 'includes username in modules' do
|
51
|
+
expect(Authenticate.configuration.modules).to include :username
|
52
|
+
end
|
53
|
+
it 'does not include email in modules' do
|
54
|
+
expect(Authenticate.configuration.modules).to_not include :email
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
== README
|
2
|
+
|
3
|
+
This README would normally document whatever steps are necessary to get the
|
4
|
+
application up and running.
|
5
|
+
|
6
|
+
Things you may want to cover:
|
7
|
+
|
8
|
+
* Ruby version
|
9
|
+
|
10
|
+
* System dependencies
|
11
|
+
|
12
|
+
* Configuration
|
13
|
+
|
14
|
+
* Database creation
|
15
|
+
|
16
|
+
* Database initialization
|
17
|
+
|
18
|
+
* How to run the test suite
|
19
|
+
|
20
|
+
* Services (job queues, cache servers, search engines, etc.)
|
21
|
+
|
22
|
+
* Deployment instructions
|
23
|
+
|
24
|
+
* ...
|
25
|
+
|
26
|
+
|
27
|
+
Please feel free to use a different markup language if you do not plan to run
|
28
|
+
<tt>rake doc:app</tt>.
|
data/spec/dummy/Rakefile
ADDED