devise_api_auth 0.0.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/lib/devise_api_auth/api_token_authentication.rb +71 -0
- data/lib/devise_api_auth/config.rb +35 -0
- data/lib/devise_api_auth/date_csrf.rb +39 -0
- data/lib/devise_api_auth/token_utils.rb +175 -0
- data/lib/devise_api_auth.rb +13 -0
- metadata +77 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8d64c0bde2b6418360e798e9f84644c4cce2f202
|
4
|
+
data.tar.gz: 12a5b6bc4a801786d567d1c05f6535f89ec997f8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3f46f95e88fbf63cacc0b73f48637f10d3ca5749cdb76ec2b5ffa7b22d01fe7d494ef5b4e80c9c08c8ee144582dee135b862fb852f84c8157eadb64300b357a9
|
7
|
+
data.tar.gz: 1643aa19a2f8a12787a57c2297614c8d3926676208d940a21048460c2eb069561987b6ff59952308c141adfc43752da6848a1d0eda04f02b2608d636b02e8542
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'devise'
|
2
|
+
module ApiTokenAuthentication
|
3
|
+
|
4
|
+
class ApiTokenAuthenticatable < Devise::Strategies::Authenticatable
|
5
|
+
def valid?
|
6
|
+
_token_matcher.valid?
|
7
|
+
end
|
8
|
+
|
9
|
+
def authenticate!
|
10
|
+
if _token_matcher.match! && !_user.nil?
|
11
|
+
success!(_user)
|
12
|
+
else
|
13
|
+
fail('Failed api token authentication')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def _validate_user
|
19
|
+
return true unless _config_options.include?(:model_auth_validation_method)
|
20
|
+
_user.send(_config_options[:model_auth_validation_method].to_sym)
|
21
|
+
end
|
22
|
+
|
23
|
+
def _token_utils
|
24
|
+
DeviseApiAuth::TokenUtils::SecureCredentials.new(headers: request.headers)
|
25
|
+
end
|
26
|
+
|
27
|
+
def _requested_time
|
28
|
+
_secure_credentials[:date]
|
29
|
+
end
|
30
|
+
|
31
|
+
def _user_token
|
32
|
+
_secure_credentials[:token]
|
33
|
+
end
|
34
|
+
|
35
|
+
def _user_id
|
36
|
+
_secure_credentials[:id]
|
37
|
+
end
|
38
|
+
|
39
|
+
def _secure_credentials
|
40
|
+
@_secure_credentials ||= _token_utils.get_header_credentials
|
41
|
+
end
|
42
|
+
|
43
|
+
def _token_matcher
|
44
|
+
@_token_matcher ||= DeviseApiAuth::TokenUtils::DateTokenMatcher.new(strings: [_requested_time, DeviseApiAuth::TokenUtils.generate_user_token(_user_id)], date: _requested_time, token: _user_token)
|
45
|
+
end
|
46
|
+
|
47
|
+
def _user
|
48
|
+
@_user ||= begin
|
49
|
+
return if _user_id.blank?
|
50
|
+
if _config_options[:model_find_method].nil?
|
51
|
+
mapping.to.where(_config_options[:model_id_attribute].to_sym => _user_id).first
|
52
|
+
else
|
53
|
+
mapping.to.send(_config_options[:model_find_method].to_sym, _user_id)
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def _config_options
|
60
|
+
DeviseApiAuth::Config.options
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
Warden::Strategies.add(:api_token_authentication, ApiTokenAuthentication::ApiTokenAuthenticatable)
|
69
|
+
Devise.add_module :api_token_authentication, strategy: true
|
70
|
+
|
71
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module DeviseApiAuth
|
2
|
+
module Config
|
3
|
+
extend self
|
4
|
+
attr_reader :options
|
5
|
+
def configure
|
6
|
+
yield self.options
|
7
|
+
end
|
8
|
+
def options
|
9
|
+
@options ||= {
|
10
|
+
header_iv: 'x-app-iv',
|
11
|
+
header_credentials: 'x-app-credentials',
|
12
|
+
param_iv: '_iv',
|
13
|
+
param_credentials: '_credentials',
|
14
|
+
model_id_attribute: 'id'
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
# DeviseApiAuth::Config.configure do |options|
|
22
|
+
# options[:app_token] # mobile app has this as well and it is used for csrf
|
23
|
+
# options[:encryption_key] # mobile app uses this to encrypt and decrypt
|
24
|
+
# options[:user_salt] # rails uses this to create a user token from a user
|
25
|
+
|
26
|
+
# # optional
|
27
|
+
# options[:header_iv] # name of the iv field in the header
|
28
|
+
# options[:header_credentials] # name of credentials field in header
|
29
|
+
# options[:param_iv] # name of the iv field in the parameters
|
30
|
+
# options[:param_credentials] # name of credentials field in parameters
|
31
|
+
# options[:model_id_attribute] # the field that is used to search for the user model in authentication
|
32
|
+
# options[:model_find_method] # passes the user identifier to a class method on the model to act as initialization and verification. Return an instance of your user model on succes and nil upon failure.
|
33
|
+
# options[:model_auth_validation_method] # name of method to run on the model during authentication
|
34
|
+
|
35
|
+
# end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module DeviseApiAuth
|
2
|
+
module DateCSRF
|
3
|
+
|
4
|
+
def verified_request?
|
5
|
+
super || app_token_verified?
|
6
|
+
end
|
7
|
+
|
8
|
+
def app_token_verified?
|
9
|
+
verify_app_token
|
10
|
+
end
|
11
|
+
|
12
|
+
def verify_app_token
|
13
|
+
TokenUtils::DateTokenMatcher.new(strings: [_requested_time,DeviseApiAuth::Config.options[:app_token]],date: _requested_time, token: _date_token).match!
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def _token_utils
|
18
|
+
@_token_utils ||= TokenUtils::SecureCredentials.new(headers: request.headers, params: params)
|
19
|
+
end
|
20
|
+
|
21
|
+
def _requested_time
|
22
|
+
_secure_header_credentials[:date]
|
23
|
+
end
|
24
|
+
|
25
|
+
def _date_token
|
26
|
+
_secure_param_credentials[:token]
|
27
|
+
end
|
28
|
+
|
29
|
+
def _secure_header_credentials
|
30
|
+
_token_utils.get_header_credentials
|
31
|
+
end
|
32
|
+
|
33
|
+
def _secure_param_credentials
|
34
|
+
_token_utils.get_param_credentials
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
module DeviseApiAuth
|
2
|
+
module TokenUtils
|
3
|
+
extend self
|
4
|
+
require 'openssl'
|
5
|
+
|
6
|
+
def generate_user_token(string, digester: Digest::SHA2)
|
7
|
+
return if !string.is_a?(String) || string.blank?
|
8
|
+
digester.hexdigest(string+DeviseApiAuth::Config.options[:user_salt])
|
9
|
+
end
|
10
|
+
|
11
|
+
class SecureCredentials
|
12
|
+
def initialize(headers: nil, params: nil)
|
13
|
+
@headers = headers
|
14
|
+
@params = params
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_header_credentials
|
18
|
+
decrypt(encrypted_header_credentials, iv: header_iv).symbolize_keys
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_param_credentials
|
22
|
+
decrypt(encrypted_param_credentials, iv: params_iv).symbolize_keys
|
23
|
+
end
|
24
|
+
|
25
|
+
def header_iv
|
26
|
+
@headers&.fetch(config_options[:header_iv],nil)
|
27
|
+
end
|
28
|
+
|
29
|
+
def params_iv
|
30
|
+
@params&.fetch(config_options[:param_iv],nil)
|
31
|
+
end
|
32
|
+
|
33
|
+
def encrypted_header_credentials
|
34
|
+
@headers&.fetch(config_options[:header_credentials],nil)
|
35
|
+
end
|
36
|
+
|
37
|
+
def encrypted_param_credentials
|
38
|
+
@params&.fetch(config_options[:param_credentials],nil)
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
def decrypt(encrypted, iv:)
|
43
|
+
decrypted = DeviseApiAuth::TokenUtils::Decryptor.new(iv).decrypt(encrypted)
|
44
|
+
JSON.parse decrypted
|
45
|
+
rescue StandardError => e
|
46
|
+
{}
|
47
|
+
end
|
48
|
+
|
49
|
+
def config_options
|
50
|
+
DeviseApiAuth::Config.options
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
private
|
57
|
+
class Crypt
|
58
|
+
# Subclasses must provide block to initializer to set encrypt or decrypt on the cipher
|
59
|
+
|
60
|
+
# Accepts iv as hex string
|
61
|
+
def initialize(iv)
|
62
|
+
@failed = false
|
63
|
+
@cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
|
64
|
+
yield @cipher # setting encrypt or decrypt to the cipher
|
65
|
+
@cipher.key = DeviseApiAuth::Config.options[:encryption_key]
|
66
|
+
@cipher.iv = [iv].pack('H*')
|
67
|
+
rescue StandardError => e
|
68
|
+
@failed = true
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
def crypt(to_crypt)
|
73
|
+
return if @failed
|
74
|
+
crypted = @cipher.update(to_crypt)
|
75
|
+
crypted << @cipher.final
|
76
|
+
rescue StandardError => e
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
public
|
82
|
+
class Decryptor < Crypt
|
83
|
+
def initialize(iv)
|
84
|
+
super {|cipher| cipher.decrypt}
|
85
|
+
end
|
86
|
+
|
87
|
+
# Accepts an encrypted hex string and returns plain text
|
88
|
+
def decrypt(encrypted)
|
89
|
+
return if encrypted.blank?
|
90
|
+
crypt([encrypted].pack('H*'))
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
class Encryptor < Crypt
|
96
|
+
def initialize(iv)
|
97
|
+
super{|cipher| cipher.encrypt}
|
98
|
+
end
|
99
|
+
|
100
|
+
# Accepts string and returns encrypted hex string
|
101
|
+
def encrypt(encrypted)
|
102
|
+
crypt(encrypted)&.unpack('H*')&.first
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
class SimpleTokenMatcher
|
109
|
+
def initialize(strings: nil, token: nil, digester: nil)
|
110
|
+
@digester = digester || Digest::SHA2
|
111
|
+
@string = strings
|
112
|
+
if @string.is_a?(Array)
|
113
|
+
@string = @string.include?(nil) ? nil : strings.join
|
114
|
+
end
|
115
|
+
@token = token
|
116
|
+
@_token = token_for @string
|
117
|
+
end
|
118
|
+
def match!
|
119
|
+
valid? && @_token == @token
|
120
|
+
end
|
121
|
+
def token_for(string)
|
122
|
+
return unless string.is_a?(String) && !string.blank?
|
123
|
+
@digester.hexdigest(string)
|
124
|
+
end
|
125
|
+
def valid?
|
126
|
+
!@_token.blank?
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
class DateTokenMatcher < SimpleTokenMatcher
|
131
|
+
# adds date and options hash that contains a verify_date bool and cutoff which is the amount of seconds to allow the date to be within
|
132
|
+
def initialize(strings: nil, token: nil, digester: nil, date: nil, **options)
|
133
|
+
@options = {verify_date: true, cutoff: 60 * 10}.merge(options)
|
134
|
+
self.date = date
|
135
|
+
super(strings: strings, token: token, digester: digester)
|
136
|
+
end
|
137
|
+
def match!
|
138
|
+
return false if verify_date? && !verify_date!
|
139
|
+
super
|
140
|
+
end
|
141
|
+
def valid?
|
142
|
+
return super if !verify_date?
|
143
|
+
verify_date! && super
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
def date=(d)
|
148
|
+
if d.is_a? String
|
149
|
+
@date = DateTime.parse(d)
|
150
|
+
elsif d.is_a? DateTime
|
151
|
+
@date = d
|
152
|
+
end
|
153
|
+
rescue ArgumentError
|
154
|
+
@date = nil
|
155
|
+
end
|
156
|
+
def verify_date!
|
157
|
+
return false if @date.nil?
|
158
|
+
a = cutoff.seconds.ago.to_datetime
|
159
|
+
b = cutoff.seconds.since.to_datetime
|
160
|
+
@date.between?(a,b)
|
161
|
+
end
|
162
|
+
def verify_date?
|
163
|
+
!!(@options[:verify_date])
|
164
|
+
end
|
165
|
+
def cutoff
|
166
|
+
[@options[:cutoff].to_i, 30].max
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
|
175
|
+
end
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: devise_api_auth
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jose Castellanos
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-11-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: devise
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: This gem provides functionality to authenticate mobile apps in addition
|
42
|
+
to web apps using devise.
|
43
|
+
email: nextgenappsllc@gmail.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- lib/devise_api_auth.rb
|
49
|
+
- lib/devise_api_auth/api_token_authentication.rb
|
50
|
+
- lib/devise_api_auth/config.rb
|
51
|
+
- lib/devise_api_auth/date_csrf.rb
|
52
|
+
- lib/devise_api_auth/token_utils.rb
|
53
|
+
homepage: http://rubygems.org/gems/devise_api_auth
|
54
|
+
licenses:
|
55
|
+
- MIT
|
56
|
+
metadata: {}
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options: []
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - "~>"
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '2.3'
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
requirements: []
|
72
|
+
rubyforge_project:
|
73
|
+
rubygems_version: 2.5.1
|
74
|
+
signing_key:
|
75
|
+
specification_version: 4
|
76
|
+
summary: Devise API Authentication
|
77
|
+
test_files: []
|