cognito_rails 0.1.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.
- checksums.yaml +7 -0
- data/lib/cognito_rails/config.rb +65 -0
- data/lib/cognito_rails/controller.rb +37 -0
- data/lib/cognito_rails/jwt.rb +38 -0
- data/lib/cognito_rails/model.rb +93 -0
- data/lib/cognito_rails/user.rb +214 -0
- data/lib/cognito_rails/version.rb +6 -0
- data/lib/cognito_rails.rb +43 -0
- data/spec/cognito_rails/controller_spec.rb +53 -0
- data/spec/cognito_rails/jwt_spec.rb +40 -0
- data/spec/cognito_rails/user_spec.rb +165 -0
- data/spec/factories/user.rb +7 -0
- data/spec/spec_helper.rb +36 -0
- data/spec/support/cognito_helpers.rb +43 -0
- data/spec/support/match_structure.rb +183 -0
- data/spec/support/schema.rb +51 -0
- metadata +141 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 48fe70f5578d90db88e1a7546f12a16a3b24941bf20a2e8bb4a37b24b3678adc
|
4
|
+
data.tar.gz: 5463df9f063b8b986ca1ef49d0f9a8efce22862854e7e74cb4aee77a8f101fea
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: '058bb0a9820ee032ca2d56f7c3954c5acb434543d599ddf3aedb029e2c8b6e06b9d93445a9989248c0a9223cc5038a91cd680313d7d1735837c18826a3008999'
|
7
|
+
data.tar.gz: 5fa75f4e6109b4144a33759340672eee19253d33ba8e9c167b0c4187b85f7b736d8129cfd22221188119c793f8250d550b36ba49b55742d47133ec9fd7d4ec14
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module CognitoRails
|
6
|
+
class Config
|
7
|
+
class << self
|
8
|
+
# @raise [RuntimeError] if not set
|
9
|
+
# @return [String] AWS access key id
|
10
|
+
def aws_access_key_id
|
11
|
+
# @type [String,nil]
|
12
|
+
@aws_access_key_id || (raise 'Missing config aws_access_key_id')
|
13
|
+
end
|
14
|
+
|
15
|
+
# @!attribute aws_access_key_id [w]
|
16
|
+
# @return [String]
|
17
|
+
# @!attribute aws_region [w]
|
18
|
+
# @return [String]
|
19
|
+
# @!attribute aws_secret_access_key [w]
|
20
|
+
# @return [String]
|
21
|
+
# @!attribute aws_user_pool_id [w]
|
22
|
+
# @return [String]
|
23
|
+
# @!attribute default_user_class [w]
|
24
|
+
# @return [String,nil]
|
25
|
+
attr_writer :aws_access_key_id, :skip_model_hooks, :aws_region,
|
26
|
+
:aws_secret_access_key, :aws_user_pool_id,
|
27
|
+
:default_user_class
|
28
|
+
|
29
|
+
# @return [Boolean] skip model hooks
|
30
|
+
def skip_model_hooks
|
31
|
+
!!@skip_model_hooks
|
32
|
+
end
|
33
|
+
|
34
|
+
# @!attribute logger [rw]
|
35
|
+
# @return [Logger]
|
36
|
+
# @!attribute cache_adapter [rw]
|
37
|
+
# @return [#fetch,nil]
|
38
|
+
attr_accessor :logger, :cache_adapter
|
39
|
+
|
40
|
+
# @return [String] AWS region
|
41
|
+
# @raise [RuntimeError] if not set
|
42
|
+
def aws_region
|
43
|
+
@aws_region || (raise 'Missing config aws_region')
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return [String] AWS secret access key
|
47
|
+
# @raise [RuntimeError] if not set
|
48
|
+
def aws_secret_access_key
|
49
|
+
@aws_secret_access_key || (raise 'Missing config aws_secret_access_key')
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [String] AWS user pool id
|
53
|
+
# @raise [RuntimeError] if not set
|
54
|
+
def aws_user_pool_id
|
55
|
+
@aws_user_pool_id || (raise 'Missing config aws_user_pool_id')
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [String] default user class
|
59
|
+
# @raise [RuntimeError] if not set
|
60
|
+
def default_user_class
|
61
|
+
@default_user_class || (raise 'Missing config default_user_class')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CognitoRails
|
4
|
+
module Controller
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
# @scope class
|
8
|
+
# @!attribute _cognito_user_class [rw]
|
9
|
+
# @return [String,nil] class name of user model
|
10
|
+
|
11
|
+
included do
|
12
|
+
class_attribute :_cognito_user_class
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [ActiveRecord::Base,nil]
|
16
|
+
def current_user
|
17
|
+
@current_user ||= cognito_user_klass.find_by_cognito(external_cognito_id) if external_cognito_id
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
# @return [#find_by_cognito]
|
23
|
+
def cognito_user_klass
|
24
|
+
@cognito_user_klass ||= (self.class._cognito_user_class || CognitoRails::Config.default_user_class)&.constantize
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [String,nil] cognito user id
|
28
|
+
def external_cognito_id
|
29
|
+
# @type [String,nil]
|
30
|
+
token = request.headers['Authorization']&.split(' ')&.last
|
31
|
+
|
32
|
+
return unless token
|
33
|
+
|
34
|
+
CognitoRails::JWT.decode(token)&.dig(0, 'sub')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'jwt'
|
4
|
+
require 'open-uri'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module CognitoRails
|
8
|
+
class JWT
|
9
|
+
class << self
|
10
|
+
# @param token [String] JWT token
|
11
|
+
# @return [Array<Hash>,nil]
|
12
|
+
def decode(token)
|
13
|
+
aws_idp = with_cache { URI.open(jwks_url).read }
|
14
|
+
jwt_config = JSON.parse(aws_idp, symbolize_names: true)
|
15
|
+
|
16
|
+
::JWT.decode(token, nil, true, { jwks: jwt_config, algorithms: ['RS256'] })
|
17
|
+
rescue ::JWT::ExpiredSignature, ::JWT::VerificationError, ::JWT::DecodeError => e
|
18
|
+
Config.logger&.error e.message
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def jwks_url
|
25
|
+
"https://cognito-idp.#{Config.aws_region}.amazonaws.com/#{Config.aws_user_pool_id}/.well-known/jwks.json"
|
26
|
+
end
|
27
|
+
|
28
|
+
# @param block [Proc]
|
29
|
+
# @yield [String] to be cached
|
30
|
+
# @return [String] cached block
|
31
|
+
def with_cache(&block)
|
32
|
+
return yield unless Config.cache_adapter.respond_to?(:fetch)
|
33
|
+
|
34
|
+
Config.cache_adapter.fetch('aws_idp', expires_in: 4.hours, &block)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_record'
|
4
|
+
|
5
|
+
module CognitoRails
|
6
|
+
# ActiveRecord model extension
|
7
|
+
module Model
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
class_attribute :_cognito_verify_email
|
12
|
+
class_attribute :_cognito_verify_phone
|
13
|
+
class_attribute :_cognito_custom_attributes
|
14
|
+
class_attribute :_cognito_attribute_name
|
15
|
+
self._cognito_custom_attributes = []
|
16
|
+
|
17
|
+
before_create do
|
18
|
+
init_cognito_user unless CognitoRails::Config.skip_model_hooks
|
19
|
+
end
|
20
|
+
|
21
|
+
after_destroy do
|
22
|
+
destroy_cognito_user unless CognitoRails::Config.skip_model_hooks
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [String]
|
27
|
+
def cognito_external_id
|
28
|
+
self[self.class._cognito_attribute_name]
|
29
|
+
end
|
30
|
+
|
31
|
+
# @param value [String]
|
32
|
+
# @return [String]
|
33
|
+
def cognito_external_id=(value)
|
34
|
+
self[self.class._cognito_attribute_name] = value
|
35
|
+
end
|
36
|
+
|
37
|
+
def cognito_user
|
38
|
+
@cognito_user ||= User.find(cognito_external_id, user_class: self.class)
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def init_cognito_user
|
44
|
+
return if cognito_external_id.present?
|
45
|
+
|
46
|
+
attrs = { email: email, user_class: self.class }
|
47
|
+
attrs[:phone] = phone if respond_to?(:phone)
|
48
|
+
attrs[:custom_attributes] = instance_custom_attributes
|
49
|
+
cognito_user = User.new(attrs)
|
50
|
+
cognito_user.save!
|
51
|
+
self.cognito_external_id = cognito_user.id
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [Array<Hash>]
|
55
|
+
def instance_custom_attributes
|
56
|
+
_cognito_custom_attributes.map { |e| { name: e[:name], value: parse_custom_attribute_value(e[:value]) } }
|
57
|
+
end
|
58
|
+
|
59
|
+
def parse_custom_attribute_value(value)
|
60
|
+
if value.is_a? Symbol
|
61
|
+
self[value]
|
62
|
+
else
|
63
|
+
value
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def destroy_cognito_user
|
68
|
+
cognito_user&.destroy!
|
69
|
+
end
|
70
|
+
|
71
|
+
class_methods do
|
72
|
+
# @param name [String] attribute name
|
73
|
+
# @return [ActiveRecord::Base] model class
|
74
|
+
def find_by_cognito(external_id)
|
75
|
+
find_by({ _cognito_attribute_name => external_id })
|
76
|
+
end
|
77
|
+
|
78
|
+
def cognito_verify_email
|
79
|
+
self._cognito_verify_email = true
|
80
|
+
end
|
81
|
+
|
82
|
+
def cognito_verify_phone
|
83
|
+
self._cognito_verify_phone = true
|
84
|
+
end
|
85
|
+
|
86
|
+
# @param name [String] attribute name
|
87
|
+
# @param value [String] attribute name
|
88
|
+
def define_cognito_attribute(name, value)
|
89
|
+
_cognito_custom_attributes << { name: "custom:#{name}", value: value }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_record'
|
4
|
+
require 'active_model/validations'
|
5
|
+
require 'securerandom'
|
6
|
+
require 'aws-sdk-cognitoidentityprovider'
|
7
|
+
|
8
|
+
module CognitoRails
|
9
|
+
# A class to map the cognito user to a model-like object
|
10
|
+
#
|
11
|
+
# @!attribute id [rw]
|
12
|
+
# @return [String]
|
13
|
+
# @!attribute email [rw]
|
14
|
+
# @return [String]
|
15
|
+
# @!attribute password [rw]
|
16
|
+
# @return [String,nil]
|
17
|
+
# @!attribute phone [rw]
|
18
|
+
# @return [String,nil]
|
19
|
+
# @!attribute custom_attributes [rw]
|
20
|
+
# @return [Array<Hash>,nil]
|
21
|
+
# @!attribute user_class [rw]
|
22
|
+
# @return [Class,nil]
|
23
|
+
# rubocop:disable Metrics/ClassLength
|
24
|
+
class User
|
25
|
+
# rubocop:enable Metrics/ClassLength
|
26
|
+
|
27
|
+
include ActiveModel::Validations
|
28
|
+
|
29
|
+
attr_accessor :id, :email, :password, :phone, :custom_attributes, :user_class
|
30
|
+
|
31
|
+
validates :email, presence: true
|
32
|
+
|
33
|
+
# @param attributes [Hash]
|
34
|
+
# @option attributes [String] :email
|
35
|
+
# @option attributes [String, nil] :password
|
36
|
+
# @option attributes [String, nil] :phone
|
37
|
+
# @option attributes [Array<Hash>, nil] :custom_attributes
|
38
|
+
# @option attributes [Class, nil] :user_class
|
39
|
+
def initialize(attributes = {})
|
40
|
+
attributes = attributes.with_indifferent_access
|
41
|
+
self.email = attributes[:email]
|
42
|
+
self.password = SecureRandom.urlsafe_base64 || attributes[:password]
|
43
|
+
self.phone = attributes[:phone]
|
44
|
+
self.user_class = attributes[:user_class] || Config.default_user_class.constantize
|
45
|
+
self.custom_attributes = attributes[:custom_attributes]
|
46
|
+
end
|
47
|
+
|
48
|
+
# @param id [String]
|
49
|
+
# @param user_class [nil,Object]
|
50
|
+
# @return [CognitoRails::User]
|
51
|
+
def self.find(id, user_class = nil)
|
52
|
+
result = cognito_client.admin_get_user(
|
53
|
+
{
|
54
|
+
user_pool_id: CognitoRails::Config.aws_user_pool_id, # required
|
55
|
+
username: id # required
|
56
|
+
}
|
57
|
+
)
|
58
|
+
user = new(user_class: user_class)
|
59
|
+
user.id = result.username
|
60
|
+
user.email = result.user_attributes.find { |attribute| attribute[:name] == 'email' }[:value]
|
61
|
+
user.phone = result.user_attributes.find { |attribute| attribute[:name] == 'phone_number' }&.dig(:value)
|
62
|
+
user
|
63
|
+
end
|
64
|
+
|
65
|
+
# @param attributes [Hash]
|
66
|
+
# @option attributes [String] :email
|
67
|
+
# @option attributes [String] :password
|
68
|
+
# @option attributes [String, nil] :phone
|
69
|
+
# @option attributes [Array<Hash>, nil] :custom_attributes
|
70
|
+
# @option attributes [Class, nil] :user_class
|
71
|
+
# @return [CognitoRails::User]
|
72
|
+
def self.create!(attributes = {})
|
73
|
+
user = new(attributes)
|
74
|
+
user.save!
|
75
|
+
user
|
76
|
+
end
|
77
|
+
|
78
|
+
# @param attributes [Hash]
|
79
|
+
# @option attributes [String] :email
|
80
|
+
# @option attributes [String] :password
|
81
|
+
# @option attributes [String, nil] :phone
|
82
|
+
# @option attributes [Array<Hash>, nil] :custom_attributes
|
83
|
+
# @option attributes [Class, nil] :user_class
|
84
|
+
# @return [CognitoRails::User]
|
85
|
+
def self.create(attributes = {})
|
86
|
+
user = new(attributes)
|
87
|
+
user.save
|
88
|
+
user
|
89
|
+
end
|
90
|
+
|
91
|
+
# @return [Boolean]
|
92
|
+
def new_record?
|
93
|
+
!persisted?
|
94
|
+
end
|
95
|
+
|
96
|
+
# @return [Boolean]
|
97
|
+
def persisted?
|
98
|
+
id.present?
|
99
|
+
end
|
100
|
+
|
101
|
+
# @return [Boolean]
|
102
|
+
# @raise [ActiveRecord::RecordInvalid]
|
103
|
+
def save!
|
104
|
+
save || (raise ActiveRecord::RecordInvalid, self)
|
105
|
+
end
|
106
|
+
|
107
|
+
# @return [Boolean]
|
108
|
+
def save
|
109
|
+
return false unless validate
|
110
|
+
|
111
|
+
if persisted?
|
112
|
+
save_for_update
|
113
|
+
else
|
114
|
+
save_for_create
|
115
|
+
end
|
116
|
+
|
117
|
+
true
|
118
|
+
end
|
119
|
+
|
120
|
+
# @return [Boolean]
|
121
|
+
def destroy
|
122
|
+
return false if new_record?
|
123
|
+
|
124
|
+
cognito_client.admin_delete_user(
|
125
|
+
{
|
126
|
+
user_pool_id: CognitoRails::Config.aws_user_pool_id,
|
127
|
+
username: id
|
128
|
+
}
|
129
|
+
)
|
130
|
+
self.id = nil
|
131
|
+
|
132
|
+
true
|
133
|
+
end
|
134
|
+
|
135
|
+
# @return [Boolean]
|
136
|
+
# @raise [ActiveRecord::RecordInvalid]
|
137
|
+
def destroy!
|
138
|
+
destroy || (raise ActiveRecord::RecordInvalid, self)
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
|
143
|
+
# @return [Aws::CognitoIdentityProvider::Client]
|
144
|
+
def cognito_client
|
145
|
+
self.class.cognito_client
|
146
|
+
end
|
147
|
+
|
148
|
+
# @return [Boolean]
|
149
|
+
def verify_email?
|
150
|
+
user_class._cognito_verify_email
|
151
|
+
end
|
152
|
+
|
153
|
+
# @return [Boolean]
|
154
|
+
def verify_phone?
|
155
|
+
user_class._cognito_verify_phone
|
156
|
+
end
|
157
|
+
|
158
|
+
# @return [Aws::CognitoIdentityProvider::Client]
|
159
|
+
# @raise [RuntimeError]
|
160
|
+
def self.cognito_client
|
161
|
+
raise 'Can\'t create user in test mode' if Rails.env.test?
|
162
|
+
|
163
|
+
@cognito_client ||= Aws::CognitoIdentityProvider::Client.new(
|
164
|
+
access_key_id: CognitoRails::Config.aws_access_key_id,
|
165
|
+
secret_access_key: CognitoRails::Config.aws_secret_access_key,
|
166
|
+
region: CognitoRails::Config.aws_region
|
167
|
+
)
|
168
|
+
end
|
169
|
+
|
170
|
+
# @return [Array<Hash>]
|
171
|
+
def general_user_attributes
|
172
|
+
[
|
173
|
+
*([{ name: 'email', value: email }] if email),
|
174
|
+
*([{ name: 'phone_number', value: phone }] if phone),
|
175
|
+
*custom_attributes
|
176
|
+
]
|
177
|
+
end
|
178
|
+
|
179
|
+
# @return [Array<Hash>]
|
180
|
+
def verify_user_attributes
|
181
|
+
[
|
182
|
+
*([{ name: 'email_verified', value: 'True' }] if verify_email?),
|
183
|
+
*([{ name: 'phone_number_verified', value: 'True' }] if verify_phone?)
|
184
|
+
]
|
185
|
+
end
|
186
|
+
|
187
|
+
def save_for_create
|
188
|
+
resp = cognito_client.admin_create_user(
|
189
|
+
{
|
190
|
+
user_pool_id: CognitoRails::Config.aws_user_pool_id,
|
191
|
+
username: email,
|
192
|
+
temporary_password: password,
|
193
|
+
user_attributes: [
|
194
|
+
*general_user_attributes,
|
195
|
+
*verify_user_attributes
|
196
|
+
]
|
197
|
+
}
|
198
|
+
)
|
199
|
+
self.id = resp.user.attributes.find { |a| a[:name] == 'sub' }[:value]
|
200
|
+
end
|
201
|
+
|
202
|
+
def save_for_update
|
203
|
+
cognito_client.admin_update_user_attributes(
|
204
|
+
{
|
205
|
+
user_pool_id: CognitoRails::Config.aws_user_pool_id,
|
206
|
+
username: id,
|
207
|
+
user_attributes: [
|
208
|
+
*general_user_attributes
|
209
|
+
]
|
210
|
+
}
|
211
|
+
)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/concern'
|
4
|
+
require 'active_support/dependencies/autoload'
|
5
|
+
require 'active_record'
|
6
|
+
require 'action_controller/metal'
|
7
|
+
|
8
|
+
# Provides a set of tools to integrate AWS Cognito in your Rails app
|
9
|
+
module CognitoRails
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
extend ActiveSupport::Autoload
|
12
|
+
|
13
|
+
autoload :Config
|
14
|
+
autoload :Controller
|
15
|
+
autoload :Model
|
16
|
+
autoload :User
|
17
|
+
autoload :JWT
|
18
|
+
|
19
|
+
# @private
|
20
|
+
module ModelInitializer
|
21
|
+
# @param attribute_name [String]
|
22
|
+
# @return [void]
|
23
|
+
def as_cognito_user(attribute_name: 'external_id')
|
24
|
+
send :include, CognitoRails::Model
|
25
|
+
self._cognito_attribute_name = attribute_name
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# @private
|
30
|
+
module ControllerInitializer
|
31
|
+
# @param user_class [Class,nil]
|
32
|
+
# @return [void]
|
33
|
+
def cognito_authentication(user_class: nil)
|
34
|
+
send :include, CognitoRails::Controller
|
35
|
+
self._cognito_user_class = user_class
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# rubocop:disable Lint/SendWithMixinArgument
|
41
|
+
ActiveRecord::Base.send(:extend, CognitoRails::ModelInitializer)
|
42
|
+
ActionController::Metal.send(:extend, CognitoRails::ControllerInitializer)
|
43
|
+
# rubocop:enable Lint/SendWithMixinArgument
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# rubocop:disable Metrics/BlockLength
|
4
|
+
RSpec.describe CognitoRails::Controller, type: :model do
|
5
|
+
# rubocop:enable Metrics/BlockLength
|
6
|
+
include CognitoRails::Helpers
|
7
|
+
|
8
|
+
context 'with an API controller' do
|
9
|
+
class MyApiController < ActionController::API
|
10
|
+
cognito_authentication
|
11
|
+
|
12
|
+
def request
|
13
|
+
@request ||= OpenStruct.new({ headers: { 'Authorization' => 'Bearer aaaaa' } })
|
14
|
+
end
|
15
|
+
end
|
16
|
+
let(:controller) { MyApiController.new }
|
17
|
+
|
18
|
+
it 'returns no user if the bearer is invalid' do
|
19
|
+
expect(CognitoRails::JWT).to receive(:decode).at_least(:once).and_return([{ 'sub' => '123123123' }])
|
20
|
+
expect(controller.current_user).to eq(nil)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'returns a user if the token is correct' do
|
24
|
+
user = User.create!(email: sample_cognito_email, external_id: '123123123')
|
25
|
+
|
26
|
+
expect(CognitoRails::JWT).to receive(:decode).at_least(:once).and_return([{ 'sub' => '123123123' }])
|
27
|
+
expect(controller.current_user).to eq(user)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'with a standard controller' do
|
32
|
+
class MyController < ActionController::Base
|
33
|
+
cognito_authentication user_class: 'Admin'
|
34
|
+
|
35
|
+
def request
|
36
|
+
@request ||= OpenStruct.new({ headers: { 'Authorization' => 'Bearer aaaaa' } })
|
37
|
+
end
|
38
|
+
end
|
39
|
+
let(:controller) { MyController.new }
|
40
|
+
|
41
|
+
it 'returns no user if the bearer is invalid' do
|
42
|
+
expect(CognitoRails::JWT).to receive(:decode).at_least(:once).and_return([{ 'sub' => '123123123' }])
|
43
|
+
expect(controller.current_user).to eq(nil)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'returns a user if the token is correct' do
|
47
|
+
user = Admin.create!(email: sample_cognito_email, phone: sample_cognito_phone, cognito_id: '123123123')
|
48
|
+
|
49
|
+
expect(CognitoRails::JWT).to receive(:decode).at_least(:once).and_return([{ 'sub' => '123123123' }])
|
50
|
+
expect(controller.current_user).to eq(user)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
# rubocop:disable Metrics/BlockLength
|
6
|
+
RSpec.describe CognitoRails::JWT, type: :model do
|
7
|
+
# rubocop:enable Metrics/BlockLength
|
8
|
+
before do
|
9
|
+
allow(URI).to receive(:open).and_return(double(read: jwks))
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'with an invalid jwtk' do
|
13
|
+
let(:jwks) { '{}' }
|
14
|
+
|
15
|
+
it 'decode returns nil' do
|
16
|
+
expect(described_class.decode('aaaa')).to be_nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'with a valid jwtk' do
|
21
|
+
let(:jwk) { JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), 'optional-kid') }
|
22
|
+
let(:jwks) { { keys: [jwk.export] }.to_json }
|
23
|
+
let(:payload) { { 'data' => 'data' } }
|
24
|
+
let(:token) do
|
25
|
+
headers = { kid: jwk.kid }
|
26
|
+
|
27
|
+
JWT.encode(payload, jwk.keypair, 'RS256', headers)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'decodes a token correctly' do
|
31
|
+
expect(described_class.decode(token)[0]).to eq({
|
32
|
+
'data' => 'data'
|
33
|
+
})
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'fails to decode if the token is invalid' do
|
37
|
+
expect(described_class.decode('aaaa')).to be_nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
# rubocop:disable Metrics/BlockLength
|
6
|
+
RSpec.describe CognitoRails::User, type: :model do
|
7
|
+
include CognitoRails::Helpers
|
8
|
+
|
9
|
+
let(:sample_cognito_email) { 'some@mail.com' }
|
10
|
+
let(:sample_cognito_phone) { '123456789' }
|
11
|
+
|
12
|
+
it 'validates email presence' do
|
13
|
+
expect(subject).to have(1).error_on(:email)
|
14
|
+
subject.email = sample_cognito_email
|
15
|
+
expect(subject).to have(0).error_on(:email)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'finds an user by id' do
|
19
|
+
expect(described_class).to receive(:cognito_client).and_return(fake_cognito_client)
|
20
|
+
|
21
|
+
record = described_class.find(sample_cognito_id)
|
22
|
+
expect(record).to be_a(described_class)
|
23
|
+
expect(record.id).to eq(sample_cognito_id)
|
24
|
+
expect(record.email).to eq(sample_cognito_email)
|
25
|
+
expect(record.user_class).to eq(User)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'finds a user with admin class' do
|
29
|
+
expect(described_class).to receive(:cognito_client).and_return(fake_cognito_client)
|
30
|
+
|
31
|
+
record = described_class.find(sample_cognito_id, Admin)
|
32
|
+
expect(record.user_class).to eq(Admin)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'finds a user with default class' do
|
36
|
+
expect(described_class).to receive(:cognito_client).and_return(fake_cognito_client)
|
37
|
+
|
38
|
+
record = described_class.find(sample_cognito_id)
|
39
|
+
expect(record.user_class).to eq(CognitoRails::Config.default_user_class.constantize)
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'persistence' do
|
43
|
+
it 'saves a new user' do
|
44
|
+
expect_any_instance_of(described_class).to receive(:cognito_client).and_return(fake_cognito_client)
|
45
|
+
subject.email = sample_cognito_email
|
46
|
+
subject.save!
|
47
|
+
expect(subject.id).to eq(sample_cognito_id)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'fails save on invalid record' do
|
51
|
+
expect { subject.save! }.to raise_error ActiveRecord::RecordInvalid
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'deletion' do
|
56
|
+
it 'deletes a new user' do
|
57
|
+
allow_any_instance_of(described_class).to receive(:cognito_client).and_return(fake_cognito_client)
|
58
|
+
subject.email = sample_cognito_email
|
59
|
+
subject.save!
|
60
|
+
|
61
|
+
subject.destroy!
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'fails save on invalid record' do
|
65
|
+
expect { subject.destroy! }.to raise_error ActiveRecord::RecordInvalid
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'user' do
|
70
|
+
it 'creates a cognito user once created a new user' do
|
71
|
+
expect_any_instance_of(CognitoRails::User).to receive(:cognito_client).and_return(fake_cognito_client)
|
72
|
+
|
73
|
+
user = User.create!(email: sample_cognito_email)
|
74
|
+
|
75
|
+
expect(user.external_id).to eq(sample_cognito_id)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'destroys the cognito user once destroyed the user' do
|
79
|
+
expect(CognitoRails::User).to receive(:cognito_client).at_least(:once).and_return(fake_cognito_client)
|
80
|
+
|
81
|
+
user = User.create!(email: sample_cognito_email)
|
82
|
+
|
83
|
+
user.destroy!
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'saves custom attributes in cognito' do
|
87
|
+
expect(CognitoRails::User).to receive(:cognito_client).at_least(:once).and_return(fake_cognito_client)
|
88
|
+
|
89
|
+
expect(fake_cognito_client).to receive(:admin_create_user).with(
|
90
|
+
hash_including(
|
91
|
+
user_attributes: array_including(
|
92
|
+
[
|
93
|
+
{
|
94
|
+
name: 'email_verified', value: 'True'
|
95
|
+
},
|
96
|
+
{
|
97
|
+
name: 'email', value: sample_cognito_email
|
98
|
+
},
|
99
|
+
{
|
100
|
+
name: 'custom:role', value: 'user'
|
101
|
+
},
|
102
|
+
{
|
103
|
+
name: 'custom:name', value: 'TestName'
|
104
|
+
}
|
105
|
+
]
|
106
|
+
)
|
107
|
+
)
|
108
|
+
)
|
109
|
+
|
110
|
+
User.create!(email: sample_cognito_email, name: 'TestName')
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context 'admin' do
|
115
|
+
before do
|
116
|
+
expect(CognitoRails::User).to receive(:cognito_client).at_least(:once).and_return(fake_cognito_client)
|
117
|
+
end
|
118
|
+
|
119
|
+
it '#find_by_cognito' do
|
120
|
+
admin = Admin.create!(email: sample_cognito_email, phone: '12345678')
|
121
|
+
|
122
|
+
expect(Admin.find_by_cognito(sample_cognito_id)).to eq(admin)
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'creates a cognito user once created a new admin' do
|
126
|
+
admin = Admin.create!(email: sample_cognito_email, phone: '12345678')
|
127
|
+
|
128
|
+
expect(admin.cognito_external_id).to eq(sample_cognito_id)
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'destroys the cognito user once destroyed the admin' do
|
132
|
+
admin = Admin.create!(email: sample_cognito_email, phone: '12345678')
|
133
|
+
|
134
|
+
admin.destroy!
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'saves custom attributes in cognito' do
|
138
|
+
expect(fake_cognito_client).to receive(:admin_create_user).with(
|
139
|
+
hash_including(
|
140
|
+
user_attributes: array_including(
|
141
|
+
[
|
142
|
+
{
|
143
|
+
name: 'phone_number_verified', value: 'True'
|
144
|
+
},
|
145
|
+
{
|
146
|
+
name: 'email_verified', value: 'True'
|
147
|
+
},
|
148
|
+
{
|
149
|
+
name: 'phone_number', value: '12345678'
|
150
|
+
},
|
151
|
+
{
|
152
|
+
name: 'email', value: sample_cognito_email
|
153
|
+
},
|
154
|
+
{
|
155
|
+
name: 'custom:role', value: 'admin'
|
156
|
+
}
|
157
|
+
]
|
158
|
+
)
|
159
|
+
)
|
160
|
+
)
|
161
|
+
|
162
|
+
Admin.create!(email: sample_cognito_email, phone: '12345678')
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'rails'
|
3
|
+
require 'rspec'
|
4
|
+
require 'active_record'
|
5
|
+
require 'action_controller'
|
6
|
+
require 'cognito_rails'
|
7
|
+
require 'factory_bot_rails'
|
8
|
+
require 'rspec/collection_matchers'
|
9
|
+
require 'factories/user'
|
10
|
+
|
11
|
+
I18n.enforce_available_locales = false
|
12
|
+
RSpec::Expectations.configuration.warn_about_potential_false_positives = false
|
13
|
+
|
14
|
+
Dir[File.expand_path('../support/*.rb', __FILE__)].each { |f| require f }
|
15
|
+
|
16
|
+
CognitoRails::Config.aws_access_key_id = 'access_key_id'
|
17
|
+
CognitoRails::Config.aws_region = 'region'
|
18
|
+
CognitoRails::Config.aws_secret_access_key = 'secret_access_key'
|
19
|
+
CognitoRails::Config.aws_user_pool_id = 'user_pool_id'
|
20
|
+
CognitoRails::Config.default_user_class = 'User'
|
21
|
+
|
22
|
+
RSpec.configure do |config|
|
23
|
+
|
24
|
+
config.include FactoryBot::Syntax::Methods
|
25
|
+
|
26
|
+
config.before(:suite) do
|
27
|
+
Schema.create
|
28
|
+
end
|
29
|
+
|
30
|
+
config.around(:each) do |example|
|
31
|
+
ActiveRecord::Base.transaction do
|
32
|
+
example.run
|
33
|
+
raise ActiveRecord::Rollback
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module CognitoRails::Helpers
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
let(:sample_cognito_id) { SecureRandom.uuid }
|
6
|
+
let(:sample_cognito_email) { 'some@mail.com' }
|
7
|
+
let(:sample_cognito_phone) { '123456789' }
|
8
|
+
let(:fake_cognito_client) do
|
9
|
+
client = double
|
10
|
+
allow(client).to receive(:admin_create_user) do |params|
|
11
|
+
expect(params).to match_structure(
|
12
|
+
user_pool_id: one_of(String, nil),
|
13
|
+
username: String,
|
14
|
+
temporary_password: String,
|
15
|
+
user_attributes: a_list_of(name: String, value: one_of(String, nil))
|
16
|
+
)
|
17
|
+
OpenStruct.new(user: OpenStruct.new(attributes: [{ name: 'sub', value: sample_cognito_id }]))
|
18
|
+
end
|
19
|
+
|
20
|
+
allow(client).to receive(:admin_delete_user) do |params|
|
21
|
+
expect(params).to match_structure(
|
22
|
+
user_pool_id: one_of(String, nil),
|
23
|
+
username: String
|
24
|
+
)
|
25
|
+
OpenStruct.new
|
26
|
+
end
|
27
|
+
allow(client).to receive(:admin_get_user).and_return(
|
28
|
+
OpenStruct.new(
|
29
|
+
{
|
30
|
+
username: sample_cognito_id,
|
31
|
+
user_attributes: [
|
32
|
+
{ name: 'sub', value: sample_cognito_id },
|
33
|
+
{ name: 'email', value: sample_cognito_email },
|
34
|
+
{ name: 'phone', value: sample_cognito_phone },
|
35
|
+
{ name: 'custom:name', value: "TestName" }
|
36
|
+
]
|
37
|
+
}
|
38
|
+
)
|
39
|
+
)
|
40
|
+
client
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pp'
|
4
|
+
|
5
|
+
module Structure
|
6
|
+
module Type
|
7
|
+
class Error < ::StandardError; end
|
8
|
+
class SizeError < Error; end
|
9
|
+
class MatchError < Error; end
|
10
|
+
|
11
|
+
class Single
|
12
|
+
attr_reader :classes
|
13
|
+
def initialize(classes)
|
14
|
+
@classes = classes
|
15
|
+
end
|
16
|
+
|
17
|
+
def class?
|
18
|
+
@classes.all? { |c| c.is_a?(Class) || c.is_a?(Module) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def matches?(json)
|
22
|
+
result = classes.any? do |s|
|
23
|
+
yield s, json
|
24
|
+
rescue Structure::Type::Error
|
25
|
+
false
|
26
|
+
end
|
27
|
+
raise MatchError, "#{json}\n is not one of #{inspect}" unless result
|
28
|
+
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
def inspect
|
33
|
+
"one_of(#{@classes})"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Array < Single
|
38
|
+
def initialize(classes)
|
39
|
+
super(classes)
|
40
|
+
@max = 999_999
|
41
|
+
@min = 0
|
42
|
+
end
|
43
|
+
|
44
|
+
def between(min, max)
|
45
|
+
@min = min
|
46
|
+
@max = max
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def at_least(number)
|
51
|
+
between(number, Float::INFINITY)
|
52
|
+
end
|
53
|
+
|
54
|
+
def with(number)
|
55
|
+
@number = number
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
def elements
|
60
|
+
@min = @number
|
61
|
+
@max = @number
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
def items
|
66
|
+
elements
|
67
|
+
end
|
68
|
+
|
69
|
+
def elements_at_most
|
70
|
+
raise 'Wrong use of at_most' unless @number
|
71
|
+
|
72
|
+
@max = @number
|
73
|
+
@number = nil
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
def elements_at_least
|
78
|
+
raise 'Wrong use of at_least' unless @number
|
79
|
+
|
80
|
+
@min = @number
|
81
|
+
@number = nil
|
82
|
+
self
|
83
|
+
end
|
84
|
+
|
85
|
+
def matches?(json)
|
86
|
+
raise SizeError, "Size Error: #{inspect} size (#{json.size}) is not between #{@min} and #{@max}." unless json.size.between?(@min, @max)
|
87
|
+
|
88
|
+
json.all? { |j| classes.any? { |s| yield s, j } }
|
89
|
+
end
|
90
|
+
|
91
|
+
def inspect
|
92
|
+
if class?
|
93
|
+
"a_list_of(#{@classes.inspect})"
|
94
|
+
else
|
95
|
+
"a_list_of(\n#{@classes.pretty_inspect})"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
module Methods
|
101
|
+
def a_list_of(*class_list)
|
102
|
+
Structure::Type::Array.new(class_list)
|
103
|
+
end
|
104
|
+
|
105
|
+
def one_of(*class_list)
|
106
|
+
Structure::Type::Single.new(class_list)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
include Structure::Type::Methods
|
113
|
+
|
114
|
+
RSpec::Matchers.define :match_structure do |structure|
|
115
|
+
match do |json|
|
116
|
+
@key = 'root'
|
117
|
+
begin
|
118
|
+
explore_structure(structure, json)
|
119
|
+
rescue Structure::Type::SizeError => e
|
120
|
+
@message = e.message
|
121
|
+
false
|
122
|
+
rescue Structure::Type::MatchError => e
|
123
|
+
@message = e.message
|
124
|
+
false
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def size_fail(structure, json)
|
129
|
+
raise Structure::Type::SizeError,
|
130
|
+
"Wrong size at #{@key}: #{json.size} != #{structure.size}"
|
131
|
+
end
|
132
|
+
|
133
|
+
def structure_fail(structure, json)
|
134
|
+
raise Structure::Type::MatchError,
|
135
|
+
"Structure:\n#{structure.pretty_inspect}\nGiven:\n#{json.pretty_inspect}"
|
136
|
+
end
|
137
|
+
|
138
|
+
def explore_structure(struc, json)
|
139
|
+
# example: 1 ~= Integer
|
140
|
+
if struc.is_a? Class
|
141
|
+
structure_fail(struc, json) unless json.is_a?(struc)
|
142
|
+
# example: "foobar" ~= /f[o]+bar/
|
143
|
+
elsif struc.is_a?(Regexp) && json.is_a?(String)
|
144
|
+
structure_fail(struc, json) unless json.match?(struc)
|
145
|
+
# example: [1, 2, 3] ~= a_list_of(Integer)
|
146
|
+
elsif struc.is_a? Structure::Type::Array
|
147
|
+
struc.matches?(json) { |j, s| explore_structure(j, s) }
|
148
|
+
# example: 1 ~= one_of(Integer, String)
|
149
|
+
elsif struc.is_a? Structure::Type::Single
|
150
|
+
struc.matches?(json) { |j, s| explore_structure(j, s) }
|
151
|
+
# example: {a: b, c: d} ~= {a: Integer, b: String}
|
152
|
+
elsif json.is_a?(Hash)
|
153
|
+
structure_fail(struc, json) unless struc.is_a? Hash
|
154
|
+
struc = struc.with_indifferent_access
|
155
|
+
json = json.with_indifferent_access
|
156
|
+
struc.all? do |k, v|
|
157
|
+
@key = k
|
158
|
+
structure_fail(struc, json) unless json.key?(k)
|
159
|
+
explore_structure(v, json[k])
|
160
|
+
end
|
161
|
+
# example: [1, 2, 3] ~= [1, 2, 3]
|
162
|
+
elsif json.is_a?(Array)
|
163
|
+
structure_fail(struc, json) unless struc.is_a? Array
|
164
|
+
size_fail(struc, json) if struc.size != json.size
|
165
|
+
return struc.zip(json).all? do |s, j|
|
166
|
+
@key = s
|
167
|
+
explore_structure(s, j)
|
168
|
+
end
|
169
|
+
# example: 3 ~= 3
|
170
|
+
else
|
171
|
+
structure_fail(struc, json) unless json == struc
|
172
|
+
end
|
173
|
+
true
|
174
|
+
end
|
175
|
+
|
176
|
+
failure_message do |json|
|
177
|
+
"#{json.pretty_inspect}\ndoes not match structure\n#{structure.pretty_inspect}\n\n#{@message}"
|
178
|
+
end
|
179
|
+
|
180
|
+
failure_message_when_negated do |json|
|
181
|
+
"#{json.pretty_inspect}\nis matching structure\n#{structure.pretty_inspect}"
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
ActiveRecord::Base.establish_connection(
|
4
|
+
adapter: 'sqlite3',
|
5
|
+
database: ':memory:'
|
6
|
+
)
|
7
|
+
|
8
|
+
class User < ActiveRecord::Base
|
9
|
+
validates :email, presence: true
|
10
|
+
validates :email, uniqueness: true
|
11
|
+
|
12
|
+
as_cognito_user
|
13
|
+
cognito_verify_email
|
14
|
+
define_cognito_attribute 'role', 'user'
|
15
|
+
define_cognito_attribute 'name', :name
|
16
|
+
end
|
17
|
+
|
18
|
+
class Admin < ActiveRecord::Base
|
19
|
+
validates :email, presence: true
|
20
|
+
validates :email, uniqueness: true
|
21
|
+
validates :phone, presence: true
|
22
|
+
validates :phone, uniqueness: true
|
23
|
+
|
24
|
+
as_cognito_user attribute_name: 'cognito_id'
|
25
|
+
cognito_verify_email
|
26
|
+
cognito_verify_phone
|
27
|
+
define_cognito_attribute 'role', 'admin'
|
28
|
+
end
|
29
|
+
|
30
|
+
module Schema
|
31
|
+
def self.create
|
32
|
+
ActiveRecord::Migration.verbose = false
|
33
|
+
|
34
|
+
ActiveRecord::Schema.define do
|
35
|
+
create_table :users, force: true do |t|
|
36
|
+
t.string "email", null: false
|
37
|
+
t.string "name"
|
38
|
+
t.string "external_id", null: false
|
39
|
+
t.timestamps null: false
|
40
|
+
end
|
41
|
+
|
42
|
+
create_table :admins, force: true do |t|
|
43
|
+
t.string "email", null: false
|
44
|
+
t.string "phone", null: false
|
45
|
+
t.string "cognito_id", null: false
|
46
|
+
t.timestamps null: false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
metadata
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cognito_rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mònade
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-03-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '8'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '5'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '8'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: aws-sdk-cognitoidentityprovider
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: jwt
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: rspec
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '3'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '3'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rubocop
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
description: Add Cognito authentication to your Rails API
|
90
|
+
email: team@monade.io
|
91
|
+
executables: []
|
92
|
+
extensions: []
|
93
|
+
extra_rdoc_files: []
|
94
|
+
files:
|
95
|
+
- lib/cognito_rails.rb
|
96
|
+
- lib/cognito_rails/config.rb
|
97
|
+
- lib/cognito_rails/controller.rb
|
98
|
+
- lib/cognito_rails/jwt.rb
|
99
|
+
- lib/cognito_rails/model.rb
|
100
|
+
- lib/cognito_rails/user.rb
|
101
|
+
- lib/cognito_rails/version.rb
|
102
|
+
- spec/cognito_rails/controller_spec.rb
|
103
|
+
- spec/cognito_rails/jwt_spec.rb
|
104
|
+
- spec/cognito_rails/user_spec.rb
|
105
|
+
- spec/factories/user.rb
|
106
|
+
- spec/spec_helper.rb
|
107
|
+
- spec/support/cognito_helpers.rb
|
108
|
+
- spec/support/match_structure.rb
|
109
|
+
- spec/support/schema.rb
|
110
|
+
homepage: https://rubygems.org/gems/cognito_rails
|
111
|
+
licenses:
|
112
|
+
- MIT
|
113
|
+
metadata: {}
|
114
|
+
post_install_message:
|
115
|
+
rdoc_options: []
|
116
|
+
require_paths:
|
117
|
+
- lib
|
118
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: 2.7.0
|
123
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - ">="
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
requirements: []
|
129
|
+
rubygems_version: 3.4.6
|
130
|
+
signing_key:
|
131
|
+
specification_version: 4
|
132
|
+
summary: Add Cognito authentication to your Rails API
|
133
|
+
test_files:
|
134
|
+
- spec/cognito_rails/controller_spec.rb
|
135
|
+
- spec/cognito_rails/jwt_spec.rb
|
136
|
+
- spec/cognito_rails/user_spec.rb
|
137
|
+
- spec/factories/user.rb
|
138
|
+
- spec/spec_helper.rb
|
139
|
+
- spec/support/cognito_helpers.rb
|
140
|
+
- spec/support/match_structure.rb
|
141
|
+
- spec/support/schema.rb
|