kinde_sdk 1.6.2 → 1.6.4
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/kinde_sdk/auth_controller.rb +131 -67
- data/app/helpers/auth_helper.rb +88 -0
- data/config/routes.rb +3 -1
- data/lib/kinde_sdk/client.rb +35 -14
- data/lib/kinde_sdk/current.rb +16 -0
- data/lib/kinde_sdk/engine.rb +6 -0
- data/lib/kinde_sdk/middleware.rb +16 -0
- data/lib/kinde_sdk/token_manager.rb +85 -0
- data/lib/kinde_sdk/token_store.rb +29 -0
- data/lib/kinde_sdk/version.rb +1 -1
- data/lib/kinde_sdk.rb +23 -26
- data/spec/examples.txt +24 -0
- data/spec/kinde_sdk_spec.rb +211 -125
- data/spec/spec_helper.rb +4 -4
- metadata +92 -88
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 897c5732757f381a16e53865635495a790a1d892f016711f5826a3b9e78a67e8
|
4
|
+
data.tar.gz: 34d03ded031c86085e03265fd4af1f427e7a0f7ba2e5d0d61ba215905f2acc43
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 98e37a1e37548d2729a839e14cfc1294b356e05eb1ba412ffb065a9bc1bf5c21e1e7b68691e76329ab32011df1b13187421223bcd5d835b44790456338b3b514
|
7
|
+
data.tar.gz: 2c92398e168a07de932144e38a80edd9504466b5b6ca3459962a7756dc1b264d5bf29eda81ab40a651fa5106dcc04f7c26c00c0d6ac9f3d36b35e3543ca301cf
|
@@ -6,18 +6,25 @@ require 'json'
|
|
6
6
|
require 'jwt'
|
7
7
|
|
8
8
|
module KindeSdk
|
9
|
+
# AuthController handles all authentication-related actions for the Kinde SDK
|
10
|
+
# including OAuth2 flows, token management, and session handling
|
9
11
|
class AuthController < ActionController::Base
|
10
|
-
|
12
|
+
include AuthHelper
|
13
|
+
|
11
14
|
before_action :validate_state, only: :callback
|
15
|
+
before_action :clear_auth_session, only: [:logout_callback, :logout]
|
12
16
|
|
17
|
+
# Initiates the OAuth2 authorization flow
|
18
|
+
# Generates a secure nonce and redirects to Kinde's authorization endpoint
|
19
|
+
# @return [void] Redirects to Kinde's authorization URL
|
13
20
|
def auth
|
14
|
-
# Generate a secure random nonce
|
21
|
+
# Generate a secure random nonce for CSRF protection
|
15
22
|
nonce = SecureRandom.urlsafe_base64(16)
|
16
23
|
|
17
|
-
#
|
24
|
+
# Get authorization URL and PKCE code verifier from SDK
|
18
25
|
auth_data = KindeSdk.auth_url(nonce: nonce)
|
19
26
|
|
20
|
-
# Store in session
|
27
|
+
# Store PKCE code verifier and nonce in session for validation
|
21
28
|
session[:code_verifier] = auth_data[:code_verifier] if auth_data[:code_verifier].present?
|
22
29
|
session[:auth_nonce] = nonce
|
23
30
|
session[:auth_state] = {
|
@@ -26,41 +33,31 @@ module KindeSdk
|
|
26
33
|
}
|
27
34
|
|
28
35
|
redirect_to auth_data[:url], allow_other_host: true
|
36
|
+
rescue StandardError => e
|
37
|
+
handle_error("Auth initialization failed", e)
|
29
38
|
end
|
30
39
|
|
40
|
+
# Handles the OAuth2 callback from Kinde
|
41
|
+
# Validates the response, exchanges code for tokens, and sets up the session
|
42
|
+
# @return [void] Redirects to root path on success
|
31
43
|
def callback
|
32
|
-
tokens =
|
33
|
-
|
34
|
-
code_verifier: KindeSdk.config.pkce_enabled ? session[:code_verifier] : nil
|
35
|
-
).slice(:access_token, :id_token, :refresh_token, :expires_at)
|
36
|
-
|
37
|
-
|
38
|
-
# Validate nonce in ID token
|
39
|
-
id_token = tokens[:id_token]
|
40
|
-
issuer = KindeSdk.config.domain
|
41
|
-
client_id = KindeSdk.config.client_id
|
42
|
-
original_nonce = session[:auth_nonce]
|
43
|
-
unless validate_nonce(id_token, original_nonce, issuer, client_id)
|
44
|
-
Rails.logger.warn("Nonce validation failed")
|
45
|
-
redirect_to "/", alert: "Invalid authentication nonce"
|
46
|
-
return
|
47
|
-
end
|
44
|
+
tokens = fetch_and_validate_tokens
|
45
|
+
return if performed?
|
48
46
|
|
49
|
-
# Store tokens and user in session
|
50
|
-
|
51
|
-
|
52
|
-
session
|
47
|
+
# Store tokens and user information in session
|
48
|
+
set_session_tokens(tokens)
|
49
|
+
|
50
|
+
# Clean up temporary auth session data
|
51
|
+
clear_auth_session
|
53
52
|
|
54
|
-
# Clear nonce and state after successful authentication
|
55
|
-
session.delete(:auth_nonce)
|
56
|
-
session.delete(:auth_state)
|
57
|
-
session.delete(:code_verifier)
|
58
53
|
redirect_to "/"
|
59
54
|
rescue StandardError => e
|
60
|
-
|
61
|
-
redirect_to "/", alert: "Authentication failed"
|
55
|
+
handle_error("Authentication callback failed", e)
|
62
56
|
end
|
63
57
|
|
58
|
+
# Handles machine-to-machine (M2M) authentication using client credentials
|
59
|
+
# Stores the access token in Redis for subsequent API calls
|
60
|
+
# @return [void] Redirects to management path on success
|
64
61
|
def client_credentials_auth
|
65
62
|
result = KindeSdk.client_credentials_access(
|
66
63
|
client_id: ENV["KINDE_MANAGEMENT_CLIENT_ID"],
|
@@ -68,30 +65,113 @@ module KindeSdk
|
|
68
65
|
)
|
69
66
|
|
70
67
|
if result["error"].present?
|
71
|
-
Rails.logger.error("Client credentials auth failed: #{result['error']}")
|
72
68
|
raise result["error"]
|
73
69
|
end
|
74
70
|
|
71
|
+
# Store M2M token in Redis with expiration
|
75
72
|
$redis.set("kinde_m2m_token", result["access_token"], ex: result["expires_in"].to_i)
|
76
73
|
redirect_to mgmt_path
|
74
|
+
rescue StandardError => e
|
75
|
+
handle_error("Client credentials authentication failed", e)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Refreshes the access token using the refresh token
|
79
|
+
# @return [void] Redirects to root path on success
|
80
|
+
def refresh_token
|
81
|
+
return redirect_with_error("No valid session found") unless session[:kinde_token_store].present?
|
82
|
+
|
83
|
+
if refresh_session_tokens
|
84
|
+
redirect_to "/"
|
85
|
+
else
|
86
|
+
redirect_with_error("Failed to refresh token")
|
87
|
+
end
|
88
|
+
rescue StandardError => e
|
89
|
+
handle_error("Token refresh failed", e)
|
77
90
|
end
|
78
91
|
|
92
|
+
# Initiates the logout process by redirecting to Kinde's logout endpoint
|
93
|
+
# @return [void] Redirects to Kinde's logout URL
|
79
94
|
def logout
|
80
|
-
|
95
|
+
# Ensure we have a valid logout URL configured
|
96
|
+
unless KindeSdk.config.logout_url.present?
|
97
|
+
redirect_with_error("Logout URL not configured")
|
98
|
+
return
|
99
|
+
end
|
100
|
+
|
101
|
+
# Get the logout URL with our callback URL
|
102
|
+
logout_url = KindeSdk.logout_url(
|
103
|
+
logout_url: KindeSdk.config.logout_url,
|
104
|
+
domain: KindeSdk.config.domain
|
105
|
+
)
|
106
|
+
|
107
|
+
# Clear local session before redirecting to Kinde
|
108
|
+
session.delete(:kinde_token_store)
|
109
|
+
session.delete(:kinde_user)
|
110
|
+
clear_auth_session
|
111
|
+
|
112
|
+
# Redirect to Kinde's logout endpoint
|
113
|
+
redirect_to logout_url, allow_other_host: true
|
114
|
+
rescue StandardError => e
|
115
|
+
handle_error("Logout initialization failed", e)
|
81
116
|
end
|
82
117
|
|
118
|
+
# Handles the callback after successful logout
|
119
|
+
# @return [void] Redirects to root path
|
83
120
|
def logout_callback
|
84
|
-
|
121
|
+
# Session is already cleared in logout method
|
122
|
+
# Just redirect to home page
|
85
123
|
redirect_to "/"
|
86
124
|
end
|
87
125
|
|
88
126
|
private
|
127
|
+
|
128
|
+
# Fetches and validates tokens from the authorization code
|
129
|
+
# @return [Hash] The validated tokens or nil if validation fails
|
130
|
+
def fetch_and_validate_tokens
|
131
|
+
tokens = KindeSdk.fetch_tokens(
|
132
|
+
params[:code],
|
133
|
+
code_verifier: KindeSdk.config.pkce_enabled ? session[:code_verifier] : nil,
|
134
|
+
redirect_uri: KindeSdk.config.callback_url
|
135
|
+
)
|
136
|
+
|
137
|
+
if tokens[:error].present?
|
138
|
+
redirect_with_error("Token exchange failed: #{tokens[:error]}")
|
139
|
+
return nil
|
140
|
+
end
|
141
|
+
|
142
|
+
# Validate tokens using SDK's built-in validation
|
143
|
+
begin
|
144
|
+
KindeSdk.validate_jwt_token(tokens)
|
145
|
+
|
146
|
+
# Validate nonce in ID token to prevent replay attacks
|
147
|
+
decoded_token = JWT.decode(tokens[:id_token], nil, false)[0]
|
148
|
+
unless decoded_token['nonce'] == session[:auth_nonce]
|
149
|
+
redirect_with_error("Invalid authentication nonce")
|
150
|
+
return nil
|
151
|
+
end
|
152
|
+
|
153
|
+
tokens
|
154
|
+
rescue JWT::DecodeError => e
|
155
|
+
redirect_with_error("Token validation failed: #{e.message}")
|
156
|
+
nil
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Clears temporary authentication data from the session
|
161
|
+
# @return [void]
|
162
|
+
def clear_auth_session
|
163
|
+
session.delete(:auth_nonce)
|
164
|
+
session.delete(:auth_state)
|
165
|
+
session.delete(:code_verifier)
|
166
|
+
end
|
89
167
|
|
168
|
+
# Validates the state parameter to prevent CSRF attacks
|
169
|
+
# Checks state existence, matches returned state with stored state, and validates expiration
|
170
|
+
# @return [void]
|
90
171
|
def validate_state
|
91
172
|
# Check if nonce and state exist in session
|
92
|
-
unless session[:
|
93
|
-
|
94
|
-
redirect_to "/", alert: "Invalid authentication state"
|
173
|
+
unless session[:auth_state]
|
174
|
+
redirect_with_error("Invalid authentication state")
|
95
175
|
return
|
96
176
|
end
|
97
177
|
|
@@ -107,46 +187,30 @@ module KindeSdk
|
|
107
187
|
|
108
188
|
# Verify returned state matches the state extracted from the redirect_url
|
109
189
|
unless returned_state.present? && returned_state == stored_state_from_url
|
110
|
-
|
111
|
-
redirect_to "/", alert: "Invalid authentication state"
|
190
|
+
redirect_with_error("Invalid authentication state")
|
112
191
|
return
|
113
192
|
end
|
114
193
|
|
115
|
-
#
|
194
|
+
# Check state age (expires after 15 minutes)
|
116
195
|
if Time.current.to_i - stored_state["requested_at"] > 900
|
117
|
-
|
118
|
-
redirect_to "/", alert: "Authentication session expired"
|
196
|
+
redirect_with_error("Authentication session expired")
|
119
197
|
return
|
120
198
|
end
|
121
199
|
end
|
122
200
|
|
201
|
+
# Handles errors during authentication
|
202
|
+
# @param message [String] The error message
|
203
|
+
# @param error [Exception] The exception that occurred
|
204
|
+
# @return [void]
|
205
|
+
def handle_error(message, error)
|
206
|
+
redirect_with_error(message)
|
207
|
+
end
|
123
208
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
decoded_token = JWT.decode(
|
130
|
-
id_token,
|
131
|
-
nil,
|
132
|
-
true,
|
133
|
-
algorithm: 'RS256',
|
134
|
-
iss: issuer,
|
135
|
-
aud: client_id,
|
136
|
-
verify_iss: true,
|
137
|
-
verify_aud: true,
|
138
|
-
jwks: { keys: jwks['keys'] }
|
139
|
-
)
|
140
|
-
|
141
|
-
payload = decoded_token[0]
|
142
|
-
nonce_from_token = payload['nonce']
|
143
|
-
|
144
|
-
nonce_from_token == original_nonce
|
145
|
-
rescue StandardError => e
|
146
|
-
Rails.logger.error("Nonce validation error: #{e.message}")
|
147
|
-
false
|
209
|
+
# Redirects to root path with an error message
|
210
|
+
# @param message [String] The error message
|
211
|
+
# @return [void]
|
212
|
+
def redirect_with_error(message)
|
213
|
+
redirect_to "/", alert: message
|
148
214
|
end
|
149
|
-
|
150
|
-
|
151
215
|
end
|
152
216
|
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module AuthHelper
|
2
|
+
# Sets up session tokens and user information after successful authentication
|
3
|
+
# @param tokens [Hash] The authentication tokens received from Kinde
|
4
|
+
# @return [void]
|
5
|
+
def set_session_tokens(tokens)
|
6
|
+
# Create token store from tokens
|
7
|
+
token_store = KindeSdk::TokenManager.create_store(tokens)
|
8
|
+
|
9
|
+
# Store minimal token data in session
|
10
|
+
session[:kinde_token_store] = {
|
11
|
+
access_token: token_store.bearer_token,
|
12
|
+
refresh_token: token_store.tokens[:refresh_token],
|
13
|
+
expires_at: token_store.expires_at
|
14
|
+
}
|
15
|
+
|
16
|
+
# Get and store minimal user info
|
17
|
+
client = KindeSdk.client(tokens)
|
18
|
+
user_info = client.oauth.get_user.to_hash
|
19
|
+
|
20
|
+
# Validate user info before storing
|
21
|
+
raise ArgumentError, "Invalid user info received" unless user_info[:id].present?
|
22
|
+
|
23
|
+
session[:kinde_user] = {
|
24
|
+
id: user_info[:id],
|
25
|
+
email: user_info[:preferred_email],
|
26
|
+
first_name: user_info[:first_name],
|
27
|
+
last_name: user_info[:last_name]
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
# Checks if the user is currently logged in
|
32
|
+
# @return [Boolean] true if the user is logged in and token is valid
|
33
|
+
def logged_in?
|
34
|
+
!token_expired?
|
35
|
+
end
|
36
|
+
|
37
|
+
# Checks if the session contains token data
|
38
|
+
# @return [Boolean] true if session contains token data
|
39
|
+
def session_present_in?
|
40
|
+
session[:kinde_token_store].present?
|
41
|
+
end
|
42
|
+
|
43
|
+
# Checks if the current token has expired
|
44
|
+
# @return [Boolean] true if token is expired or invalid
|
45
|
+
def token_expired?
|
46
|
+
return true unless session[:kinde_token_store].present?
|
47
|
+
|
48
|
+
client = get_client
|
49
|
+
return true unless client
|
50
|
+
|
51
|
+
client.token_expired?
|
52
|
+
rescue JWT::DecodeError => e
|
53
|
+
Rails.logger.error("JWT decode error: #{e.message}")
|
54
|
+
true
|
55
|
+
rescue StandardError => e
|
56
|
+
Rails.logger.error("Error checking token expiration: #{e.message}")
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
# Attempts to refresh the session tokens
|
61
|
+
# @return [Boolean] true if refresh was successful
|
62
|
+
def refresh_session_tokens
|
63
|
+
return false unless session[:kinde_token_store].present?
|
64
|
+
|
65
|
+
# Create token store from session data
|
66
|
+
token_store = KindeSdk::TokenStore.new(session[:kinde_token_store])
|
67
|
+
|
68
|
+
# Attempt to refresh tokens
|
69
|
+
new_tokens = KindeSdk::TokenManager.refresh_tokens(token_store, session)
|
70
|
+
if new_tokens
|
71
|
+
set_session_tokens(new_tokens)
|
72
|
+
true
|
73
|
+
else
|
74
|
+
false
|
75
|
+
end
|
76
|
+
rescue StandardError => e
|
77
|
+
Rails.logger.error("Error refreshing tokens: #{e.message}")
|
78
|
+
session.delete(:kinde_token_store)
|
79
|
+
false
|
80
|
+
end
|
81
|
+
|
82
|
+
# Gets a Kinde client instance for the current session
|
83
|
+
# @return [KindeSdk::Client, nil] The client instance or nil if no valid session
|
84
|
+
def get_client
|
85
|
+
return nil unless session[:kinde_token_store].present?
|
86
|
+
KindeSdk.client(session[:kinde_token_store])
|
87
|
+
end
|
88
|
+
end
|
data/config/routes.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
KindeSdk::Engine.routes.draw do
|
2
|
-
|
2
|
+
# Authentication routes
|
3
3
|
get "auth" => "auth#auth"
|
4
|
+
get "callback" => "auth#callback"
|
4
5
|
get "logout" => "auth#logout"
|
5
6
|
get "logout_callback" => "auth#logout_callback"
|
7
|
+
get "refresh_token" => "auth#refresh_token"
|
6
8
|
get "client_credentials_auth" => "auth#client_credentials_auth"
|
7
9
|
end
|
data/lib/kinde_sdk/client.rb
CHANGED
@@ -1,26 +1,52 @@
|
|
1
1
|
require_relative '../../kinde_api/lib/kinde_api'
|
2
|
+
require_relative 'token_manager'
|
3
|
+
require_relative 'token_store'
|
4
|
+
require_relative 'current'
|
2
5
|
|
3
6
|
module KindeSdk
|
4
7
|
class Client
|
5
8
|
include FeatureFlags
|
6
9
|
include Permissions
|
7
10
|
|
8
|
-
attr_accessor :kinde_api_client, :auto_refresh_tokens, :
|
11
|
+
attr_accessor :kinde_api_client, :auto_refresh_tokens, :token_store
|
9
12
|
|
10
13
|
def initialize(sdk_api_client, tokens_hash, auto_refresh_tokens)
|
11
14
|
@kinde_api_client = sdk_api_client
|
12
15
|
@auto_refresh_tokens = auto_refresh_tokens
|
13
|
-
|
16
|
+
@token_store = TokenManager.create_store(tokens_hash)
|
17
|
+
|
18
|
+
# refresh the token if it's expired and auto_refresh_tokens is enabled
|
19
|
+
refresh_token if auto_refresh_tokens && TokenManager.token_expired?(@token_store)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns the bearer token for backwards compatibility
|
23
|
+
# @return [String]
|
24
|
+
def bearer_token
|
25
|
+
@token_store.bearer_token
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the tokens hash for backwards compatibility
|
29
|
+
# @return [Hash]
|
30
|
+
def tokens_hash
|
31
|
+
@token_store.tokens
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns the token expiration time for backwards compatibility
|
35
|
+
# @return [Integer]
|
36
|
+
def expires_at
|
37
|
+
@token_store.expires_at
|
14
38
|
end
|
15
39
|
|
16
40
|
def token_expired?
|
17
|
-
|
41
|
+
TokenManager.token_expired?(@token_store)
|
18
42
|
end
|
19
43
|
|
20
44
|
def refresh_token
|
21
|
-
new_tokens_hash =
|
22
|
-
|
23
|
-
|
45
|
+
new_tokens_hash = TokenManager.refresh_tokens(@token_store, Current.session)
|
46
|
+
return nil unless new_tokens_hash
|
47
|
+
|
48
|
+
@token_store.set_tokens(new_tokens_hash)
|
49
|
+
@kinde_api_client = KindeSdk.api_client(@token_store.bearer_token)
|
24
50
|
new_tokens_hash
|
25
51
|
end
|
26
52
|
|
@@ -29,7 +55,9 @@ module KindeSdk
|
|
29
55
|
# @return [Hash]
|
30
56
|
# @example {name: "scp", value: ["openid", "offline"]}
|
31
57
|
def get_claim(claim, token_type = :access_token)
|
32
|
-
|
58
|
+
refresh_token if auto_refresh_tokens && token_expired?
|
59
|
+
|
60
|
+
token = @token_store.tokens[token_type.to_sym]
|
33
61
|
return unless token
|
34
62
|
|
35
63
|
value = JWT.decode(token, nil, false)[0][claim]
|
@@ -48,13 +76,6 @@ module KindeSdk
|
|
48
76
|
|
49
77
|
private
|
50
78
|
|
51
|
-
def set_hash_related_data(tokens_hash)
|
52
|
-
@tokens_hash = tokens_hash.transform_keys(&:to_sym)
|
53
|
-
@bearer_token = @tokens_hash[:access_token]
|
54
|
-
@expires_at = @tokens_hash[:expires_at]
|
55
|
-
end
|
56
|
-
|
57
|
-
# going from another side: prepending each api_client's public method to check token for expiration
|
58
79
|
def init_instance_api(api_klass)
|
59
80
|
instance = api_klass.new(kinde_api_client)
|
60
81
|
main_client = self
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module KindeSdk
|
2
|
+
class Current < ActiveSupport::CurrentAttributes
|
3
|
+
attribute :session
|
4
|
+
attribute :token_store
|
5
|
+
|
6
|
+
def self.set_session(session)
|
7
|
+
self.session = session
|
8
|
+
self.token_store = TokenStore.from_session(session[:kinde_token_store]) if session[:kinde_token_store]
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.clear_session
|
12
|
+
self.session = nil
|
13
|
+
self.token_store = nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/kinde_sdk/engine.rb
CHANGED
@@ -1,5 +1,11 @@
|
|
1
|
+
require "kinde_sdk/version"
|
2
|
+
require "kinde_sdk/configuration"
|
3
|
+
require "kinde_sdk/current"
|
4
|
+
require "kinde_sdk/middleware"
|
5
|
+
|
1
6
|
module KindeSdk
|
2
7
|
class Engine < ::Rails::Engine
|
3
8
|
isolate_namespace KindeSdk # Optional, for mountable engines
|
9
|
+
config.app_middleware.use KindeSdk::Middleware
|
4
10
|
end
|
5
11
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module KindeSdk
|
2
|
+
class Middleware
|
3
|
+
def initialize(app)
|
4
|
+
@app = app
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(env)
|
8
|
+
request = ActionDispatch::Request.new(env)
|
9
|
+
Current.set_session(request.session)
|
10
|
+
|
11
|
+
@app.call(env)
|
12
|
+
ensure
|
13
|
+
Current.clear_session
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module KindeSdk
|
2
|
+
class TokenManager
|
3
|
+
attr_reader :tokens, :bearer_token, :expires_at
|
4
|
+
|
5
|
+
def initialize(tokens = nil)
|
6
|
+
set_tokens(tokens) if tokens
|
7
|
+
end
|
8
|
+
|
9
|
+
def set_tokens(tokens)
|
10
|
+
@tokens = tokens.transform_keys(&:to_sym).freeze
|
11
|
+
@bearer_token = @tokens[:access_token]
|
12
|
+
@expires_at = @tokens[:expires_at]
|
13
|
+
@tokens
|
14
|
+
end
|
15
|
+
|
16
|
+
def token_expired?
|
17
|
+
return true unless @tokens.present?
|
18
|
+
begin
|
19
|
+
KindeSdk.validate_jwt_token(@tokens)
|
20
|
+
@expires_at.to_i > 0 && (@expires_at <= Time.now.to_i)
|
21
|
+
rescue Exception => e
|
22
|
+
Rails.logger.error("Error checking token expiration: #{e.message}")
|
23
|
+
true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def clear_tokens
|
28
|
+
@tokens = nil
|
29
|
+
@bearer_token = nil
|
30
|
+
@expires_at = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
class << self
|
34
|
+
def create_store(tokens = nil)
|
35
|
+
TokenStore.new(tokens)
|
36
|
+
end
|
37
|
+
|
38
|
+
def refresh_tokens(store, session = nil)
|
39
|
+
return nil unless store&.tokens
|
40
|
+
new_tokens = safe_refresh(store.tokens)
|
41
|
+
return nil unless new_tokens
|
42
|
+
store.set_tokens(new_tokens)
|
43
|
+
sync_session(store, session)
|
44
|
+
new_tokens
|
45
|
+
end
|
46
|
+
|
47
|
+
def validate_tokens(store)
|
48
|
+
return false unless store&.tokens
|
49
|
+
begin
|
50
|
+
KindeSdk.validate_jwt_token(store.tokens)
|
51
|
+
true
|
52
|
+
rescue JWT::DecodeError, StandardError
|
53
|
+
false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def token_expired?(store)
|
58
|
+
return true unless store&.tokens
|
59
|
+
return true unless validate_tokens(store)
|
60
|
+
store.expires_at.to_i > 0 && (store.expires_at <= Time.now.to_i)
|
61
|
+
end
|
62
|
+
|
63
|
+
def clear_tokens(store, session = nil)
|
64
|
+
store.set_tokens(nil)
|
65
|
+
target_session = session || Current.session
|
66
|
+
target_session&.delete(:kinde_token_store)
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def safe_refresh(tokens)
|
72
|
+
KindeSdk.refresh_token(tokens)
|
73
|
+
rescue StandardError => e
|
74
|
+
Rails.logger.error("Token refresh failed: #{e.message}")
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
|
78
|
+
def sync_session(store, session)
|
79
|
+
return unless (session || Current.session) && store.tokens
|
80
|
+
target_session = session || Current.session
|
81
|
+
target_session[:kinde_token_store] = store.to_session
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module KindeSdk
|
2
|
+
class TokenStore
|
3
|
+
attr_reader :tokens, :bearer_token, :expires_at
|
4
|
+
|
5
|
+
def initialize(tokens = nil)
|
6
|
+
set_tokens(tokens) if tokens
|
7
|
+
end
|
8
|
+
|
9
|
+
def set_tokens(tokens)
|
10
|
+
@tokens = (tokens || {}).transform_keys(&:to_sym)
|
11
|
+
@bearer_token = @tokens[:access_token]
|
12
|
+
@expires_at = @tokens[:expires_at]
|
13
|
+
@tokens
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_session
|
17
|
+
{
|
18
|
+
access_token: @bearer_token,
|
19
|
+
refresh_token: @tokens[:refresh_token],
|
20
|
+
expires_at: @expires_at
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.from_session(session_data)
|
25
|
+
return nil unless session_data
|
26
|
+
new(session_data)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/kinde_sdk/version.rb
CHANGED