clerk-sdk-ruby 1.0.3 → 2.0.0.alpha.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: b552d3c5f39415cb8ec3b53e0161d141fa551a2fb57caf3428ac74c7696c4703
4
- data.tar.gz: a04cbc5d764f5afcca0b8a16e1f54759ca0e11d9879d92192d7fde554d71de7a
3
+ metadata.gz: 1e7b69485de55997a4dac75759908e7709dcb7fdb83c92fe09d2c4748a0a74f7
4
+ data.tar.gz: a0afbe19f7d6a7a9a998e82b798a8a548f051505e14f3b1d50688d2aaa436d7f
5
5
  SHA512:
6
- metadata.gz: ec8fba622266a4b2ea2bd99ba3cc08dafe92c7daa130fc1d0955a68af56314c3eabd740f164931270d3da25a603b213b24024ee5cb96dabb0afd067a4eecd31a
7
- data.tar.gz: 52edbcac0b3af2396c11240e3e438f2560a7c2f312ae5299025ce6b22e3b345b32159ef9ec9123d22fc5c3b6a556155558f0581d10d6a95bc9f07cd248b6eb9f
6
+ metadata.gz: 062e4297db4b8b20e1a6ed6bf6d69a2b0f2df0f5aecc5d716e6b883877fea484476c9d75bf7e7fda294b88774b707b60fd09a85604f7f9363b1161f622d4947a
7
+ data.tar.gz: 50ecd09031d4383b5d136840f8457841253c86122fb3a4a8769d8d86553fb3d2ff9323f0cb98bdde68f4990d2c4cc955b9996a3fca09a0c597e10a7bbb39d8eb
data/.gitignore CHANGED
@@ -6,3 +6,5 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+
10
+ .byebug_history
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  ## unreleased
2
2
 
3
+ This release (v2) introduces the new networkless middleware which is compatible
4
+ with the new authentication scheme, dubbed *AuthV2*.
5
+
6
+ It is backwards-incompatible with applications using AuthV1.
7
+
8
+ - [BREAKING]: In order to use this version, you must set the authVersion prop
9
+ accordingly in your frontend: `Clerk.load({authVersion: 2})`
10
+
3
11
  ## 1.0.3 - 2021-07-21
4
12
 
5
13
  - fix: Proper endpoint for oauth_access_token method
data/Gemfile.lock CHANGED
@@ -1,13 +1,15 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- clerk-sdk-ruby (1.0.2)
4
+ clerk-sdk-ruby (1.0.3)
5
5
  faraday (~> 1.4.1)
6
+ jwt (~> 2.2)
6
7
 
7
8
  GEM
8
9
  remote: https://rubygems.org/
9
10
  specs:
10
- faraday (1.4.2)
11
+ byebug (11.1.3)
12
+ faraday (1.4.3)
11
13
  faraday-em_http (~> 1.0)
12
14
  faraday-em_synchrony (~> 1.0)
13
15
  faraday-excon (~> 1.1)
@@ -19,19 +21,23 @@ GEM
19
21
  faraday-em_synchrony (1.0.0)
20
22
  faraday-excon (1.1.0)
21
23
  faraday-net_http (1.0.1)
22
- faraday-net_http_persistent (1.1.0)
24
+ faraday-net_http_persistent (1.2.0)
25
+ jwt (2.2.3)
23
26
  minitest (5.14.2)
24
27
  multipart-post (2.1.1)
25
28
  rake (13.0.3)
26
- ruby2_keywords (0.0.4)
29
+ ruby2_keywords (0.0.5)
30
+ timecop (0.9.4)
27
31
 
28
32
  PLATFORMS
29
33
  x86_64-linux
30
34
 
31
35
  DEPENDENCIES
36
+ byebug (~> 11.1)
32
37
  clerk-sdk-ruby!
33
38
  minitest (~> 5.0)
34
39
  rake (~> 13.0)
40
+ timecop (~> 0.9.4)
35
41
 
36
42
  BUNDLED WITH
37
- 2.2.15
43
+ 2.2.24
data/README.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Clerk Ruby SDK
2
2
 
3
+ **NOTE**: This is the v2 branch of the SDK, which requires that you use AuthV2
4
+ in your frontend. This means that you have to set the `authVersion` prop
5
+ accordingly in your frontend:
6
+
7
+ ```javascript
8
+ Clerk.load({authVersion: 2})
9
+ ```
10
+
11
+ ----------
12
+
3
13
  Thank you for choosing [Clerk](https://clerk.dev/) for your authentication,
4
14
  session & user management needs!
5
15
 
data/bin/console CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  require "bundler/setup"
5
5
  require "clerk"
6
+ require "byebug"
6
7
 
7
8
  # You can add fixtures and/or initialization code here to make experimenting
8
9
  # with your gem easier. You can also use a different console, if you like.
@@ -28,4 +28,8 @@ Gem::Specification.new do |spec|
28
28
  spec.require_paths = ["lib"]
29
29
 
30
30
  spec.add_dependency "faraday", "~> 1.4.1"
31
+ spec.add_dependency "jwt", '~> 2.2'
32
+
33
+ spec.add_development_dependency "byebug", "~> 11.1"
34
+ spec.add_development_dependency "timecop", "~> 0.9.4"
31
35
  end
@@ -5,16 +5,52 @@ module Clerk
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  protected
8
+
9
+ # Makes a request to the Clerk API to verify the session again and return
10
+ # the Session object. Subsequent calls to this method will return the cached
11
+ # Session object.
12
+ #
13
+ # NOTE: For better performance, you can instead use `#clerk_session_claims`
14
+ # which already contains the verified claims as retrieved from the session
15
+ # token.
8
16
  def clerk_session
9
17
  request.env["clerk"].session
10
18
  end
11
19
 
20
+ # Makes a request to the Clerk API to verify the session again. Returns the
21
+ # session object as fetched from the API.
22
+ #
23
+ # NOTE: For better performance, you can instead use `#clerk_session_claims`
24
+ # which already contains the verified claims as retrieved from the session
25
+ # token.
26
+ #
27
+ # See https://docs.clerk.dev/reference/backend-api-reference/sessions#verify-a-session
28
+ def clerk_reverify_session!
29
+ request.env["clerk"].verify_session
30
+ end
31
+
32
+ def clerk_verified_session_claims
33
+ request.env["clerk"].session_claims
34
+ end
35
+
36
+ def clerk_verified_session_token
37
+ request.env["clerk"].session_token
38
+ end
39
+
40
+ # Makes a request to the Clerk API to fetch the data of the authenticated
41
+ # session's user. If caching is configured (see
42
+ # Config.middleware_cache_store), subsequent calls will return the cached
43
+ # object.
12
44
  def clerk_user
13
45
  request.env["clerk"].user
14
46
  end
15
47
 
48
+ def clerk_user_id
49
+ request.env["clerk"].user_id
50
+ end
51
+
16
52
  def clerk_user_signed_in?
17
- !!clerk_user
53
+ !!clerk_verified_session_claims
18
54
  end
19
55
 
20
56
  def clerk_sign_in_url
@@ -30,8 +66,10 @@ module Clerk
30
66
  end
31
67
 
32
68
  included do
33
- helper_method :clerk_session, :clerk_user, :clerk_user_signed_in?,
34
- :clerk_sign_in_url, :clerk_sign_up_url, :clerk_user_profile_url
69
+ helper_method :clerk_session, :clerk_reverify_session!,
70
+ :clerk_verified_session_claims, :clerk_verified_session_token,
71
+ :clerk_user, :clerk_user_id, :clerk_user_signed_in?, :clerk_sign_in_url,
72
+ :clerk_sign_up_url, :clerk_user_profile_url
35
73
  end
36
74
  end
37
75
  end
@@ -0,0 +1,168 @@
1
+ require "clerk"
2
+
3
+ module Clerk
4
+ class RackMiddlewareV2
5
+ class ProxyV2
6
+ CACHE_TTL = 60 # seconds
7
+
8
+ attr_reader :session_claims, :session_token
9
+
10
+ def initialize(session_claims: nil, session_token: nil)
11
+ @session_claims = session_claims
12
+ @session_token = session_token
13
+ @session = nil
14
+ end
15
+
16
+ def session
17
+ return nil if @session_claims.nil?
18
+
19
+ @session ||= verify_session
20
+ end
21
+
22
+ def verify_session
23
+ return nil if @session_claims.nil?
24
+
25
+ sdk.sessions.verify_token(@session_claims["sid"], @session_token)
26
+ end
27
+
28
+ def user
29
+ return nil if user_id.nil?
30
+
31
+ @user ||= fetch_user(user_id)
32
+ end
33
+
34
+ def user_id
35
+ return nil if @session_claims.nil?
36
+
37
+ @session_claims["sub"]
38
+ end
39
+
40
+ private
41
+
42
+ def fetch_user(user_id)
43
+ cached_fetch("clerk_user:#{user_id}") do
44
+ sdk.users.find(user_id)
45
+ end
46
+ end
47
+
48
+ def cached_fetch(key, &block)
49
+ if store = Clerk.configuration.middleware_cache_store
50
+ store.fetch(key, expires_in: CACHE_TTL, &block)
51
+ else
52
+ yield
53
+ end
54
+ end
55
+
56
+ def sdk
57
+ @sdk ||= Clerk::SDK.new
58
+ end
59
+ end
60
+
61
+ def initialize(app)
62
+ @app = app
63
+ end
64
+
65
+ def call(env)
66
+ @env = env
67
+ @req = Rack::Request.new(env)
68
+ @env["clerk"] = ProxyV2.new
69
+ @header_token = @req.env["HTTP_AUTHORIZATION"]&.strip
70
+ @cookie_token = @req.cookies["__session"]
71
+ @client_uat = @req.cookies["__client_uat"]
72
+
73
+ ##########################################################################
74
+ # #
75
+ # HEADER AUTHENTICATION #
76
+ # #
77
+ ##########################################################################
78
+ if @header_token
79
+ return signed_out if !sdk.decode_token(@header_token) # malformed JWT
80
+
81
+ token = verify_token(@header_token)
82
+ return signed_in(token, @header_token) if token
83
+
84
+ # Clerk.js should refresh the token and retry
85
+ return unknown(interstitial: false)
86
+ end
87
+
88
+ # in cross-origin XHRs the use of Authorization header is mandatory.
89
+ if cross_origin_request?(@req) && @header_token.nil?
90
+ return signed_out
91
+ end
92
+
93
+ ##########################################################################
94
+ # #
95
+ # COOKIE AUTHENTICATION #
96
+ # #
97
+ ##########################################################################
98
+ if development_or_staging? && (@req.referrer.nil? || cross_origin_request?(@req))
99
+ return unknown(interstitial: true)
100
+ end
101
+
102
+ if production? && @client_uat.nil?
103
+ return signed_out
104
+ end
105
+
106
+ if @client_uat == "0"
107
+ return signed_out
108
+ end
109
+
110
+ token = verify_token(@cookie_token)
111
+
112
+ if token && token["iat"] && @client_uat && Integer(@client_uat) <= token["iat"]
113
+ return signed_in(token, @cookie_token)
114
+ end
115
+
116
+ unknown(interstitial: true)
117
+ end
118
+
119
+ private
120
+
121
+ # Outcome A
122
+ def signed_in(claims, token)
123
+ @env["clerk"] = ProxyV2.new(session_claims: claims, session_token: token)
124
+
125
+ @app.call(@env)
126
+ end
127
+
128
+ # Outcome B
129
+ def signed_out
130
+ @app.call(@env)
131
+ end
132
+
133
+ # Outcome C
134
+ def unknown(interstitial: false)
135
+ return [401, {}, []] if !interstitial
136
+
137
+ # Load Clerk.js to update the __session and __client_uat cookies.
138
+ [401, {"Content-Type" => "text/html"}, [sdk.interstitial]]
139
+ end
140
+
141
+ def development_or_staging?
142
+ Clerk.configuration.api_key.start_with?("test_")
143
+ end
144
+
145
+ def production?
146
+ !development_or_staging?
147
+ end
148
+
149
+ def cross_origin_request?(req)
150
+ origin = req.env["HTTP_ORIGIN"]
151
+ origin && origin != req.host
152
+ end
153
+
154
+ def verify_token(token)
155
+ return false if token.nil? || token.strip.empty?
156
+
157
+ begin
158
+ @session = sdk.verify_token(token)
159
+ rescue JWT::DecodeError, JWT::RequiredDependencyError => e
160
+ false
161
+ end
162
+ end
163
+
164
+ def sdk
165
+ @sdk ||= Clerk::SDK.new
166
+ end
167
+ end
168
+ end
data/lib/clerk/railtie.rb CHANGED
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
  #
3
3
  require_relative "rack_middleware"
4
+ require_relative "rack_middleware_v2"
4
5
 
5
6
  module Clerk
6
7
  class Railtie < ::Rails::Railtie
7
8
  initializer "clerk_railtie.configure_rails_initialization" do |app|
8
- app.middleware.use Clerk::RackMiddleware
9
+ app.middleware.use Clerk::RackMiddlewareV2
9
10
  end
10
11
  end
11
12
  end
@@ -0,0 +1,18 @@
1
+ require "forwardable"
2
+ require_relative "plural_resource"
3
+
4
+ module Clerk
5
+ module Resources
6
+ class JWKS
7
+ extend Forwardable
8
+
9
+ def initialize(client)
10
+ @client = client
11
+ end
12
+
13
+ def all(timeout: 5)
14
+ @client.request(:get, "jwks", timeout: timeout)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -5,3 +5,4 @@ require_relative "resources/emails"
5
5
  require_relative "resources/sessions"
6
6
  require_relative "resources/sms_messages"
7
7
  require_relative "resources/users"
8
+ require_relative "resources/jwks"
data/lib/clerk/sdk.rb CHANGED
@@ -4,6 +4,7 @@ require "faraday"
4
4
  require "logger"
5
5
  require "net/http"
6
6
  require "json"
7
+ require "jwt"
7
8
 
8
9
  require_relative "resources/allowlist_identifiers"
9
10
  require_relative "resources/allowlist"
@@ -12,6 +13,8 @@ require_relative "resources/emails"
12
13
  require_relative "resources/sessions"
13
14
  require_relative "resources/sms_messages"
14
15
  require_relative "resources/users"
16
+ require_relative "resources/users"
17
+ require_relative "resources/jwks"
15
18
  require_relative "errors"
16
19
 
17
20
  module Clerk
@@ -20,8 +23,13 @@ module Clerk
20
23
  "User-Agent" => "Clerk/#{Clerk::VERSION}; Faraday/#{Faraday::VERSION}; Ruby/#{RUBY_VERSION}"
21
24
  }
22
25
 
26
+ # How often (in seconds) should JWKs be refreshed
27
+ JWKS_CACHE_LIFETIME = 3600 # 1 hour
28
+
23
29
  def initialize(api_key: nil, base_url: nil, logger: nil, ssl_verify: true,
24
30
  connection: nil)
31
+ @jwks_fetched_at = nil
32
+
25
33
  if connection # Inject a Faraday::Connection for testing or full control over Faraday
26
34
  @conn = connection
27
35
  return
@@ -48,16 +56,26 @@ module Clerk
48
56
  end
49
57
  end
50
58
 
51
- def request(method, path, query: [], body: nil)
59
+ def request(method, path, query: [], body: nil, timeout: nil)
52
60
  response = case method
53
61
  when :get
54
- @conn.get(path, query)
62
+ @conn.get(path, query) do |req|
63
+ req.options.timeout = timeout if timeout
64
+ end
55
65
  when :post
56
- @conn.post(path, body)
66
+ @conn.post(path, body) do |req|
67
+ req.body = body
68
+ req.options.timeout = timeout if timeout
69
+ end
57
70
  when :patch
58
- @conn.patch(path, body)
71
+ @conn.patch(path, body) do |req|
72
+ req.body = body
73
+ req.options.timeout = timeout if timeout
74
+ end
59
75
  when :delete
60
- @conn.delete(path)
76
+ @conn.delete(path) do |req|
77
+ req.options.timeout = timeout if timeout
78
+ end
61
79
  end
62
80
 
63
81
  body = if response["Content-Type"] == "application/json"
@@ -106,5 +124,50 @@ module Clerk
106
124
  def users
107
125
  Resources::Users.new(self)
108
126
  end
127
+
128
+ def jwks
129
+ Resources::JWKS.new(self)
130
+ end
131
+
132
+ def interstitial(refresh=false)
133
+ request(:get, "internal/interstitial")
134
+ end
135
+
136
+ # Returns the decoded JWT payload without verifying if the signature is
137
+ # valid.
138
+ #
139
+ # WARNING: This will not verify whether the signature is valid. You
140
+ # should not use this for untrusted messages! You most likely want to use
141
+ # verify_token.
142
+ def decode_token(token)
143
+ JWT.decode(token, nil, false).first
144
+ end
145
+
146
+ # Decode the JWT and verify it's valid (verify claims, signature etc.) using
147
+ # the provided algorithms.
148
+ #
149
+ # JWKS are cached for JWKS_CACHE_LIFETIME seconds, in order to avoid
150
+ # unecessary roundtrips. In order to invalidate the cache, pass
151
+ # `force_refresh_jwks: true`.
152
+ #
153
+ # A timeout for the request to the JWKs endpoint can be set with the
154
+ # `timeout` argument.
155
+ def verify_token(token, force_refresh_jwks: false, algorithms: ['RS256'], timeout: 5)
156
+ jwk_loader = ->(options) do
157
+ @cached_jwks = nil if options[:invalidate] || force_refresh_jwks
158
+ @cached_jwks = nil if @jwks_fetched_at && Time.now.to_i - @jwks_fetched_at > JWKS_CACHE_LIFETIME
159
+
160
+ @cached_jwks ||= begin
161
+ keys = jwks.all["keys"]
162
+ @jwks_fetched_at = Time.now.to_i
163
+
164
+ # JWT.decode requires that the 'keys' key in the Hash is a symbol (as
165
+ # opposed to a string which our SDK returns by default)
166
+ { keys: keys }
167
+ end
168
+ end
169
+
170
+ JWT.decode(token, nil, true, algorithms: algorithms, jwks: jwk_loader).first
171
+ end
109
172
  end
110
173
  end
data/lib/clerk/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Clerk
4
- VERSION = "1.0.3"
4
+ VERSION = "2.0.0.alpha.1"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: clerk-sdk-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 2.0.0.alpha.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Clerk
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-07-21 00:00:00.000000000 Z
11
+ date: 2021-10-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -24,6 +24,48 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: 1.4.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: jwt
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '11.1'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '11.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: timecop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.9.4
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.9.4
27
69
  description: Client SDK for the Clerk backend API.
28
70
  email:
29
71
  - ruby-sdk@clerk.dev
@@ -47,12 +89,14 @@ files:
47
89
  - lib/clerk/errors.rb
48
90
  - lib/clerk/proxy.rb
49
91
  - lib/clerk/rack_middleware.rb
92
+ - lib/clerk/rack_middleware_v2.rb
50
93
  - lib/clerk/railtie.rb
51
94
  - lib/clerk/resources.rb
52
95
  - lib/clerk/resources/allowlist.rb
53
96
  - lib/clerk/resources/allowlist_identifiers.rb
54
97
  - lib/clerk/resources/clients.rb
55
98
  - lib/clerk/resources/emails.rb
99
+ - lib/clerk/resources/jwks.rb
56
100
  - lib/clerk/resources/plural_resource.rb
57
101
  - lib/clerk/resources/sessions.rb
58
102
  - lib/clerk/resources/singular_resource.rb
@@ -68,7 +112,7 @@ metadata:
68
112
  homepage_uri: https://github.com/clerkinc/clerk-sdk-ruby
69
113
  source_code_uri: https://github.com/clerkinc/clerk-sdk-ruby
70
114
  changelog_uri: https://github.com/clerkinc/clerk-sdk-ruby/blob/main/CHANGELOG.md
71
- post_install_message:
115
+ post_install_message:
72
116
  rdoc_options: []
73
117
  require_paths:
74
118
  - lib
@@ -79,12 +123,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
79
123
  version: 2.4.0
80
124
  required_rubygems_version: !ruby/object:Gem::Requirement
81
125
  requirements:
82
- - - ">="
126
+ - - ">"
83
127
  - !ruby/object:Gem::Version
84
- version: '0'
128
+ version: 1.3.1
85
129
  requirements: []
86
- rubygems_version: 3.2.15
87
- signing_key:
130
+ rubygems_version: 3.2.5
131
+ signing_key:
88
132
  specification_version: 4
89
133
  summary: Clerk SDK for Ruby.
90
134
  test_files: []