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.
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