warden-cognito 0.1.0 → 0.2.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/.rubocop.yml +1 -0
- data/.travis.yml +13 -2
- data/CHANGELOG.md +12 -0
- data/README.md +90 -4
- data/lib/warden/cognito.rb +19 -1
- data/lib/warden/cognito/authenticatable_strategy.rb +11 -10
- data/lib/warden/cognito/jwk_loader.rb +28 -0
- data/lib/warden/cognito/local_user_mapper.rb +34 -0
- data/lib/warden/cognito/test_helpers.rb +41 -0
- data/lib/warden/cognito/token_authenticatable_strategy.rb +10 -49
- data/lib/warden/cognito/token_decoder.rb +45 -0
- data/lib/warden/cognito/user_helper.rb +7 -13
- data/lib/warden/cognito/user_not_found_callback.rb +17 -0
- data/lib/warden/cognito/version.rb +1 -1
- data/warden-cognito.gemspec +2 -2
- metadata +16 -11
- data/.ruby-version +0 -1
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: ad71888695cae9d89e314a98ee48954af77ae821210bc11c4585027f770d6e6b
         | 
| 4 | 
            +
              data.tar.gz: a0cf1379d7ac70acb57d9f96aa606ff9760c6d50529d3206f77ee79cfaea8b42
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: e74522ac52fc4986766bbdea18437a6996eef668ccd547c80b7cc4f048c1d31c0293614f889f241c86fb67cdbda2e837b1142ce1e69439ff296ff4ae63306827
         | 
| 7 | 
            +
              data.tar.gz: b7911fa57b3eccc01bffee7a660e6c70c40fad15aa1e42d83b9895d5f8920220345cead762c51b1d9a20424dca4cc9314f6b49681ad010eedfa394e8f2480669
         | 
    
        data/.rubocop.yml
    CHANGED
    
    
    
        data/.travis.yml
    CHANGED
    
    | @@ -3,5 +3,16 @@ sudo: false | |
| 3 3 | 
             
            language: ruby
         | 
| 4 4 | 
             
            cache: bundler
         | 
| 5 5 | 
             
            rvm:
         | 
| 6 | 
            -
              - 2.6 | 
| 7 | 
            -
             | 
| 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 | 
            +
             | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -5,4 +5,16 @@ 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.2.0]
         | 
| 10 | 
            +
            - Extended exposed API
         | 
| 11 | 
            +
            - TestHelpers utils
         | 
| 12 | 
            +
            - Add Travis setup
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            ## [0.1.0]
         | 
| 15 | 
            +
             | 
| 8 16 | 
             
            - Scratching the gem
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            [Unreleased]: https://github.com/barkibu/warden-cognito/compare/v0.2.0...HEAD
         | 
| 19 | 
            +
            [0.2.0]: https://github.com/barkibu/warden-cognito/compare/v0.1.0...v0.2.0
         | 
| 20 | 
            +
            [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 | 
            -
            [](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,8 +24,7 @@ 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
         | 
| @@ -35,12 +34,35 @@ Add to  your initializers the following: | |
| 35 34 | 
             
            end
         | 
| 36 35 | 
             
            ```
         | 
| 37 36 |  | 
| 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 | 
            +
            ### With Devise
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            You can know protects endpoints by settings the available strategies in the Warden section of your Device's configuration:
         | 
| 45 | 
            +
            ```ruby
         | 
| 46 | 
            +
              # config/initializers/devise.rb
         | 
| 47 | 
            +
              # 
         | 
| 48 | 
            +
              # /***/
         | 
| 49 | 
            +
              config.warden do |manager|
         | 
| 50 | 
            +
                manager.default_strategies(scope: :user).unshift :cognito_auth
         | 
| 51 | 
            +
                manager.default_strategies(scope: :user).unshift :cognito_jwt
         | 
| 52 | 
            +
                # /***/
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
            ```
         | 
| 55 | 
            +
             | 
| 38 56 | 
             
            ### User Repository
         | 
| 39 57 |  | 
| 40 58 | 
             
            The user repository will be used to look for an entity to mark as authenticated, it must implement the following:
         | 
| 41 59 | 
             
            - `find_by_cognito_username` that should return the user identified by the given username or nil
         | 
| 42 60 | 
             
            - `find_by_cognito_attribute` that should return the user identified by the given Cognito User attribute (`config.identifying_attribute`) or nil
         | 
| 43 61 |  | 
| 62 | 
            +
            ### User Model
         | 
| 63 | 
            +
             | 
| 64 | 
            +
            The user model must expose a message `cognito_id` that returns the `identifying_attribute` for the given user.
         | 
| 65 | 
            +
             | 
| 44 66 | 
             
            ### `after_local_user_not_found` Callback
         | 
| 45 67 |  | 
| 46 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))
         | 
| @@ -49,6 +71,70 @@ A callback triggered whenever the user correctly authenticated on Cognito but no | |
| 49 71 | 
             
            The cache used to store the AWS Json Web Keys as well as the mapping between local and remote identifiers.
         | 
| 50 72 | 
             
            Defaults to `ActiveSupport::Cache::NullStore`
         | 
| 51 73 |  | 
| 74 | 
            +
            ### Testing
         | 
| 75 | 
            +
             | 
| 76 | 
            +
            The TestHelpers module is here to help testing code using this gem to validate tokens and authenticate users:
         | 
| 77 | 
            +
             | 
| 78 | 
            +
            Create a module and make sure it is loaded as part of the support files of your rspec configuration:
         | 
| 79 | 
            +
             | 
| 80 | 
            +
            ```ruby
         | 
| 81 | 
            +
            module Helpers
         | 
| 82 | 
            +
              module JWT
         | 
| 83 | 
            +
                def self.included(base)
         | 
| 84 | 
            +
                  base.class_eval do
         | 
| 85 | 
            +
                    Warden::Cognito::TestHelpers.setup
         | 
| 86 | 
            +
                  end
         | 
| 87 | 
            +
                end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                def auth_headers_for_user(user, headers = {})
         | 
| 90 | 
            +
                  Warden::Cognito::TestHelpers.auth_headers(headers, user)
         | 
| 91 | 
            +
                end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                def jwt_for_user(user)
         | 
| 94 | 
            +
                  auth_headers_for_user(user)[:Authorization].split[1]
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
              end
         | 
| 97 | 
            +
            end
         | 
| 98 | 
            +
            ```
         | 
| 99 | 
            +
             | 
| 100 | 
            +
            Include this module in the relevant test types:
         | 
| 101 | 
            +
            ```ruby
         | 
| 102 | 
            +
            RSpec.configure do |config|
         | 
| 103 | 
            +
              # /***/
         | 
| 104 | 
            +
              config.include Helpers::JWT, type: :request
         | 
| 105 | 
            +
            end
         | 
| 106 | 
            +
            ```
         | 
| 107 | 
            +
             | 
| 108 | 
            +
            You can now generate tokens for your users in your tests, for instance:
         | 
| 109 | 
            +
            ```ruby
         | 
| 110 | 
            +
            let(:user) { create(:user) } # Your users needs to be available through the UserRepository you defined
         | 
| 111 | 
            +
            let(:headers) { auth_headers_for_user(user) }
         | 
| 112 | 
            +
            let(:token) { jwt_for_user(user) }
         | 
| 113 | 
            +
            ```
         | 
| 114 | 
            +
             | 
| 115 | 
            +
            ### API
         | 
| 116 | 
            +
             | 
| 117 | 
            +
            This gem also exposes classes that you can use to validate tokens and/or fetch a user from a given token:
         | 
| 118 | 
            +
             | 
| 119 | 
            +
            ```ruby
         | 
| 120 | 
            +
            token = 'The token a user passed along in a request'
         | 
| 121 | 
            +
            token_decoder = TokenDecoder.new(token)
         | 
| 122 | 
            +
             | 
| 123 | 
            +
            # Is the token valid ?
         | 
| 124 | 
            +
            token_decoder.validate!
         | 
| 125 | 
            +
             | 
| 126 | 
            +
            # What's in this token ?
         | 
| 127 | 
            +
            token_decoder.decoded_token
         | 
| 128 | 
            +
             | 
| 129 | 
            +
            # What's the phone_number attribute of the user identified by this token ?
         | 
| 130 | 
            +
            token_decoder.user_attribute('phone_number')
         | 
| 131 | 
            +
             | 
| 132 | 
            +
            # Who is the local user associated with this token
         | 
| 133 | 
            +
            user = LocalUserMapper.find(token_decoder)
         | 
| 134 | 
            +
            # or 
         | 
| 135 | 
            +
            user = LocalUserMapper.find_by_token(token)
         | 
| 136 | 
            +
            ```
         | 
| 137 | 
            +
             | 
| 52 138 | 
             
            ## Development
         | 
| 53 139 |  | 
| 54 140 | 
             
            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 +153,7 @@ An then, for example: | |
| 67 153 |  | 
| 68 154 | 
             
            ## Contributing
         | 
| 69 155 |  | 
| 70 | 
            -
            Bug reports and pull requests are welcome on GitHub at https://github.com/ | 
| 156 | 
            +
            Bug reports and pull requests are welcome on GitHub at https://github.com/barkibu/warden-cognito.
         | 
| 71 157 |  | 
| 72 158 | 
             
            ## License
         | 
| 73 159 |  | 
    
        data/lib/warden/cognito.rb
    CHANGED
    
    | @@ -10,17 +10,35 @@ module Warden | |
| 10 10 | 
             
              module Cognito
         | 
| 11 11 | 
             
                extend Dry::Configurable
         | 
| 12 12 |  | 
| 13 | 
            +
                def jwk_config_keys
         | 
| 14 | 
            +
                  %i[key issuer]
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def jwk_instance(value)
         | 
| 18 | 
            +
                  attributes = value&.symbolize_keys&.slice(*jwk_config_keys) || {}
         | 
| 19 | 
            +
                  Struct.new(*jwk_config_keys, keyword_init: true).new(attributes)
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                module_function :jwk_config_keys, :jwk_instance
         | 
| 23 | 
            +
             | 
| 13 24 | 
             
                setting :user_repository
         | 
| 14 | 
            -
                setting | 
| 25 | 
            +
                setting(:identifying_attribute, 'sub', &:to_s)
         | 
| 15 26 | 
             
                setting :after_local_user_not_found
         | 
| 16 27 | 
             
                setting :cache, ActiveSupport::Cache::NullStore.new
         | 
| 17 28 |  | 
| 29 | 
            +
                setting(:jwk, nil) { |value| jwk_instance(value) }
         | 
| 30 | 
            +
             | 
| 18 31 | 
             
                Import = Dry::AutoInject(config)
         | 
| 19 32 | 
             
              end
         | 
| 20 33 | 
             
            end
         | 
| 21 34 |  | 
| 35 | 
            +
            require 'warden/cognito/jwk_loader'
         | 
| 22 36 | 
             
            require 'warden/cognito/version'
         | 
| 37 | 
            +
            require 'warden/cognito/user_not_found_callback'
         | 
| 38 | 
            +
            require 'warden/cognito/local_user_mapper'
         | 
| 23 39 | 
             
            require 'warden/cognito/authenticatable_strategy'
         | 
| 24 40 | 
             
            require 'warden/cognito/token_authenticatable_strategy'
         | 
| 41 | 
            +
            require 'warden/cognito/token_decoder'
         | 
| 25 42 | 
             
            require 'warden/cognito/user_helper'
         | 
| 26 43 | 
             
            require 'warden/cognito/cognito_client'
         | 
| 44 | 
            +
            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 | 
            -
                    @ | 
| 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 | 
            -
                     | 
| 20 | 
            +
                    attempt = CognitoClient.initiate_auth(email, password)
         | 
| 20 21 |  | 
| 21 | 
            -
                    return fail(:unknow_cognito_response) unless  | 
| 22 | 
            +
                    return fail(:unknow_cognito_response) unless attempt
         | 
| 22 23 |  | 
| 23 | 
            -
                    user = local_user ||  | 
| 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,13 @@ module Warden | |
| 32 33 |  | 
| 33 34 | 
             
                  private
         | 
| 34 35 |  | 
| 35 | 
            -
                  def  | 
| 36 | 
            -
                     | 
| 36 | 
            +
                  def trigger_callback(authentication_result)
         | 
| 37 | 
            +
                    cognito_user = CognitoClient.fetch(authentication_result.access_token)
         | 
| 38 | 
            +
                    user_not_found_callback.call(cognito_user)
         | 
| 37 39 | 
             
                  end
         | 
| 38 40 |  | 
| 39 | 
            -
                  def  | 
| 40 | 
            -
                     | 
| 41 | 
            -
                    Cognito.config.after_local_user_not_found&.call(user_response)
         | 
| 41 | 
            +
                  def local_user
         | 
| 42 | 
            +
                    helper.find_by_cognito_username(email)
         | 
| 42 43 | 
             
                  end
         | 
| 43 44 |  | 
| 44 45 | 
             
                  def cognito_authenticable?
         | 
| @@ -0,0 +1,28 @@ | |
| 1 | 
            +
            module Warden
         | 
| 2 | 
            +
              module Cognito
         | 
| 3 | 
            +
                class JwkLoader
         | 
| 4 | 
            +
                  include Cognito::Import['cache']
         | 
| 5 | 
            +
                  include Cognito::Import['jwk']
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  def jwt_issuer
         | 
| 8 | 
            +
                    jwk.issuer || "https://cognito-idp.#{ENV['AWS_REGION']}.amazonaws.com/#{ENV['AWS_COGNITO_USER_POOL_ID']}"
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  def call(options)
         | 
| 12 | 
            +
                    return { keys: [jwk.key.export] } if jwk.key.present?
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                    cache.delete(jwk_url) if options[:invalidate]
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                    cache.fetch(jwk_url, expires_in: 1.hour) do
         | 
| 17 | 
            +
                      JSON.parse(HTTP.get(jwk_url).body.to_s).deep_symbolize_keys
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  private
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  def jwk_url
         | 
| 24 | 
            +
                    "#{jwt_issuer}/.well-known/jwks.json"
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
            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)
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  private
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def local_identifier(token_decoder)
         | 
| 23 | 
            +
                    cache_key = "COGNITO_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,41 @@ | |
| 1 | 
            +
            require 'rspec'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Warden
         | 
| 4 | 
            +
              module Cognito
         | 
| 5 | 
            +
                class TestHelpers
         | 
| 6 | 
            +
                  class EnvironmentError < StandardError; end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  @jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048))
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  class << self
         | 
| 11 | 
            +
                    attr_reader :jwk
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    def setup
         | 
| 14 | 
            +
                      Warden::Cognito.config.jwk = { key: jwk, issuer: local_issuer }
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    def auth_headers(headers, user)
         | 
| 18 | 
            +
                      headers.merge(Authorization: "Bearer #{generate_token(user)}")
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    def local_issuer
         | 
| 22 | 
            +
                      'local_issuer'
         | 
| 23 | 
            +
                    end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    private
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    def generate_token(user)
         | 
| 28 | 
            +
                      payload = { sub: user.object_id,
         | 
| 29 | 
            +
                                  "#{identifying_attribute}": user.cognito_id,
         | 
| 30 | 
            +
                                  iss: local_issuer }
         | 
| 31 | 
            +
                      headers = { kid: jwk.kid }
         | 
| 32 | 
            +
                      JWT.encode(payload, jwk.keypair, 'RS256', headers)
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    def identifying_attribute
         | 
| 36 | 
            +
                      Warden::Cognito.config.identifying_attribute
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
            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 | 
| 9 | 
            +
                  attr_reader :helper
         | 
| 10 10 |  | 
| 11 11 | 
             
                  def initialize(env, scope = nil)
         | 
| 12 12 | 
             
                    super
         | 
| 13 | 
            -
                    @ | 
| 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 | 
            -
                     | 
| 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 ||  | 
| 25 | 
            +
                    user = local_user || UserNotFoundCallback.call(cognito_user)
         | 
| 26 | 
            +
             | 
| 33 27 | 
             
                    fail!(:unknown_user) unless user.present?
         | 
| 34 28 | 
             
                    success!(user)
         | 
| 35 29 | 
             
                  rescue ::JWT::ExpiredSignature
         | 
| @@ -40,49 +34,16 @@ 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 | 
            -
                     | 
| 71 | 
            -
                  end
         | 
| 72 | 
            -
             | 
| 73 | 
            -
                  def user_attribute(attribute_name)
         | 
| 74 | 
            -
                    cognito_user.user_attributes.detect do |attribute|
         | 
| 75 | 
            -
                      attribute.name == attribute_name
         | 
| 76 | 
            -
                    end&.value
         | 
| 38 | 
            +
                    token_decoder.cognito_user
         | 
| 77 39 | 
             
                  end
         | 
| 78 40 |  | 
| 79 | 
            -
                  def  | 
| 80 | 
            -
                     | 
| 41 | 
            +
                  def local_user
         | 
| 42 | 
            +
                    LocalUserMapper.find token_decoder
         | 
| 81 43 | 
             
                  end
         | 
| 82 44 |  | 
| 83 | 
            -
                  def  | 
| 84 | 
            -
                    @ | 
| 85 | 
            -
                                                                      algorithms: ['RS256'], jwks: jwks)
         | 
| 45 | 
            +
                  def token_decoder
         | 
| 46 | 
            +
                    @token_decoder ||= TokenDecoder.new(token)
         | 
| 86 47 | 
             
                  end
         | 
| 87 48 |  | 
| 88 49 | 
             
                  def token
         | 
| @@ -0,0 +1,45 @@ | |
| 1 | 
            +
            module Warden
         | 
| 2 | 
            +
              module Cognito
         | 
| 3 | 
            +
                class TokenDecoder
         | 
| 4 | 
            +
                  attr_reader :jwk_loader, :token
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  def initialize(token)
         | 
| 7 | 
            +
                    @token = token
         | 
| 8 | 
            +
                    @jwk_loader = JwkLoader.new
         | 
| 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.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 | 
            +
                  private
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  def token_attribute(attribute_name)
         | 
| 35 | 
            +
                    decoded_token.first[attribute_name] if decoded_token.first.key? attribute_name
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  def cognito_user_attribute(attribute_name)
         | 
| 39 | 
            +
                    cognito_user.user_attributes.detect do |attribute|
         | 
| 40 | 
            +
                      attribute.name == attribute_name
         | 
| 41 | 
            +
                    end&.value
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
            end
         | 
| @@ -1,20 +1,14 @@ | |
| 1 1 | 
             
            module Warden
         | 
| 2 2 | 
             
              module Cognito
         | 
| 3 | 
            -
                 | 
| 4 | 
            -
                   | 
| 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 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
                    private
         | 
| 6 | 
            +
                  def find_by_cognito_username(username)
         | 
| 7 | 
            +
                    user_repository.find_by_cognito_username(username)
         | 
| 8 | 
            +
                  end
         | 
| 14 9 |  | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
                    end
         | 
| 10 | 
            +
                  def find_by_cognito_attribute(arg)
         | 
| 11 | 
            +
                    user_repository.find_by_cognito_attribute(arg)
         | 
| 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)
         | 
| 8 | 
            +
                      new.call(cognito_user)
         | 
| 9 | 
            +
                    end
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def call(cognito_user)
         | 
| 13 | 
            +
                    after_local_user_not_found&.call(cognito_user)
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
            end
         | 
    
        data/warden-cognito.gemspec
    CHANGED
    
    | @@ -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'
         | 
| @@ -39,7 +39,7 @@ Gem::Specification.new do |spec| | |
| 39 39 | 
             
              spec.add_dependency 'jwt', '~> 2.1'
         | 
| 40 40 | 
             
              spec.add_dependency 'warden', '~> 1.2'
         | 
| 41 41 |  | 
| 42 | 
            -
              spec.add_development_dependency 'bundler' | 
| 42 | 
            +
              spec.add_development_dependency 'bundler'
         | 
| 43 43 | 
             
              spec.add_development_dependency 'pry-byebug', '~> 3.7'
         | 
| 44 44 | 
             
              spec.add_development_dependency 'rack-test', '~> 1.1'
         | 
| 45 45 | 
             
              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. | 
| 4 | 
            +
              version: 0.2.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Juan F. Pérez
         | 
| 8 | 
            -
             | 
| 8 | 
            +
            - Léo Figea
         | 
| 9 | 
            +
            autorequire:
         | 
| 9 10 | 
             
            bindir: exe
         | 
| 10 11 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2020-11- | 
| 12 | 
            +
            date: 2020-11-26 00:00:00.000000000 Z
         | 
| 12 13 | 
             
            dependencies:
         | 
| 13 14 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 15 | 
             
              name: activesupport
         | 
| @@ -98,16 +99,16 @@ dependencies: | |
| 98 99 | 
             
              name: bundler
         | 
| 99 100 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 100 101 | 
             
                requirements:
         | 
| 101 | 
            -
                - - " | 
| 102 | 
            +
                - - ">="
         | 
| 102 103 | 
             
                  - !ruby/object:Gem::Version
         | 
| 103 | 
            -
                    version: ' | 
| 104 | 
            +
                    version: '0'
         | 
| 104 105 | 
             
              type: :development
         | 
| 105 106 | 
             
              prerelease: false
         | 
| 106 107 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 107 108 | 
             
                requirements:
         | 
| 108 | 
            -
                - - " | 
| 109 | 
            +
                - - ">="
         | 
| 109 110 | 
             
                  - !ruby/object:Gem::Version
         | 
| 110 | 
            -
                    version: ' | 
| 111 | 
            +
                    version: '0'
         | 
| 111 112 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 112 113 | 
             
              name: pry-byebug
         | 
| 113 114 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -189,7 +190,6 @@ files: | |
| 189 190 | 
             
            - ".gitignore"
         | 
| 190 191 | 
             
            - ".rspec"
         | 
| 191 192 | 
             
            - ".rubocop.yml"
         | 
| 192 | 
            -
            - ".ruby-version"
         | 
| 193 193 | 
             
            - ".travis.yml"
         | 
| 194 194 | 
             
            - CHANGELOG.md
         | 
| 195 195 | 
             
            - Dockerfile
         | 
| @@ -203,8 +203,13 @@ files: | |
| 203 203 | 
             
            - lib/warden/cognito.rb
         | 
| 204 204 | 
             
            - lib/warden/cognito/authenticatable_strategy.rb
         | 
| 205 205 | 
             
            - lib/warden/cognito/cognito_client.rb
         | 
| 206 | 
            +
            - lib/warden/cognito/jwk_loader.rb
         | 
| 207 | 
            +
            - lib/warden/cognito/local_user_mapper.rb
         | 
| 208 | 
            +
            - lib/warden/cognito/test_helpers.rb
         | 
| 206 209 | 
             
            - lib/warden/cognito/token_authenticatable_strategy.rb
         | 
| 210 | 
            +
            - lib/warden/cognito/token_decoder.rb
         | 
| 207 211 | 
             
            - lib/warden/cognito/user_helper.rb
         | 
| 212 | 
            +
            - lib/warden/cognito/user_not_found_callback.rb
         | 
| 208 213 | 
             
            - lib/warden/cognito/version.rb
         | 
| 209 214 | 
             
            - warden-cognito.gemspec
         | 
| 210 215 | 
             
            homepage: https://github.com/barkibu/warden-cognito
         | 
| @@ -214,7 +219,7 @@ metadata: | |
| 214 219 | 
             
              homepage_uri: https://github.com/barkibu/warden-cognito
         | 
| 215 220 | 
             
              source_code_uri: https://github.com/barkibu/warden-cognito
         | 
| 216 221 | 
             
              changelog_uri: https://github.com/barkibu/warden-cognito/blob/master/CHANGELOG.md
         | 
| 217 | 
            -
            post_install_message: | 
| 222 | 
            +
            post_install_message:
         | 
| 218 223 | 
             
            rdoc_options: []
         | 
| 219 224 | 
             
            require_paths:
         | 
| 220 225 | 
             
            - lib
         | 
| @@ -229,8 +234,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 229 234 | 
             
                - !ruby/object:Gem::Version
         | 
| 230 235 | 
             
                  version: '0'
         | 
| 231 236 | 
             
            requirements: []
         | 
| 232 | 
            -
            rubygems_version: 3. | 
| 233 | 
            -
            signing_key: | 
| 237 | 
            +
            rubygems_version: 3.1.4
         | 
| 238 | 
            +
            signing_key:
         | 
| 234 239 | 
             
            specification_version: 4
         | 
| 235 240 | 
             
            summary: Amazon Cognito authentication for Warden
         | 
| 236 241 | 
             
            test_files: []
         | 
    
        data/.ruby-version
    DELETED
    
    | @@ -1 +0,0 @@ | |
| 1 | 
            -
            2.6.5
         |