authenticate 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +21 -0
  6. data/Gemfile.lock +154 -0
  7. data/LICENSE +20 -0
  8. data/README.md +240 -0
  9. data/Rakefile +6 -0
  10. data/app/assets/config/authenticate_manifest.js +0 -0
  11. data/app/assets/images/authenticate/.keep +0 -0
  12. data/app/assets/javascripts/authenticate/.keep +0 -0
  13. data/app/assets/stylesheets/authenticate/.keep +0 -0
  14. data/app/controllers/.keep +0 -0
  15. data/app/helpers/.keep +0 -0
  16. data/app/mailers/.keep +0 -0
  17. data/app/models/.keep +0 -0
  18. data/app/views/.keep +0 -0
  19. data/authenticate.gemspec +38 -0
  20. data/bin/rails +12 -0
  21. data/config/routes.rb +2 -0
  22. data/lib/authenticate.rb +12 -0
  23. data/lib/authenticate/callbacks/authenticatable.rb +4 -0
  24. data/lib/authenticate/callbacks/brute_force.rb +31 -0
  25. data/lib/authenticate/callbacks/lifetimed.rb +5 -0
  26. data/lib/authenticate/callbacks/timeoutable.rb +15 -0
  27. data/lib/authenticate/callbacks/trackable.rb +8 -0
  28. data/lib/authenticate/configuration.rb +144 -0
  29. data/lib/authenticate/controller.rb +110 -0
  30. data/lib/authenticate/crypto/bcrypt.rb +30 -0
  31. data/lib/authenticate/debug.rb +10 -0
  32. data/lib/authenticate/engine.rb +21 -0
  33. data/lib/authenticate/lifecycle.rb +120 -0
  34. data/lib/authenticate/login_status.rb +27 -0
  35. data/lib/authenticate/model/brute_force.rb +51 -0
  36. data/lib/authenticate/model/db_password.rb +71 -0
  37. data/lib/authenticate/model/email.rb +76 -0
  38. data/lib/authenticate/model/lifetimed.rb +48 -0
  39. data/lib/authenticate/model/timeoutable.rb +47 -0
  40. data/lib/authenticate/model/trackable.rb +43 -0
  41. data/lib/authenticate/model/username.rb +45 -0
  42. data/lib/authenticate/modules.rb +61 -0
  43. data/lib/authenticate/session.rb +123 -0
  44. data/lib/authenticate/token.rb +7 -0
  45. data/lib/authenticate/user.rb +50 -0
  46. data/lib/authenticate/version.rb +3 -0
  47. data/lib/tasks/authenticate_tasks.rake +4 -0
  48. data/spec/configuration_spec.rb +60 -0
  49. data/spec/dummy/README.rdoc +28 -0
  50. data/spec/dummy/Rakefile +6 -0
  51. data/spec/dummy/app/assets/images/.keep +0 -0
  52. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  53. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  54. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  55. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  56. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  57. data/spec/dummy/app/mailers/.keep +0 -0
  58. data/spec/dummy/app/models/.keep +0 -0
  59. data/spec/dummy/app/models/concerns/.keep +0 -0
  60. data/spec/dummy/app/models/user.rb +3 -0
  61. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  62. data/spec/dummy/bin/bundle +3 -0
  63. data/spec/dummy/bin/rails +4 -0
  64. data/spec/dummy/bin/rake +4 -0
  65. data/spec/dummy/bin/setup +29 -0
  66. data/spec/dummy/config.ru +4 -0
  67. data/spec/dummy/config/application.rb +26 -0
  68. data/spec/dummy/config/boot.rb +5 -0
  69. data/spec/dummy/config/database.yml +25 -0
  70. data/spec/dummy/config/environment.rb +5 -0
  71. data/spec/dummy/config/environments/development.rb +41 -0
  72. data/spec/dummy/config/environments/production.rb +79 -0
  73. data/spec/dummy/config/environments/test.rb +42 -0
  74. data/spec/dummy/config/initializers/assets.rb +11 -0
  75. data/spec/dummy/config/initializers/authenticate.rb +7 -0
  76. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  77. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  78. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  79. data/spec/dummy/config/initializers/inflections.rb +16 -0
  80. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  81. data/spec/dummy/config/initializers/session_store.rb +3 -0
  82. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  83. data/spec/dummy/config/locales/en.yml +23 -0
  84. data/spec/dummy/config/routes.rb +56 -0
  85. data/spec/dummy/config/secrets.yml +22 -0
  86. data/spec/dummy/db/development.sqlite3 +0 -0
  87. data/spec/dummy/db/migrate/20160120003910_create_users.rb +18 -0
  88. data/spec/dummy/db/schema.rb +31 -0
  89. data/spec/dummy/db/test.sqlite3 +0 -0
  90. data/spec/dummy/lib/assets/.keep +0 -0
  91. data/spec/dummy/log/.keep +0 -0
  92. data/spec/dummy/public/404.html +67 -0
  93. data/spec/dummy/public/422.html +67 -0
  94. data/spec/dummy/public/500.html +66 -0
  95. data/spec/dummy/public/favicon.ico +0 -0
  96. data/spec/factories/users.rb +23 -0
  97. data/spec/model/session_spec.rb +86 -0
  98. data/spec/model/token_spec.rb +11 -0
  99. data/spec/model/user_spec.rb +12 -0
  100. data/spec/orm/active_record.rb +17 -0
  101. data/spec/spec_helper.rb +148 -0
  102. 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,7 @@
1
+ module Authenticate
2
+ class Token
3
+ def self.new
4
+ SecureRandom.hex(20).encode('UTF-8')
5
+ end
6
+ end
7
+ end
@@ -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,3 @@
1
+ module Authenticate
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :authenticate do
3
+ # # Task goes here
4
+ # end
@@ -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>.
@@ -0,0 +1,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require File.expand_path('../config/application', __FILE__)
5
+
6
+ Rails.application.load_tasks