clerk-sdk-ruby 1.0.2 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cef3c6d803258b6f1e265dad2cd2de936f96c50df20b87cfbf2b925c37ecd443
4
- data.tar.gz: d280a0b94a0fa8a89d267a7af6915171c5e4860a21fafad2c400f9c3a1610126
3
+ metadata.gz: a7c5c3acce169c08f7086a82fa9cca56744cfe3e04b38108d3ed0b67b513465c
4
+ data.tar.gz: f6f7c462e1e20dc21aae755ba3d1415a532e064bd261bccd5b70326535771314
5
5
  SHA512:
6
- metadata.gz: 3784def3d89def58174ee71733119162a4e579ac99ee4b016bbea7d234e28caa71e59d0c74e3cf3b06b557164ed81c9cdb9630d000929656e4cc4dcb16254861
7
- data.tar.gz: 0f71e96b2781cdfbade6173cb28f9ea14d6f61b4dcd11014dc95b8baa2d45c660ea4d1c21ff5fa87c1bbb60daff8d6eab1c61d354a8f45be21e282e585390ef3
6
+ metadata.gz: 9dff36e343ccaf010b311f24daaee819602ea1b1861b4aeece0961c417bc98ede1e9e70598cc4b41f83cd1a64a3114dd00b1df0cca321a380a3fe4b2e9049112
7
+ data.tar.gz: b0cff1342e5175b4264258fcb581698d514ba693000d3165f7187b994f649b40c40b88033e2f383071f42db48752d2218413b49a310752def343d17d7f2c3ff6
data/.gitignore CHANGED
@@ -6,3 +6,6 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+
10
+ .byebug_history
11
+ *.gem
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  ## unreleased
2
2
 
3
+ ## 2.0.0 - 2021-10-21
4
+
5
+ This release introduces the new networkless middleware which works with the new
6
+ authentication scheme, [Auth v2](https://docs.clerk.dev/main-concepts/auth-v2).
7
+
8
+ It is backwards-incompatible with applications using Auth v1.
9
+
10
+ - [BREAKING]: In order to use this version, you must set the authVersion prop
11
+ accordingly in your frontend: `Clerk.load({authVersion: 2})`
12
+
13
+ For more information on Auth v2, please refer to
14
+ https://docs.clerk.dev/main-concepts/auth-v2.
15
+
16
+ ## 1.0.3 - 2021-07-21
17
+
18
+ - fix: Proper endpoint for oauth_access_token method
19
+
3
20
  ## 1.0.2 - 2021-06-03
4
21
 
5
22
  - fix: Instantiation of `Clerk::SDK` without prior call to `Clerk.configure`
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
@@ -6,6 +6,16 @@ session & user management needs!
6
6
  This SDK allows you to call the Clerk Backend API from Ruby code without having
7
7
  to implement the calls yourself.
8
8
 
9
+ ---------
10
+
11
+ **Note**: This is the v2 branch, which requires that you use [Auth
12
+ v2](https://docs.clerk.dev/main-concepts/auth-v2).
13
+
14
+ If you're looking for the legacy authentication scheme (Auth v1), refer to the
15
+ [`main`](https://github.com/clerkinc/clerk-sdk-ruby/tree/main) branch.
16
+
17
+ ----------
18
+
9
19
  ## Installation
10
20
 
11
21
  Add this line to your application's Gemfile:
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)
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.sub(/(^\w+:|^)\/\//, '') != 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
@@ -7,10 +7,15 @@ module Clerk
7
7
  extend Forwardable
8
8
 
9
9
  def initialize(client)
10
+ @client = client
10
11
  @resource = PluralResource.new(client, "users")
11
12
  end
12
13
 
13
14
  def_delegators :@resource, :all, :find, :update, :delete
15
+
16
+ def oauth_access_token(user_id, provider)
17
+ @client.request(:get, "#{@resource.resource_path(user_id)}/oauth_access_tokens/#{provider}")
18
+ end
14
19
  end
15
20
  end
16
21
  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.2"
4
+ VERSION = "2.0.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.2
4
+ version: 2.0.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-06-03 00:00:00.000000000 Z
11
+ date: 2021-10-21 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
@@ -83,8 +127,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
83
127
  - !ruby/object:Gem::Version
84
128
  version: '0'
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: []