strongmind-auth 1.0.11 → 1.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5744571fb4c34f46eab563a74d0f09ff0da1a5d88be521d785d42bf11537b354
4
- data.tar.gz: '036694d87a468271a854342b34823357649def9b7ee12fbccbaa9e1384c90a70'
3
+ metadata.gz: 5f8024e8a6ede6f16c3ab5a900c19e0aa736a703ac36d6668287b2a27ec61928
4
+ data.tar.gz: 7d63c841f85f811124902d077989ea5bc683923258048164f10d85f79ca41b06
5
5
  SHA512:
6
- metadata.gz: 56e34ffb177bf0949cf0d8e670dd407e8c554ad88138432054b9bd1439ca989b6d5a020a461c9dc9204b5ead7dd81ef852e89114b7ea2d534dca7c44e6e8d610
7
- data.tar.gz: 144c638476dabe423587f743b175a49a0e3a325ac64db51af008aab7ef3282e37e0471e99ff7c9698721bd74852e50d246cc7bf75e5fa6f4e5d4a5fc111dbc28
6
+ metadata.gz: 836af65fff974d2ec9b16166a72e0203f41eb7a3b59cee6054bd32b53fb7e1980b6e9f4295586731a54ab20c20d5205ba4a91687cdfe68c01d269d979d74bbd3
7
+ data.tar.gz: d4195a3e2c67eeb856e6374d0d9386af87001fa730708ba578b3f3a92ee7a15b26fd3965df6597f91680ce660744d1642f2cb1b931937e3bdc609229fe905e93
@@ -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 Strongmind::Exceptions::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,4 +1,5 @@
1
1
  require "strongmind/common_nav_fetcher"
2
+ require "strongmind/exceptions"
2
3
 
3
4
  module StrongMindNav
4
5
  extend ActiveSupport::Concern
@@ -10,7 +11,7 @@ module StrongMindNav
10
11
  @top_navbar_html = navbar[:top_navbar_html]
11
12
  @bottom_navbar_html = navbar[:bottom_navbar_html]
12
13
  @theme_css = navbar[:theme_css]
13
- rescue Strongmind::CommonNavFetcher::TokenNotFoundError, Strongmind::CommonNavFetcher::UserNotFoundError => e
14
+ rescue Strongmind::Exceptions::TokenNotFoundError, Strongmind::Exceptions::UserNotFoundError => e
14
15
  Sentry.capture_exception(e)
15
16
  Rails.logger.error(e)
16
17
  flash[:alert] = e.inspect if Rails.env.development?
@@ -18,6 +19,7 @@ module StrongMindNav
18
19
  render 'logins/index'
19
20
  rescue Exception => e
20
21
  Sentry.capture_exception(e)
22
+ Rails.logger.error(e)
21
23
  @top_navbar_html = render_to_string(partial: 'layouts/loading_navbar').html_safe
22
24
  end
23
25
  end
@@ -10,6 +10,8 @@ module Users
10
10
  User.auth_token_cache = auth
11
11
  @user = User.with_credentials(auth)
12
12
 
13
+ render plain: "You do not have permission to access this application.", status: :unauthorized and return if @user.nil?
14
+
13
15
  session[:refresh_token] = request.env['omniauth.auth'].credentials['refresh_token']
14
16
  flash.delete(:notice)
15
17
 
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Users
4
+
4
5
  class SessionsController < Devise::SessionsController
6
+ include JwtUtilities
7
+
5
8
  skip_before_action :fetch_common_nav
9
+ skip_before_action :verify_authenticity_token, only: :endsession
6
10
 
7
11
  def login
8
12
  redirect_to user_strongmind_omniauth_authorize_url
@@ -12,5 +16,31 @@ module Users
12
16
  redirect_to user_strongmind_omniauth_authorize_url
13
17
  end
14
18
 
19
+ def endsession
20
+ headers = { 'Cache-Control' => 'no-store' }
21
+ if jwt_valid?(params[:logout_token], 'http://schemas.openid.net/event/backchannel-logout')
22
+ payload, _header = JWT.decode(params[:logout_token], nil, false)
23
+ user_identity = payload['sub']
24
+ user = User.find_by(uid: user_identity)
25
+ user.invalidate_all_sessions!
26
+ render json: {}, status: :ok, headers:
27
+ else
28
+ render json: {}, status: :bad_request, headers:
29
+ end
30
+ end
31
+
32
+ def initiate_backchannel_logout
33
+ user_token_info = fetch_user_token_info
34
+
35
+ id_token_hint = user_token_info[:id_token]
36
+ token = user_token_info[:access_token]
37
+ current_user&.invalidate_all_sessions!
38
+ identity_base_url = ENV['IDENTITY_BASE_URL']
39
+ redirect_to "#{identity_base_url}/connect/endsession?id_token_hint=#{id_token_hint}", headers: {
40
+ 'Content-Type' => 'application/json',
41
+ 'Authorization' => "Bearer #{token}"
42
+ }, allow_other_host: true
43
+ end
44
+
15
45
  end
16
46
  end
@@ -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
- post 'users/sign_out', to: 'devise/sessions#destroy'
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\n before_action :authenticate_user!\n before_action :fetch_common_nav\n
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\n\n"
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 "add_uid_to_user.rb", "db/migrate/add_uid_to_user.rb"
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)
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class AddUidToUser < ActiveRecord::Migration[7.0]
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
@@ -1,5 +1,5 @@
1
1
  module Strongmind
2
2
  module Auth
3
- VERSION = "1.0.11"
3
+ VERSION = "1.0.13"
4
4
  end
5
5
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
-
2
+ require "strongmind/exceptions"
3
3
  require 'platform_sdk'
4
4
 
5
5
  module Strongmind
@@ -9,12 +9,8 @@ module Strongmind
9
9
 
10
10
  include Rails.application.routes.url_helpers
11
11
 
12
- class TokenNotFoundError < StandardError; end
13
-
14
- class UserNotFoundError < StandardError; end
15
-
16
12
  def initialize(user, request)
17
- raise UserNotFoundError, 'User not found' unless user.present?
13
+ raise Strongmind::Exceptions::UserNotFoundError, 'User not found' unless user.present?
18
14
  raise ArgumentError, 'Request not found' unless request.present?
19
15
 
20
16
  @user = user
@@ -54,7 +50,7 @@ module Strongmind
54
50
  cache_data = Rails.cache.fetch(user.uid)
55
51
  cache_missing_message = " - check your caching settings (switch to file or redis)" if Rails.env.development?
56
52
  unless cache_data&.key?(:access_token)
57
- raise TokenNotFoundError, "Token not found for user #{user.uid}#{cache_missing_message}"
53
+ raise Strongmind::Exceptions::TokenNotFoundError, "Token not found for user #{user.uid}#{cache_missing_message}"
58
54
  end
59
55
 
60
56
  cache_data[:access_token]
@@ -83,14 +79,12 @@ module Strongmind
83
79
  end
84
80
 
85
81
  def nav_item_data(item)
86
- url = send(item[:path_method])
82
+ url = item[:url]
87
83
  {
88
84
  name: item[:name],
89
85
  icon: item[:icon],
90
- url:,
91
- is_disabled: item[:feature_flag] ? !user.feature_flag_enabled?(item[:feature_flag]) : false,
92
- is_active: current_page?(url),
93
- is_external: false
86
+ url: url,
87
+ is_active: current_page?(url)
94
88
  }
95
89
  end
96
90
  end
@@ -0,0 +1,9 @@
1
+ module Strongmind
2
+ module Exceptions
3
+ class TokenNotFoundError < StandardError; end
4
+
5
+ class UserNotFoundError < StandardError; end
6
+
7
+ class RefreshTokenExpiredError < StandardError; end
8
+ end
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.11
4
+ version: 1.0.13
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-15 00:00:00.000000000 Z
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,13 +121,14 @@ 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/add_uid_to_user.rb
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
127
128
  - lib/strongmind/auth/engine.rb
128
129
  - lib/strongmind/auth/version.rb
129
130
  - lib/strongmind/common_nav_fetcher.rb
131
+ - lib/strongmind/exceptions.rb
130
132
  - lib/tasks/rails/auth_tasks.rake
131
133
  - lib/tasks/strongmind/auth_tasks.rake
132
134
  homepage: https://www.strongmind.com