strongmind-auth 1.0.11 → 1.0.12
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/app/controllers/concerns/jwt_utilities.rb +90 -0
- data/app/controllers/users/sessions_controller.rb +32 -0
- data/app/models/user_base.rb +10 -0
- data/config/routes.rb +1 -1
- data/lib/generators/strongmind/install_generator.rb +15 -3
- data/lib/generators/strongmind/templates/{add_uid_to_user.rb → add_uid_and_session_token_to_user.rb} +2 -1
- data/lib/strongmind/auth/version.rb +1 -1
- metadata +4 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 9f0de84646fc0bb458a34c17e3b6ba8e8f5754252cb5da1957822e75a7c52d4f
         | 
| 4 | 
            +
              data.tar.gz: 3f944f111eb1b254ed97a13baf7b5e0d74401bb4edce2326ed36a94742af4499
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: cbba7ce9b16417331e9ec33a7b25225bb2ff3d3bc9651ac36936543197f1e95caec5c842100965b6564150c92015d46af23dfab26999db0f4847755d112eb775
         | 
| 7 | 
            +
              data.tar.gz: 48fa3ec4e620252c4f1dfb88cd3f58df46f0054defe2f348d3282cfde28dc58829b628b6dfa5f054c5e2681b105b92b31e5c984a5711fdb68cfd1abd5b6cc7f0
         | 
| @@ -0,0 +1,90 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            # Mix-in for handling JWTs
         | 
| 4 | 
            +
            module JwtUtilities
         | 
| 5 | 
            +
              extend ActiveSupport::Concern
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              def jwt_valid?(jwt, condition_key = nil, scopes = [])
         | 
| 8 | 
            +
                begin
         | 
| 9 | 
            +
                  payload, _header = JWT.decode(jwt, public_key, true, {
         | 
| 10 | 
            +
                    verify_iat: true,
         | 
| 11 | 
            +
                    verify_iss: true,
         | 
| 12 | 
            +
                    verify_aud: true,
         | 
| 13 | 
            +
                    verify_sub: true,
         | 
| 14 | 
            +
                    algorithm: 'RS256',
         | 
| 15 | 
            +
                    leeway: 60
         | 
| 16 | 
            +
                  })
         | 
| 17 | 
            +
                rescue JWT::DecodeError => e
         | 
| 18 | 
            +
                  Rails.logger.error e.message
         | 
| 19 | 
            +
                  return false
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                payload = payload.with_indifferent_access
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                unless !scopes.empty? && payload['scope'].present? && payload['scope'].all? { |elem| scopes.include?(elem) }
         | 
| 25 | 
            +
                  return false
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                return false unless payload['nonce'].nil?
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                return false unless condition_key.nil? || payload['events'].key?(condition_key)
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                true
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
              def public_key
         | 
| 36 | 
            +
                Rails.cache.fetch('jwt_utilities_public_key', expires_in: 1.day) do
         | 
| 37 | 
            +
                  x5c_val = OpenIDConnect::Discovery::Provider::Config.discover!(ENV['IDENTITY_BASE_URL']).jwks.first['x5c'].first
         | 
| 38 | 
            +
                  cert = OpenSSL::X509::Certificate.new(Base64.decode64(x5c_val))
         | 
| 39 | 
            +
                  cert.public_key
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
              private
         | 
| 44 | 
            +
             | 
| 45 | 
            +
              def fetch_user_token_info
         | 
| 46 | 
            +
                user_jwt(session)
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
              def user_access_token(session_data)
         | 
| 50 | 
            +
                tokens = user_jwt(session_data)
         | 
| 51 | 
            +
                tokens[:access_token]
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
              def user_jwt(session_data)
         | 
| 55 | 
            +
                tokens = current_user.nil? ? nil : Rails.cache.read(current_user&.uid)
         | 
| 56 | 
            +
                validate_tokens(tokens) unless tokens.nil?
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                if tokens.nil?
         | 
| 59 | 
            +
                  tokens = generate_tokens(session_data)
         | 
| 60 | 
            +
                  validate_tokens(tokens)
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                  unless current_user.nil?
         | 
| 63 | 
            +
                    tokens[:expires_in] = 1.hour.to_i if tokens[:expires_in].nil?
         | 
| 64 | 
            +
                    Rails.cache.write(current_user&.uid, tokens)
         | 
| 65 | 
            +
                  end
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
                session_data[:refresh_token] = tokens[:refresh_token]
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                tokens
         | 
| 70 | 
            +
              end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
              def validate_tokens(tokens)
         | 
| 73 | 
            +
                return unless tokens[:error] == 'invalid_grant' || !tokens[:refresh_token]
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                raise RefreshTokenExpired, tokens[:error]
         | 
| 76 | 
            +
              end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
              def generate_tokens(session_data)
         | 
| 79 | 
            +
                identity_base_url = ENV['IDENTITY_BASE_URL']
         | 
| 80 | 
            +
                identity_client_id = ENV['IDENTITY_CLIENT_ID']
         | 
| 81 | 
            +
                response = Faraday.post("#{identity_base_url}/connect/token", {
         | 
| 82 | 
            +
                  client_id: identity_client_id,
         | 
| 83 | 
            +
                  client_secret: ENV['IDENTITY_CLIENT_SECRET'],
         | 
| 84 | 
            +
                  grant_type: 'refresh_token',
         | 
| 85 | 
            +
                  refresh_token: session_data[:refresh_token]
         | 
| 86 | 
            +
                })
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                JSON.parse(response.body, symbolize_names: true)
         | 
| 89 | 
            +
              end
         | 
| 90 | 
            +
            end
         | 
| @@ -1,8 +1,14 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            module Users
         | 
| 4 | 
            +
              class RefreshTokenExpired < StandardError
         | 
| 5 | 
            +
              end
         | 
| 6 | 
            +
             | 
| 4 7 | 
             
              class SessionsController < Devise::SessionsController
         | 
| 8 | 
            +
                include JwtUtilities
         | 
| 9 | 
            +
             | 
| 5 10 | 
             
                skip_before_action :fetch_common_nav
         | 
| 11 | 
            +
                skip_before_action :verify_authenticity_token, only: :endsession
         | 
| 6 12 |  | 
| 7 13 | 
             
                def login
         | 
| 8 14 | 
             
                  redirect_to user_strongmind_omniauth_authorize_url
         | 
| @@ -12,5 +18,31 @@ module Users | |
| 12 18 | 
             
                  redirect_to user_strongmind_omniauth_authorize_url
         | 
| 13 19 | 
             
                end
         | 
| 14 20 |  | 
| 21 | 
            +
                def endsession
         | 
| 22 | 
            +
                  headers = { 'Cache-Control' => 'no-store' }
         | 
| 23 | 
            +
                  if jwt_valid?(params[:logout_token], 'http://schemas.openid.net/event/backchannel-logout')
         | 
| 24 | 
            +
                    payload, _header = JWT.decode(params[:logout_token], nil, false)
         | 
| 25 | 
            +
                    user_identity = payload['sub']
         | 
| 26 | 
            +
                    user = User.find_by(uid: user_identity)
         | 
| 27 | 
            +
                    user.invalidate_all_sessions!
         | 
| 28 | 
            +
                    render json: {}, status: :ok, headers:
         | 
| 29 | 
            +
                  else
         | 
| 30 | 
            +
                    render json: {}, status: :bad_request, headers:
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                def initiate_backchannel_logout
         | 
| 35 | 
            +
                  user_token_info = fetch_user_token_info
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  id_token_hint = user_token_info[:id_token]
         | 
| 38 | 
            +
                  token = user_token_info[:access_token]
         | 
| 39 | 
            +
                  current_user&.invalidate_all_sessions!
         | 
| 40 | 
            +
                  identity_base_url = ENV['IDENTITY_BASE_URL']
         | 
| 41 | 
            +
                  redirect_to "#{identity_base_url}/connect/endsession?id_token_hint=#{id_token_hint}", headers: {
         | 
| 42 | 
            +
                    'Content-Type' => 'application/json',
         | 
| 43 | 
            +
                    'Authorization' => "Bearer #{token}"
         | 
| 44 | 
            +
                  }, allow_other_host: true
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 15 47 | 
             
              end
         | 
| 16 48 | 
             
            end
         | 
    
        data/app/models/user_base.rb
    CHANGED
    
    | @@ -28,4 +28,14 @@ class UserBase < ApplicationRecord | |
| 28 28 | 
             
              def auth_token_cache
         | 
| 29 29 | 
             
                Rails.cache.read(uid)
         | 
| 30 30 | 
             
              end
         | 
| 31 | 
            +
              
         | 
| 32 | 
            +
              def authenticatable_salt
         | 
| 33 | 
            +
                return super unless session_token
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                "#{super}#{session_token}"
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
              def invalidate_all_sessions!
         | 
| 39 | 
            +
                update_attribute(:session_token, SecureRandom.hex)
         | 
| 40 | 
            +
              end
         | 
| 31 41 | 
             
            end
         | 
    
        data/config/routes.rb
    CHANGED
    
    | @@ -8,7 +8,7 @@ Rails.application.routes.draw do | |
| 8 8 | 
             
              }
         | 
| 9 9 |  | 
| 10 10 | 
             
              devise_scope :user do
         | 
| 11 | 
            -
                 | 
| 11 | 
            +
                get 'users/sign_out', to: 'users/sessions#initiate_backchannel_logout'
         | 
| 12 12 |  | 
| 13 13 | 
             
                unauthenticated do
         | 
| 14 14 | 
             
                  root 'logins#index', as: :unauthenticated_root
         | 
| @@ -13,13 +13,25 @@ module Strongmind | |
| 13 13 |  | 
| 14 14 | 
             
                def protect_app_files_and_add_nav
         | 
| 15 15 | 
             
                  inject_into_file "app/controllers/application_controller.rb", after: "class ApplicationController < ActionController::Base\n" do
         | 
| 16 | 
            -
                    "  include StrongMindNav | 
| 16 | 
            +
                    "  include StrongMindNav
         | 
| 17 | 
            +
              before_action :authenticate_user!
         | 
| 18 | 
            +
              before_action :fetch_common_nav
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              rescue_from Exceptions::RefreshTokenExpiredError do
         | 
| 21 | 
            +
                current_user&.invalidate_all_sessions!
         | 
| 22 | 
            +
                redirect_to \"#{ENV['IDENTITY_BASE_URL']}/connect/endsession\", headers: {
         | 
| 23 | 
            +
                    'Content-Type' => 'application/json'
         | 
| 24 | 
            +
                    }, allow_other_host: true
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 17 27 | 
             
              # Implement the list of menu items for the application
         | 
| 18 28 | 
             
              # def menu_items
         | 
| 19 29 | 
             
              #   [
         | 
| 20 30 | 
             
              #     { name: 'Home', icon: 'fa-solid fa-house', path_method: :root_path }
         | 
| 21 31 | 
             
              #   ]
         | 
| 22 | 
            -
              # end | 
| 32 | 
            +
              # end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            "
         | 
| 23 35 |  | 
| 24 36 | 
             
                  end
         | 
| 25 37 | 
             
                end
         | 
| @@ -38,7 +50,7 @@ devise_scope :user do | |
| 38 50 | 
             
                end
         | 
| 39 51 |  | 
| 40 52 | 
             
                def uid_migration
         | 
| 41 | 
            -
                  migration_template " | 
| 53 | 
            +
                  migration_template "add_uid_and_session_token_to_user.rb", "db/migrate/add_uid_and_session_token_to_user.rb"
         | 
| 42 54 | 
             
                end
         | 
| 43 55 |  | 
| 44 56 | 
             
                def self.next_migration_number(path)
         | 
    
        data/lib/generators/strongmind/templates/{add_uid_to_user.rb → add_uid_and_session_token_to_user.rb}
    RENAMED
    
    | @@ -1,8 +1,9 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            class  | 
| 3 | 
            +
            class AddUidAndSessionTokenToUser < ActiveRecord::Migration[7.0]
         | 
| 4 4 | 
             
              def change
         | 
| 5 5 | 
             
                add_column :users, :uid, :string
         | 
| 6 | 
            +
                add_column :users, :session_token, :string
         | 
| 6 7 | 
             
                add_index :users, :uid, unique: true
         | 
| 7 8 | 
             
              end
         | 
| 8 9 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: strongmind-auth
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 1.0. | 
| 4 | 
            +
              version: 1.0.12
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Team Belding
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2024-03- | 
| 11 | 
            +
            date: 2024-03-18 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: rails
         | 
| @@ -105,6 +105,7 @@ files: | |
| 105 105 | 
             
            - Rakefile
         | 
| 106 106 | 
             
            - app/assets/config/strongmind_auth_manifest.js
         | 
| 107 107 | 
             
            - app/assets/stylesheets/strongmind/auth/application.css
         | 
| 108 | 
            +
            - app/controllers/concerns/jwt_utilities.rb
         | 
| 108 109 | 
             
            - app/controllers/concerns/strong_mind_nav.rb
         | 
| 109 110 | 
             
            - app/controllers/logins_controller.rb
         | 
| 110 111 | 
             
            - app/controllers/users/omniauth_callbacks_controller.rb
         | 
| @@ -120,7 +121,7 @@ files: | |
| 120 121 | 
             
            - config/routes.rb
         | 
| 121 122 | 
             
            - lib/generators/strongmind/USAGE
         | 
| 122 123 | 
             
            - lib/generators/strongmind/install_generator.rb
         | 
| 123 | 
            -
            - lib/generators/strongmind/templates/ | 
| 124 | 
            +
            - lib/generators/strongmind/templates/add_uid_and_session_token_to_user.rb
         | 
| 124 125 | 
             
            - lib/generators/strongmind/templates/env
         | 
| 125 126 | 
             
            - lib/generators/strongmind/templates/user.rb
         | 
| 126 127 | 
             
            - lib/strongmind/auth.rb
         |