warden-cognito 0.1.0 → 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: bf59a14b691d78eb16f048087c84307efd4a37789d2f85fe5100523571fcc134
4
- data.tar.gz: 0be9c19501448ac6d78498e1570c3e2d763726463ab672d6b4194a5f3836ebf3
3
+ metadata.gz: 67b38f626935f1b428eade990cec7e0aa79be73de6ecdf12f747a0cf9fca8b9d
4
+ data.tar.gz: f9e1ae36d5bc9508e02e57c479848d195ad5d47baa01b597f819479dd285d954
5
5
  SHA512:
6
- metadata.gz: 19570e460e1d0d19b7e8c13d6bef79effaf77abb9770b48c0806bdd156e6357072f670b28cd8044d2ae34cb3f4ad822329942a9b860c7f636889fcb9b7a96b55
7
- data.tar.gz: 8170cedc239e47cf814e8ec2fdb5e5a5c89b63443593fcd155a6de440440de5d5c061d3218f211f49bd9b43acbb6885afcd1acd13b403e5438893f8b941c3df3
6
+ metadata.gz: 185dc40763cc5a39964b9f088cb122597abc78bef86b6937a8a630bb5813bb3cfbaa7bc7a7e32732d8daff9892f1033e30a408b8bf2121376b16b220f6c4afae
7
+ data.tar.gz: 49917c80e5c75c2a29fc62597e697438e8e21bffe15f53ec4c79ec701f50dc57de8e836f87b16cf39ff5fb6390f0d4a10016167ca7140622d5a97b42b6b336f1
@@ -2,6 +2,7 @@ AllCops:
2
2
  Exclude:
3
3
  - 'bin/*'
4
4
  - 'db/**/*'
5
+ - 'vendor/**/*'
5
6
 
6
7
  Style/FrozenStringLiteralComment:
7
8
  Enabled: false
@@ -3,5 +3,16 @@ sudo: false
3
3
  language: ruby
4
4
  cache: bundler
5
5
  rvm:
6
- - 2.6.3
7
- before_install: gem install bundler -v 1.17.2
6
+ - 2.6
7
+ - 2.7
8
+ - ruby-head
9
+ before_install:
10
+ - gem update --system --no-doc
11
+ - gem install bundler
12
+ script:
13
+ - bundle exec rspec
14
+ - bundle exec rubocop
15
+ jobs:
16
+ allow_failures:
17
+ - rvm: ruby-head
18
+
@@ -5,4 +5,32 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
+
9
+ ## [0.3.0]
10
+ - **Breaking Changes**: Configuration explicitly moved to `user_pools` object
11
+
12
+ ## [0.2.3]
13
+ - Require the HTTP dependency
14
+
15
+ ## [0.2.2]
16
+ - Fix missing HTTP dependency
17
+
18
+ ## [0.2.1]
19
+ - Fix rspec dependency in implementation
20
+
21
+ ## [0.2.0]
22
+ - Extended exposed API
23
+ - TestHelpers utils
24
+ - Add Travis setup
25
+
26
+ ## [0.1.0]
27
+
8
28
  - Scratching the gem
29
+
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
32
+ [0.2.3]: https://github.com/barkibu/warden-cognito/compare/v0.2.2...v0.2.3
33
+ [0.2.2]: https://github.com/barkibu/warden-cognito/compare/v0.2.1...v0.2.2
34
+ [0.2.1]: https://github.com/barkibu/warden-cognito/compare/v0.2.0...v0.2.1
35
+ [0.2.0]: https://github.com/barkibu/warden-cognito/compare/v0.1.0...v0.2.0
36
+ [0.1.0]: https://github.com/barkibu/warden-cognito/releases/tag/v0.1.0
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Warden::Cognito
2
2
 
3
- [![Codeship Status for barkibu/warden-cognito](https://app.codeship.com/projects/f305e0e4-e1e3-40ef-90b3-ab7475ed480c/status?branch=master)](https://app.codeship.com/projects/417617)
3
+ [![Build Status](https://travis-ci.com/barkibu/warden-cognito.svg?branch=master)](https://travis-ci.com/barkibu/warden-cognito)
4
4
 
5
5
  Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/warden/cognito`. To experiment with that code, run `bin/console` for an interactive prompt.
6
6
 
@@ -24,31 +24,113 @@ Or install it yourself as:
24
24
 
25
25
  ## Usage
26
26
 
27
-
28
- Add to your initializers the following:
27
+ Configure how the gem maps Cognito users to local ones adding to your initializers the following:
29
28
  ```ruby
30
29
  Warden::Cognito.configure do |config|
31
30
  config.user_repository = User
32
31
  config.identifying_attribute = 'sub'
33
- 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) }
34
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'} }
35
35
  end
36
36
  ```
37
37
 
38
+ ### With Devise
39
+
40
+ You can know protects endpoints by settings the available strategies in the Warden section of your Device's configuration:
41
+ ```ruby
42
+ # config/initializers/devise.rb
43
+ #
44
+ # /***/
45
+ config.warden do |manager|
46
+ manager.default_strategies(scope: :user).unshift :cognito_auth
47
+ manager.default_strategies(scope: :user).unshift :cognito_jwt
48
+ # /***/
49
+ end
50
+ ```
51
+
38
52
  ### User Repository
39
53
 
40
54
  The user repository will be used to look for an entity to mark as authenticated, it must implement the following:
41
- - `find_by_cognito_username` that should return the user identified by the given username or nil
42
- - `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)
57
+
58
+ ### User Model
59
+
60
+ The user model must expose a message `cognito_id` that returns the `identifying_attribute` for the given user.
43
61
 
44
62
  ### `after_local_user_not_found` Callback
45
63
 
46
- 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).
47
65
 
48
66
  ### Cache
49
67
  The cache used to store the AWS Json Web Keys as well as the mapping between local and remote identifiers.
50
68
  Defaults to `ActiveSupport::Cache::NullStore`
51
69
 
70
+ ### Testing
71
+
72
+ The TestHelpers module is here to help testing code using this gem to validate tokens and authenticate users:
73
+
74
+ Create a module and make sure it is loaded as part of the support files of your rspec configuration:
75
+
76
+ ```ruby
77
+ module Helpers
78
+ module JWT
79
+ def self.included(base)
80
+ base.class_eval do
81
+ Warden::Cognito::TestHelpers.setup
82
+ end
83
+ end
84
+
85
+ def auth_headers_for_user(user, headers = {})
86
+ Warden::Cognito::TestHelpers.auth_headers(headers, user)
87
+ end
88
+
89
+ def jwt_for_user(user)
90
+ auth_headers_for_user(user)[:Authorization].split[1]
91
+ end
92
+ end
93
+ end
94
+ ```
95
+
96
+ Include this module in the relevant test types:
97
+ ```ruby
98
+ RSpec.configure do |config|
99
+ # /***/
100
+ config.include Helpers::JWT, type: :request
101
+ end
102
+ ```
103
+
104
+ You can now generate tokens for your users in your tests, for instance:
105
+ ```ruby
106
+ let(:user) { create(:user) } # Your users needs to be available through the UserRepository you defined
107
+ let(:headers) { auth_headers_for_user(user) }
108
+ let(:token) { jwt_for_user(user) }
109
+ ```
110
+
111
+ ### API
112
+
113
+ This gem also exposes classes that you can use to validate tokens and/or fetch a user from a given token:
114
+
115
+ ```ruby
116
+ token = 'The token a user passed along in a request'
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]
118
+
119
+ # Is the token valid ?
120
+ token_decoder.validate!
121
+
122
+ # What's in this token ?
123
+ token_decoder.decoded_token
124
+
125
+ # What's the phone_number attribute of the user identified by this token ?
126
+ token_decoder.user_attribute('phone_number')
127
+
128
+ # Who is the local user associated with this token
129
+ user = LocalUserMapper.find(token_decoder)
130
+ # or
131
+ user = LocalUserMapper.find_by_token(token)
132
+ ```
133
+
52
134
  ## Development
53
135
 
54
136
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -67,7 +149,7 @@ An then, for example:
67
149
 
68
150
  ## Contributing
69
151
 
70
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/warden-cognito.
152
+ Bug reports and pull requests are welcome on GitHub at https://github.com/barkibu/warden-cognito.
71
153
 
72
154
  ## License
73
155
 
@@ -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:
@@ -1,3 +1,4 @@
1
+ require 'http'
1
2
  require 'jwt'
2
3
  require 'warden'
3
4
  require 'dry/configurable'
@@ -8,19 +9,54 @@ require 'active_support/core_ext'
8
9
 
9
10
  module Warden
10
11
  module Cognito
12
+ class CognitoError < StandardError; end
13
+
11
14
  extend Dry::Configurable
12
15
 
16
+ def jwk_config_keys
17
+ %i[key issuer]
18
+ end
19
+
20
+ def jwk_instance(value)
21
+ attributes = value&.symbolize_keys&.slice(*jwk_config_keys) || {}
22
+ Struct.new(*jwk_config_keys, keyword_init: true).new(attributes)
23
+ end
24
+
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
37
+
13
38
  setting :user_repository
14
- setting :identifying_attribute, 'sub'
39
+ setting(:identifying_attribute, 'sub', &:to_s)
15
40
  setting :after_local_user_not_found
16
41
  setting :cache, ActiveSupport::Cache::NullStore.new
17
42
 
43
+ setting(:jwk, nil) { |value| jwk_instance(value) }
44
+
45
+ setting(:user_pools, []) { |value| user_pool_configurations(value) }
46
+
18
47
  Import = Dry::AutoInject(config)
19
48
  end
20
49
  end
21
50
 
51
+ require 'warden/cognito/pool_related_iterator'
52
+ require 'warden/cognito/has_user_pool_identifier'
53
+ require 'warden/cognito/jwk_loader'
22
54
  require 'warden/cognito/version'
55
+ require 'warden/cognito/user_not_found_callback'
56
+ require 'warden/cognito/local_user_mapper'
23
57
  require 'warden/cognito/authenticatable_strategy'
24
58
  require 'warden/cognito/token_authenticatable_strategy'
59
+ require 'warden/cognito/token_decoder'
25
60
  require 'warden/cognito/user_helper'
26
61
  require 'warden/cognito/cognito_client'
62
+ require 'warden/cognito/test_helpers'
@@ -4,11 +4,12 @@ require 'aws-sdk-cognitoidentityprovider'
4
4
  module Warden
5
5
  module Cognito
6
6
  class AuthenticatableStrategy < Warden::Strategies::Base
7
- attr_reader :helper
7
+ attr_reader :helper, :user_not_found_callback
8
8
 
9
9
  def initialize(env, scope = nil)
10
10
  super
11
- @helper = UserHelper
11
+ @user_not_found_callback = UserNotFoundCallback.new
12
+ @helper = UserHelper.new
12
13
  end
13
14
 
14
15
  def valid?
@@ -16,11 +17,11 @@ module Warden
16
17
  end
17
18
 
18
19
  def authenticate!
19
- initiate_auth_response = CognitoClient.initiate_auth(email, password)
20
+ attempt = cognito_client.initiate_auth(email, password)
20
21
 
21
- return fail(:unknow_cognito_response) unless initiate_auth_response
22
+ return fail(:unknow_cognito_response) unless attempt
22
23
 
23
- user = local_user || after_user_local_not_found(initiate_auth_response.authentication_result)
24
+ user = local_user || trigger_callback(attempt.authentication_result)
24
25
 
25
26
  fail!(:unknown_user) unless user.present?
26
27
  success!(user)
@@ -32,13 +33,17 @@ module Warden
32
33
 
33
34
  private
34
35
 
35
- def local_user
36
- helper.find_by_cognito_username(email)
36
+ def cognito_client
37
+ CognitoClient.scope pool_identifier
38
+ end
39
+
40
+ def trigger_callback(authentication_result)
41
+ cognito_user = cognito_client.fetch(authentication_result.access_token)
42
+ user_not_found_callback.call(cognito_user, cognito_client.pool_identifier)
37
43
  end
38
44
 
39
- def after_user_local_not_found(authentication_result)
40
- user_response = CognitoClient.fetch(authentication_result.access_token)
41
- Cognito.config.after_local_user_not_found&.call(user_response)
45
+ def local_user
46
+ helper.find_by_cognito_username(email, cognito_client.pool_identifier)
42
47
  end
43
48
 
44
49
  def cognito_authenticable?
@@ -53,8 +58,12 @@ module Warden
53
58
  auth_params[:password]
54
59
  end
55
60
 
61
+ def pool_identifier
62
+ auth_params[:pool_identifier]&.to_sym
63
+ end
64
+
56
65
  def auth_params
57
- params[scope.to_s].symbolize_keys.slice(:password, :email)
66
+ params[scope.to_s].symbolize_keys.slice(:password, :email, :pool_identifier)
58
67
  end
59
68
  end
60
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
@@ -0,0 +1,34 @@
1
+ module Warden
2
+ module Cognito
3
+ class JwkLoader
4
+ include Cognito::Import['cache', 'jwk', 'user_pools']
5
+ include HasUserPoolIdentifier
6
+
7
+ def jwt_issuer
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
15
+ end
16
+
17
+ def call(options)
18
+ return { keys: [jwk.key.export] } if jwk.key.present?
19
+
20
+ cache.delete(jwk_url) if options[:invalidate]
21
+
22
+ cache.fetch(jwk_url, expires_in: 1.hour) do
23
+ JSON.parse(HTTP.get(jwk_url).body.to_s).deep_symbolize_keys
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def jwk_url
30
+ "#{jwt_issuer}/.well-known/jwks.json"
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,34 @@
1
+ module Warden
2
+ module Cognito
3
+ class LocalUserMapper
4
+ include Cognito::Import['cache', 'identifying_attribute']
5
+
6
+ class << self
7
+ def find(token_decoder)
8
+ new.call(token_decoder)
9
+ end
10
+
11
+ def find_by_token(token)
12
+ find(TokenDecoder.new(token))
13
+ end
14
+ end
15
+
16
+ def call(token_decoder)
17
+ helper.find_by_cognito_attribute local_identifier(token_decoder), token_decoder.pool_identifier
18
+ end
19
+
20
+ private
21
+
22
+ def local_identifier(token_decoder)
23
+ cache_key = "COGNITO_POOL_#{token_decoder.pool_identifier}LOCAL_IDENTIFIER_#{token_decoder.sub}"
24
+ cache.fetch(cache_key, skip_nil: true) do
25
+ token_decoder.user_attribute(identifying_attribute)
26
+ end
27
+ end
28
+
29
+ def helper
30
+ UserHelper.new
31
+ end
32
+ end
33
+ end
34
+ 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
@@ -0,0 +1,39 @@
1
+ module Warden
2
+ module Cognito
3
+ class TestHelpers
4
+ class EnvironmentError < StandardError; end
5
+
6
+ @jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048))
7
+
8
+ class << self
9
+ attr_reader :jwk
10
+
11
+ def setup
12
+ Warden::Cognito.config.jwk = { key: jwk, issuer: local_issuer }
13
+ end
14
+
15
+ def auth_headers(headers, user)
16
+ headers.merge(Authorization: "Bearer #{generate_token(user)}")
17
+ end
18
+
19
+ def local_issuer
20
+ 'local_issuer'
21
+ end
22
+
23
+ private
24
+
25
+ def generate_token(user)
26
+ payload = { sub: user.object_id,
27
+ "#{identifying_attribute}": user.cognito_id,
28
+ iss: local_issuer }
29
+ headers = { kid: jwk.kid }
30
+ JWT.encode(payload, jwk.keypair, 'RS256', headers)
31
+ end
32
+
33
+ def identifying_attribute
34
+ Warden::Cognito.config.identifying_attribute
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -6,22 +6,15 @@ module Warden
6
6
  class TokenAuthenticatableStrategy < Warden::Strategies::Base
7
7
  METHOD = 'Bearer'.freeze
8
8
 
9
- attr_reader :helper, :config
9
+ attr_reader :helper
10
10
 
11
11
  def initialize(env, scope = nil)
12
12
  super
13
- @config = Cognito.config
14
- @helper = UserHelper
15
- end
16
-
17
- def jwks
18
- config.cache.fetch(jwk_url, expires_in: 1.hour) do
19
- JSON.parse(HTTP.get(jwk_url).body.to_s).deep_symbolize_keys
20
- end
13
+ @helper = UserHelper.new
21
14
  end
22
15
 
23
16
  def valid?
24
- decoded_token.present?
17
+ token_decoder.validate!
25
18
  rescue ::JWT::ExpiredSignature
26
19
  true
27
20
  rescue StandardError
@@ -29,7 +22,8 @@ module Warden
29
22
  end
30
23
 
31
24
  def authenticate!
32
- user = local_user || config.after_local_user_not_found&.call(cognito_user)
25
+ user = local_user || UserNotFoundCallback.call(cognito_user, token_decoder.pool_identifier)
26
+
33
27
  fail!(:unknown_user) unless user.present?
34
28
  success!(user)
35
29
  rescue ::JWT::ExpiredSignature
@@ -40,49 +34,20 @@ module Warden
40
34
 
41
35
  private
42
36
 
43
- def jwt_issuer
44
- "https://cognito-idp.#{ENV['AWS_REGION']}.amazonaws.com/#{ENV['AWS_COGNITO_USER_POOL_ID']}"
45
- end
46
-
47
- def jwk_url
48
- "#{jwt_issuer}/.well-known/jwks.json"
49
- end
50
-
51
- def local_user
52
- helper.find_by_cognito_attribute(local_identifier)
53
- end
54
-
55
- def cognito_user_cache_key
56
- "COGNITO_LOCAL_IDENTIFIER_#{cognito_user_identifier}"
57
- end
58
-
59
- def cognito_user_identifier
60
- decoded_token.first['sub']
61
- end
62
-
63
- def local_identifier
64
- config.cache.fetch(cognito_user_cache_key, skip_nil: true) do
65
- user_attribute identifying_attribute
66
- end
67
- end
68
-
69
37
  def cognito_user
70
- @cognito_user ||= CognitoClient.fetch(token)
38
+ token_decoder.cognito_user
71
39
  end
72
40
 
73
- def user_attribute(attribute_name)
74
- cognito_user.user_attributes.detect do |attribute|
75
- attribute.name == attribute_name
76
- end&.value
41
+ def local_user
42
+ LocalUserMapper.find token_decoder
77
43
  end
78
44
 
79
- def identifying_attribute
80
- config.identifying_attribute.to_s
45
+ def token_decoder
46
+ @token_decoder ||= TokenDecoder.new(token, pool_identifier)
81
47
  end
82
48
 
83
- def decoded_token
84
- @decoded_token ||= ::JWT.decode(token, nil, true, iss: jwt_issuer, verify_iss: true,
85
- algorithms: ['RS256'], jwks: jwks)
49
+ def pool_identifier
50
+ env['HTTP_X_AUTHORIZATION_POOL_IDENTIFIER']
86
51
  end
87
52
 
88
53
  def token
@@ -0,0 +1,60 @@
1
+ module Warden
2
+ module Cognito
3
+ class TokenDecoder
4
+ attr_reader :jwk_loader, :token
5
+
6
+ def initialize(token, pool_identifier = nil)
7
+ @token = token
8
+ @jwk_loader = find_loader(pool_identifier)
9
+ end
10
+
11
+ def validate!
12
+ decoded_token.present?
13
+ end
14
+
15
+ def sub
16
+ decoded_token.first['sub']
17
+ end
18
+
19
+ def decoded_token
20
+ @decoded_token ||= ::JWT.decode(token, nil, true, iss: jwk_loader.jwt_issuer, verify_iss: true,
21
+ algorithms: ['RS256'], jwks: jwk_loader)
22
+ end
23
+
24
+ def cognito_user
25
+ @cognito_user ||= CognitoClient.scope(pool_identifier).fetch(token)
26
+ end
27
+
28
+ def user_attribute(attribute_name)
29
+ token_attribute(attribute_name).presence || cognito_user_attribute(attribute_name)
30
+ end
31
+
32
+ def pool_identifier
33
+ jwk_loader.pool_identifier
34
+ end
35
+
36
+ private
37
+
38
+ def token_attribute(attribute_name)
39
+ decoded_token.first[attribute_name] if decoded_token.first.key? attribute_name
40
+ end
41
+
42
+ def cognito_user_attribute(attribute_name)
43
+ cognito_user.user_attributes.detect do |attribute|
44
+ attribute.name == attribute_name
45
+ end&.value
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
58
+ end
59
+ end
60
+ end
@@ -1,20 +1,14 @@
1
1
  module Warden
2
2
  module Cognito
3
- module UserHelper
4
- class << self
5
- def find_by_cognito_username(username)
6
- user_repository.find_by_cognito_username(username)
7
- end
3
+ class UserHelper
4
+ include Cognito::Import['user_repository']
8
5
 
9
- def find_by_cognito_attribute(arg)
10
- user_repository.find_by_cognito_attribute(arg)
11
- end
12
-
13
- private
6
+ def find_by_cognito_username(username, pool_identifier)
7
+ user_repository.find_by_cognito_username(username, pool_identifier)
8
+ end
14
9
 
15
- def user_repository
16
- Cognito.config.user_repository
17
- end
10
+ def find_by_cognito_attribute(arg, pool_identifier)
11
+ user_repository.find_by_cognito_attribute(arg, pool_identifier)
18
12
  end
19
13
  end
20
14
  end
@@ -0,0 +1,17 @@
1
+ module Warden
2
+ module Cognito
3
+ class UserNotFoundCallback
4
+ include Cognito::Import['after_local_user_not_found']
5
+
6
+ class << self
7
+ def call(cognito_user, pool_identifier)
8
+ new.call(cognito_user, pool_identifier)
9
+ end
10
+ end
11
+
12
+ def call(cognito_user, pool_identifier)
13
+ after_local_user_not_found&.call(cognito_user, pool_identifier)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,5 +1,5 @@
1
1
  module Warden
2
2
  module Cognito
3
- VERSION = '0.1.0'.freeze
3
+ VERSION = '0.3.0'.freeze
4
4
  end
5
5
  end
@@ -5,7 +5,7 @@ require 'warden/cognito/version'
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = 'warden-cognito'
7
7
  spec.version = Warden::Cognito::VERSION
8
- spec.authors = ['Juan F. Pérez']
8
+ spec.authors = ['Juan F. Pérez', 'Léo Figea']
9
9
  spec.email = ['761794+jguitar@users.noreply.github.com']
10
10
 
11
11
  spec.summary = 'Amazon Cognito authentication for Warden'
@@ -36,10 +36,11 @@ Gem::Specification.new do |spec|
36
36
  spec.add_dependency 'aws-sdk-cognitoidentityprovider', '~> 1.47'
37
37
  spec.add_dependency 'dry-auto_inject', '~> 0.6'
38
38
  spec.add_dependency 'dry-configurable', '~> 0.9'
39
+ spec.add_dependency 'http'
39
40
  spec.add_dependency 'jwt', '~> 2.1'
40
41
  spec.add_dependency 'warden', '~> 1.2'
41
42
 
42
- spec.add_development_dependency 'bundler', '~> 1.17'
43
+ spec.add_development_dependency 'bundler'
43
44
  spec.add_development_dependency 'pry-byebug', '~> 3.7'
44
45
  spec.add_development_dependency 'rack-test', '~> 1.1'
45
46
  spec.add_development_dependency 'rake', '>= 12.3.3'
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: warden-cognito
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Juan F. Pérez
8
+ - Léo Figea
8
9
  autorequire:
9
10
  bindir: exe
10
11
  cert_chain: []
11
- date: 2020-11-12 00:00:00.000000000 Z
12
+ date: 2021-01-20 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: activesupport
@@ -66,6 +67,20 @@ dependencies:
66
67
  - - "~>"
67
68
  - !ruby/object:Gem::Version
68
69
  version: '0.9'
70
+ - !ruby/object:Gem::Dependency
71
+ name: http
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
69
84
  - !ruby/object:Gem::Dependency
70
85
  name: jwt
71
86
  requirement: !ruby/object:Gem::Requirement
@@ -98,16 +113,16 @@ dependencies:
98
113
  name: bundler
99
114
  requirement: !ruby/object:Gem::Requirement
100
115
  requirements:
101
- - - "~>"
116
+ - - ">="
102
117
  - !ruby/object:Gem::Version
103
- version: '1.17'
118
+ version: '0'
104
119
  type: :development
105
120
  prerelease: false
106
121
  version_requirements: !ruby/object:Gem::Requirement
107
122
  requirements:
108
- - - "~>"
123
+ - - ">="
109
124
  - !ruby/object:Gem::Version
110
- version: '1.17'
125
+ version: '0'
111
126
  - !ruby/object:Gem::Dependency
112
127
  name: pry-byebug
113
128
  requirement: !ruby/object:Gem::Requirement
@@ -189,7 +204,6 @@ files:
189
204
  - ".gitignore"
190
205
  - ".rspec"
191
206
  - ".rubocop.yml"
192
- - ".ruby-version"
193
207
  - ".travis.yml"
194
208
  - CHANGELOG.md
195
209
  - Dockerfile
@@ -203,8 +217,15 @@ files:
203
217
  - lib/warden/cognito.rb
204
218
  - lib/warden/cognito/authenticatable_strategy.rb
205
219
  - lib/warden/cognito/cognito_client.rb
220
+ - lib/warden/cognito/has_user_pool_identifier.rb
221
+ - lib/warden/cognito/jwk_loader.rb
222
+ - lib/warden/cognito/local_user_mapper.rb
223
+ - lib/warden/cognito/pool_related_iterator.rb
224
+ - lib/warden/cognito/test_helpers.rb
206
225
  - lib/warden/cognito/token_authenticatable_strategy.rb
226
+ - lib/warden/cognito/token_decoder.rb
207
227
  - lib/warden/cognito/user_helper.rb
228
+ - lib/warden/cognito/user_not_found_callback.rb
208
229
  - lib/warden/cognito/version.rb
209
230
  - warden-cognito.gemspec
210
231
  homepage: https://github.com/barkibu/warden-cognito
@@ -1 +0,0 @@
1
- 2.6.5