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
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
File without changes
File without changes
File without changes
File without changes
File without changes
data/app/helpers/.keep ADDED
File without changes
data/app/mailers/.keep ADDED
File without changes
data/app/models/.keep ADDED
File without changes
data/app/views/.keep ADDED
File without changes
@@ -0,0 +1,38 @@
1
+ $LOAD_PATH.push File.expand_path('../lib', __FILE__)
2
+ # $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ require 'authenticate/version'
5
+ require 'date'
6
+
7
+ # Describe your gem and declare its dependencies:
8
+ Gem::Specification.new do |s|
9
+ s.name = 'authenticate'
10
+ s.version = Authenticate::VERSION
11
+ s.authors = ['Justin Tomich']
12
+ s.email = ['justin@tomich.org']
13
+ s.homepage = 'http://github.com/tomichj/authenticate'
14
+ s.summary = 'Rails authentication with email & password'
15
+ s.description = 'Rails authentication with email & password'
16
+ s.license = 'MIT'
17
+
18
+ # s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"]
19
+ s.files = `git ls-files`.split("\n")
20
+ # s.test_files = `git ls-files -- {spec}/*`.split("\n")
21
+ s.test_files = Dir['spec/**/*_spec.rb']
22
+
23
+ s.extra_rdoc_files = %w(LICENSE README.md)
24
+ s.rdoc_options = ['--charset=UTF-8']
25
+
26
+ s.require_paths = ['lib']
27
+
28
+ s.add_dependency 'bcrypt'
29
+ s.add_dependency 'email_validator', '~> 1.6'
30
+ s.add_dependency 'rails', '>= 4.0', '< 5.1'
31
+ s.add_development_dependency 'sqlite3'
32
+ s.add_development_dependency 'rspec'
33
+ s.add_development_dependency 'rspec-rails'
34
+ # s.add_development_dependency 'capybara'
35
+ s.add_development_dependency 'factory_girl_rails'
36
+
37
+ s.required_ruby_version = Gem::Requirement.new('>= 2.0')
38
+ end
data/bin/rails ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application.
3
+
4
+ ENGINE_ROOT = File.expand_path('../..', __FILE__)
5
+ ENGINE_PATH = File.expand_path('../../lib/authenticate/engine', __FILE__)
6
+
7
+ # Set up gems listed in the Gemfile.
8
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
9
+ require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
10
+
11
+ require 'rails/all'
12
+ require 'rails/engine/commands'
data/config/routes.rb ADDED
@@ -0,0 +1,2 @@
1
+ Rails.application.routes.draw do
2
+ end
@@ -0,0 +1,12 @@
1
+ require 'authenticate/debug'
2
+ require 'authenticate/configuration'
3
+ require 'authenticate/lifecycle'
4
+ require 'authenticate/controller'
5
+ require 'authenticate/session'
6
+ require 'authenticate/user'
7
+ require 'authenticate/engine'
8
+ require 'authenticate/modules'
9
+
10
+ module Authenticate
11
+ # Your code goes here...
12
+ end
@@ -0,0 +1,4 @@
1
+ # If user failed to authenticate, toss them out.
2
+ Authenticate.lifecycle.after_authentication name: 'authenticatable' do |user, session, opts|
3
+ throw(:failure, 'Wrong email or password.') unless session.authenticated?
4
+ end
@@ -0,0 +1,31 @@
1
+ # Prevents a locked user from logging in, and unlocks users that expired their lock time.
2
+ # Runs as a hook after authentication.
3
+ Authenticate.lifecycle.prepend_after_authentication name: 'brute force protection' do |user, session, options|
4
+ include ActionView::Helpers::DateHelper
5
+
6
+ # puts 'bf: about to check authentication'
7
+ unless session.authenticated?
8
+ # puts 'bf: session not authenticated'
9
+
10
+ user_credentials = User.credentials(session.request.params)
11
+ # puts "brute force protection user_credentials: #{user_credentials}"
12
+ user ||= User.find_by_credentials(user_credentials)
13
+
14
+ # puts 'bf: looked up user by credentials, found:' + user.inspect
15
+ if user
16
+ # puts 'found user, about to register failed attempt'
17
+ user.register_failed_login!
18
+ user.save!
19
+ end
20
+ end
21
+
22
+ if user && user.locked? && Authenticate.configuration.bad_login_lockout_period != nil
23
+ user.unlock! if user.lock_expires_at <= Time.now.utc
24
+ end
25
+
26
+ if user && user.locked?
27
+ remaining = time_ago_in_words(user.lock_expires_at)
28
+ throw(:failure, "Your account is locked, will unlock in #{remaining.to_s}")
29
+ end
30
+
31
+ end
@@ -0,0 +1,5 @@
1
+ Authenticate.lifecycle.after_set_user name: 'lifetimed after set_user', except: :authentication do |user, session, options|
2
+ if user && user.respond_to?(:max_session_timedout?)
3
+ throw(:failure, "Your session has reached it's maximum allowed lifetime, you must log in again") if user.max_session_timedout?
4
+ end
5
+ end
@@ -0,0 +1,15 @@
1
+ Authenticate.lifecycle.after_authentication name: 'timeoutable after authentication' do |user, session, options|
2
+ if user && user.respond_to?(:last_access_at)
3
+ user.last_access_at = Time.now.utc
4
+ user.save!
5
+ end
6
+ end
7
+
8
+ Authenticate.lifecycle.after_set_user name: 'timeoutable after set_user', except: :authentication do |user, session, options|
9
+ puts "user.respond_to?(:timedout?) #{user.respond_to?(:timedout?).inspect}" if user
10
+ if user && user.respond_to?(:timedout?)
11
+ throw(:failure, 'Your session has expired') if user.timedout?
12
+ user.last_access_at = Time.now.utc
13
+ user.save!
14
+ end
15
+ end
@@ -0,0 +1,8 @@
1
+ # After each sign in: ”update sign in time, sign in count and sign in IP.
2
+ # This is only triggered when the user is explicitly set (with set_user)
3
+ # and on authentication.
4
+ Authenticate.lifecycle.after_authentication name: 'trackable' do |user, session, options|
5
+ if user.respond_to?(:update_tracked_fields!)
6
+ user.update_tracked_fields!(session.request)
7
+ end
8
+ end
@@ -0,0 +1,144 @@
1
+ module Authenticate
2
+ class Configuration
3
+
4
+ # ActiveRecord model class name that represents your user.
5
+ # Specify as a String. Defaults to '::User'.
6
+ # To set to a different class:
7
+ #
8
+ # Authenticate.configure do |config|
9
+ # config.user_model = 'BlogUser'
10
+ # end
11
+ #
12
+ # @return [String]
13
+ attr_accessor :user_model
14
+
15
+ # Name of the session cookie Authenticate will send to client browser.
16
+ # Defaults to 'authenticate_session_token'.
17
+ # @return [String]
18
+ attr_accessor :cookie_name
19
+
20
+ # A lambda called to set the remember token cookie expires attribute. Defaults to 1 year expiration.
21
+ # Note this is NOT the session's max lifetime, see #max_session_lifetime.
22
+ # To set cookie expiration yourself:
23
+ #
24
+ # Authenticate.configure do |config|
25
+ # config.cookie_expiration = { 1.month.from_now.utc }
26
+ # end
27
+ #
28
+ # @return [Lambda]
29
+ attr_accessor :cookie_expiration
30
+
31
+ # The domain to set for the Authenticate session cookie.
32
+ # Defaults to nil, which will cause the cookie domain to set
33
+ # to the domain of the request.
34
+ # @return [String]
35
+ attr_accessor :cookie_domain
36
+
37
+ # Crypto used when authenticating and setting passwords.
38
+ # Defaults to {Authenticate::Model::BCrypt}.
39
+ # Crypto implementations must provide:
40
+ # * match?(secret, encrypted)
41
+ # * encrypt(secret)
42
+ #
43
+ # @return [Module #match? #encrypt]
44
+ attr_accessor :crypto_provider
45
+
46
+ # Invalidate the session after the specified period of idle time.
47
+ # Defaults to nil, which is no idle timeout.
48
+ #
49
+ # Authenticate.configure do |config|
50
+ # config.timeout_in = 45.minutes
51
+ # end
52
+ #
53
+ # @return [ActiveSupport::CoreExtensions::Numeric::Time]
54
+ attr_accessor :timeout_in
55
+
56
+ # Allow a session to 'live' for no more than the given elapsed time, e.g. 8.hours.
57
+ # Defaults to nil, or no max session time.
58
+ # @return [ActiveSupport::CoreExtensions::Numeric::Time]
59
+ attr_accessor :max_session_lifetime
60
+
61
+ # Number of consecutive bad login attempts allowed.
62
+ # Default is nil, which disables this feature.
63
+ # @return [Integer]
64
+ attr_accessor :max_consecutive_bad_logins_allowed
65
+
66
+ # Time period to lock an account for if the user exceeds
67
+ # max_consecutive_bad_logins_allowed (and it's set to nonzero).
68
+ # If set to nil, account is locked out indefinitely.
69
+ # @return [ActiveSupport::CoreExtensions::Numeric::Time]
70
+ attr_accessor :bad_login_lockout_period
71
+
72
+ # Strategy for authentication.
73
+ #
74
+ # Available strategies:
75
+ # :email - requires user have attribute :email
76
+ # :username - requires user have attribute :username
77
+ # Defaults to :email. To set to :username:
78
+ # Configuration.configure do |config|
79
+ # config.authentication_strategy = :username
80
+ # end
81
+ #
82
+ # Or, you can plug in your own authentication class, eg:
83
+ # Configuration.configure do |config|
84
+ # config.authentication_strategy = MyFunkyAuthClass
85
+ # end
86
+ # @return [Symbol or Class]
87
+ attr_accessor :authentication_strategy
88
+
89
+
90
+ # Enable debugging messages.
91
+ # @private
92
+ # @return [Boolean]
93
+ attr_accessor :debug
94
+
95
+ # An array of additional modules to load into the User module.
96
+ # Defaults to an empty array.
97
+ # @return [Array]
98
+ attr_accessor :modules
99
+
100
+
101
+ def initialize
102
+ # Defaults
103
+ @debug = false
104
+ @cookie_name = 'authenticate_session_token'
105
+ @cookie_expiration = -> { 1.year.from_now.utc }
106
+ @modules = []
107
+ @user_model = '::User'
108
+ @authentication_strategy = :email
109
+ end
110
+
111
+ def user_model_class
112
+ @user_model_class ||= user_model.constantize
113
+ end
114
+
115
+
116
+ # List of symbols naming modules to load.
117
+ def modules
118
+ modules = @modules.dup # in case the user pushes any on
119
+ modules << @authentication_strategy
120
+ modules << :db_password
121
+ modules << :trackable # needs configuration
122
+ modules << :timeoutable if @timeout_in
123
+ modules << :lifetimed if @max_session_lifetime
124
+ modules << :brute_force if @max_consecutive_bad_logins_allowed
125
+ modules
126
+ end
127
+
128
+
129
+ end # end of Configuration class
130
+
131
+
132
+ def self.configuration
133
+ @configuration ||= Configuration.new
134
+ end
135
+
136
+ def self.configuration=(config)
137
+ @configuration = config
138
+ end
139
+
140
+ def self.configure
141
+ yield configuration
142
+ end
143
+
144
+ end
@@ -0,0 +1,110 @@
1
+ module Authenticate
2
+ module Controller
3
+ extend ActiveSupport::Concern
4
+ include Debug
5
+
6
+ included do
7
+ helper_method :current_user, :authenticated?
8
+ attr_writer :authenticate_session
9
+ end
10
+
11
+
12
+ # Validate a user's identity with (typically) email/ID & password, and return the User if valid, or nil.
13
+ # After calling this, call login(user) to complete the process.
14
+ def authenticate(params)
15
+ # todo: get params from User model
16
+ user_credentials = Authenticate.configuration.user_model_class.credentials(params)
17
+ puts "Controller::user_credentials: #{user_credentials.inspect}"
18
+ Authenticate.configuration.user_model_class.authenticate(user_credentials)
19
+ end
20
+
21
+
22
+ # Complete the user's sign in process: after calling authenticate, or after user creates account.
23
+ # Runs all valid callbacks and sends the user a session token.
24
+ def login(user, &block)
25
+ authenticate_session.login user, &block
26
+ end
27
+
28
+
29
+ # Log the user out. Typically used in session controller.
30
+ #
31
+ # class SessionsController < ActionController::Base
32
+ # include Authenticate::Controller
33
+ #
34
+ # def destroy
35
+ # logout
36
+ # redirect_to '/', notice: 'You logged out successfully'
37
+ # end
38
+ def logout
39
+ authenticate_session.deauthenticate
40
+ end
41
+
42
+
43
+ # Use this as a before_action to restrict controller actions to authenticated users.
44
+ # Consider using in application_controller to restrict access to all controllers.
45
+ #
46
+ # Example:
47
+ #
48
+ # class ApplicationController < ActionController::Base
49
+ # before_action :require_authentication
50
+ #
51
+ # def index
52
+ # # ...
53
+ # end
54
+ # end
55
+ #
56
+ def require_authentication
57
+ d 'Controller::require_authentication'
58
+ unless authenticated?
59
+ unauthorized
60
+ end
61
+
62
+ message = catch(:failure) do
63
+ current_user = authenticate_session.current_user
64
+ Authenticate.lifecycle.run_callbacks(:after_set_user, current_user, authenticate_session, {event: :set_user })
65
+ end
66
+ unauthorized(message) if message
67
+ end
68
+
69
+
70
+ # Has the user been logged in? Exposed as a helper, can be called from views.
71
+ #
72
+ # <% if authenticated? %>
73
+ # <%= link_to logout_path, "Sign out" %>
74
+ # <% else %>
75
+ # <%= link_to login_path, "Sign in" %>
76
+ # <% end %>
77
+ #
78
+ def authenticated?
79
+ authenticate_session.authenticated?
80
+ end
81
+
82
+
83
+ # Get the current user from the current Authenticate session.
84
+ # Exposed as a helper , can be called from controllers, views, and other helpers.
85
+ #
86
+ # <p>Your email address: <%= current_user.email %></p>
87
+ #
88
+ def current_user
89
+ authenticate_session.current_user
90
+ end
91
+
92
+ private
93
+
94
+ def authenticate_session
95
+ @authenticate_session ||= Authenticate::Session.new(request, cookies)
96
+ end
97
+
98
+ def unauthorized(msg = 'You must sign in')
99
+ respond_to do |format|
100
+ format.any(:js, :json, :xml) { head :unauthorized }
101
+ format.any {
102
+ flash[:notice] = msg # TODO use locales
103
+ redirect_to '/authenticate' #TODO something better baby
104
+ }
105
+ end
106
+ end
107
+
108
+
109
+ end
110
+ end
@@ -0,0 +1,30 @@
1
+ module Authenticate
2
+ module Crypto
3
+
4
+ # All crypto providers must implement encrypt(secret) and match?(secret, encrypted)
5
+ module BCrypt
6
+ require 'bcrypt'
7
+
8
+ def encrypt(secret)
9
+ ::BCrypt::Password.create secret, cost: cost
10
+ end
11
+
12
+ def match?(secret, encrypted)
13
+ return false unless encrypted.present?
14
+ ::BCrypt::Password.new(encrypted) == secret
15
+ end
16
+
17
+ def cost
18
+ @cost ||= ::BCrypt::Engine::DEFAULT_COST
19
+ end
20
+
21
+ def cost=(val)
22
+ if val < ::BCrypt::Engine::MIN_COST
23
+ raise ArgumentError.new("bcrypt cost cannot be set below the engine's min cost (#{::BCrypt::Engine::MIN_COST})")
24
+ end
25
+ @cost = val
26
+ end
27
+
28
+ end
29
+ end
30
+ end