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
|