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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5744571fb4c34f46eab563a74d0f09ff0da1a5d88be521d785d42bf11537b354
4
- data.tar.gz: '036694d87a468271a854342b34823357649def9b7ee12fbccbaa9e1384c90a70'
3
+ metadata.gz: 9f0de84646fc0bb458a34c17e3b6ba8e8f5754252cb5da1957822e75a7c52d4f
4
+ data.tar.gz: 3f944f111eb1b254ed97a13baf7b5e0d74401bb4edce2326ed36a94742af4499
5
5
  SHA512:
6
- metadata.gz: 56e34ffb177bf0949cf0d8e670dd407e8c554ad88138432054b9bd1439ca989b6d5a020a461c9dc9204b5ead7dd81ef852e89114b7ea2d534dca7c44e6e8d610
7
- data.tar.gz: 144c638476dabe423587f743b175a49a0e3a325ac64db51af008aab7ef3282e37e0471e99ff7c9698721bd74852e50d246cc7bf75e5fa6f4e5d4a5fc111dbc28
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
@@ -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.12"
4
4
  end
5
5
  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.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-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,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/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