authem 1.5.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +6 -0
- data/Appraisals +12 -0
- data/CHANGELOG.md +42 -0
- data/Gemfile +10 -0
- data/README.markdown +15 -1
- data/Rakefile +11 -5
- data/authem.gemspec +25 -0
- data/gemfiles/rails_4.0.gemfile +16 -0
- data/gemfiles/rails_4.1.gemfile +15 -0
- data/lib/authem.rb +4 -10
- data/lib/authem/controller.rb +50 -0
- data/lib/authem/errors/ambigous_role.rb +8 -0
- data/lib/authem/errors/unknown_role.rb +7 -0
- data/lib/authem/railtie.rb +12 -0
- data/lib/authem/role.rb +62 -0
- data/lib/authem/session.rb +41 -0
- data/lib/authem/support.rb +129 -0
- data/lib/authem/token.rb +5 -5
- data/lib/authem/user.rb +27 -13
- data/lib/authem/version.rb +1 -1
- data/lib/generators/authem/session/session_generator.rb +12 -0
- data/lib/generators/authem/session/templates/create_sessions.rb +15 -0
- data/lib/generators/authem/user/templates/create_table_migration.rb +22 -0
- data/lib/generators/authem/user/templates/model.rb +11 -0
- data/lib/generators/authem/user/user_generator.rb +13 -0
- data/spec/controller_spec.rb +413 -0
- data/spec/session_spec.rb +52 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/support/active_record.rb +45 -0
- data/spec/support/i18n.rb +1 -0
- data/spec/support/time.rb +1 -0
- data/spec/token_spec.rb +10 -0
- data/spec/user_spec.rb +115 -0
- metadata +42 -112
- data/lib/authem/base_user.rb +0 -54
- data/lib/authem/config.rb +0 -21
- data/lib/authem/controller_support.rb +0 -51
- data/lib/authem/sorcery_user.rb +0 -24
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 67a264031f3e96725f18dc7e77c427ebd4708716
|
4
|
+
data.tar.gz: 09c0291510e6909b13ea9c5a3efa473e3054020b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 771d1413927363e3c2426d0cee8d28fabf08389ce16a4aa6eefd06c6da6499177213f271fbc5ef2c111e48a2433c9695c1747877a6bb1b32e15aca99e4f491b7
|
7
|
+
data.tar.gz: 6e3adc3d207f865439888550c6aeb1d8f84f202e6a2ec31f132a23e4a254a218d5bd905a148309966e6917426cb89544cc4501c0208a81a955ab6ee7c198cf81
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
authem
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.1.2
|
data/.travis.yml
ADDED
data/Appraisals
ADDED
@@ -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
|
data/CHANGELOG.md
ADDED
@@ -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
data/README.markdown
CHANGED
@@ -6,7 +6,7 @@ Authem is an email-based authentication library for ruby web apps.
|
|
6
6
|
|
7
7
|
## Compatibility
|
8
8
|
|
9
|
-
Authem
|
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 "
|
2
|
-
|
3
|
-
spec.pattern = "spec/**/*_spec.rb"
|
4
|
-
end
|
1
|
+
require "bundler/setup"
|
2
|
+
require "bundler/gem_tasks"
|
5
3
|
|
6
|
-
|
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
|
data/authem.gemspec
ADDED
@@ -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
|
data/lib/authem.rb
CHANGED
@@ -1,12 +1,6 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
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
|
-
|
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
|
data/lib/authem/role.rb
ADDED
@@ -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
|