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 +4 -4
- data/CHANGELOG.md +5 -1
- data/README.md +6 -10
- data/docker-compose.yml +4 -0
- data/lib/warden/cognito.rb +18 -1
- data/lib/warden/cognito/authenticatable_strategy.rb +13 -5
- data/lib/warden/cognito/cognito_client.rb +31 -16
- data/lib/warden/cognito/has_user_pool_identifier.rb +34 -0
- data/lib/warden/cognito/jwk_loader.rb +9 -3
- data/lib/warden/cognito/local_user_mapper.rb +2 -2
- data/lib/warden/cognito/pool_related_iterator.rb +15 -0
- data/lib/warden/cognito/token_authenticatable_strategy.rb +6 -2
- data/lib/warden/cognito/token_decoder.rb +18 -3
- data/lib/warden/cognito/user_helper.rb +4 -4
- data/lib/warden/cognito/user_not_found_callback.rb +4 -4
- data/lib/warden/cognito/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 67b38f626935f1b428eade990cec7e0aa79be73de6ecdf12f747a0cf9fca8b9d
|
4
|
+
data.tar.gz: f9e1ae36d5bc9508e02e57c479848d195ad5d47baa01b597f819479dd285d954
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 185dc40763cc5a39964b9f088cb122597abc78bef86b6937a8a630bb5813bb3cfbaa7bc7a7e32732d8daff9892f1033e30a408b8bf2121376b16b220f6c4afae
|
7
|
+
data.tar.gz: 49917c80e5c75c2a29fc62597e697438e8e21bffe15f53ec4c79ec701f50dc57de8e836f87b16cf39ff5fb6390f0d4a10016167ca7140622d5a97b42b6b336f1
|
data/CHANGELOG.md
CHANGED
@@ -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.
|
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!
|
data/docker-compose.yml
CHANGED
data/lib/warden/cognito.rb
CHANGED
@@ -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
|
-
|
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 =
|
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 =
|
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
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
24
|
-
|
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
|
4
|
+
include Cognito::Import['cache', 'jwk', 'user_pools']
|
5
|
+
include HasUserPoolIdentifier
|
6
6
|
|
7
7
|
def jwt_issuer
|
8
|
-
jwk.issuer || "https://cognito-idp.#{
|
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 = "
|
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 =
|
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
|
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.
|
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:
|
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
|