Skip to content

Commit

Permalink
feat(lib): allow to configure and customize a password generator for …
Browse files Browse the repository at this point in the history
…the temporary password
  • Loading branch information
ProGM committed Apr 27, 2023
1 parent e35ac59 commit 60733ec
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 3 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
# Changelog
All notable changes to this project made by Monade Team are documented in this file. For info refer to [email protected]

## [1.1.0] - 2023-04-27
### Added
- A password generator that follows [Cognito policies](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-policies.html)

### Fixed
- Allow passing a custom password generator
- Override default password generated when explicitly passing one to the model

## [1.0.0] - 2023-03-30
### Added
- `sync_from_cognito!` to create users in the local database from cognito
Expand Down
1 change: 1 addition & 0 deletions lib/cognito_rails.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module CognitoRails
autoload :Model
autoload :User
autoload :JWT
autoload :PasswordGenerator

# @private
module ModelInitializer
Expand Down
6 changes: 5 additions & 1 deletion lib/cognito_rails/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def aws_client_credentials
# @!attribute default_user_class [w]
# @return [String,nil]
attr_writer :aws_client_credentials, :skip_model_hooks, :aws_region,
:aws_user_pool_id, :default_user_class
:aws_user_pool_id, :default_user_class, :password_generator

# @return [Boolean] skip model hooks
def skip_model_hooks
Expand Down Expand Up @@ -49,6 +49,10 @@ def aws_user_pool_id
def default_user_class
@default_user_class || (raise 'Missing config default_user_class')
end

def password_generator
@password_generator || CognitoRails::PasswordGenerator.method(:generate)
end
end
end
end
35 changes: 35 additions & 0 deletions lib/cognito_rails/password_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

module CognitoRails
class PasswordGenerator
NUMERIC = (0..9).to_a.freeze
LOWER_CASE = ('a'..'z').to_a.freeze
UPPER_CASE = ('A'..'Z').to_a.freeze
SPECIAL = [
'^', '$', '*', '.', '[', ']', '{', '}',
'(', ')', '?', '"', '!', '@', '#', '%',
'&', '/', '\\', ',', '>', '<', "'", ':',
';', '|', '_', '~', '`', '=', '+', '-'
].freeze

# Generates a random password given a length range
#
# @param range [Range]
# @return [String]
def self.generate(range = 8..16)
password_length = rand(range)
numeric_count = rand(1..(password_length-3))

lower_case_count = rand(1..(password_length-(numeric_count+2)))
upper_case_count = rand(1..(password_length-(numeric_count + lower_case_count + 1)))
special_count = password_length-(numeric_count + lower_case_count + upper_case_count)

numeric_characters = numeric_count.times.map { NUMERIC.sample }
lower_case_characters = lower_case_count.times.map { LOWER_CASE.sample }
upper_case_characters = upper_case_count.times.map { UPPER_CASE.sample }
special_characters = special_count.times.map { SPECIAL.sample }

(numeric_characters + lower_case_characters + upper_case_characters + special_characters).shuffle.join
end
end
end
2 changes: 1 addition & 1 deletion lib/cognito_rails/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class User
def initialize(attributes = {})
attributes = attributes.with_indifferent_access
self.email = attributes[:email]
self.password = attributes[:password] || SecureRandom.urlsafe_base64
self.password = attributes[:password] || Config.password_generator.call
self.phone = attributes[:phone]
self.user_class = attributes[:user_class] || Config.default_user_class.constantize
self.custom_attributes = attributes[:custom_attributes]
Expand Down
2 changes: 1 addition & 1 deletion lib/cognito_rails/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

module CognitoRails
# @return [String] gem version
VERSION = '1.0.0'
VERSION = '1.1.0'
end
28 changes: 28 additions & 0 deletions spec/cognito_rails/password_generator_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe CognitoRails::PasswordGenerator do
it 'generates a password' do
expect(described_class.generate).to be_a(String)
end

it 'generates a password with the correct length' do
1000.times do
expect(described_class.generate(8..8).length).to eq(8)
end
end

it 'contains at least one letter, one number, one upper case letter, one symbol' do
1000.times do
password = described_class.generate
expect(password).to match(/[a-z]/)
expect(password).to match(/[A-Z]/)
expect(password).to match(/[0-9]/)
include_symbol = CognitoRails::PasswordGenerator::SPECIAL.any? do |symbol|
password.include?(symbol)
end
expect(include_symbol).to be_truthy
end
end
end
15 changes: 15 additions & 0 deletions spec/cognito_rails/user_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,21 @@
user.destroy!
end

it 'uses the password generator defined in config' do
CognitoRails::Config.password_generator = -> { 'ciao' }
expect(CognitoRails::User).to receive(:cognito_client).at_least(:once).and_return(fake_cognito_client)

expect(fake_cognito_client).to receive(:admin_create_user).with(
hash_including(
temporary_password: 'ciao'
)
)
user = User.new(email: sample_cognito_email)
user.save!
ensure
CognitoRails::Config.password_generator = nil
end

it 'uses the custom password passed as parameter' do
expect(CognitoRails::User).to receive(:cognito_client).at_least(:once).and_return(fake_cognito_client)

Expand Down

0 comments on commit 60733ec

Please sign in to comment.