warden-cognito 0.2.3 → 0.3.0

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