authem 1.5.0 → 2.0.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 (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