warden-cognito 0.2.3 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6162b68479e99705070713da8dfe0e70fdcad9653e4813226823b822ad8a4e21
4
- data.tar.gz: 6e4bd989b4877cdeb23d59b8cdc68948ccfbc2a14dddc7161098e62ad9b8ff21
3
+ metadata.gz: 67b38f626935f1b428eade990cec7e0aa79be73de6ecdf12f747a0cf9fca8b9d
4
+ data.tar.gz: f9e1ae36d5bc9508e02e57c479848d195ad5d47baa01b597f819479dd285d954
5
5
  SHA512:
6
- metadata.gz: 9f49d78156bf56c63caf72cdb12657d9050a0684dc9cf35dfe2c87a29de2bebb40dcae7f6f04ae39d24297988e1bdb34ffa634079e3696a1ce871bc94f7a9ebc
7
- data.tar.gz: 9e9bccc3c7f92a852e608fe336c056676c6c7248712bbf6309cb6a51f842b6362b93d9eca57f86af20c2985f1817f1a1ed6f7b159b63372117cb17d3da929bb6
6
+ metadata.gz: 185dc40763cc5a39964b9f088cb122597abc78bef86b6937a8a630bb5813bb3cfbaa7bc7a7e32732d8daff9892f1033e30a408b8bf2121376b16b220f6c4afae
7
+ data.tar.gz: 49917c80e5c75c2a29fc62597e697438e8e21bffe15f53ec4c79ec701f50dc57de8e836f87b16cf39ff5fb6390f0d4a10016167ca7140622d5a97b42b6b336f1
@@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.3.0]
10
+ - **Breaking Changes**: Configuration explicitly moved to `user_pools` object
11
+
9
12
  ## [0.2.3]
10
13
  - Require the HTTP dependency
11
14
 
@@ -24,7 +27,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
24
27
 
25
28
  - Scratching the gem
26
29
 
27
- [Unreleased]: https://github.com/barkibu/warden-cognito/compare/v0.2.3...HEAD
30
+ [Unreleased]: https://github.com/barkibu/warden-cognito/compare/v0.3.0...HEAD
31
+ [0.3.0]: https://github.com/barkibu/warden-cognito/compare/v0.2.3...v0.3.0
28
32
  [0.2.3]: https://github.com/barkibu/warden-cognito/compare/v0.2.2...v0.2.3
29
33
  [0.2.2]: https://github.com/barkibu/warden-cognito/compare/v0.2.1...v0.2.2
30
34
  [0.2.1]: https://github.com/barkibu/warden-cognito/compare/v0.2.0...v0.2.1
data/README.md CHANGED
@@ -29,16 +29,12 @@ Configure how the gem maps Cognito users to local ones adding to your initialize
29
29
  Warden::Cognito.configure do |config|
30
30
  config.user_repository = User
31
31
  config.identifying_attribute = 'sub'
32
- config.after_local_user_not_found = ->(cognito_user) { User.create(username: cognito_user.username) }
32
+ config.after_local_user_not_found = ->(cognito_user, pool_identifier) { User.create(username: cognito_user.username) }
33
33
  config.cache = ActiveSupport::Cache::MemCacheStore.new
34
+ config.user_pools = { default: {region: 'AWS_REGION', pool_id: 'AWS Cognito UserPool Id', client_id: 'AWS Cognito Client Id'} }
34
35
  end
35
36
  ```
36
37
 
37
- This gem will look for the following the env variables:
38
- - **AWS_REGION**
39
- - **AWS_COGNITO_USER_POOL_ID**
40
- - **AWS_COGNITO_CLIENT_ID**
41
-
42
38
  ### With Devise
43
39
 
44
40
  You can know protects endpoints by settings the available strategies in the Warden section of your Device's configuration:
@@ -56,8 +52,8 @@ You can know protects endpoints by settings the available strategies in the Ward
56
52
  ### User Repository
57
53
 
58
54
  The user repository will be used to look for an entity to mark as authenticated, it must implement the following:
59
- - `find_by_cognito_username` that should return the user identified by the given username or nil
60
- - `find_by_cognito_attribute` that should return the user identified by the given Cognito User attribute (`config.identifying_attribute`) or nil
55
+ - `find_by_cognito_username` that should return the user identified by the given username or nil (receives as second param the pool_identifier)
56
+ - `find_by_cognito_attribute` that should return the user identified by the given Cognito User attribute (`config.identifying_attribute`) or nil (receives as second param the pool_identifier)
61
57
 
62
58
  ### User Model
63
59
 
@@ -65,7 +61,7 @@ The user model must expose a message `cognito_id` that returns the `identifying_
65
61
 
66
62
  ### `after_local_user_not_found` Callback
67
63
 
68
- A callback triggered whenever the user correctly authenticated on Cognito but no local user exists (receives the full [cognito user](https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/CognitoIdentityProvider/Types/GetUserResponse.html))
64
+ A callback triggered whenever the user correctly authenticated on Cognito but no local user exists (receives the full [cognito user](https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/CognitoIdentityProvider/Types/GetUserResponse.html), and the pool_identifier as second parameter).
69
65
 
70
66
  ### Cache
71
67
  The cache used to store the AWS Json Web Keys as well as the mapping between local and remote identifiers.
@@ -118,7 +114,7 @@ This gem also exposes classes that you can use to validate tokens and/or fetch a
118
114
 
119
115
  ```ruby
120
116
  token = 'The token a user passed along in a request'
121
- token_decoder = TokenDecoder.new(token)
117
+ token_decoder = TokenDecoder.new(token, nil) # Pass nil as pool_identifier to loop over all the configured pools and automatically bind the right one [Based on the issuer]
122
118
 
123
119
  # Is the token valid ?
124
120
  token_decoder.validate!
@@ -5,5 +5,9 @@ services:
5
5
  command: tail -f Gemfile
6
6
  volumes:
7
7
  - .:/app
8
+ - bundle:/usr/local/bundle
9
+ - ~/.ssh:/root/.ssh
8
10
  - ~/.gitconfig:/root/.gitconfig
9
11
 
12
+ volumes:
13
+ bundle:
@@ -9,6 +9,8 @@ require 'active_support/core_ext'
9
9
 
10
10
  module Warden
11
11
  module Cognito
12
+ class CognitoError < StandardError; end
13
+
12
14
  extend Dry::Configurable
13
15
 
14
16
  def jwk_config_keys
@@ -20,7 +22,18 @@ module Warden
20
22
  Struct.new(*jwk_config_keys, keyword_init: true).new(attributes)
21
23
  end
22
24
 
23
- module_function :jwk_config_keys, :jwk_instance
25
+ def user_pool_configuration_keys
26
+ %i[identifier region pool_id client_id]
27
+ end
28
+
29
+ def user_pool_configurations(value)
30
+ value.map do |key, conf|
31
+ attributes = conf.symbolize_keys.slice(*user_pool_configuration_keys).merge(identifier: key)
32
+ Struct.new(*user_pool_configuration_keys, keyword_init: true).new(attributes)
33
+ end
34
+ end
35
+
36
+ module_function :jwk_config_keys, :jwk_instance, :user_pool_configuration_keys, :user_pool_configurations
24
37
 
25
38
  setting :user_repository
26
39
  setting(:identifying_attribute, 'sub', &:to_s)
@@ -29,10 +42,14 @@ module Warden
29
42
 
30
43
  setting(:jwk, nil) { |value| jwk_instance(value) }
31
44
 
45
+ setting(:user_pools, []) { |value| user_pool_configurations(value) }
46
+
32
47
  Import = Dry::AutoInject(config)
33
48
  end
34
49
  end
35
50
 
51
+ require 'warden/cognito/pool_related_iterator'
52
+ require 'warden/cognito/has_user_pool_identifier'
36
53
  require 'warden/cognito/jwk_loader'
37
54
  require 'warden/cognito/version'
38
55
  require 'warden/cognito/user_not_found_callback'
@@ -17,7 +17,7 @@ module Warden
17
17
  end
18
18
 
19
19
  def authenticate!
20
- attempt = CognitoClient.initiate_auth(email, password)
20
+ attempt = cognito_client.initiate_auth(email, password)
21
21
 
22
22
  return fail(:unknow_cognito_response) unless attempt
23
23
 
@@ -33,13 +33,17 @@ module Warden
33
33
 
34
34
  private
35
35
 
36
+ def cognito_client
37
+ CognitoClient.scope pool_identifier
38
+ end
39
+
36
40
  def trigger_callback(authentication_result)
37
- cognito_user = CognitoClient.fetch(authentication_result.access_token)
38
- user_not_found_callback.call(cognito_user)
41
+ cognito_user = cognito_client.fetch(authentication_result.access_token)
42
+ user_not_found_callback.call(cognito_user, cognito_client.pool_identifier)
39
43
  end
40
44
 
41
45
  def local_user
42
- helper.find_by_cognito_username(email)
46
+ helper.find_by_cognito_username(email, cognito_client.pool_identifier)
43
47
  end
44
48
 
45
49
  def cognito_authenticable?
@@ -54,8 +58,12 @@ module Warden
54
58
  auth_params[:password]
55
59
  end
56
60
 
61
+ def pool_identifier
62
+ auth_params[:pool_identifier]&.to_sym
63
+ end
64
+
57
65
  def auth_params
58
- params[scope.to_s].symbolize_keys.slice(:password, :email)
66
+ params[scope.to_s].symbolize_keys.slice(:password, :email, :pool_identifier)
59
67
  end
60
68
  end
61
69
  end
@@ -1,27 +1,42 @@
1
1
  module Warden
2
2
  module Cognito
3
3
  class CognitoClient
4
- class << self
5
- # https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/CognitoIdentityProvider/Types/GetUserResponse.html
6
- def fetch(access_token)
7
- client.get_user(access_token: access_token)
8
- end
4
+ include Cognito::Import['user_pools']
5
+ include HasUserPoolIdentifier
6
+
7
+ # https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/CognitoIdentityProvider/Types/GetUserResponse.html
8
+ def fetch(access_token)
9
+ client.get_user(access_token: access_token)
10
+ end
11
+
12
+ def initiate_auth(email, password)
13
+ client.initiate_auth(
14
+ client_id: user_pool.client_id,
15
+ auth_flow: 'USER_PASSWORD_AUTH',
16
+ auth_parameters: {
17
+ 'USERNAME' => email,
18
+ 'PASSWORD' => password
19
+ }
20
+ )
21
+ end
22
+
23
+ private
9
24
 
10
- def initiate_auth(email, password)
11
- client.initiate_auth(
12
- client_id: ENV['AWS_COGNITO_CLIENT_ID'],
13
- auth_flow: 'USER_PASSWORD_AUTH',
14
- auth_parameters: {
15
- 'USERNAME' => email,
16
- 'PASSWORD' => password
17
- }
18
- )
25
+ def client
26
+ Aws::CognitoIdentityProvider::Client.new
27
+ end
28
+
29
+ class << self
30
+ def scope(pool_identifier)
31
+ new.tap do |client|
32
+ client.user_pool = pool_identifier || default_pool_identifier
33
+ end
19
34
  end
20
35
 
21
36
  private
22
37
 
23
- def client
24
- Aws::CognitoIdentityProvider::Client.new
38
+ def default_pool_identifier
39
+ Warden::Cognito.config.user_pools.first.identifier
25
40
  end
26
41
  end
27
42
  end
@@ -0,0 +1,34 @@
1
+ module Warden
2
+ module Cognito
3
+ module HasUserPoolIdentifier
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ base.class_eval do
7
+ attr_reader :user_pool
8
+ end
9
+ end
10
+
11
+ def user_pool=(pool_identifier)
12
+ @user_pool = user_pools.detect(self.class.invalid_issuer_error) { |pool| pool.identifier == pool_identifier }
13
+ end
14
+
15
+ def pool_identifier
16
+ user_pool.identifier
17
+ end
18
+
19
+ module ClassMethods
20
+ def pool_iterator
21
+ PoolRelatedIterator.new do |pool|
22
+ new.tap do |pool_related|
23
+ pool_related.user_pool = pool.identifier
24
+ end
25
+ end
26
+ end
27
+
28
+ def invalid_issuer_error
29
+ -> { raise ::JWT::InvalidIssuerError, 'The token was not generated by any of the configured User Pools' }
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,11 +1,17 @@
1
1
  module Warden
2
2
  module Cognito
3
3
  class JwkLoader
4
- include Cognito::Import['cache']
5
- include Cognito::Import['jwk']
4
+ include Cognito::Import['cache', 'jwk', 'user_pools']
5
+ include HasUserPoolIdentifier
6
6
 
7
7
  def jwt_issuer
8
- jwk.issuer || "https://cognito-idp.#{ENV['AWS_REGION']}.amazonaws.com/#{ENV['AWS_COGNITO_USER_POOL_ID']}"
8
+ jwk.issuer || "https://cognito-idp.#{user_pool.region}.amazonaws.com/#{user_pool.pool_id}"
9
+ end
10
+
11
+ def issued?(token)
12
+ ::JWT.decode(token, nil, false).first['iss'] == jwt_issuer
13
+ rescue StandardError
14
+ false
9
15
  end
10
16
 
11
17
  def call(options)
@@ -14,13 +14,13 @@ module Warden
14
14
  end
15
15
 
16
16
  def call(token_decoder)
17
- helper.find_by_cognito_attribute local_identifier(token_decoder)
17
+ helper.find_by_cognito_attribute local_identifier(token_decoder), token_decoder.pool_identifier
18
18
  end
19
19
 
20
20
  private
21
21
 
22
22
  def local_identifier(token_decoder)
23
- cache_key = "COGNITO_LOCAL_IDENTIFIER_#{token_decoder.sub}"
23
+ cache_key = "COGNITO_POOL_#{token_decoder.pool_identifier}LOCAL_IDENTIFIER_#{token_decoder.sub}"
24
24
  cache.fetch(cache_key, skip_nil: true) do
25
25
  token_decoder.user_attribute(identifying_attribute)
26
26
  end
@@ -0,0 +1,15 @@
1
+ class PoolRelatedIterator
2
+ include Enumerable
3
+
4
+ attr_reader :factory
5
+
6
+ def initialize(&factory)
7
+ @factory = factory
8
+ end
9
+
10
+ def each(&block)
11
+ Warden::Cognito.config.user_pools.each do |pool|
12
+ block.call factory.call(pool)
13
+ end
14
+ end
15
+ end
@@ -22,7 +22,7 @@ module Warden
22
22
  end
23
23
 
24
24
  def authenticate!
25
- user = local_user || UserNotFoundCallback.call(cognito_user)
25
+ user = local_user || UserNotFoundCallback.call(cognito_user, token_decoder.pool_identifier)
26
26
 
27
27
  fail!(:unknown_user) unless user.present?
28
28
  success!(user)
@@ -43,7 +43,11 @@ module Warden
43
43
  end
44
44
 
45
45
  def token_decoder
46
- @token_decoder ||= TokenDecoder.new(token)
46
+ @token_decoder ||= TokenDecoder.new(token, pool_identifier)
47
+ end
48
+
49
+ def pool_identifier
50
+ env['HTTP_X_AUTHORIZATION_POOL_IDENTIFIER']
47
51
  end
48
52
 
49
53
  def token
@@ -3,9 +3,9 @@ module Warden
3
3
  class TokenDecoder
4
4
  attr_reader :jwk_loader, :token
5
5
 
6
- def initialize(token)
6
+ def initialize(token, pool_identifier = nil)
7
7
  @token = token
8
- @jwk_loader = JwkLoader.new
8
+ @jwk_loader = find_loader(pool_identifier)
9
9
  end
10
10
 
11
11
  def validate!
@@ -22,13 +22,17 @@ module Warden
22
22
  end
23
23
 
24
24
  def cognito_user
25
- @cognito_user ||= CognitoClient.fetch(token)
25
+ @cognito_user ||= CognitoClient.scope(pool_identifier).fetch(token)
26
26
  end
27
27
 
28
28
  def user_attribute(attribute_name)
29
29
  token_attribute(attribute_name).presence || cognito_user_attribute(attribute_name)
30
30
  end
31
31
 
32
+ def pool_identifier
33
+ jwk_loader.pool_identifier
34
+ end
35
+
32
36
  private
33
37
 
34
38
  def token_attribute(attribute_name)
@@ -40,6 +44,17 @@ module Warden
40
44
  attribute.name == attribute_name
41
45
  end&.value
42
46
  end
47
+
48
+ def find_loader(pool_identifier)
49
+ if pool_identifier.present?
50
+ return JwkLoader.new.tap do |loader|
51
+ loader.user_pool = pool_identifier
52
+ end
53
+ end
54
+ JwkLoader.pool_iterator.detect(JwkLoader.invalid_issuer_error) do |loader|
55
+ loader.issued? token
56
+ end
57
+ end
43
58
  end
44
59
  end
45
60
  end
@@ -3,12 +3,12 @@ module Warden
3
3
  class UserHelper
4
4
  include Cognito::Import['user_repository']
5
5
 
6
- def find_by_cognito_username(username)
7
- user_repository.find_by_cognito_username(username)
6
+ def find_by_cognito_username(username, pool_identifier)
7
+ user_repository.find_by_cognito_username(username, pool_identifier)
8
8
  end
9
9
 
10
- def find_by_cognito_attribute(arg)
11
- user_repository.find_by_cognito_attribute(arg)
10
+ def find_by_cognito_attribute(arg, pool_identifier)
11
+ user_repository.find_by_cognito_attribute(arg, pool_identifier)
12
12
  end
13
13
  end
14
14
  end
@@ -4,13 +4,13 @@ module Warden
4
4
  include Cognito::Import['after_local_user_not_found']
5
5
 
6
6
  class << self
7
- def call(cognito_user)
8
- new.call(cognito_user)
7
+ def call(cognito_user, pool_identifier)
8
+ new.call(cognito_user, pool_identifier)
9
9
  end
10
10
  end
11
11
 
12
- def call(cognito_user)
13
- after_local_user_not_found&.call(cognito_user)
12
+ def call(cognito_user, pool_identifier)
13
+ after_local_user_not_found&.call(cognito_user, pool_identifier)
14
14
  end
15
15
  end
16
16
  end
@@ -1,5 +1,5 @@
1
1
  module Warden
2
2
  module Cognito
3
- VERSION = '0.2.3'.freeze
3
+ VERSION = '0.3.0'.freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: warden-cognito
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Juan F. Pérez
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2020-11-27 00:00:00.000000000 Z
12
+ date: 2021-01-20 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -217,8 +217,10 @@ files:
217
217
  - lib/warden/cognito.rb
218
218
  - lib/warden/cognito/authenticatable_strategy.rb
219
219
  - lib/warden/cognito/cognito_client.rb
220
+ - lib/warden/cognito/has_user_pool_identifier.rb
220
221
  - lib/warden/cognito/jwk_loader.rb
221
222
  - lib/warden/cognito/local_user_mapper.rb
223
+ - lib/warden/cognito/pool_related_iterator.rb
222
224
  - lib/warden/cognito/test_helpers.rb
223
225
  - lib/warden/cognito/token_authenticatable_strategy.rb
224
226
  - lib/warden/cognito/token_decoder.rb