Skip to content

Commit d0ba22a

Browse files
committed
feat(user): add feature to sync from and to cognito
1 parent c0ef734 commit d0ba22a

File tree

6 files changed

+129
-12
lines changed

6 files changed

+129
-12
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
# Changelog
22
All notable changes to this project made by Monade Team are documented in this file. For info refer to team@monade.io
33

4-
## [UNRELEASED] - 2023-03-30
4+
## [1.0.0] - 2023-03-30
5+
### Added
6+
- `sync_from_cognito!` to create users in the local database from cognito
7+
- `sync_to_cognito!` to create cognito users based from the local database
8+
59
### Changed
610
- [BREAKING] Switched from explicit `aws_access_key`/`aws_secret_access_key` to a more flexible `aws_client_credentials`
711

lib/cognito_rails/model.rb

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,55 @@ module Model
2323
end
2424
end
2525

26+
# rubocop:disable Metrics/BlockLength
27+
class_methods do
28+
# @return [Array<ActiveRecord::Base>] all users
29+
# @raise [CognitoRails::Error] if failed to fetch users
30+
# @raise [ActiveRecord::RecordInvalid] if failed to save user
31+
def sync_from_cognito!
32+
response = User.all
33+
response.users.map do |user_data|
34+
sync_user!(user_data)
35+
end
36+
end
37+
38+
# @return [Array<ActiveRecord::Base>] all users
39+
# @raise [CognitoRails::Error] if failed to fetch users
40+
# @raise [ActiveRecord::RecordInvalid] if failed to save user
41+
def sync_to_cognito!
42+
find_each.map do |user|
43+
user.init_cognito_user
44+
user.save!
45+
end
46+
end
47+
48+
private
49+
50+
def sync_user!(user_data)
51+
external_id = user_data.username
52+
return if external_id.blank?
53+
54+
user = find_or_initialize_by(_cognito_attribute_name => external_id)
55+
user.email = User.extract_cognito_attribute(user_data.attributes, :email)
56+
user.phone = User.extract_cognito_attribute(user_data.attributes, :phone_number) if user.respond_to?(:phone)
57+
_cognito_resolve_custom_attribute(user, user_data)
58+
59+
user.save!
60+
user
61+
end
62+
63+
def _cognito_resolve_custom_attribute(user, user_data)
64+
_cognito_custom_attributes.each do |attribute|
65+
next if attribute[:value].is_a?(String)
66+
67+
value = User.extract_cognito_attribute(user_data.attributes, attribute[:name])
68+
next unless value
69+
70+
user[attribute[:name].gsub('custom:', '')] = value
71+
end
72+
end
73+
end
74+
2675
# @return [String]
2776
def cognito_external_id
2877
self[self.class._cognito_attribute_name]

lib/cognito_rails/user.rb

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,15 @@ def self.find(id, user_class = nil)
5757
)
5858
user = new(user_class: user_class)
5959
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)
60+
user.email = extract_cognito_attribute(result.user_attributes, :email)
61+
user.phone = extract_cognito_attribute(result.user_attributes, :phone_number)
6262
user
6363
end
6464

65+
def self.all
66+
cognito_client.list_users(user_pool_id: CognitoRails::Config.aws_user_pool_id)
67+
end
68+
6569
# @param attributes [Hash]
6670
# @option attributes [String] :email
6771
# @option attributes [String] :password
@@ -138,6 +142,18 @@ def destroy!
138142
destroy || (raise ActiveRecord::RecordInvalid, self)
139143
end
140144

145+
# @return [Aws::CognitoIdentityProvider::Client]
146+
# @raise [RuntimeError]
147+
def self.cognito_client
148+
@cognito_client ||= Aws::CognitoIdentityProvider::Client.new(
149+
{ region: CognitoRails::Config.aws_region }.merge(CognitoRails::Config.aws_client_credentials)
150+
)
151+
end
152+
153+
def self.extract_cognito_attribute(attributes, column)
154+
attributes.find { |attribute| attribute[:name] == column.to_s }&.dig(:value)
155+
end
156+
141157
private
142158

143159
# @return [Aws::CognitoIdentityProvider::Client]
@@ -155,14 +171,6 @@ def verify_phone?
155171
user_class._cognito_verify_phone
156172
end
157173

158-
# @return [Aws::CognitoIdentityProvider::Client]
159-
# @raise [RuntimeError]
160-
def self.cognito_client
161-
@cognito_client ||= Aws::CognitoIdentityProvider::Client.new(
162-
{ region: CognitoRails::Config.aws_region }.merge(CognitoRails::Config.aws_client_credentials)
163-
)
164-
end
165-
166174
# @return [Array<Hash>]
167175
def general_user_attributes
168176
[

lib/cognito_rails/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22

33
module CognitoRails
44
# @return [String] gem version
5-
VERSION = '0.1.0'
5+
VERSION = '1.0.0'
66
end

spec/cognito_rails/user_spec.rb

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,42 @@
111111
end
112112
end
113113

114+
context 'class methods' 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 '#sync_from_cognito!' do
120+
expect(fake_cognito_client).to receive(:list_users).and_return(
121+
OpenStruct.new(
122+
users: [
123+
build_cognito_user_data('some@example.com'),
124+
build_cognito_user_data('some2@example.com')
125+
],
126+
pagination_token: nil
127+
)
128+
)
129+
130+
expect do
131+
users = User.sync_from_cognito!
132+
133+
expect(users).to be_a(Array)
134+
expect(users.size).to eq(2)
135+
expect(users.first).to be_a(User)
136+
end.to change { User.count }.by(2)
137+
138+
expect(User.pluck(:email)).to match_array(['some@example.com', 'some2@example.com'])
139+
expect(User.pluck(:name)).to match_array(['Giovanni', 'Giovanni'])
140+
end
141+
142+
it '#sync_to_cognito!' do
143+
User.create!(email: sample_cognito_email)
144+
145+
expect_any_instance_of(User).to receive(:init_cognito_user).exactly(1).times
146+
User.sync_to_cognito!
147+
end
148+
end
149+
114150
context 'admin' do
115151
before do
116152
expect(CognitoRails::User).to receive(:cognito_client).at_least(:once).and_return(fake_cognito_client)

spec/support/cognito_helpers.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,24 @@ module CognitoRails::Helpers
4040
client
4141
end
4242
end
43+
44+
def build_cognito_user_data(email)
45+
OpenStruct.new(
46+
username: SecureRandom.uuid,
47+
user_status: 'CONFIRMED',
48+
enabled: true,
49+
user_last_modified_date: Time.now,
50+
attributes: [
51+
OpenStruct.new(
52+
name: 'email',
53+
value: email
54+
),
55+
OpenStruct.new(
56+
name: 'custom:name',
57+
value: 'Giovanni'
58+
)
59+
],
60+
mfa_options: []
61+
)
62+
end
4363
end

0 commit comments

Comments
 (0)