warden-cognito 0.1.0 → 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: 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