kinde_sdk 1.3.0 → 1.3.1

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: 8c359bdcb6d579be388fe1461d4305a5c10dacdc27f1fc06d244db3c042d247c
4
- data.tar.gz: e1be276fe179c87fba4060317630cf85e29b91d1f05f52837eaaeaf5a227fb38
3
+ metadata.gz: bc5ca096d7760fdbb44aad50f80581bedad34c59961401731a46fdcfe12de6e8
4
+ data.tar.gz: 7c172304b38b70bb4446e25db64cd3ce98c28aadded415d32a776b82885095d5
5
5
  SHA512:
6
- metadata.gz: fa55ff69411d58b639df64c43f65ed26e10ccc3f54a6e65d5ef421936c3e50759cdaca26003146c0b7f6befa704603304e7488da4112b15e7c5271bdc7f724f7
7
- data.tar.gz: 7fc8ada7e5d0a1d958f787898a98f0eeb68b4597dfe4affa66811f5167d04b744cbb2067db0b32b94c6bab96bca50c6813a9697c9b703cd385e3010a3dc3f144
6
+ metadata.gz: 963aabbb2e5d9ea92c3369d127f21ea3181aa646bbf087a1e4322c7d4721f0c267a8ba0f5f55d5a1c389bdca7d75844193fa039969f6760fd0c59419e6e8897b
7
+ data.tar.gz: 520cd6180f22cf93041b0de2cd89898d6640c275dd4f9d8e19efa4514969b18dc4a440265ab3a1b35aa81ab0707adad6eeb45c11d7bc8ab68eb978c7bb969616
@@ -0,0 +1,152 @@
1
+ require 'action_controller'
2
+ require 'uri'
3
+ require 'cgi'
4
+ require 'net/http'
5
+ require 'json'
6
+ require 'jwt'
7
+
8
+ module KindeSdk
9
+ class AuthController < ActionController::Base
10
+ # Add before_action to validate nonce in callback
11
+ before_action :validate_state, only: :callback
12
+
13
+ def auth
14
+ # Generate a secure random nonce
15
+ nonce = SecureRandom.urlsafe_base64(16)
16
+
17
+ # Call KindeSdk.auth_url with nonce
18
+ auth_data = KindeSdk.auth_url(nonce: nonce)
19
+
20
+ # Store in session
21
+ session[:code_verifier] = auth_data[:code_verifier] if auth_data[:code_verifier].present?
22
+ session[:auth_nonce] = nonce
23
+ session[:auth_state] = {
24
+ requested_at: Time.current.to_i,
25
+ redirect_url: auth_data[:url]
26
+ }
27
+
28
+ redirect_to auth_data[:url], allow_other_host: true
29
+ end
30
+
31
+ 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
48
+
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
53
+
54
+ # Clear nonce and state after successful authentication
55
+ session.delete(:auth_nonce)
56
+ session.delete(:auth_state)
57
+ session.delete(:code_verifier)
58
+ redirect_to "/"
59
+ rescue StandardError => e
60
+ Rails.logger.error("Authentication callback failed: #{e.message}")
61
+ redirect_to "/", alert: "Authentication failed"
62
+ end
63
+
64
+ def client_credentials_auth
65
+ result = KindeSdk.client_credentials_access(
66
+ client_id: ENV["KINDE_MANAGEMENT_CLIENT_ID"],
67
+ client_secret: ENV["KINDE_MANAGEMENT_CLIENT_SECRET"]
68
+ )
69
+
70
+ if result["error"].present?
71
+ Rails.logger.error("Client credentials auth failed: #{result['error']}")
72
+ raise result["error"]
73
+ end
74
+
75
+ $redis.set("kinde_m2m_token", result["access_token"], ex: result["expires_in"].to_i)
76
+ redirect_to mgmt_path
77
+ end
78
+
79
+ def logout
80
+ redirect_to KindeSdk.logout_url, allow_other_host: true
81
+ end
82
+
83
+ def logout_callback
84
+ reset_session
85
+ redirect_to "/"
86
+ end
87
+
88
+ private
89
+
90
+ def validate_state
91
+ # 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")
94
+ redirect_to "/", alert: "Invalid authentication state"
95
+ return
96
+ end
97
+
98
+ # Verify nonce returned matches stored nonce
99
+ returned_state = params[:state]
100
+ stored_state = session[:auth_state]
101
+ stored_url = stored_state["redirect_url"]
102
+
103
+ # Extract the state from the stored redirect_url
104
+ parsed_url = URI.parse(stored_url)
105
+ query_params = CGI.parse(parsed_url.query || "")
106
+ stored_state_from_url = query_params["state"]&.first
107
+
108
+ # Verify returned state matches the state extracted from the redirect_url
109
+ 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"
112
+ return
113
+ end
114
+
115
+ # Optional: Check state age (e.g., expires after 15 minutes)
116
+ if Time.current.to_i - stored_state["requested_at"] > 900
117
+ Rails.logger.warn("Authentication state expired")
118
+ redirect_to "/", alert: "Authentication session expired"
119
+ return
120
+ end
121
+ end
122
+
123
+
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
148
+ end
149
+
150
+
151
+ end
152
+ end
@@ -1,3 +1,3 @@
1
1
  module KindeSdk
2
- VERSION = "1.3.0"
2
+ VERSION = "1.3.1"
3
3
  end
data/lib/kinde_sdk.rb CHANGED
@@ -1,9 +1,10 @@
1
+ require "logger"
1
2
  require "kinde_sdk/version"
2
3
  require "kinde_sdk/configuration"
3
4
  require "kinde_sdk/client/feature_flags"
4
5
  require "kinde_sdk/client/permissions"
6
+ require "kinde_sdk/controllers/auth_controller"
5
7
  require "kinde_sdk/client"
6
-
7
8
  require 'securerandom'
8
9
  require 'oauth2'
9
10
  require 'pkce_challenge'
@@ -36,7 +37,7 @@ module KindeSdk
36
37
  params = {
37
38
  redirect_uri: redirect_uri,
38
39
  state: SecureRandom.hex,
39
- scope: @config.scope
40
+ scope: @config.scope,
40
41
  }.merge(**kwargs)
41
42
  return { url: @config.oauth_client(
42
43
  client_id: client_id,
@@ -74,12 +75,21 @@ module KindeSdk
74
75
  headers: { 'User-Agent' => "Kinde-SDK: Ruby/#{KindeSdk::VERSION}" }
75
76
  }
76
77
  params[:code_verifier] = code_verifier if code_verifier
77
- @config.oauth_client(
78
+ token = @config.oauth_client(
78
79
  client_id: client_id,
79
80
  client_secret: client_secret,
80
81
  domain: domain,
81
82
  authorize_url: "#{domain}/oauth2/auth",
82
- token_url: "#{domain}/oauth2/token").auth_code.get_token(code.to_s, params).to_hash
83
+ token_url: "#{domain}/oauth2/token").auth_code.get_token(code.to_s, params)
84
+
85
+ {
86
+ access_token: token.token, # The access token
87
+ id_token: token.params['id_token'], # The ID token from params
88
+ expires_at: token.expires_at, # Optional: expiration time
89
+ refresh_token: token.refresh_token, # Optional: if present
90
+ scope: token.params['scope'], # The scopes requested
91
+ token_type: token.params['token_type'] # The token type
92
+ }.compact
83
93
  end
84
94
 
85
95
  # tokens_hash #=>
@@ -92,7 +102,7 @@ module KindeSdk
92
102
  #
93
103
  # @return [KindeSdk::Client]
94
104
  def client(tokens_hash)
95
- sdk_api_client = api_client(tokens_hash["access_token"])
105
+ sdk_api_client = api_client(tokens_hash[:access_token] || tokens_hash["access_token"])
96
106
  KindeSdk::Client.new(sdk_api_client, tokens_hash, @config.auto_refresh_tokens)
97
107
  end
98
108
 
@@ -74,20 +74,20 @@ describe KindeSdk do
74
74
  )
75
75
  .to_return(
76
76
  status: 200,
77
- body: { "access_token": "eyJ", "expires_in": 86399, "scope": "", "token_type": "bearer" }.to_json,
77
+ body: { "access_token": "eyJ", "id_token": "test", "refresh_token": "test","expires_in": 86399, "scope": "", "token_type": "bearer" }.to_json,
78
78
  headers: { "content-type" => "application/json;charset=UTF-8" }
79
79
  )
80
80
  end
81
81
 
82
82
  it "calls /token url with proper body and headers" do
83
- expect(described_class.fetch_tokens(code).keys).to eq(%w[scope token_type access_token refresh_token expires_at])
83
+ expect(described_class.fetch_tokens(code).keys.map(&:to_s)).to eq(%w[access_token id_token expires_at refresh_token scope token_type])
84
84
  end
85
85
 
86
86
  context "with redefined callback_url" do
87
87
  let(:callback_url) { "another-callback" }
88
88
 
89
89
  it "calls /token url with proper body and headers" do
90
- expect(described_class.fetch_tokens(code).keys.size).to eq(5)
90
+ expect(described_class.fetch_tokens(code).keys.size).to eq(6)
91
91
  end
92
92
  end
93
93
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kinde_sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kinde Australia Pty Ltd
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-02-18 00:00:00.000000000 Z
11
+ date: 2025-03-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: typhoeus
@@ -422,6 +422,7 @@ files:
422
422
  - lib/kinde_sdk/client/feature_flags.rb
423
423
  - lib/kinde_sdk/client/permissions.rb
424
424
  - lib/kinde_sdk/configuration.rb
425
+ - lib/kinde_sdk/controllers/auth_controller.rb
425
426
  - lib/kinde_sdk/version.rb
426
427
  - spec/kinde_sdk_spec.rb
427
428
  - spec/spec_helper.rb