cognito_rails 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|