kinde_sdk 1.6.6 → 1.7.0

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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/kinde_sdk/auth_controller.rb +96 -13
  3. data/kinde_api/lib/kinde_api/api/frontend/billing_api.rb +148 -0
  4. data/kinde_api/lib/kinde_api/api/frontend/feature_flags_api.rb +85 -0
  5. data/kinde_api/lib/kinde_api/api/frontend/o_auth_api.rb +241 -0
  6. data/kinde_api/lib/kinde_api/api/frontend/permissions_api.rb +85 -0
  7. data/kinde_api/lib/kinde_api/api/frontend/properties_api.rb +85 -0
  8. data/kinde_api/lib/kinde_api/api/frontend/roles_api.rb +85 -0
  9. data/kinde_api/lib/kinde_api/api/frontend/self_serve_portal_api.rb +89 -0
  10. data/kinde_api/lib/kinde_api/models/frontend/error.rb +230 -0
  11. data/kinde_api/lib/kinde_api/models/frontend/error_response.rb +221 -0
  12. data/kinde_api/lib/kinde_api/models/frontend/get_entitlement_response.rb +228 -0
  13. data/kinde_api/lib/kinde_api/models/frontend/get_entitlement_response_data.rb +229 -0
  14. data/kinde_api/lib/kinde_api/models/frontend/get_entitlement_response_data_entitlement.rb +295 -0
  15. data/kinde_api/lib/kinde_api/models/frontend/get_entitlements_response.rb +228 -0
  16. data/kinde_api/lib/kinde_api/models/frontend/get_entitlements_response_data.rb +244 -0
  17. data/kinde_api/lib/kinde_api/models/frontend/get_entitlements_response_data_entitlements_inner.rb +294 -0
  18. data/kinde_api/lib/kinde_api/models/frontend/get_entitlements_response_data_plans_inner.rb +240 -0
  19. data/kinde_api/lib/kinde_api/models/frontend/get_entitlements_response_metadata.rb +230 -0
  20. data/kinde_api/lib/kinde_api/models/frontend/get_feature_flags_response.rb +219 -0
  21. data/kinde_api/lib/kinde_api/models/frontend/get_feature_flags_response_data.rb +222 -0
  22. data/kinde_api/lib/kinde_api/models/frontend/get_feature_flags_response_data_feature_flags_inner.rb +259 -0
  23. data/kinde_api/lib/kinde_api/models/frontend/get_feature_flags_response_data_feature_flags_inner_value.rb +108 -0
  24. data/kinde_api/lib/kinde_api/models/frontend/get_user_permissions_response.rb +228 -0
  25. data/kinde_api/lib/kinde_api/models/frontend/get_user_permissions_response_data.rb +232 -0
  26. data/kinde_api/lib/kinde_api/models/frontend/get_user_permissions_response_data_permissions_inner.rb +240 -0
  27. data/kinde_api/lib/kinde_api/models/frontend/get_user_permissions_response_metadata.rb +230 -0
  28. data/kinde_api/lib/kinde_api/models/frontend/get_user_properties_response.rb +228 -0
  29. data/kinde_api/lib/kinde_api/models/frontend/get_user_properties_response_data.rb +222 -0
  30. data/kinde_api/lib/kinde_api/models/frontend/get_user_properties_response_data_properties_inner.rb +249 -0
  31. data/kinde_api/lib/kinde_api/models/frontend/get_user_properties_response_data_properties_inner_value.rb +107 -0
  32. data/kinde_api/lib/kinde_api/models/frontend/get_user_properties_response_metadata.rb +230 -0
  33. data/kinde_api/lib/kinde_api/models/frontend/get_user_roles_response.rb +228 -0
  34. data/kinde_api/lib/kinde_api/models/frontend/get_user_roles_response_data.rb +232 -0
  35. data/kinde_api/lib/kinde_api/models/frontend/get_user_roles_response_data_roles_inner.rb +240 -0
  36. data/kinde_api/lib/kinde_api/models/frontend/get_user_roles_response_metadata.rb +230 -0
  37. data/kinde_api/lib/kinde_api/models/frontend/portal_link.rb +220 -0
  38. data/kinde_api/lib/kinde_api/models/frontend/token_error_response.rb +230 -0
  39. data/kinde_api/lib/kinde_api/models/frontend/token_introspect.rb +262 -0
  40. data/kinde_api/lib/kinde_api/models/frontend/user_profile_v2.rb +323 -0
  41. data/kinde_api/lib/kinde_api.rb +28 -0
  42. data/lib/kinde_sdk/client/entitlements.rb +86 -0
  43. data/lib/kinde_sdk/client/feature_flags.rb +246 -10
  44. data/lib/kinde_sdk/client/permissions.rb +197 -6
  45. data/lib/kinde_sdk/client/roles.rb +218 -0
  46. data/lib/kinde_sdk/client.rb +242 -3
  47. data/lib/kinde_sdk/configuration.rb +2 -0
  48. data/lib/kinde_sdk/errors.rb +7 -0
  49. data/lib/kinde_sdk/internal/frontend_client.rb +111 -0
  50. data/lib/kinde_sdk/version.rb +1 -1
  51. data/lib/kinde_sdk.rb +9 -2
  52. metadata +54 -12
@@ -1,7 +1,168 @@
1
1
  module KindeSdk
2
2
  class Client
3
3
  module FeatureFlags
4
- def get_flag(name, opts = {}, flag_type = nil)
4
+ # Get all feature flags for the authenticated user
5
+ # Matches the JavaScript SDK API: getFlags(options?)
6
+ #
7
+ # @param options [Hash] Options for retrieving feature flags
8
+ # @option options [Boolean] :force_api (false) If true, calls the API to get fresh flags,
9
+ # otherwise extracts from token claims. Useful for ensuring latest flags but may incur additional API calls
10
+ # @option options [Symbol] :token_type (:access_token) The token type to use for soft check (:access_token or :id_token)
11
+ # @return [Array] Array of flag objects with key, value, and type
12
+ # @example
13
+ # # Soft check (from token)
14
+ # client.get_flags
15
+ # # => [{ key: "feature_a", value: true, type: "boolean" }]
16
+ #
17
+ # # Hard check (from API)
18
+ # client.get_flags(force_api: true)
19
+ # # => [{ key: "feature_a", value: true, type: "boolean" }]
20
+ def get_flags(options = {})
21
+ # Handle legacy positional argument for backward compatibility
22
+ if options.is_a?(Symbol)
23
+ options = { token_type: options }
24
+ end
25
+
26
+ # Extract options with defaults - use member variable if not overridden
27
+ force_api = options[:force_api] || @force_api || false
28
+ token_type = options[:token_type] || :access_token
29
+
30
+ if force_api
31
+ # Hard check - call API for fresh flags
32
+ get_flags_from_api
33
+ else
34
+ # Soft check - extract from token claims
35
+ get_flags_from_token(token_type)
36
+ end
37
+ end
38
+
39
+ # Get a specific feature flag
40
+ # Supports both new API-style calls and legacy 3-parameter calls
41
+ #
42
+ # @param name [String] The flag name/key to retrieve
43
+ # @param options_or_opts [Hash] Options for retrieving flags OR legacy opts hash
44
+ # @param flag_type [String, nil] Legacy flag type parameter (for 3-param calls)
45
+ # @return [Hash, nil] Flag object or nil if not found
46
+ def get_flag(name, options_or_opts = {}, flag_type = nil)
47
+ # Handle legacy 3-parameter signature: get_flag(name, opts, flag_type)
48
+ if flag_type || (options_or_opts.is_a?(Hash) && options_or_opts.key?(:default_value) && !options_or_opts.key?(:force_api))
49
+ return get_flag_legacy(name, options_or_opts, flag_type)
50
+ end
51
+
52
+ get_flag_new_api(name, options_or_opts)
53
+ end
54
+
55
+ # Check if user has specific feature flags
56
+ #
57
+ # @param flag_conditions [Array] Array of flag keys or flag condition objects
58
+ # @param options [Hash] Options for retrieving flags (same as get_flags)
59
+ # @return [Boolean] True if user has all specified flags, false otherwise
60
+ def has_feature_flags?(flag_conditions, options = {})
61
+ return true if flag_conditions.nil? || flag_conditions.empty?
62
+
63
+ flags = get_flags(options)
64
+ flag_conditions_array = Array(flag_conditions)
65
+
66
+ flag_conditions_array.all? { |condition| check_flag_condition(condition, flags) }
67
+ end
68
+
69
+ # Legacy methods for backward compatibility
70
+ def get_boolean_flag(name, default_value = nil)
71
+ flag_getter_wrapper(name, "b", default_value)
72
+ end
73
+
74
+ def get_string_flag(name, default_value = nil)
75
+ flag_getter_wrapper(name, "s", default_value)
76
+ end
77
+
78
+ def get_integer_flag(name, default_value = nil)
79
+ flag_getter_wrapper(name, "i", default_value)
80
+ end
81
+
82
+ # PHP SDK compatible alias
83
+ def getFlags
84
+ # Use client's force_api setting, default to true for PHP SDK compatibility
85
+ force_api_setting = @force_api.nil? ? true : @force_api
86
+ get_flags(force_api: force_api_setting)
87
+ end
88
+
89
+ # JavaScript SDK compatible alias
90
+ alias_method :hasFeatureFlags, :has_feature_flags?
91
+
92
+ private
93
+
94
+ # New API-style get_flag implementation
95
+ # Handles the modern get_flag(name, options) signature
96
+ #
97
+ # @param name [String] The flag name/key to retrieve
98
+ # @param options [Hash] Options for retrieving flags
99
+ # @return [Hash, nil] Flag object or nil if not found
100
+ def get_flag_new_api(name, options)
101
+ flags = get_flags(options)
102
+ flag = find_flag_by_key(flags, name)
103
+
104
+ return nil unless flag
105
+
106
+ {
107
+ code: get_flag_key(flag),
108
+ type: get_flag_type(flag),
109
+ value: get_flag_value(flag),
110
+ is_default: false
111
+ }
112
+ end
113
+
114
+ # Check a single flag condition against available flags
115
+ #
116
+ # @param condition [Hash, String] Flag condition to check
117
+ # @param flags [Array] Available flags to check against
118
+ # @return [Boolean] True if condition is met, false otherwise
119
+ def check_flag_condition(condition, flags)
120
+ if condition.is_a?(Hash) && condition.key?(:flag) && condition.key?(:value)
121
+ # Custom condition with specific value
122
+ flag = find_flag_by_key(flags, condition[:flag])
123
+ flag && get_flag_value(flag) == condition[:value]
124
+ else
125
+ # Simple existence check
126
+ !!find_flag_by_key(flags, condition.to_s)
127
+ end
128
+ end
129
+
130
+ # Find a flag by its key in the flags array
131
+ #
132
+ # @param flags [Array] Array of flag objects
133
+ # @param key [String] The key to search for
134
+ # @return [Hash, nil] Flag object if found, nil otherwise
135
+ def find_flag_by_key(flags, key)
136
+ flags.find { |f| get_flag_key(f) == key.to_s }
137
+ end
138
+
139
+ # Extract the key from a flag object (handles both symbol and string keys)
140
+ #
141
+ # @param flag [Hash] Flag object
142
+ # @return [String] The flag key
143
+ def get_flag_key(flag)
144
+ flag[:key] || flag['key']
145
+ end
146
+
147
+ # Extract the value from a flag object (handles both symbol and string keys)
148
+ #
149
+ # @param flag [Hash] Flag object
150
+ # @return [Object] The flag value
151
+ def get_flag_value(flag)
152
+ flag[:value] || flag['value']
153
+ end
154
+
155
+ # Extract the type from a flag object (handles both symbol and string keys)
156
+ #
157
+ # @param flag [Hash] Flag object
158
+ # @return [String] The flag type
159
+ def get_flag_type(flag)
160
+ flag[:type] || flag['type']
161
+ end
162
+
163
+ # Legacy get_flag implementation for backward compatibility
164
+ # Handles the 3-parameter signature: get_flag(name, opts, flag_type)
165
+ def get_flag_legacy(name, opts = {}, flag_type = nil)
5
166
  res = get_claim("feature_flags")&.dig(:value, name)
6
167
  return try_default_flag(flag_type, name, opts) unless res
7
168
 
@@ -20,22 +181,84 @@ module KindeSdk
20
181
  }
21
182
  end
22
183
 
23
- def get_boolean_flag(name, default_value = nil)
24
- flag_getter_wrapper(name, "b", default_value)
25
- end
184
+ # Get feature flags from token claims (soft check)
185
+ # Matches JavaScript logic: token.feature_flags || token["x-hasura-feature-flags"] || null
186
+ #
187
+ # @param token_type [Symbol] The token type to use
188
+ # @return [Array] Array of flag objects
189
+ def get_flags_from_token(token_type = :access_token)
190
+ # First try standard feature_flags claim
191
+ flags = get_claim("feature_flags", token_type)&.dig(:value)
192
+
193
+ # Fallback to Hasura-specific flags (matches JS SDK)
194
+ if flags.nil? || flags.empty?
195
+ flags = get_claim("x-hasura-feature-flags", token_type)&.dig(:value)
196
+ end
197
+
198
+ return [] if flags.nil? || flags.empty?
26
199
 
27
- def get_string_flag(name, default_value = nil)
28
- flag_getter_wrapper(name, "s", default_value)
200
+ # Convert from token format to consistent array format
201
+ if flags.is_a?(Hash)
202
+ flags.map do |key, value|
203
+ {
204
+ key: key.to_s,
205
+ value: value.is_a?(Hash) ? value['v'] : value,
206
+ type: value.is_a?(Hash) ? map_flag_type(value['t']) : 'string'
207
+ }
208
+ end
209
+ else
210
+ []
211
+ end
29
212
  end
30
213
 
31
- def get_integer_flag(name, default_value = nil)
32
- flag_getter_wrapper(name, "i", default_value)
214
+ # Get feature flags from API (hard check)
215
+ # Matches JavaScript API endpoint and data extraction exactly
216
+ #
217
+ # @return [Array] Array of flag objects
218
+ def get_flags_from_api
219
+ unless token_store.bearer_token
220
+ return []
221
+ end
222
+
223
+ begin
224
+ # Use the same pagination pattern as getAllEntitlements
225
+ all_flags = paginate_all_results('feature_flags') do |starting_after|
226
+ user_feature_flags(page_size: 100, starting_after: starting_after)
227
+ end
228
+
229
+ # Extract flag data (matches JS: feature_flags?.map((flag) => ({ key, value, type })))
230
+ all_flags.map do |flag|
231
+ {
232
+ key: flag.respond_to?(:key) ? flag.key : flag['key'],
233
+ value: flag.respond_to?(:value) ? flag.value : flag['value'],
234
+ type: flag.respond_to?(:type) ? flag.type : flag['type']
235
+ }
236
+ end.compact
237
+ rescue KindeSdk::APIError => e
238
+ log_error("API Error getting feature flags: #{e.message}")
239
+ # Graceful fallback to token-based flags (matches JS behavior)
240
+ get_flags_from_token
241
+ rescue StandardError => e
242
+ log_error("Unexpected error getting feature flags from API: #{e.message}")
243
+ # Graceful fallback to token-based flags
244
+ get_flags_from_token
245
+ end
33
246
  end
34
247
 
35
- private
248
+ # Map token flag type codes to full names
249
+ def map_flag_type(type_code)
250
+ case type_code
251
+ when "b" then "boolean"
252
+ when "s" then "string"
253
+ when "i" then "integer"
254
+ when "o" then "object"
255
+ else "string"
256
+ end
257
+ end
36
258
 
259
+ # Legacy helper methods
37
260
  def flag_getter_wrapper(name, type, default_value = nil)
38
- v = get_flag(name, { default_value: default_value }, type)[:value]
261
+ v = get_flag_legacy(name, { default_value: default_value }, type)[:value]
39
262
  raise ArgumentError, "Flag #{name} value type is different from requested type" unless check_type(v, type)
40
263
 
41
264
  v
@@ -59,6 +282,19 @@ module KindeSdk
59
282
  def check_type(value, type)
60
283
  type == "s" && value.is_a?(String) || type == "b" && (value == false || value == true) || type == "i" && value.is_a?(Integer)
61
284
  end
285
+
286
+ # Configurable logging that works with or without Rails
287
+ def log_error(message)
288
+ if defined?(Rails) && Rails.logger
289
+ Rails.logger.error(message)
290
+ elsif @logger
291
+ @logger.error(message)
292
+ elsif respond_to?(:logger) && logger
293
+ logger.error(message)
294
+ else
295
+ $stderr.puts "[KindeSdk] ERROR: #{message}"
296
+ end
297
+ end
62
298
  end
63
299
  end
64
300
  end
@@ -1,19 +1,210 @@
1
1
  module KindeSdk
2
2
  class Client
3
3
  module Permissions
4
- def get_permissions(token_type = :access_token)
4
+ # Get all permissions for the authenticated user
5
+ # Matches the JavaScript SDK API: getPermissions(options?)
6
+ #
7
+ # @param options [Hash, Symbol] Options for retrieving permissions, or legacy token_type symbol
8
+ # @option options [Boolean] :force_api (false) If true, calls the API to get fresh permissions,
9
+ # otherwise extracts from token claims. Useful for ensuring latest permissions but may incur additional API calls
10
+ # @option options [Symbol] :token_type (:access_token) The token type to use for soft check (:access_token or :id_token)
11
+ # @return [Hash] Hash containing org_code and permissions array
12
+ # @example
13
+ # # Soft check (from token)
14
+ # client.get_permissions
15
+ # # => { org_code: "org_123", permissions: ["read:users", "write:posts"] }
16
+ #
17
+ # # Hard check (from API)
18
+ # client.get_permissions(force_api: true)
19
+ # # => { org_code: "org_123", permissions: ["read:users", "write:posts", "admin:all"] }
20
+ #
21
+ # # Legacy backward compatibility
22
+ # client.get_permissions(:id_token)
23
+ # # => { org_code: "org_123", permissions: ["read:users", "write:posts"] }
24
+ def get_permissions(options = {})
25
+ # Handle legacy positional argument for backward compatibility
26
+ if options.is_a?(Symbol)
27
+ options = { token_type: options }
28
+ end
29
+
30
+ # Extract options with defaults - use member variable if not overridden
31
+ force_api = options[:force_api] || @force_api || false
32
+ token_type = options[:token_type] || :access_token
33
+
34
+ if force_api
35
+ # Hard check - call API for fresh permissions
36
+ get_permissions_from_api
37
+ else
38
+ # Soft check - extract from token claims
39
+ get_permissions_from_token(token_type)
40
+ end
41
+ end
42
+
43
+ # Get a specific permission status
44
+ #
45
+ # @param permission [String] The permission key to check
46
+ # @param options [Hash] Options for retrieving permissions (same as get_permissions)
47
+ # @return [Hash] Hash containing org_code and is_granted status
48
+ def get_permission(permission, options = {})
49
+ permissions_data = get_permissions(options)
50
+
51
+ {
52
+ org_code: permissions_data[:org_code],
53
+ is_granted: permissions_data[:permissions]&.include?(permission) || false
54
+ }
55
+ end
56
+
57
+ # Check if a permission is granted
58
+ #
59
+ # @param permission [String] The permission key to check
60
+ # @param options [Hash] Options for retrieving permissions
61
+ # @return [Boolean] True if permission is granted, false otherwise
62
+ def permission_granted?(permission, options = {})
63
+ get_permission(permission, options)[:is_granted]
64
+ end
65
+
66
+ # PHP SDK compatible alias for get_permissions with hard check
67
+ # Matches PHP: $client->getPermissions()
68
+ #
69
+ # @return [Hash] Hash containing org_code and permissions array
70
+ def getPermissions
71
+ # Use client's force_api setting, default to true for PHP SDK compatibility
72
+ force_api_setting = @force_api.nil? ? true : @force_api
73
+ get_permissions(force_api: force_api_setting)
74
+ end
75
+
76
+ # Get all permissions with automatic pagination (hard check)
77
+ # Matches PHP: $client->getAllPermissions()
78
+ #
79
+ # @return [Array] Array of permission keys
80
+ def getAllPermissions
81
+ # Use client's force_api setting, default to true for PHP SDK compatibility
82
+ force_api_setting = @force_api.nil? ? true : @force_api
83
+ permissions_data = get_permissions(force_api: force_api_setting)
84
+ permissions_data[:permissions] || []
85
+ end
86
+
87
+ # JavaScript SDK compatible alias
88
+ alias_method :all_permissions, :getAllPermissions
89
+
90
+ # Backward compatibility method - matches existing Ruby SDK API
91
+ def get_permissions_legacy(token_type = :access_token)
5
92
  get_claim("permissions", token_type)&.dig(:value)
6
93
  end
7
94
 
8
- def get_permission(permission, token_type = :access_token)
95
+ private
96
+
97
+ # Get permissions from token claims (soft check)
98
+ # Matches JavaScript logic exactly: token.permissions || token["x-hasura-permissions"] || []
99
+ #
100
+ # @param token_type [Symbol] The token type to use
101
+ # @return [Hash] Hash containing org_code and permissions array
102
+ def get_permissions_from_token(token_type = :access_token)
103
+ # First try standard permissions claim
104
+ permissions = get_claim("permissions", token_type)&.dig(:value)
105
+
106
+ # Fallback to Hasura-specific permissions (matches JS SDK)
107
+ if permissions.nil? || permissions.empty?
108
+ permissions = get_claim("x-hasura-permissions", token_type)&.dig(:value)
109
+ end
110
+
111
+ # Final fallback to empty array
112
+ permissions ||= []
113
+
114
+ # Get org_code with same fallback pattern
115
+ org_code = get_claim("org_code", token_type)&.dig(:value)
116
+ if org_code.nil?
117
+ org_code = get_claim("x-hasura-org-code", token_type)&.dig(:value)
118
+ end
119
+
120
+ # Log warning if no permissions found (helpful for debugging)
121
+ if permissions.empty?
122
+ log_warning("No permissions found in token. This may be expected if user has no permissions assigned.")
123
+ end
124
+
9
125
  {
10
- org_code: get_claim("org_code", token_type)&.dig(:value),
11
- is_granted: permission_granted?(permission)
126
+ org_code: org_code,
127
+ permissions: permissions
12
128
  }
13
129
  end
14
130
 
15
- def permission_granted?(permission, token_type = :access_token)
16
- get_claim("permissions", token_type)&.dig(:value)&.include?(permission) || false
131
+ # Get permissions from API (hard check)
132
+ # Matches JavaScript API endpoint and data extraction exactly
133
+ #
134
+ # @return [Hash] Hash containing org_code and permissions array
135
+ def get_permissions_from_api
136
+ unless token_store.bearer_token
137
+ return {
138
+ org_code: nil,
139
+ permissions: []
140
+ }
141
+ end
142
+
143
+ begin
144
+ # Use the same pagination pattern as getAllEntitlements
145
+ all_permissions = paginate_all_results('permissions') do |starting_after|
146
+ user_permissions(page_size: 100, starting_after: starting_after)
147
+ end
148
+
149
+ # Extract permission keys (matches JS: data.permissions?.map((permission) => permission.key))
150
+ permission_keys = all_permissions.map do |permission|
151
+ # Handle both OpenStruct and Hash responses
152
+ permission.respond_to?(:key) ? permission.key : permission['key']
153
+ end.compact
154
+
155
+ # Extract org_code from API response or fallback to token
156
+ org_code = nil
157
+ if all_permissions.any?
158
+ first_permission = all_permissions.first
159
+ org_code = first_permission.respond_to?(:org_code) ?
160
+ first_permission.org_code :
161
+ first_permission['org_code']
162
+ end
163
+
164
+ # Fallback to token if API doesn't provide org_code
165
+ org_code ||= get_claim("org_code", :access_token)&.dig(:value)
166
+
167
+ {
168
+ org_code: org_code,
169
+ permissions: permission_keys
170
+ }
171
+ rescue KindeSdk::APIError => e
172
+ log_error("API Error getting permissions: #{e.message}")
173
+ # Graceful fallback to token-based permissions (matches JS behavior)
174
+ get_permissions_from_token
175
+ rescue StandardError => e
176
+ log_error("Unexpected error getting permissions from API: #{e.message}")
177
+ # Graceful fallback to token-based permissions
178
+ get_permissions_from_token
179
+ end
180
+ end
181
+
182
+ # Configurable logging that works with or without Rails
183
+ #
184
+ # @param message [String] The error message to log
185
+ def log_error(message)
186
+ if defined?(Rails) && Rails.logger
187
+ Rails.logger.error(message)
188
+ elsif @logger
189
+ @logger.error(message)
190
+ elsif respond_to?(:logger) && logger
191
+ logger.error(message)
192
+ else
193
+ # Fallback to STDERR if no logger available
194
+ $stderr.puts "[KindeSdk] ERROR: #{message}"
195
+ end
196
+ end
197
+
198
+ def log_warning(message)
199
+ if defined?(Rails) && Rails.logger
200
+ Rails.logger.warn(message)
201
+ elsif @logger
202
+ @logger.warn(message)
203
+ elsif respond_to?(:logger) && logger
204
+ logger.warn(message)
205
+ else
206
+ $stderr.puts "[KindeSdk] WARNING: #{message}"
207
+ end
17
208
  end
18
209
  end
19
210
  end