cognito_rails 1.0.0 → 1.2.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 +4 -4
- data/lib/cognito_rails/config.rb +5 -1
- data/lib/cognito_rails/model.rb +14 -4
- data/lib/cognito_rails/password_generator.rb +35 -0
- data/lib/cognito_rails/user.rb +1 -1
- data/lib/cognito_rails/version.rb +1 -1
- data/lib/cognito_rails.rb +1 -0
- data/spec/cognito_rails/controller_spec.rb +0 -2
- data/spec/cognito_rails/jwt_spec.rb +0 -2
- data/spec/cognito_rails/password_generator_spec.rb +28 -0
- data/spec/cognito_rails/user_spec.rb +71 -21
- data/spec/support/cognito_helpers.rb +1 -1
- data/spec/support/schema.rb +23 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2d8bc1e0dc872a16d7406a72e5028da49b62c80c5297d6c4428ea45efd5293a1
|
4
|
+
data.tar.gz: 71d87358a0e584eba16ada273a582caa7e560b84ba58b06d6b5752b7d1769f9e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c22e85bfa4b48b5f5614314ae5cf2f696342fdf3ca204ca92a9d23107a1097da897dcd23a67d7635f1a2cd83b8ecaacbf4bdf20bfeba785fb0c9a1328fb671e9
|
7
|
+
data.tar.gz: def01df4152f2fa0e0fc3991e1438d5898af2d3485be0824f0d59e83987d65d27268897e90308e353fdc04e7b8658ff88f0e54fd92dad41d263292fed80756d9
|
data/lib/cognito_rails/config.rb
CHANGED
@@ -19,7 +19,7 @@ module CognitoRails
|
|
19
19
|
# @!attribute default_user_class [w]
|
20
20
|
# @return [String,nil]
|
21
21
|
attr_writer :aws_client_credentials, :skip_model_hooks, :aws_region,
|
22
|
-
:aws_user_pool_id, :default_user_class
|
22
|
+
:aws_user_pool_id, :default_user_class, :password_generator
|
23
23
|
|
24
24
|
# @return [Boolean] skip model hooks
|
25
25
|
def skip_model_hooks
|
@@ -49,6 +49,10 @@ module CognitoRails
|
|
49
49
|
def default_user_class
|
50
50
|
@default_user_class || (raise 'Missing config default_user_class')
|
51
51
|
end
|
52
|
+
|
53
|
+
def password_generator
|
54
|
+
@password_generator || CognitoRails::PasswordGenerator.method(:generate)
|
55
|
+
end
|
52
56
|
end
|
53
57
|
end
|
54
58
|
end
|
data/lib/cognito_rails/model.rb
CHANGED
@@ -28,10 +28,13 @@ module CognitoRails
|
|
28
28
|
# @return [Array<ActiveRecord::Base>] all users
|
29
29
|
# @raise [CognitoRails::Error] if failed to fetch users
|
30
30
|
# @raise [ActiveRecord::RecordInvalid] if failed to save user
|
31
|
+
# @yield [user, user_data] yields user and user_data just before saving
|
31
32
|
def sync_from_cognito!
|
32
33
|
response = User.all
|
33
34
|
response.users.map do |user_data|
|
34
|
-
sync_user!(user_data)
|
35
|
+
sync_user!(user_data) do |user|
|
36
|
+
yield user, user_data if block_given?
|
37
|
+
end
|
35
38
|
end
|
36
39
|
end
|
37
40
|
|
@@ -56,6 +59,8 @@ module CognitoRails
|
|
56
59
|
user.phone = User.extract_cognito_attribute(user_data.attributes, :phone_number) if user.respond_to?(:phone)
|
57
60
|
_cognito_resolve_custom_attribute(user, user_data)
|
58
61
|
|
62
|
+
yield user if block_given?
|
63
|
+
|
59
64
|
user.save!
|
60
65
|
user
|
61
66
|
end
|
@@ -92,12 +97,17 @@ module CognitoRails
|
|
92
97
|
def init_cognito_user
|
93
98
|
return if cognito_external_id.present?
|
94
99
|
|
100
|
+
cognito_user = User.new(init_attributes)
|
101
|
+
cognito_user.save!
|
102
|
+
self.cognito_external_id = cognito_user.id
|
103
|
+
end
|
104
|
+
|
105
|
+
def init_attributes
|
95
106
|
attrs = { email: email, user_class: self.class }
|
96
107
|
attrs[:phone] = phone if respond_to?(:phone)
|
108
|
+
attrs[:password] = password if respond_to?(:password)
|
97
109
|
attrs[:custom_attributes] = instance_custom_attributes
|
98
|
-
|
99
|
-
cognito_user.save!
|
100
|
-
self.cognito_external_id = cognito_user.id
|
110
|
+
attrs
|
101
111
|
end
|
102
112
|
|
103
113
|
# @return [Array<Hash>]
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CognitoRails
|
4
|
+
class PasswordGenerator
|
5
|
+
NUMERIC = (0..9).to_a.freeze
|
6
|
+
LOWER_CASE = ('a'..'z').to_a.freeze
|
7
|
+
UPPER_CASE = ('A'..'Z').to_a.freeze
|
8
|
+
SPECIAL = [
|
9
|
+
'^', '$', '*', '.', '[', ']', '{', '}',
|
10
|
+
'(', ')', '?', '"', '!', '@', '#', '%',
|
11
|
+
'&', '/', '\\', ',', '>', '<', "'", ':',
|
12
|
+
';', '|', '_', '~', '`', '=', '+', '-'
|
13
|
+
].freeze
|
14
|
+
|
15
|
+
# Generates a random password given a length range
|
16
|
+
#
|
17
|
+
# @param range [Range]
|
18
|
+
# @return [String]
|
19
|
+
def self.generate(range = 8..16)
|
20
|
+
password_length = rand(range)
|
21
|
+
numeric_count = rand(1..(password_length-3))
|
22
|
+
|
23
|
+
lower_case_count = rand(1..(password_length-(numeric_count+2)))
|
24
|
+
upper_case_count = rand(1..(password_length-(numeric_count + lower_case_count + 1)))
|
25
|
+
special_count = password_length-(numeric_count + lower_case_count + upper_case_count)
|
26
|
+
|
27
|
+
numeric_characters = numeric_count.times.map { NUMERIC.sample }
|
28
|
+
lower_case_characters = lower_case_count.times.map { LOWER_CASE.sample }
|
29
|
+
upper_case_characters = upper_case_count.times.map { UPPER_CASE.sample }
|
30
|
+
special_characters = special_count.times.map { SPECIAL.sample }
|
31
|
+
|
32
|
+
(numeric_characters + lower_case_characters + upper_case_characters + special_characters).shuffle.join
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/cognito_rails/user.rb
CHANGED
@@ -39,7 +39,7 @@ module CognitoRails
|
|
39
39
|
def initialize(attributes = {})
|
40
40
|
attributes = attributes.with_indifferent_access
|
41
41
|
self.email = attributes[:email]
|
42
|
-
self.password =
|
42
|
+
self.password = attributes[:password] || Config.password_generator.call
|
43
43
|
self.phone = attributes[:phone]
|
44
44
|
self.user_class = attributes[:user_class] || Config.default_user_class.constantize
|
45
45
|
self.custom_attributes = attributes[:custom_attributes]
|
data/lib/cognito_rails.rb
CHANGED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe CognitoRails::PasswordGenerator do
|
6
|
+
it 'generates a password' do
|
7
|
+
expect(described_class.generate).to be_a(String)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'generates a password with the correct length' do
|
11
|
+
1000.times do
|
12
|
+
expect(described_class.generate(8..8).length).to eq(8)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'contains at least one letter, one number, one upper case letter, one symbol' do
|
17
|
+
1000.times do
|
18
|
+
password = described_class.generate
|
19
|
+
expect(password).to match(/[a-z]/)
|
20
|
+
expect(password).to match(/[A-Z]/)
|
21
|
+
expect(password).to match(/[0-9]/)
|
22
|
+
include_symbol = CognitoRails::PasswordGenerator::SPECIAL.any? do |symbol|
|
23
|
+
password.include?(symbol)
|
24
|
+
end
|
25
|
+
expect(include_symbol).to be_truthy
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
|
-
# rubocop:disable Metrics/BlockLength
|
6
5
|
RSpec.describe CognitoRails::User, type: :model do
|
7
6
|
include CognitoRails::Helpers
|
8
7
|
|
@@ -83,6 +82,34 @@ RSpec.describe CognitoRails::User, type: :model do
|
|
83
82
|
user.destroy!
|
84
83
|
end
|
85
84
|
|
85
|
+
it 'uses the password generator defined in config' do
|
86
|
+
CognitoRails::Config.password_generator = -> { 'ciao' }
|
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
|
+
temporary_password: 'ciao'
|
92
|
+
)
|
93
|
+
)
|
94
|
+
user = User.new(email: sample_cognito_email)
|
95
|
+
user.save!
|
96
|
+
ensure
|
97
|
+
CognitoRails::Config.password_generator = nil
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'uses the custom password passed as parameter' do
|
101
|
+
expect(CognitoRails::User).to receive(:cognito_client).at_least(:once).and_return(fake_cognito_client)
|
102
|
+
|
103
|
+
expect(fake_cognito_client).to receive(:admin_create_user).with(
|
104
|
+
hash_including(
|
105
|
+
temporary_password: '12345678'
|
106
|
+
)
|
107
|
+
)
|
108
|
+
user = User.new(email: sample_cognito_email)
|
109
|
+
user.password = '12345678'
|
110
|
+
user.save!
|
111
|
+
end
|
112
|
+
|
86
113
|
it 'saves custom attributes in cognito' do
|
87
114
|
expect(CognitoRails::User).to receive(:cognito_client).at_least(:once).and_return(fake_cognito_client)
|
88
115
|
|
@@ -116,27 +143,50 @@ RSpec.describe CognitoRails::User, type: :model do
|
|
116
143
|
expect(CognitoRails::User).to receive(:cognito_client).at_least(:once).and_return(fake_cognito_client)
|
117
144
|
end
|
118
145
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
146
|
+
context '#sync_from_cognito!' do
|
147
|
+
before do
|
148
|
+
expect(fake_cognito_client).to receive(:list_users).and_return(
|
149
|
+
OpenStruct.new(
|
150
|
+
users: [
|
151
|
+
build_cognito_user_data('some@example.com'),
|
152
|
+
build_cognito_user_data('some2@example.com')
|
153
|
+
],
|
154
|
+
pagination_token: nil
|
155
|
+
)
|
127
156
|
)
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
157
|
+
end
|
158
|
+
it 'imports all users correctly' do
|
159
|
+
expect do
|
160
|
+
users = User.sync_from_cognito!
|
161
|
+
|
162
|
+
expect(users).to be_a(Array)
|
163
|
+
expect(users.size).to eq(2)
|
164
|
+
expect(users.first).to be_a(User)
|
165
|
+
end.to change { User.count }.by(2)
|
166
|
+
|
167
|
+
expect(User.pluck(:email)).to match_array(['some@example.com', 'some2@example.com'])
|
168
|
+
expect(User.pluck(:name)).to match_array(['John Doe', 'John Doe'])
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'allows to specify a block with extra changes applied pre-save' do
|
172
|
+
expect do
|
173
|
+
i = 0
|
174
|
+
users = EnrichedUser.sync_from_cognito! do |user, cognito_user|
|
175
|
+
i += 1
|
176
|
+
name = cognito_user.attributes.find { |a| a.name == 'custom:name' }
|
177
|
+
user.first_name = name.value.split(' ').first + i.to_s
|
178
|
+
user.last_name = name.value.split(' ').last
|
179
|
+
end
|
180
|
+
|
181
|
+
expect(users).to be_a(Array)
|
182
|
+
expect(users.size).to eq(2)
|
183
|
+
expect(users.first).to be_a(EnrichedUser)
|
184
|
+
end.to change { EnrichedUser.count }.by(2)
|
185
|
+
|
186
|
+
expect(EnrichedUser.pluck(:email)).to match_array(['some@example.com', 'some2@example.com'])
|
187
|
+
expect(EnrichedUser.order(:id).pluck(:first_name)).to match_array(['John1', 'John2'])
|
188
|
+
expect(EnrichedUser.order(:id).pluck(:last_name)).to match_array(['Doe', 'Doe'])
|
189
|
+
end
|
140
190
|
end
|
141
191
|
|
142
192
|
it '#sync_to_cognito!' do
|
data/spec/support/schema.rb
CHANGED
@@ -13,8 +13,23 @@ class User < ActiveRecord::Base
|
|
13
13
|
cognito_verify_email
|
14
14
|
define_cognito_attribute 'role', 'user'
|
15
15
|
define_cognito_attribute 'name', :name
|
16
|
+
|
17
|
+
attr_accessor :password
|
18
|
+
end
|
19
|
+
|
20
|
+
class EnrichedUser < ActiveRecord::Base
|
21
|
+
validates :email, presence: true
|
22
|
+
validates :email, uniqueness: true
|
23
|
+
validates :first_name, :last_name, presence: true
|
24
|
+
|
25
|
+
as_cognito_user
|
26
|
+
cognito_verify_email
|
27
|
+
define_cognito_attribute 'role', 'user'
|
28
|
+
|
29
|
+
attr_accessor :password
|
16
30
|
end
|
17
31
|
|
32
|
+
|
18
33
|
class Admin < ActiveRecord::Base
|
19
34
|
validates :email, presence: true
|
20
35
|
validates :email, uniqueness: true
|
@@ -39,6 +54,14 @@ module Schema
|
|
39
54
|
t.timestamps null: false
|
40
55
|
end
|
41
56
|
|
57
|
+
create_table :enriched_users, force: true do |t|
|
58
|
+
t.string "email", null: false
|
59
|
+
t.string "first_name", null: false
|
60
|
+
t.string "last_name", null: false
|
61
|
+
t.string "external_id", null: false
|
62
|
+
t.timestamps null: false
|
63
|
+
end
|
64
|
+
|
42
65
|
create_table :admins, force: true do |t|
|
43
66
|
t.string "email", null: false
|
44
67
|
t.string "phone", null: false
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cognito_rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mònade
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-05-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -97,10 +97,12 @@ files:
|
|
97
97
|
- lib/cognito_rails/controller.rb
|
98
98
|
- lib/cognito_rails/jwt.rb
|
99
99
|
- lib/cognito_rails/model.rb
|
100
|
+
- lib/cognito_rails/password_generator.rb
|
100
101
|
- lib/cognito_rails/user.rb
|
101
102
|
- lib/cognito_rails/version.rb
|
102
103
|
- spec/cognito_rails/controller_spec.rb
|
103
104
|
- spec/cognito_rails/jwt_spec.rb
|
105
|
+
- spec/cognito_rails/password_generator_spec.rb
|
104
106
|
- spec/cognito_rails/user_spec.rb
|
105
107
|
- spec/factories/user.rb
|
106
108
|
- spec/spec_helper.rb
|
@@ -133,6 +135,7 @@ summary: Add Cognito authentication to your Rails API
|
|
133
135
|
test_files:
|
134
136
|
- spec/cognito_rails/controller_spec.rb
|
135
137
|
- spec/cognito_rails/jwt_spec.rb
|
138
|
+
- spec/cognito_rails/password_generator_spec.rb
|
136
139
|
- spec/cognito_rails/user_spec.rb
|
137
140
|
- spec/factories/user.rb
|
138
141
|
- spec/spec_helper.rb
|