stn-simple_token_authentication 1.7.1
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.
- checksums.yaml +7 -0
- data/LICENSE +674 -0
- data/README.md +270 -0
- data/Rakefile +61 -0
- data/doc/README.md +18 -0
- data/lib/simple_token_authentication.rb +58 -0
- data/lib/simple_token_authentication/acts_as_token_authenticatable.rb +49 -0
- data/lib/simple_token_authentication/acts_as_token_authentication_handler.rb +22 -0
- data/lib/simple_token_authentication/adapter.rb +7 -0
- data/lib/simple_token_authentication/adapters/active_record_adapter.rb +14 -0
- data/lib/simple_token_authentication/adapters/mongoid_adapter.rb +14 -0
- data/lib/simple_token_authentication/adapters/rails_adapter.rb +14 -0
- data/lib/simple_token_authentication/adapters/rails_api_adapter.rb +18 -0
- data/lib/simple_token_authentication/configuration.rb +45 -0
- data/lib/simple_token_authentication/entities_manager.rb +10 -0
- data/lib/simple_token_authentication/entity.rb +64 -0
- data/lib/simple_token_authentication/fallback_authentication_handler.rb +11 -0
- data/lib/simple_token_authentication/sign_in_handler.rb +19 -0
- data/lib/simple_token_authentication/token_authentication_handler.rb +149 -0
- data/lib/simple_token_authentication/token_comparator.rb +20 -0
- data/lib/simple_token_authentication/token_generator.rb +9 -0
- data/lib/simple_token_authentication/version.rb +3 -0
- data/lib/tasks/simple_token_authentication_tasks.rake +4 -0
- data/spec/configuration/action_controller_callbacks_options_spec.rb +53 -0
- data/spec/configuration/fallback_to_devise_option_spec.rb +128 -0
- data/spec/configuration/header_names_option_spec.rb +463 -0
- data/spec/configuration/sign_in_token_option_spec.rb +92 -0
- data/spec/lib/simple_token_authentication/acts_as_token_authenticatable_spec.rb +108 -0
- data/spec/lib/simple_token_authentication/acts_as_token_authentication_handler_spec.rb +127 -0
- data/spec/lib/simple_token_authentication/adapter_spec.rb +19 -0
- data/spec/lib/simple_token_authentication/adapters/active_record_adapter_spec.rb +21 -0
- data/spec/lib/simple_token_authentication/adapters/mongoid_adapter_spec.rb +21 -0
- data/spec/lib/simple_token_authentication/adapters/rails_adapter_spec.rb +21 -0
- data/spec/lib/simple_token_authentication/adapters/rails_api_adapter_spec.rb +43 -0
- data/spec/lib/simple_token_authentication/configuration_spec.rb +133 -0
- data/spec/lib/simple_token_authentication/entities_manager_spec.rb +67 -0
- data/spec/lib/simple_token_authentication/entity_spec.rb +190 -0
- data/spec/lib/simple_token_authentication/errors_spec.rb +8 -0
- data/spec/lib/simple_token_authentication/fallback_authentication_handler_spec.rb +24 -0
- data/spec/lib/simple_token_authentication/sign_in_handler_spec.rb +43 -0
- data/spec/lib/simple_token_authentication/token_authentication_handler_spec.rb +351 -0
- data/spec/lib/simple_token_authentication/token_comparator_spec.rb +19 -0
- data/spec/lib/simple_token_authentication/token_generator_spec.rb +19 -0
- data/spec/lib/simple_token_authentication_spec.rb +181 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/dummy_classes_helper.rb +80 -0
- data/spec/support/spec_for_adapter.rb +10 -0
- data/spec/support/spec_for_authentication_handler_interface.rb +8 -0
- data/spec/support/spec_for_configuration_option_interface.rb +28 -0
- data/spec/support/spec_for_entities_manager_interface.rb +8 -0
- data/spec/support/spec_for_sign_in_handler_interface.rb +8 -0
- data/spec/support/spec_for_token_comparator_interface.rb +8 -0
- data/spec/support/spec_for_token_generator_interface.rb +8 -0
- data/spec/support/specs_for_token_authentication_handler_interface.rb +8 -0
- metadata +250 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'active_support/deprecation'
|
2
|
+
require 'simple_token_authentication/token_authentication_handler'
|
3
|
+
|
4
|
+
module SimpleTokenAuthentication
|
5
|
+
module ActsAsTokenAuthenticationHandler
|
6
|
+
|
7
|
+
# This module ensures that no TokenAuthenticationHandler behaviour
|
8
|
+
# is added before the class actually `acts_as_token_authentication_handler_for`
|
9
|
+
# some token authenticatable model.
|
10
|
+
# See https://github.com/gonzalo-bulnes/simple_token_authentication/issues/8#issuecomment-31707201
|
11
|
+
|
12
|
+
def acts_as_token_authentication_handler_for(model, options = {})
|
13
|
+
include SimpleTokenAuthentication::TokenAuthenticationHandler
|
14
|
+
handle_token_authentication_for(model, options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def acts_as_token_authentication_handler
|
18
|
+
::ActiveSupport::Deprecation.warn "`acts_as_token_authentication_handler()` is deprecated and may be removed from future releases, use `acts_as_token_authentication_handler_for(User)` instead.", caller
|
19
|
+
acts_as_token_authentication_handler_for User
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'simple_token_authentication/adapter'
|
3
|
+
|
4
|
+
module SimpleTokenAuthentication
|
5
|
+
module Adapters
|
6
|
+
class ActiveRecordAdapter
|
7
|
+
extend SimpleTokenAuthentication::Adapter
|
8
|
+
|
9
|
+
def self.base_class
|
10
|
+
::ActiveRecord::Base
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'mongoid'
|
2
|
+
require 'simple_token_authentication/adapter'
|
3
|
+
|
4
|
+
module SimpleTokenAuthentication
|
5
|
+
module Adapters
|
6
|
+
class MongoidAdapter
|
7
|
+
extend SimpleTokenAuthentication::Adapter
|
8
|
+
|
9
|
+
def self.base_class
|
10
|
+
::Mongoid::Document
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'action_controller'
|
2
|
+
require 'simple_token_authentication/adapter'
|
3
|
+
|
4
|
+
module SimpleTokenAuthentication
|
5
|
+
module Adapters
|
6
|
+
class RailsAdapter
|
7
|
+
extend SimpleTokenAuthentication::Adapter
|
8
|
+
|
9
|
+
def self.base_class
|
10
|
+
::ActionController::Base
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'action_controller'
|
2
|
+
require 'simple_token_authentication/adapter'
|
3
|
+
|
4
|
+
module SimpleTokenAuthentication
|
5
|
+
module Adapters
|
6
|
+
class RailsAPIAdapter
|
7
|
+
extend SimpleTokenAuthentication::Adapter
|
8
|
+
|
9
|
+
def self.base_class
|
10
|
+
::ActionController::API
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# make the adpater available even if the 'API' acronym is not defined
|
15
|
+
RailsApiAdapter = RailsAPIAdapter
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module SimpleTokenAuthentication
|
2
|
+
module Configuration
|
3
|
+
|
4
|
+
mattr_reader :fallback
|
5
|
+
mattr_accessor :header_names
|
6
|
+
mattr_accessor :sign_in_token
|
7
|
+
mattr_accessor :controller_adapters
|
8
|
+
mattr_accessor :model_adapters
|
9
|
+
mattr_accessor :adapters_dependencies
|
10
|
+
|
11
|
+
# Default configuration
|
12
|
+
@@fallback = :devise
|
13
|
+
@@header_names = {}
|
14
|
+
@@sign_in_token = false
|
15
|
+
@@controller_adapters = ['rails', 'rails_api']
|
16
|
+
@@model_adapters = ['active_record', 'mongoid']
|
17
|
+
@@adapters_dependencies = { 'active_record' => 'ActiveRecord::Base',
|
18
|
+
'mongoid' => 'Mongoid::Document',
|
19
|
+
'rails' => 'ActionController::Base',
|
20
|
+
'rails_api' => 'ActionController::API' }
|
21
|
+
|
22
|
+
# Allow the default configuration to be overwritten from initializers
|
23
|
+
def configure
|
24
|
+
yield self if block_given?
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse_options(options)
|
28
|
+
unless options[:fallback].presence
|
29
|
+
if options[:fallback_to_devise]
|
30
|
+
options[:fallback] = :devise
|
31
|
+
elsif options[:fallback_to_devise] == false
|
32
|
+
if SimpleTokenAuthentication.fallback == :devise
|
33
|
+
options[:fallback] = :none
|
34
|
+
else
|
35
|
+
options[:fallback] = SimpleTokenAuthentication.fallback
|
36
|
+
end
|
37
|
+
else
|
38
|
+
options[:fallback] = SimpleTokenAuthentication.fallback
|
39
|
+
end
|
40
|
+
end
|
41
|
+
options.reject! { |k,v| k == :fallback_to_devise }
|
42
|
+
options
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module SimpleTokenAuthentication
|
2
|
+
class Entity
|
3
|
+
def initialize model, name=nil
|
4
|
+
@model = model
|
5
|
+
@name = name || model.name
|
6
|
+
end
|
7
|
+
|
8
|
+
def model
|
9
|
+
@model
|
10
|
+
end
|
11
|
+
|
12
|
+
def name
|
13
|
+
@name
|
14
|
+
end
|
15
|
+
|
16
|
+
def name_underscore
|
17
|
+
name.underscore
|
18
|
+
end
|
19
|
+
|
20
|
+
# Private: Return the name of the header to watch for the token authentication param
|
21
|
+
def token_header_name
|
22
|
+
if SimpleTokenAuthentication.header_names["#{name_underscore}".to_sym].presence \
|
23
|
+
&& token_header_name = SimpleTokenAuthentication.header_names["#{name_underscore}".to_sym][:authentication_token]
|
24
|
+
token_header_name
|
25
|
+
else
|
26
|
+
"X-#{name}-Token"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Private: Return the name of the header to watch for the email param
|
31
|
+
def identifier_header_name
|
32
|
+
if SimpleTokenAuthentication.header_names["#{name_underscore}".to_sym].presence \
|
33
|
+
&& identifier_header_name = SimpleTokenAuthentication.header_names["#{name_underscore}".to_sym][:email]
|
34
|
+
identifier_header_name
|
35
|
+
else
|
36
|
+
"X-#{name}-Email"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def token_param_name
|
41
|
+
"#{name_underscore}_token".to_sym
|
42
|
+
end
|
43
|
+
|
44
|
+
def identifier_param_name
|
45
|
+
"#{name_underscore}_email".to_sym
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_token_from_params_or_headers controller
|
49
|
+
# if the token is not present among params, get it from headers
|
50
|
+
if token = controller.params[token_param_name].blank? && controller.request.headers[token_header_name]
|
51
|
+
controller.params[token_param_name] = token
|
52
|
+
end
|
53
|
+
controller.params[token_param_name]
|
54
|
+
end
|
55
|
+
|
56
|
+
def get_identifier_from_params_or_headers controller
|
57
|
+
# if the identifier (email) is not present among params, get it from headers
|
58
|
+
if email = controller.params[identifier_param_name].blank? && controller.request.headers[identifier_header_name]
|
59
|
+
controller.params[identifier_param_name] = email
|
60
|
+
end
|
61
|
+
controller.params[identifier_param_name]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module SimpleTokenAuthentication
|
2
|
+
class FallbackAuthenticationHandler
|
3
|
+
# Devise authentication is performed through a controller
|
4
|
+
# which includes Devise::Controllers::Helpers
|
5
|
+
# See http://rdoc.info/github/plataformatec/devise/master/\
|
6
|
+
# Devise/Controllers/Helpers#define_helpers-class_method
|
7
|
+
def authenticate_entity!(controller, entity)
|
8
|
+
controller.send("authenticate_#{entity.name_underscore}!".to_sym)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module SimpleTokenAuthentication
|
2
|
+
class SignInHandler
|
3
|
+
# Devise sign in is performed through a controller
|
4
|
+
# which includes Devise::Controllers::SignInOut
|
5
|
+
def sign_in(controller, record, *args)
|
6
|
+
integrate_with_devise_trackable!(controller)
|
7
|
+
|
8
|
+
controller.send(:sign_in, record, *args)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def integrate_with_devise_trackable!(controller)
|
14
|
+
# Sign in using token should not be tracked by Devise trackable
|
15
|
+
# See https://github.com/plataformatec/devise/issues/953
|
16
|
+
controller.env["devise.skip_trackable"] = true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'devise'
|
3
|
+
|
4
|
+
require 'simple_token_authentication/entities_manager'
|
5
|
+
require 'simple_token_authentication/fallback_authentication_handler'
|
6
|
+
require 'simple_token_authentication/sign_in_handler'
|
7
|
+
require 'simple_token_authentication/token_comparator'
|
8
|
+
|
9
|
+
module SimpleTokenAuthentication
|
10
|
+
module TokenAuthenticationHandler
|
11
|
+
extend ::ActiveSupport::Concern
|
12
|
+
|
13
|
+
included do
|
14
|
+
private_class_method :define_token_authentication_helpers_for
|
15
|
+
private_class_method :set_token_authentication_hooks
|
16
|
+
private_class_method :entities_manager
|
17
|
+
private_class_method :fallback_authentication_handler
|
18
|
+
|
19
|
+
private :authenticate_entity_from_token!
|
20
|
+
private :authenticate_entity_from_fallback!
|
21
|
+
private :token_correct?
|
22
|
+
private :perform_sign_in!
|
23
|
+
private :token_comparator
|
24
|
+
private :sign_in_handler
|
25
|
+
private :find_record_from_identifier
|
26
|
+
private :integrate_with_devise_case_insensitive_keys
|
27
|
+
end
|
28
|
+
|
29
|
+
def authenticate_entity_from_token!(entity)
|
30
|
+
record = find_record_from_identifier(entity)
|
31
|
+
|
32
|
+
if token_correct?(record, entity, token_comparator)
|
33
|
+
perform_sign_in!(record, sign_in_handler)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def authenticate_entity_from_fallback!(entity, fallback_authentication_handler)
|
38
|
+
fallback_authentication_handler.authenticate_entity!(self, entity)
|
39
|
+
end
|
40
|
+
|
41
|
+
def token_correct?(record, entity, token_comparator)
|
42
|
+
record && token_comparator.compare(record.authentication_token,
|
43
|
+
entity.get_token_from_params_or_headers(self))
|
44
|
+
end
|
45
|
+
|
46
|
+
def perform_sign_in!(record, sign_in_handler)
|
47
|
+
# Notice the store option defaults to false, so the record
|
48
|
+
# identifier is not actually stored in the session and a token
|
49
|
+
# is needed for every request. That behaviour can be configured
|
50
|
+
# through the sign_in_token option.
|
51
|
+
sign_in_handler.sign_in self, record, store: SimpleTokenAuthentication.sign_in_token
|
52
|
+
end
|
53
|
+
|
54
|
+
def find_record_from_identifier(entity)
|
55
|
+
email = entity.get_identifier_from_params_or_headers(self).presence
|
56
|
+
|
57
|
+
email = integrate_with_devise_case_insensitive_keys(email)
|
58
|
+
|
59
|
+
# The finder method should be compatible with all the model adapters,
|
60
|
+
# namely ActiveRecord and Mongoid in all their supported versions.
|
61
|
+
record = nil
|
62
|
+
record = email && entity.model.where(email: email).first
|
63
|
+
end
|
64
|
+
|
65
|
+
# Private: Take benefit from Devise case-insensitive keys
|
66
|
+
#
|
67
|
+
# See https://github.com/plataformatec/devise/blob/v3.4.1/lib/generators/templates/devise.rb#L45-L48
|
68
|
+
#
|
69
|
+
# email - the original email String
|
70
|
+
#
|
71
|
+
# Returns an email String which case follows the Devise case-insensitive keys policy
|
72
|
+
def integrate_with_devise_case_insensitive_keys(email)
|
73
|
+
email.downcase! if email && Devise.case_insensitive_keys.include?(:email)
|
74
|
+
email
|
75
|
+
end
|
76
|
+
|
77
|
+
# Private: Get one (always the same) object which behaves as a token comprator
|
78
|
+
def token_comparator
|
79
|
+
@@token_comparator ||= TokenComparator.new
|
80
|
+
end
|
81
|
+
|
82
|
+
# Private: Get one (always the same) object which behaves as a sign in handler
|
83
|
+
def sign_in_handler
|
84
|
+
@@sign_in_handler ||= SignInHandler.new
|
85
|
+
end
|
86
|
+
|
87
|
+
module ClassMethods
|
88
|
+
|
89
|
+
# Provide token authentication handling for a token authenticatable class
|
90
|
+
#
|
91
|
+
# model - the token authenticatable Class
|
92
|
+
#
|
93
|
+
# Returns nothing.
|
94
|
+
def handle_token_authentication_for(model, options = {})
|
95
|
+
name = options[:name] || model.name
|
96
|
+
entity = entities_manager.find_or_create_entity(model, name)
|
97
|
+
options = SimpleTokenAuthentication.parse_options(options)
|
98
|
+
define_token_authentication_helpers_for(entity, fallback_authentication_handler)
|
99
|
+
set_token_authentication_hooks(entity, options)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Private: Get one (always the same) object which behaves as an entities manager
|
103
|
+
def entities_manager
|
104
|
+
if class_variable_defined?(:@@entities_manager)
|
105
|
+
class_variable_get(:@@entities_manager)
|
106
|
+
else
|
107
|
+
class_variable_set(:@@entities_manager, EntitiesManager.new)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Private: Get one (always the same) object which behaves as a fallback authentication handler
|
112
|
+
def fallback_authentication_handler
|
113
|
+
if class_variable_defined?(:@@fallback_authentication_handler)
|
114
|
+
class_variable_get(:@@fallback_authentication_handler)
|
115
|
+
else
|
116
|
+
class_variable_set(:@@fallback_authentication_handler, FallbackAuthenticationHandler.new)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def define_token_authentication_helpers_for(entity, fallback_authentication_handler)
|
121
|
+
|
122
|
+
method_name = "authenticate_#{entity.name_underscore}_from_token"
|
123
|
+
method_name_bang = method_name + '!'
|
124
|
+
|
125
|
+
class_eval do
|
126
|
+
define_method method_name.to_sym do
|
127
|
+
lambda { |_entity| authenticate_entity_from_token!(_entity) }.call(entity)
|
128
|
+
end
|
129
|
+
|
130
|
+
define_method method_name_bang.to_sym do
|
131
|
+
lambda do |_entity|
|
132
|
+
authenticate_entity_from_token!(_entity)
|
133
|
+
authenticate_entity_from_fallback!(_entity, fallback_authentication_handler)
|
134
|
+
end.call(entity)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def set_token_authentication_hooks(entity, options)
|
140
|
+
authenticate_method = unless options[:fallback] == :none
|
141
|
+
:"authenticate_#{entity.name_underscore}_from_token!"
|
142
|
+
else
|
143
|
+
:"authenticate_#{entity.name_underscore}_from_token"
|
144
|
+
end
|
145
|
+
before_filter authenticate_method, options.slice(:only, :except)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'devise'
|
2
|
+
|
3
|
+
module SimpleTokenAuthentication
|
4
|
+
class TokenComparator
|
5
|
+
|
6
|
+
# Compare two String instances
|
7
|
+
#
|
8
|
+
# Important: this method is cryptographically critical and
|
9
|
+
# must be implemented with care when defining new token comparators.
|
10
|
+
#
|
11
|
+
# Returns true if String instances do match, false otherwise
|
12
|
+
def compare(a, b)
|
13
|
+
# Notice how we use Devise.secure_compare to compare tokens
|
14
|
+
# while mitigating timing attacks.
|
15
|
+
# See http://rubydoc.info/github/plataformatec/\
|
16
|
+
# devise/master/Devise#secure_compare-class_method
|
17
|
+
Devise.secure_compare(a, b)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|