devise_api_auth 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|