zaikio-oauth_client 0.17.2 → 0.19.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/README.md +23 -6
- data/app/models/zaikio/access_token.rb +17 -3
- data/db/migrate/20220425130923_encrypt_tokens.rb +45 -0
- data/lib/zaikio/oauth_client/authenticatable.rb +5 -2
- data/lib/zaikio/oauth_client/version.rb +1 -1
- data/lib/zaikio/oauth_client.rb +24 -14
- metadata +5 -4
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: d82ba1e01192f3e9fac8f47bc5ded67fb2b869f724fcdcd26ec42718c4ca53f5
         | 
| 4 | 
            +
              data.tar.gz: 48a24101ed54396c68d96077c0eaf7dc16fbd317ecbae092773ae9f29a31b9bd
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 46884a09302f56b53e64e8ee2cd1172c5792a1ecf82ff6e0a8b054d979f86f3b63449a4bd79633c3f95f076e11e0f3bc5669b26d8988643232c784c92f447d5b
         | 
| 7 | 
            +
              data.tar.gz: f1e5992edef1cda3bd6b68f2089c07201264d2b1e64a4713ae0d6ec84af5e708172b9b53ceb76d38245645ec41ebf79f4da7d63a40e17a6441e4d7171f9e47e8
         | 
    
        data/README.md
    CHANGED
    
    | @@ -14,7 +14,17 @@ Then run `bundle install`. | |
| 14 14 |  | 
| 15 15 | 
             
            ## Setup & Configuration
         | 
| 16 16 |  | 
| 17 | 
            -
            ### 1.  | 
| 17 | 
            +
            ### 1. Setup Active Record encryption
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            Setup [Active Record Encryption](https://guides.rubyonrails.org/active_record_encryption.html#setup) by running:
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            ```
         | 
| 22 | 
            +
            rails db:encryption:init
         | 
| 23 | 
            +
            ```
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            (Continue generating the credentials each for different environments)
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            ### 2. Copy & run Migrations
         | 
| 18 28 |  | 
| 19 29 | 
             
            ```bash
         | 
| 20 30 | 
             
            rails zaikio_oauth_client:install:migrations
         | 
| @@ -24,7 +34,7 @@ rails db:migrate | |
| 24 34 | 
             
            This will create the tables:
         | 
| 25 35 | 
             
            + `zaikio_access_tokens`
         | 
| 26 36 |  | 
| 27 | 
            -
            ###  | 
| 37 | 
            +
            ### 3. Mount routes
         | 
| 28 38 |  | 
| 29 39 | 
             
            Add this to `config/routes.rb`:
         | 
| 30 40 |  | 
| @@ -32,7 +42,7 @@ Add this to `config/routes.rb`: | |
| 32 42 | 
             
            mount Zaikio::OAuthClient::Engine => "/zaikio"
         | 
| 33 43 | 
             
            ```
         | 
| 34 44 |  | 
| 35 | 
            -
            ###  | 
| 45 | 
            +
            ### 4. Configure Gem
         | 
| 36 46 |  | 
| 37 47 | 
             
            ```rb
         | 
| 38 48 | 
             
            # config/initializers/zaikio_oauth_client.rb
         | 
| @@ -70,7 +80,7 @@ end | |
| 70 80 | 
             
            ```
         | 
| 71 81 |  | 
| 72 82 |  | 
| 73 | 
            -
            ###  | 
| 83 | 
            +
            ### 5. Clean up outdated access tokens (recommended)
         | 
| 74 84 |  | 
| 75 85 | 
             
            To avoid keeping all expired oath and refresh tokens in your database, we recommend to implement their scheduled deletion. We recommend therefore to use a schedule gems such as [sidekiq](https://github.com/mperham/sidekiq) and [sidekiq-scheduler](https://github.com/moove-it/sidekiq-scheduler).
         | 
| 76 86 |  | 
| @@ -135,12 +145,19 @@ redirect_to zaikio_oauth_client.new_subscription_path(plan: "free") | |
| 135 145 |  | 
| 136 146 | 
             
            #### Session handling
         | 
| 137 147 |  | 
| 138 | 
            -
            The Zaikio gem engine will set a cookie for the  | 
| 148 | 
            +
            The Zaikio gem engine will set a cookie for the access token after a successful OAuth flow: `session[:zaikio_access_token_id]`.
         | 
| 139 149 |  | 
| 140 150 | 
             
            If you are using for example `Zaikio::Hub::Models`, you can use this snippet to set the current user:
         | 
| 141 151 |  | 
| 142 152 | 
             
            ```ruby
         | 
| 143 | 
            -
             | 
| 153 | 
            +
            access_token = Zaikio::OAuthClient.find_active_access_token(session[:zaikio_access_token_id])
         | 
| 154 | 
            +
            session[:zaikio_access_token_id] = access_token&.id
         | 
| 155 | 
            +
            Current.user = Zaikio::Hub::Models::Person.find_by(id: access_token&.bearer_id)
         | 
| 156 | 
            +
             | 
| 157 | 
            +
            unless Current.user
         | 
| 158 | 
            +
              session[:origin] = request.fullpath
         | 
| 159 | 
            +
              redirect_to zaikio_oauth_client.new_session_path
         | 
| 160 | 
            +
            end
         | 
| 144 161 | 
             
            ````
         | 
| 145 162 |  | 
| 146 163 | 
             
            You can then use `Current.user` anywhere.
         | 
| @@ -5,7 +5,11 @@ module Zaikio | |
| 5 5 | 
             
              class AccessToken < ApplicationRecord
         | 
| 6 6 | 
             
                self.table_name = "zaikio_access_tokens"
         | 
| 7 7 |  | 
| 8 | 
            -
                 | 
| 8 | 
            +
                # Encryption
         | 
| 9 | 
            +
                encrypts :token
         | 
| 10 | 
            +
                encrypts :refresh_token
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def self.build_from_access_token(access_token, requested_scopes: nil, include_refresh_token: true)
         | 
| 9 13 | 
             
                  payload = JWT.decode(access_token.token, nil, false).first rescue {} # rubocop:disable Style/RescueModifier
         | 
| 10 14 | 
             
                  scopes = access_token.params["scope"].split(",")
         | 
| 11 15 | 
             
                  new(
         | 
| @@ -14,7 +18,7 @@ module Zaikio | |
| 14 18 | 
             
                    bearer_id: access_token.params["bearer"]["id"],
         | 
| 15 19 | 
             
                    audience: access_token.params["audiences"].first,
         | 
| 16 20 | 
             
                    token: access_token.token,
         | 
| 17 | 
            -
                    refresh_token: access_token.refresh_token,
         | 
| 21 | 
            +
                    refresh_token: (access_token.refresh_token if include_refresh_token),
         | 
| 18 22 | 
             
                    expires_at: Time.strptime(access_token.expires_at.to_s, "%s"),
         | 
| 19 23 | 
             
                    scopes: scopes,
         | 
| 20 24 | 
             
                    requested_scopes: requested_scopes || scopes
         | 
| @@ -63,7 +67,7 @@ module Zaikio | |
| 63 67 | 
             
                end
         | 
| 64 68 |  | 
| 65 69 | 
             
                def bearer_klass
         | 
| 66 | 
            -
                  return unless Zaikio.const_defined?("Hub::Models", false) | 
| 70 | 
            +
                  return unless Zaikio.const_defined?("Hub::Models", false)
         | 
| 67 71 |  | 
| 68 72 | 
             
                  if Zaikio::Hub::Models.configuration.respond_to?(:"#{bearer_type.underscore}_class_name")
         | 
| 69 73 | 
             
                    Zaikio::Hub::Models.configuration.public_send(:"#{bearer_type.underscore}_class_name").constantize
         | 
| @@ -91,5 +95,15 @@ module Zaikio | |
| 91 95 | 
             
                  destroy
         | 
| 92 96 | 
             
                  nil
         | 
| 93 97 | 
             
                end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                def revoke!
         | 
| 100 | 
            +
                  return unless Zaikio.const_defined?("Hub::RevokedAccessToken", false)
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                  Zaikio::Hub.with_token(token) do
         | 
| 103 | 
            +
                    Zaikio::Hub::RevokedAccessToken.create
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
                rescue Zaikio::ConnectionError => e
         | 
| 106 | 
            +
                  Zaikio::OAuthClient.configuration.logger.warn "Access Token #{id} could not be revoked: #{e.message}"
         | 
| 107 | 
            +
                end
         | 
| 94 108 | 
             
              end
         | 
| 95 109 | 
             
            end
         | 
| @@ -0,0 +1,45 @@ | |
| 1 | 
            +
            class EncryptTokens < ActiveRecord::Migration[7.0]
         | 
| 2 | 
            +
              def change
         | 
| 3 | 
            +
                reversible do |dir|
         | 
| 4 | 
            +
                  dir.up do
         | 
| 5 | 
            +
                    rename_column :zaikio_access_tokens, :token, :unencrypted_token
         | 
| 6 | 
            +
                    rename_column :zaikio_access_tokens, :refresh_token, :unencrypted_refresh_token
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                    add_column :zaikio_access_tokens, :token, :string
         | 
| 9 | 
            +
                    add_column :zaikio_access_tokens, :refresh_token, :string
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    Zaikio::AccessToken.find_each do |access_token|
         | 
| 12 | 
            +
                      access_token.update(
         | 
| 13 | 
            +
                        token: access_token.unencrypted_token,
         | 
| 14 | 
            +
                        refresh_token: access_token.unencrypted_refresh_token
         | 
| 15 | 
            +
                      )
         | 
| 16 | 
            +
                    end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    change_column_null :zaikio_access_tokens, :token, false
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    remove_column :zaikio_access_tokens, :unencrypted_token, :string
         | 
| 21 | 
            +
                    remove_column :zaikio_access_tokens, :unencrypted_refresh_token, :string
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  dir.down do
         | 
| 25 | 
            +
                    add_column :zaikio_access_tokens, :unencrypted_token, :string
         | 
| 26 | 
            +
                    add_column :zaikio_access_tokens, :unencrypted_refresh_token, :string
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    Zaikio::AccessToken.find_each do |access_token|
         | 
| 29 | 
            +
                      access_token.update_columns(
         | 
| 30 | 
            +
                        unencrypted_token: access_token.token,
         | 
| 31 | 
            +
                        unencrypted_refresh_token: access_token.refresh_token
         | 
| 32 | 
            +
                      )
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    remove_column :zaikio_access_tokens, :token, :string
         | 
| 36 | 
            +
                    remove_column :zaikio_access_tokens, :refresh_token, :string
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    rename_column :zaikio_access_tokens, :unencrypted_token, :token
         | 
| 39 | 
            +
                    rename_column :zaikio_access_tokens, :unencrypted_refresh_token, :refresh_token
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    change_column_null :zaikio_access_tokens, :token, false
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
            end
         | 
| @@ -55,13 +55,16 @@ module Zaikio | |
| 55 55 | 
             
                  end
         | 
| 56 56 |  | 
| 57 57 | 
             
                  def destroy
         | 
| 58 | 
            -
                     | 
| 58 | 
            +
                    if (access_token = Zaikio::AccessToken.valid.or(Zaikio::AccessToken.valid_refresh)
         | 
| 59 | 
            +
                                                         .find_by(id: session[:zaikio_access_token_id]))
         | 
| 60 | 
            +
                      access_token.revoke!
         | 
| 61 | 
            +
                    end
         | 
| 59 62 | 
             
                    session.delete(:zaikio_access_token_id)
         | 
| 60 63 | 
             
                    session.delete(:origin)
         | 
| 61 64 |  | 
| 62 65 | 
             
                    redirect_to send(
         | 
| 63 66 | 
             
                      respond_to?(:after_destroy_path_for) ? :after_destroy_path_for : :default_after_destroy_path_for,
         | 
| 64 | 
            -
                       | 
| 67 | 
            +
                      access_token.id
         | 
| 65 68 | 
             
                    )
         | 
| 66 69 | 
             
                  end
         | 
| 67 70 |  | 
    
        data/lib/zaikio/oauth_client.rb
    CHANGED
    
    | @@ -6,7 +6,7 @@ require "zaikio/oauth_client/configuration" | |
| 6 6 | 
             
            require "zaikio/oauth_client/authenticatable"
         | 
| 7 7 |  | 
| 8 8 | 
             
            module Zaikio
         | 
| 9 | 
            -
              module OAuthClient
         | 
| 9 | 
            +
              module OAuthClient # rubocop:disable Metrics/ModuleLength
         | 
| 10 10 | 
             
                class << self
         | 
| 11 11 | 
             
                  attr_reader :client_name
         | 
| 12 12 |  | 
| @@ -58,9 +58,8 @@ module Zaikio | |
| 58 58 | 
             
                    end
         | 
| 59 59 | 
             
                  end
         | 
| 60 60 |  | 
| 61 | 
            -
                  # Finds  | 
| 62 | 
            -
                  #   *  | 
| 63 | 
            -
                  #     (if this fails, we fallback to getting a new token using client_credentials)
         | 
| 61 | 
            +
                  # Finds active access token, using the DB or Client Credentials flow
         | 
| 62 | 
            +
                  #   * It searches in the DB for an active access token
         | 
| 64 63 | 
             
                  #   * If the token does not exist, we'll get a new one using the client_credentials flow
         | 
| 65 64 | 
             
                  def get_access_token(bearer_id:, client_name: nil, bearer_type: "Person", scopes: nil, valid_for: 30.seconds)
         | 
| 66 65 | 
             
                    client_config = client_config_for(client_name || self.client_name)
         | 
| @@ -72,8 +71,6 @@ module Zaikio | |
| 72 71 | 
             
                                                     requested_scopes: scopes,
         | 
| 73 72 | 
             
                                                     valid_for: valid_for)
         | 
| 74 73 |  | 
| 75 | 
            -
                    token = token.refresh! if token&.expired?
         | 
| 76 | 
            -
             | 
| 77 74 | 
             
                    token ||= fetch_new_token(client_config: client_config,
         | 
| 78 75 | 
             
                                              bearer_type: bearer_type,
         | 
| 79 76 | 
             
                                              bearer_id: bearer_id,
         | 
| @@ -81,21 +78,31 @@ module Zaikio | |
| 81 78 | 
             
                    token
         | 
| 82 79 | 
             
                  end
         | 
| 83 80 |  | 
| 84 | 
            -
                  #  | 
| 85 | 
            -
                  #  | 
| 86 | 
            -
                  def  | 
| 87 | 
            -
                     | 
| 81 | 
            +
                  # This method can be used to find an active access token by id.
         | 
| 82 | 
            +
                  # It might refresh the access token to get an active one.
         | 
| 83 | 
            +
                  def find_active_access_token(id)
         | 
| 84 | 
            +
                    return unless id
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                    access_token = Zaikio::AccessToken.find_by(id: id)
         | 
| 87 | 
            +
                    access_token = access_token.refresh! if access_token&.expired?
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                    access_token
         | 
| 90 | 
            +
                  end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                  # Finds active access token with matching criteria for bearer and scopes.
         | 
| 93 | 
            +
                  def find_usable_access_token(client_name:, bearer_type:, bearer_id:, requested_scopes:, valid_for: 30.seconds) # rubocop:disable Metrics/MethodLength
         | 
| 94 | 
            +
                    configuration.logger.debug "Try to fetch token for client_name: #{client_name}, " \
         | 
| 88 95 | 
             
                                               "bearer #{bearer_type}/#{bearer_id}, requested_scopes: #{requested_scopes}"
         | 
| 89 96 |  | 
| 90 97 | 
             
                    fetch_access_token = lambda {
         | 
| 91 98 | 
             
                      Zaikio::AccessToken
         | 
| 92 99 | 
             
                        .where(audience: client_name)
         | 
| 93 | 
            -
                        . | 
| 100 | 
            +
                        .by_bearer(
         | 
| 94 101 | 
             
                          bearer_type: bearer_type,
         | 
| 95 102 | 
             
                          bearer_id: bearer_id,
         | 
| 96 | 
            -
                          requested_scopes: requested_scopes | 
| 97 | 
            -
                          valid_until: valid_for.from_now
         | 
| 103 | 
            +
                          requested_scopes: requested_scopes
         | 
| 98 104 | 
             
                        )
         | 
| 105 | 
            +
                        .valid(valid_for.from_now)
         | 
| 99 106 | 
             
                        .first
         | 
| 100 107 | 
             
                    }
         | 
| 101 108 |  | 
| @@ -113,7 +120,10 @@ module Zaikio | |
| 113 120 | 
             
                        bearer_id: bearer_id,
         | 
| 114 121 | 
             
                        scopes: scopes
         | 
| 115 122 | 
             
                      ),
         | 
| 116 | 
            -
                      requested_scopes: scopes
         | 
| 123 | 
            +
                      requested_scopes: scopes,
         | 
| 124 | 
            +
                      include_refresh_token: false
         | 
| 125 | 
            +
                      # Do not store refresh token on client credentials flow
         | 
| 126 | 
            +
                      # https://docs.zaikio.com/changelog/2022-08-09_client-credentials-drop-refresh-token.html
         | 
| 117 127 | 
             
                    ).tap(&:save!)
         | 
| 118 128 | 
             
                  end
         | 
| 119 129 |  | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: zaikio-oauth_client
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.19.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Zaikio GmbH
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2022- | 
| 11 | 
            +
            date: 2022-08-03 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: actionpack
         | 
| @@ -89,7 +89,7 @@ dependencies: | |
| 89 89 | 
             
                    version: '0.5'
         | 
| 90 90 | 
             
                - - "<"
         | 
| 91 91 | 
             
                  - !ruby/object:Gem::Version
         | 
| 92 | 
            -
                    version: ' | 
| 92 | 
            +
                    version: '3.0'
         | 
| 93 93 | 
             
              type: :runtime
         | 
| 94 94 | 
             
              prerelease: false
         | 
| 95 95 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| @@ -99,7 +99,7 @@ dependencies: | |
| 99 99 | 
             
                    version: '0.5'
         | 
| 100 100 | 
             
                - - "<"
         | 
| 101 101 | 
             
                  - !ruby/object:Gem::Version
         | 
| 102 | 
            -
                    version: ' | 
| 102 | 
            +
                    version: '3.0'
         | 
| 103 103 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 104 104 | 
             
              name: pg
         | 
| 105 105 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -157,6 +157,7 @@ files: | |
| 157 157 | 
             
            - db/migrate/20191017132048_create_zaikio_access_tokens.rb
         | 
| 158 158 | 
             
            - db/migrate/20210222135920_enhance_access_token_index.rb
         | 
| 159 159 | 
             
            - db/migrate/20210224154303_add_requested_scopes_to_zaikio_access_tokens.rb
         | 
| 160 | 
            +
            - db/migrate/20220425130923_encrypt_tokens.rb
         | 
| 160 161 | 
             
            - lib/tasks/zaikio_tasks.rake
         | 
| 161 162 | 
             
            - lib/zaikio/oauth_client.rb
         | 
| 162 163 | 
             
            - lib/zaikio/oauth_client/authenticatable.rb
         |