authem 1.5.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +6 -0
  7. data/Appraisals +12 -0
  8. data/CHANGELOG.md +42 -0
  9. data/Gemfile +10 -0
  10. data/README.markdown +15 -1
  11. data/Rakefile +11 -5
  12. data/authem.gemspec +25 -0
  13. data/gemfiles/rails_4.0.gemfile +16 -0
  14. data/gemfiles/rails_4.1.gemfile +15 -0
  15. data/lib/authem.rb +4 -10
  16. data/lib/authem/controller.rb +50 -0
  17. data/lib/authem/errors/ambigous_role.rb +8 -0
  18. data/lib/authem/errors/unknown_role.rb +7 -0
  19. data/lib/authem/railtie.rb +12 -0
  20. data/lib/authem/role.rb +62 -0
  21. data/lib/authem/session.rb +41 -0
  22. data/lib/authem/support.rb +129 -0
  23. data/lib/authem/token.rb +5 -5
  24. data/lib/authem/user.rb +27 -13
  25. data/lib/authem/version.rb +1 -1
  26. data/lib/generators/authem/session/session_generator.rb +12 -0
  27. data/lib/generators/authem/session/templates/create_sessions.rb +15 -0
  28. data/lib/generators/authem/user/templates/create_table_migration.rb +22 -0
  29. data/lib/generators/authem/user/templates/model.rb +11 -0
  30. data/lib/generators/authem/user/user_generator.rb +13 -0
  31. data/spec/controller_spec.rb +413 -0
  32. data/spec/session_spec.rb +52 -0
  33. data/spec/spec_helper.rb +4 -0
  34. data/spec/support/active_record.rb +45 -0
  35. data/spec/support/i18n.rb +1 -0
  36. data/spec/support/time.rb +1 -0
  37. data/spec/token_spec.rb +10 -0
  38. data/spec/user_spec.rb +115 -0
  39. metadata +42 -112
  40. data/lib/authem/base_user.rb +0 -54
  41. data/lib/authem/config.rb +0 -21
  42. data/lib/authem/controller_support.rb +0 -51
  43. data/lib/authem/sorcery_user.rb +0 -24
  44. data/lib/generators/authem/model/model_generator.rb +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0a94ea5cf81c75464c3a4c15f8cad8380967c225
4
- data.tar.gz: 7506a4ded4f2de14fdbecc411192a1a90fada85d
3
+ metadata.gz: 67a264031f3e96725f18dc7e77c427ebd4708716
4
+ data.tar.gz: 09c0291510e6909b13ea9c5a3efa473e3054020b
5
5
  SHA512:
6
- metadata.gz: 57c5091384d69b193e710fe349d1d35cd18671a58e90ae0055c6cc9a527cca8a0c36495a3ad1baeaa889813a03de67ca338629f131a20fa7c8762c69bf202383
7
- data.tar.gz: 45a4f7c51d2518cf67746989e35d9912b70d49cfc47a8832d2266602bc8f7860f52fe8fb189af45b18fca7b90920414b17f043a7f2e94a1675f50b429b2657b4
6
+ metadata.gz: 771d1413927363e3c2426d0cee8d28fabf08389ce16a4aa6eefd06c6da6499177213f271fbc5ef2c111e48a2433c9695c1747877a6bb1b32e15aca99e4f491b7
7
+ data.tar.gz: 6e3adc3d207f865439888550c6aeb1d8f84f202e6a2ec31f132a23e4a254a218d5bd905a148309966e6917426cb89544cc4501c0208a81a955ab6ee7c198cf81
@@ -0,0 +1,3 @@
1
+ pkg
2
+ Gemfile.lock
3
+ gemfiles/*.lock
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --format progress
@@ -0,0 +1 @@
1
+ authem
@@ -0,0 +1 @@
1
+ ruby-2.1.2
@@ -0,0 +1,6 @@
1
+ gemfile:
2
+ - gemfiles/rails_4.0.gemfile
3
+ - gemfiles/rails_4.1.gemfile
4
+ rvm:
5
+ - 2.0.0
6
+ - 2.1.1
@@ -0,0 +1,12 @@
1
+ appraise "rails-4.1" do
2
+ gem "activerecord", "~> 4.1.0", require: "active_record"
3
+ gem "bcrypt", "~> 3.1"
4
+ gem "railties", "~> 4.0"
5
+ end
6
+
7
+ appraise "rails-4.0" do
8
+ gem "activerecord", "~> 4.0.0", require: "active_record"
9
+ gem "bcrypt", "~> 3.1"
10
+ gem "railties", "~> 4.0"
11
+ gem "protected_attributes"
12
+ end
@@ -0,0 +1,42 @@
1
+ ### 2.0.0 ###
2
+
3
+ * Complete rewrite from scratch
4
+ * Store sessions in the database
5
+ * Multiple sessions and models support
6
+ * Drop Sorcery support
7
+ * Initializer is no longer needed
8
+
9
+ ### 1.4.0 ###
10
+
11
+ * Use SecureRandom for token generation
12
+ * Lots of support file cleanup
13
+ * Some code cleanup
14
+ * All of this is thanks to Pavel Pravosud (@rwz). This guy is fantastic.
15
+
16
+ ### 1.3.3 ###
17
+
18
+ * Regenerate session token when user signs out (Issue #21)
19
+
20
+ ### 1.3.2 ###
21
+
22
+ * Prevent duplicate password validations on Authem::User
23
+
24
+ ### 1.3.1 ###
25
+
26
+ * Bump bcrypt dependency for Rails 4.0.1 compatibility
27
+
28
+ ### 1.3.0 ###
29
+
30
+ * Check for presence of password in authenticate
31
+ * Reconstantize user class when requested
32
+ * Add remember token to generated migrations
33
+ * Remove weird commas from generated migrations
34
+
35
+ ### 1.2.0 ###
36
+
37
+ * Use `sign_in?` helper in `require_user` (Issue #13)
38
+ * Update rails dependencies to final release versions
39
+
40
+ ### 1.1.1 ###
41
+
42
+ * Lock down bcrypt version per Rails' requirements
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "appraisal"
4
+
5
+ group :test do
6
+ gem "rspec", "~> 3.0"
7
+ gem "rake", "~> 10.3"
8
+ gem "sqlite3"
9
+ gem "pry"
10
+ end
@@ -6,7 +6,7 @@ Authem is an email-based authentication library for ruby web apps.
6
6
 
7
7
  ## Compatibility
8
8
 
9
- Authem is tested against Ruby 1.9.3, 2.0.0, and Rubinius.
9
+ Authem requires Ruby 2.0.0 or newer
10
10
 
11
11
  [![Build Status](https://secure.travis-ci.org/paulelliott/authem.png)](http://travis-ci.org/paulelliott/authem)
12
12
  [![Code Climate](https://codeclimate.com/github/paulelliott/authem.png)](https://codeclimate.com/github/paulelliott/authem)
@@ -14,3 +14,17 @@ Authem is tested against Ruby 1.9.3, 2.0.0, and Rubinius.
14
14
  ## Documentation
15
15
 
16
16
  Please see the Authem website for up-to-date documentation: http://authem.org
17
+
18
+ ## Upgrading to 2.0
19
+
20
+ - Specify the latest alpha release in your Gemfile: `gem 'authem', '2.0.0.alpha.3'`
21
+ - Remove references to the old Authem::Config object.
22
+ - Create the new sessions table with `rails g authem:session`.
23
+ - Replace `include Authem::ControllerSupport` with `authem_for :user`.
24
+ - Rename `signed_in?` to `user_signed_in? OR `alias_method :signed_in?, :user_signed_in?` in your controller.
25
+ - Rename column `User#reset_password_token` to `User#password_reset_token` OR `alias_attribute :password_reset_token, :reset_password_token` in your `User` model.
26
+ - Replace calls to `user#reset_password_token!` with `user#password_reset_token`. Tokens are now generated automatically and the bang method is deprecated.
27
+ - Rename `sign_out` to `sign_out_user` OR `alias_method :sign_out, :sign_out_user`
28
+ - If you were passing a remember flag as the second argument to `sign_in`, you need to provide an options hash instead. For example, `sign_in(user, params[:remember])` would become `sign_in(user, remember: params[:remember])`.
29
+ - Blank email addresses will now produce the proper "can't be blank" validation message". Update your tests accordingly.
30
+ - Email addresses are no longer automatically downcased when calling `find_by_email` on your model. You will need to downcase the value manually if you wish to retain this behavior.
data/Rakefile CHANGED
@@ -1,6 +1,12 @@
1
- require "rspec/core/rake_task"
2
- RSpec::Core::RakeTask.new(:spec) do |spec|
3
- spec.pattern = "spec/**/*_spec.rb"
4
- end
1
+ require "bundler/setup"
2
+ require "bundler/gem_tasks"
5
3
 
6
- task :default => :spec
4
+ if !ENV["APPRAISAL_INITIALIZED"] && !ENV["TRAVIS"]
5
+ require "appraisal/task"
6
+ Appraisal::Task.new
7
+ task default: :appraisal
8
+ else
9
+ require "rspec/core/rake_task"
10
+ RSpec::Core::RakeTask.new
11
+ task default: :spec
12
+ end
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'authem/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "authem"
8
+ spec.version = Authem::VERSION
9
+ spec.authors = ["Paul Elliott", "Pavel Pravosud"]
10
+ spec.email = ["paul@codingfrontier.com", "pavel@pravosud.com"]
11
+ spec.summary = "Authem authenticates them by email"
12
+ spec.description = "Authem provides a simple solution for email-based authentication"
13
+ spec.homepage = "https://github.com/paulelliott/authem"
14
+ spec.license = "MIT"
15
+
16
+ spec.required_ruby_version = ">= 2.0.0"
17
+
18
+ spec.files = `git ls-files`.split($/)
19
+ spec.test_files = spec.files.grep("spec")
20
+ spec.require_path = "lib"
21
+
22
+ spec.add_dependency "activesupport", ">= 4.0.4"
23
+ spec.add_dependency "railties", "~> 4.0"
24
+ spec.add_dependency "bcrypt", "~> 3.1"
25
+ end
@@ -0,0 +1,16 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "appraisal"
6
+ gem "activerecord", "~> 4.0.0", :require => "active_record"
7
+ gem "bcrypt", "~> 3.1"
8
+ gem "railties", "~> 4.0"
9
+ gem "protected_attributes"
10
+
11
+ group :test do
12
+ gem "rspec", "~> 3.0"
13
+ gem "rake", "~> 10.3"
14
+ gem "sqlite3"
15
+ gem "pry"
16
+ end
@@ -0,0 +1,15 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "appraisal"
6
+ gem "activerecord", "~> 4.1.0", :require => "active_record"
7
+ gem "bcrypt", "~> 3.1"
8
+ gem "railties", "~> 4.0"
9
+
10
+ group :test do
11
+ gem "rspec", "~> 3.0"
12
+ gem "rake", "~> 10.3"
13
+ gem "sqlite3"
14
+ gem "pry"
15
+ end
@@ -1,12 +1,6 @@
1
- module Authem
2
- autoload :BaseUser, 'authem/base_user'
3
- autoload :User, 'authem/user'
4
- autoload :SorceryUser, 'authem/sorcery_user'
5
- autoload :Config, 'authem/config'
6
- autoload :ControllerSupport, 'authem/controller_support'
7
- autoload :Token, 'authem/token'
1
+ require "authem/railtie"
2
+ require "authem/user"
3
+ require "authem/version"
8
4
 
9
- def self.configure(&block)
10
- Config.configure(&block)
11
- end
5
+ module Authem
12
6
  end
@@ -0,0 +1,50 @@
1
+ require "active_support/concern"
2
+ require "authem/role"
3
+
4
+ module Authem
5
+ module Controller
6
+ extend ActiveSupport::Concern
7
+
8
+ included{ class_attribute :authem_roles }
9
+
10
+ module SessionManagementMethods
11
+ def sign_in(model, **options)
12
+ role = options.fetch(:as){ self.class.authem_role_for(model) }
13
+ public_send "sign_in_#{role}", model, options
14
+ end
15
+
16
+ def sign_out(model, **options)
17
+ role = options.fetch(:as){ self.class.authem_role_for(model) }
18
+ public_send "sign_out_#{role}"
19
+ end
20
+
21
+ def clear_all_sessions_for(model, **options)
22
+ role = options.fetch(:as){ self.class.authem_role_for(model) }
23
+ public_send "clear_all_#{role}_sessions_for", model
24
+ end
25
+
26
+ def redirect_back_or_to(url, **options)
27
+ url = session.delete(:return_to_url) || url
28
+ redirect_to url, options
29
+ end
30
+ end
31
+
32
+ module ClassMethods
33
+ def authem_for(role_name, **options)
34
+ include SessionManagementMethods
35
+ Authem::Role.new(self, role_name, options).setup!
36
+ end
37
+
38
+ def authem_role_for(record)
39
+ fail ArgumentError if record.nil?
40
+
41
+ matches = authem_roles.select{ |role| record.class == role.klass }
42
+
43
+ fail UnknownRoleError.build(record) if matches.empty?
44
+ fail AmbigousRoleError.build(record, matches) unless matches.one?
45
+
46
+ matches.first.name
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,8 @@
1
+ module Authem
2
+ class AmbigousRoleError < StandardError
3
+ def self.build(record, roles)
4
+ role_names = roles.map(&:name) * ", "
5
+ new("Ambigous match for #{record.inspect}: #{role_names}")
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ module Authem
2
+ class UnknownRoleError < StandardError
3
+ def self.build(record)
4
+ new("Unknown authem role: #{record.inspect}")
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,12 @@
1
+ require "authem/controller"
2
+ require "rails/railtie"
3
+
4
+ module Authem
5
+ class Railtie < ::Rails::Railtie
6
+ initializer "authem.controller" do
7
+ ActiveSupport.on_load :action_controller do
8
+ include Authem::Controller
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,62 @@
1
+ require "authem/support"
2
+
3
+ module Authem
4
+ class Role
5
+ attr_reader :controller, :name, :options
6
+
7
+ METHODS = %i[current sign_in signed_in? require sign_out clear_for deny_access]
8
+
9
+ METHODS.each do |method_name|
10
+ define_method method_name do |controller, *args|
11
+ Support.new(self, controller).public_send(method_name, *args)
12
+ end
13
+ end
14
+
15
+ def initialize(controller, name, **options)
16
+ @controller, @name, @options = controller, name.to_s, options
17
+ end
18
+
19
+ def klass
20
+ @klass ||= options.fetch(:model){ name.classify.constantize }
21
+ end
22
+
23
+ def setup!
24
+ setup_controller_settings
25
+ setup_controller_instance_methods
26
+ setup_view_helpers
27
+ end
28
+
29
+ private
30
+
31
+ def setup_controller_settings
32
+ controller.authem_roles ||= []
33
+ controller.authem_roles += [self]
34
+ end
35
+
36
+ def setup_controller_instance_methods
37
+ role = self
38
+
39
+ method_mapping.each do |inner_method, exposed_method|
40
+ define_controller_method exposed_method do |*args|
41
+ role.public_send(inner_method, self, *args)
42
+ end
43
+ end
44
+ end
45
+
46
+ def setup_view_helpers
47
+ controller.helper_method *%I[current_#{name} #{name}_signed_in?]
48
+ end
49
+
50
+ def define_controller_method(*args, &block)
51
+ controller.instance_eval{ define_method *args, &block }
52
+ end
53
+
54
+ def method_mapping
55
+ exposed_methods = %I[current_#{name} sign_in_#{name}
56
+ #{name}_signed_in? require_#{name} sign_out_#{name}
57
+ clear_all_#{name}_sessions_for deny_#{name}_access]
58
+
59
+ Hash[[METHODS, exposed_methods].transpose]
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,41 @@
1
+ require "active_record"
2
+ require "authem/token"
3
+
4
+ module Authem
5
+ class Session < ::ActiveRecord::Base
6
+ self.table_name = :authem_sessions
7
+
8
+ belongs_to :subject, polymorphic: true
9
+
10
+ before_create do
11
+ self.token ||= Authem::Token.generate
12
+ self.ttl ||= 30.days
13
+ self.expires_at ||= ttl_from_now
14
+ end
15
+
16
+ class << self
17
+ def by_subject(record)
18
+ where(subject_type: record.class.name, subject_id: record.id)
19
+ end
20
+
21
+ def active
22
+ where(arel_table[:expires_at].gteq(Time.zone.now))
23
+ end
24
+
25
+ def expired
26
+ where(arel_table[:expires_at].lt(Time.zone.now))
27
+ end
28
+ end
29
+
30
+ def refresh
31
+ self.expires_at = ttl_from_now
32
+ save!
33
+ end
34
+
35
+ private
36
+
37
+ def ttl_from_now
38
+ ttl.to_i.seconds.from_now
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,129 @@
1
+ require "active_support/core_ext/module/delegation"
2
+ require "authem/session"
3
+ require "authem/errors/ambigous_role"
4
+ require "authem/errors/unknown_role"
5
+
6
+ module Authem
7
+ class Support
8
+ attr_reader :role, :controller
9
+
10
+ def initialize(role, controller)
11
+ @role, @controller = role, controller
12
+ end
13
+
14
+ def current
15
+ if ivar_defined?
16
+ ivar_get
17
+ else
18
+ ivar_set fetch_subject_by_token
19
+ end
20
+ end
21
+
22
+ def sign_in(record, **options)
23
+ check_record! record
24
+ ivar_set record
25
+ auth_session = create_auth_session(record, options)
26
+ save_session auth_session
27
+ save_cookie auth_session if options[:remember]
28
+ auth_session
29
+ end
30
+
31
+ def signed_in?
32
+ current.present?
33
+ end
34
+
35
+ def sign_out
36
+ ivar_set nil
37
+ Authem::Session.where(role: role_name, token: current_auth_token).delete_all
38
+ cookies.delete key, domain: :all
39
+ session.delete key
40
+ end
41
+
42
+ def clear_for(record)
43
+ check_record! record
44
+ sign_out
45
+ Authem::Session.by_subject(record).where(role: role_name).delete_all
46
+ end
47
+
48
+ def require
49
+ unless signed_in?
50
+ session[:return_to_url] = request.url unless request.xhr?
51
+ controller.send "deny_#{role_name}_access"
52
+ end
53
+ end
54
+
55
+ def deny_access
56
+ # default landing point for deny_#{role_name}_access
57
+ fail NotImplementedError, "No strategy for require_#{role_name} defined. Please define `deny_#{role_name}_access` method in your controller"
58
+ end
59
+
60
+ private
61
+
62
+ delegate :name, to: :role, prefix: true
63
+
64
+ def check_record!(record)
65
+ fail ArgumentError if record.nil?
66
+ end
67
+
68
+ def fetch_subject_by_token
69
+ return if current_auth_token.blank?
70
+ auth_session = get_auth_session_by_token(current_auth_token)
71
+ return nil unless auth_session
72
+ auth_session.refresh
73
+ save_cookie auth_session if cookies.signed[key].present?
74
+ auth_session.subject
75
+ end
76
+
77
+ def current_auth_token
78
+ session[key] || cookies.signed[key]
79
+ end
80
+
81
+ def create_auth_session(record, options)
82
+ Authem::Session.create!(role: role_name, subject: record, ttl: options[:ttl])
83
+ end
84
+
85
+ def save_session(auth_session)
86
+ session[key] = auth_session.token
87
+ end
88
+
89
+ def save_cookie(auth_session)
90
+ cookie_value = {
91
+ value: auth_session.token,
92
+ expires: auth_session.expires_at,
93
+ domain: :all
94
+ }
95
+ cookies.signed[key] = cookie_value
96
+ end
97
+
98
+ def get_auth_session_by_token(token)
99
+ Authem::Session.active.find_by(role: role_name, token: token)
100
+ end
101
+
102
+ def key
103
+ "_authem_current_#{role_name}"
104
+ end
105
+
106
+ def ivar_defined?
107
+ controller.instance_variable_defined?(ivar_name)
108
+ end
109
+
110
+ def ivar_set(value)
111
+ controller.instance_variable_set ivar_name, value
112
+ end
113
+
114
+ def ivar_get
115
+ controller.instance_variable_get ivar_name
116
+ end
117
+
118
+ def ivar_name
119
+ @ivar_name ||= "@_#{key}".to_sym
120
+ end
121
+
122
+ # exposing private controller methods
123
+ %i[cookies session redirect_to request].each do |method_name|
124
+ define_method method_name do |*args|
125
+ controller.send(method_name, *args)
126
+ end
127
+ end
128
+ end
129
+ end