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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2c65c1b91fb94f9002ee2ce815a2455b7185db3ca91c7babfb7d2c8bc557eb5d
4
- data.tar.gz: 6ca01f15fba17e280a8f3708ffa08ac86b613a2cf7de0ada9e9111eefb43d813
3
+ metadata.gz: 897c5732757f381a16e53865635495a790a1d892f016711f5826a3b9e78a67e8
4
+ data.tar.gz: 34d03ded031c86085e03265fd4af1f427e7a0f7ba2e5d0d61ba215905f2acc43
5
5
  SHA512:
6
- metadata.gz: 5cbb3e4d0d6b473a0a8037b944e463526f58264557ff8963958baeacf586ee33823c79a5d47ee8221cd468e5128f5e9fe36f59070fd80f09e8a4af061261115f
7
- data.tar.gz: cbdb9bea2bb157d69c818a2e41e95bd47b47e69bab9aa60189549130cdb4fd86610b8df0b9d266e2924f9c0cf1006f8ba5c7b59be37e7775ee1aba5d31caeb46
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
- # Add before_action to validate nonce in callback
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
- # Call KindeSdk.auth_url with nonce
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 = KindeSdk.fetch_tokens(
33
- params[:code],
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
- session[:kinde_auth] = OAuth2::AccessToken.from_hash(KindeSdk.config.oauth_client, tokens).to_hash
51
- .slice(:access_token, :refresh_token, :expires_at)
52
- session[:kinde_user] = KindeSdk.client(tokens).oauth.get_user.to_hash
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
- Rails.logger.error("Authentication callback failed: #{e.message}")
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
- redirect_to KindeSdk.logout_url, allow_other_host: true
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
- reset_session
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[:auth_nonce] && session[:auth_state]
93
- Rails.logger.warn("Missing session state or nonce [#{session[:auth_nonce]}] [#{session[:auth_state]}]")
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
- Rails.logger.warn("State validation failed: returned=#{returned_state}, expected=#{stored_state_from_url}")
111
- redirect_to "/", alert: "Invalid authentication state"
190
+ redirect_with_error("Invalid authentication state")
112
191
  return
113
192
  end
114
193
 
115
- # Optional: Check state age (e.g., expires after 15 minutes)
194
+ # Check state age (expires after 15 minutes)
116
195
  if Time.current.to_i - stored_state["requested_at"] > 900
117
- Rails.logger.warn("Authentication state expired")
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
- def validate_nonce(id_token, original_nonce, issuer, client_id)
125
- jwks_uri = URI.parse("#{issuer}/.well-known/jwks.json")
126
- jwks_response = Net::HTTP.get(jwks_uri)
127
- jwks = JSON.parse(jwks_response)
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
- get "callback" => "auth#callback"
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
@@ -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, :bearer_token, :tokens_hash, :expires_at
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
- set_hash_related_data(tokens_hash)
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
- expires_at.to_i > 0 && (expires_at <= Time.now.to_i)
41
+ TokenManager.token_expired?(@token_store)
18
42
  end
19
43
 
20
44
  def refresh_token
21
- new_tokens_hash = KindeSdk.refresh_token(tokens_hash)
22
- set_hash_related_data(new_tokens_hash)
23
- @kinde_api_client = KindeSdk.api_client(tokens_hash["access_token"])
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
- token = tokens_hash[token_type]
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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module KindeSdk
2
- VERSION = "1.6.2"
2
+ VERSION = "1.6.4"
3
3
  end