authenticate 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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