th7-clerk-sdk-ruby 4.2.2

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 (195) hide show
  1. checksums.yaml +7 -0
  2. data/.env.example +3 -0
  3. data/.github/workflows/main.yml +30 -0
  4. data/.github/workflows/semgrep.yml +24 -0
  5. data/.gitignore +21 -0
  6. data/.rspec +3 -0
  7. data/.ruby-version +1 -0
  8. data/CHANGELOG.md +212 -0
  9. data/Gemfile +33 -0
  10. data/Gemfile.lock +300 -0
  11. data/Guardfile +14 -0
  12. data/LICENSE.txt +21 -0
  13. data/README.md +278 -0
  14. data/Rakefile +56 -0
  15. data/apps/rack/app.rb +67 -0
  16. data/apps/rack/config.ru +17 -0
  17. data/apps/rack/middleware/disable_paths.rb +13 -0
  18. data/apps/rails-api/.dockerignore +41 -0
  19. data/apps/rails-api/.gitattributes +9 -0
  20. data/apps/rails-api/.gitignore +32 -0
  21. data/apps/rails-api/.kamal/hooks/docker-setup.sample +3 -0
  22. data/apps/rails-api/.kamal/hooks/post-deploy.sample +14 -0
  23. data/apps/rails-api/.kamal/hooks/post-proxy-reboot.sample +3 -0
  24. data/apps/rails-api/.kamal/hooks/pre-build.sample +51 -0
  25. data/apps/rails-api/.kamal/hooks/pre-connect.sample +47 -0
  26. data/apps/rails-api/.kamal/hooks/pre-deploy.sample +109 -0
  27. data/apps/rails-api/.kamal/hooks/pre-proxy-reboot.sample +3 -0
  28. data/apps/rails-api/.kamal/secrets +17 -0
  29. data/apps/rails-api/.rubocop.yml +8 -0
  30. data/apps/rails-api/.ruby-version +1 -0
  31. data/apps/rails-api/Dockerfile +69 -0
  32. data/apps/rails-api/Gemfile +54 -0
  33. data/apps/rails-api/Gemfile.lock +374 -0
  34. data/apps/rails-api/README.md +24 -0
  35. data/apps/rails-api/Rakefile +6 -0
  36. data/apps/rails-api/app/controllers/application_controller.rb +3 -0
  37. data/apps/rails-api/app/controllers/home_controller.rb +5 -0
  38. data/apps/rails-api/app/jobs/application_job.rb +7 -0
  39. data/apps/rails-api/app/mailers/application_mailer.rb +4 -0
  40. data/apps/rails-api/app/models/application_record.rb +3 -0
  41. data/apps/rails-api/app/views/layouts/mailer.html.erb +13 -0
  42. data/apps/rails-api/app/views/layouts/mailer.text.erb +1 -0
  43. data/apps/rails-api/bin/brakeman +7 -0
  44. data/apps/rails-api/bin/bundle +109 -0
  45. data/apps/rails-api/bin/dev +2 -0
  46. data/apps/rails-api/bin/docker-entrypoint +14 -0
  47. data/apps/rails-api/bin/jobs +6 -0
  48. data/apps/rails-api/bin/kamal +27 -0
  49. data/apps/rails-api/bin/rails +4 -0
  50. data/apps/rails-api/bin/rake +4 -0
  51. data/apps/rails-api/bin/rubocop +8 -0
  52. data/apps/rails-api/bin/setup +34 -0
  53. data/apps/rails-api/bin/thrust +5 -0
  54. data/apps/rails-api/config/application.rb +36 -0
  55. data/apps/rails-api/config/boot.rb +4 -0
  56. data/apps/rails-api/config/cable.yml +17 -0
  57. data/apps/rails-api/config/cache.yml +16 -0
  58. data/apps/rails-api/config/credentials.yml.enc +1 -0
  59. data/apps/rails-api/config/database.yml +41 -0
  60. data/apps/rails-api/config/deploy.yml +116 -0
  61. data/apps/rails-api/config/environment.rb +5 -0
  62. data/apps/rails-api/config/environments/development.rb +70 -0
  63. data/apps/rails-api/config/environments/production.rb +88 -0
  64. data/apps/rails-api/config/environments/test.rb +53 -0
  65. data/apps/rails-api/config/initializers/cors.rb +16 -0
  66. data/apps/rails-api/config/initializers/filter_parameter_logging.rb +8 -0
  67. data/apps/rails-api/config/initializers/inflections.rb +16 -0
  68. data/apps/rails-api/config/locales/en.yml +31 -0
  69. data/apps/rails-api/config/puma.rb +41 -0
  70. data/apps/rails-api/config/queue.yml +18 -0
  71. data/apps/rails-api/config/recurring.yml +10 -0
  72. data/apps/rails-api/config/routes.rb +10 -0
  73. data/apps/rails-api/config/storage.yml +34 -0
  74. data/apps/rails-api/config.ru +6 -0
  75. data/apps/rails-api/db/cable_schema.rb +11 -0
  76. data/apps/rails-api/db/cache_schema.rb +14 -0
  77. data/apps/rails-api/db/queue_schema.rb +129 -0
  78. data/apps/rails-api/db/seeds.rb +9 -0
  79. data/apps/rails-api/public/robots.txt +1 -0
  80. data/apps/rails-api/test/controllers/home_controller_test.rb +7 -0
  81. data/apps/rails-api/test/test_helper.rb +15 -0
  82. data/apps/rails-full/.dockerignore +47 -0
  83. data/apps/rails-full/.gitattributes +9 -0
  84. data/apps/rails-full/.gitignore +34 -0
  85. data/apps/rails-full/.kamal/hooks/docker-setup.sample +3 -0
  86. data/apps/rails-full/.kamal/hooks/post-deploy.sample +14 -0
  87. data/apps/rails-full/.kamal/hooks/post-proxy-reboot.sample +3 -0
  88. data/apps/rails-full/.kamal/hooks/pre-build.sample +51 -0
  89. data/apps/rails-full/.kamal/hooks/pre-connect.sample +47 -0
  90. data/apps/rails-full/.kamal/hooks/pre-deploy.sample +109 -0
  91. data/apps/rails-full/.kamal/hooks/pre-proxy-reboot.sample +3 -0
  92. data/apps/rails-full/.kamal/secrets +17 -0
  93. data/apps/rails-full/.rubocop.yml +8 -0
  94. data/apps/rails-full/.ruby-version +1 -0
  95. data/apps/rails-full/Dockerfile +72 -0
  96. data/apps/rails-full/Gemfile +70 -0
  97. data/apps/rails-full/Gemfile.lock +429 -0
  98. data/apps/rails-full/README.md +24 -0
  99. data/apps/rails-full/Rakefile +6 -0
  100. data/apps/rails-full/app/assets/stylesheets/application.css +10 -0
  101. data/apps/rails-full/app/controllers/application_controller.rb +6 -0
  102. data/apps/rails-full/app/controllers/home_controller.rb +11 -0
  103. data/apps/rails-full/app/helpers/application_helper.rb +2 -0
  104. data/apps/rails-full/app/helpers/home_helper.rb +2 -0
  105. data/apps/rails-full/app/javascript/application.js +3 -0
  106. data/apps/rails-full/app/javascript/controllers/application.js +9 -0
  107. data/apps/rails-full/app/javascript/controllers/hello_controller.js +7 -0
  108. data/apps/rails-full/app/javascript/controllers/index.js +4 -0
  109. data/apps/rails-full/app/jobs/application_job.rb +7 -0
  110. data/apps/rails-full/app/mailers/application_mailer.rb +4 -0
  111. data/apps/rails-full/app/models/application_record.rb +3 -0
  112. data/apps/rails-full/app/views/home/index.html.erb +7 -0
  113. data/apps/rails-full/app/views/layouts/application.html.erb +60 -0
  114. data/apps/rails-full/app/views/layouts/mailer.html.erb +13 -0
  115. data/apps/rails-full/app/views/layouts/mailer.text.erb +1 -0
  116. data/apps/rails-full/app/views/pwa/manifest.json.erb +22 -0
  117. data/apps/rails-full/app/views/pwa/service-worker.js +26 -0
  118. data/apps/rails-full/bin/brakeman +7 -0
  119. data/apps/rails-full/bin/bundle +109 -0
  120. data/apps/rails-full/bin/dev +2 -0
  121. data/apps/rails-full/bin/docker-entrypoint +14 -0
  122. data/apps/rails-full/bin/importmap +4 -0
  123. data/apps/rails-full/bin/jobs +6 -0
  124. data/apps/rails-full/bin/kamal +27 -0
  125. data/apps/rails-full/bin/rails +4 -0
  126. data/apps/rails-full/bin/rake +4 -0
  127. data/apps/rails-full/bin/rubocop +8 -0
  128. data/apps/rails-full/bin/setup +34 -0
  129. data/apps/rails-full/bin/thrust +5 -0
  130. data/apps/rails-full/config/application.rb +31 -0
  131. data/apps/rails-full/config/boot.rb +4 -0
  132. data/apps/rails-full/config/cable.yml +17 -0
  133. data/apps/rails-full/config/cache.yml +16 -0
  134. data/apps/rails-full/config/credentials.yml.enc +1 -0
  135. data/apps/rails-full/config/database.yml +41 -0
  136. data/apps/rails-full/config/deploy.yml +116 -0
  137. data/apps/rails-full/config/environment.rb +5 -0
  138. data/apps/rails-full/config/environments/development.rb +72 -0
  139. data/apps/rails-full/config/environments/production.rb +91 -0
  140. data/apps/rails-full/config/environments/test.rb +53 -0
  141. data/apps/rails-full/config/importmap.rb +7 -0
  142. data/apps/rails-full/config/initializers/assets.rb +7 -0
  143. data/apps/rails-full/config/initializers/clerk.rb +4 -0
  144. data/apps/rails-full/config/initializers/content_security_policy.rb +25 -0
  145. data/apps/rails-full/config/initializers/filter_parameter_logging.rb +8 -0
  146. data/apps/rails-full/config/initializers/inflections.rb +16 -0
  147. data/apps/rails-full/config/locales/en.yml +31 -0
  148. data/apps/rails-full/config/puma.rb +41 -0
  149. data/apps/rails-full/config/queue.yml +18 -0
  150. data/apps/rails-full/config/recurring.yml +10 -0
  151. data/apps/rails-full/config/routes.rb +15 -0
  152. data/apps/rails-full/config/storage.yml +34 -0
  153. data/apps/rails-full/config.ru +6 -0
  154. data/apps/rails-full/db/cable_schema.rb +11 -0
  155. data/apps/rails-full/db/cache_schema.rb +14 -0
  156. data/apps/rails-full/db/queue_schema.rb +129 -0
  157. data/apps/rails-full/db/seeds.rb +9 -0
  158. data/apps/rails-full/public/400.html +114 -0
  159. data/apps/rails-full/public/404.html +114 -0
  160. data/apps/rails-full/public/406-unsupported-browser.html +114 -0
  161. data/apps/rails-full/public/422.html +114 -0
  162. data/apps/rails-full/public/500.html +114 -0
  163. data/apps/rails-full/public/icon.png +0 -0
  164. data/apps/rails-full/public/icon.svg +3 -0
  165. data/apps/rails-full/public/robots.txt +1 -0
  166. data/apps/rails-full/test/application_system_test_case.rb +5 -0
  167. data/apps/rails-full/test/controllers/home_controller_test.rb +7 -0
  168. data/apps/rails-full/test/test_helper.rb +15 -0
  169. data/apps/sinatra/app.rb +29 -0
  170. data/apps/sinatra/config.ru +8 -0
  171. data/apps/sinatra/views/index.erb +44 -0
  172. data/bin/console +16 -0
  173. data/bin/release +21 -0
  174. data/bin/setup +8 -0
  175. data/clerk-sdk-ruby.gemspec +38 -0
  176. data/docs/clerk-logo-dark.png +0 -0
  177. data/docs/clerk-logo-light.png +0 -0
  178. data/lib/clerk/authenticatable.rb +32 -0
  179. data/lib/clerk/authenticate_context.rb +168 -0
  180. data/lib/clerk/authenticate_request.rb +261 -0
  181. data/lib/clerk/configuration.rb +84 -0
  182. data/lib/clerk/constants.rb +74 -0
  183. data/lib/clerk/error.rb +17 -0
  184. data/lib/clerk/jwks_cache.rb +37 -0
  185. data/lib/clerk/proxy.rb +135 -0
  186. data/lib/clerk/rack.rb +2 -0
  187. data/lib/clerk/rack_middleware.rb +112 -0
  188. data/lib/clerk/rails.rb +3 -0
  189. data/lib/clerk/railtie.rb +15 -0
  190. data/lib/clerk/sdk.rb +84 -0
  191. data/lib/clerk/sinatra.rb +52 -0
  192. data/lib/clerk/utils.rb +73 -0
  193. data/lib/clerk/version.rb +5 -0
  194. data/lib/clerk.rb +27 -0
  195. metadata +340 -0
@@ -0,0 +1,37 @@
1
+ require "concurrent"
2
+
3
+ module Clerk
4
+ class JWKSCache
5
+ def initialize(lifetime)
6
+ @lifetime = lifetime
7
+ @jwks = nil
8
+ @last_update = nil
9
+ @lock = Concurrent::ReadWriteLock.new
10
+ end
11
+
12
+ def fetch(sdk, force_refresh: false, kid_not_found: false)
13
+ should_refresh = @lock.with_read_lock do
14
+ now = Time.now.to_i
15
+
16
+ @jwks.nil? || @last_update.nil? || force_refresh ||
17
+ (now - @last_update > @lifetime) ||
18
+ (kid_not_found && now - @last_update > 300)
19
+ end
20
+
21
+ if should_refresh
22
+ @lock.with_write_lock do
23
+ @last_update = Time.now.to_i
24
+ @jwks = begin
25
+ sdk.jwks.get_jwks.keys.map(&:to_hash)
26
+ rescue Clerk::Error, ClerkHttpClient::ApiError
27
+ nil
28
+ end
29
+ end
30
+ end
31
+
32
+ @lock.with_read_lock do
33
+ @jwks
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,135 @@
1
+ require "clerk"
2
+ require "clerk/authenticate_context"
3
+ require "clerk/authenticate_request"
4
+
5
+ module Clerk
6
+ class Proxy
7
+ CACHE_TTL = 60 # seconds
8
+
9
+ attr_reader :session_claims, :session_token
10
+
11
+ def initialize(session_claims: nil, session_token: nil)
12
+ @session_claims = session_claims
13
+ @session_token = session_token
14
+ end
15
+
16
+ def user?
17
+ !@session_claims.nil?
18
+ end
19
+
20
+ def user
21
+ return nil unless user?
22
+
23
+ @user ||= fetch_user(user_id)
24
+ end
25
+
26
+ def user_id
27
+ return nil unless user?
28
+
29
+ @session_claims["sub"]
30
+ end
31
+
32
+ def organization?
33
+ !organization_id.nil?
34
+ end
35
+
36
+ def organization
37
+ return nil unless organization?
38
+
39
+ @org ||= fetch_org(organization_id)
40
+ end
41
+
42
+ def organization_id
43
+ return nil unless user?
44
+
45
+ @session_claims["org_id"]
46
+ end
47
+
48
+ def organization_role
49
+ return nil if @session_claims.nil?
50
+
51
+ @session_claims["org_role"]
52
+ end
53
+
54
+ def organization_permissions
55
+ return nil if @session_claims.nil?
56
+
57
+ @session_claims["org_permissions"]
58
+ end
59
+
60
+ # Returns true if the session needs to perform step up verification
61
+ def user_reverified?(params)
62
+ return false unless user?
63
+
64
+ fva = session_claims["fva"]
65
+
66
+ # the feature is disabled
67
+ return true if fva.nil?
68
+
69
+ level = params[:level]
70
+ after_minutes = params[:after_minutes].to_i
71
+
72
+ return false if after_minutes.nil? || level.nil?
73
+
74
+ factor1_age, factor2_age = fva
75
+ is_valid_factor1 = factor1_age != -1 && after_minutes > factor1_age
76
+ is_valid_factor2 = factor2_age != -1 && after_minutes > factor2_age
77
+
78
+ case level
79
+ when :first_factor
80
+ is_valid_factor1
81
+ when :second_factor
82
+ (factor2_age == -1) ? is_valid_factor1 : is_valid_factor2
83
+ when :multi_factor
84
+ (factor2_age == -1) ? is_valid_factor1 : is_valid_factor1 && is_valid_factor2
85
+ end
86
+ end
87
+
88
+ def user_needs_reverification?(preset = StepUp::Preset::STRICT)
89
+ !user_reverified?(preset)
90
+ end
91
+
92
+ def user_require_reverification!(preset = StepUp::Preset::STRICT, &block)
93
+ return unless user_needs_reverification?(preset)
94
+ yield(preset) if block_given?
95
+ end
96
+
97
+ def user_reverification_rack_response(config = nil)
98
+ raise ArgumentError, "Missing config, please pass a preset a la `Clerk::StepUp::Preset::*`" if config.nil?
99
+
100
+ [
101
+ 403,
102
+ {Clerk::CONTENT_TYPE_HEADER => "application/json"},
103
+ [StepUp::Reverification.error_payload(config).to_json]
104
+ ]
105
+ end
106
+
107
+ private
108
+
109
+ def fetch_user(user_id)
110
+ cached_fetch("clerk:user:#{user_id}") do
111
+ sdk.users.get_user(user_id)
112
+ end
113
+ end
114
+
115
+ def fetch_org(org_id)
116
+ cached_fetch("clerk:org:#{org_id}") do
117
+ sdk.organizations.get_organization(org_id)
118
+ end
119
+ end
120
+
121
+ def cached_fetch(key, &block)
122
+ store = Clerk.configuration.cache_store
123
+
124
+ if store
125
+ store.fetch(key, expires_in: CACHE_TTL, &block)
126
+ else
127
+ yield
128
+ end
129
+ end
130
+
131
+ def sdk
132
+ @sdk ||= Clerk::SDK.new
133
+ end
134
+ end
135
+ end
data/lib/clerk/rack.rb ADDED
@@ -0,0 +1,2 @@
1
+ require "clerk"
2
+ require "clerk/rack_middleware"
@@ -0,0 +1,112 @@
1
+ require "clerk"
2
+ require "clerk/authenticate_context"
3
+ require "clerk/authenticate_request"
4
+ require "clerk/proxy"
5
+ require "clerk/utils"
6
+
7
+ module Clerk
8
+ module Rack
9
+ class Middleware
10
+ def initialize(app, options = {})
11
+ @app = app
12
+
13
+ Clerk.configuration.update(options) if options
14
+ @excluded_routes, @excluded_routes_wildcards = Clerk::Utils.filter_routes(Clerk.configuration.excluded_routes)
15
+ end
16
+
17
+ def call(env)
18
+ env["clerk.initialized"] = true
19
+
20
+ req = ::Rack::Request.new(env)
21
+
22
+ if @excluded_routes[req.path]
23
+ env["clerk.excluded_route"] = true
24
+ return @app.call(env)
25
+ end
26
+
27
+ @excluded_routes_wildcards.each do |route|
28
+ if req.path.start_with?(route)
29
+ env["clerk.excluded_route"] = true
30
+ return @app.call(env)
31
+ end
32
+ end
33
+
34
+ env["clerk"] = Clerk::Proxy.new
35
+
36
+ auth_context = AuthenticateContext.new(req, Clerk.configuration)
37
+ auth_request = AuthenticateRequest.new(auth_context)
38
+
39
+ status, auth_request_headers, body = auth_request.resolve(env)
40
+
41
+ return [status, auth_request_headers, body] if status
42
+
43
+ status, headers, body = @app.call(env)
44
+
45
+ unless auth_request_headers.empty?
46
+ # Remove them to avoid overriding existing cookies set in headers by other middlewares
47
+ auth_request_cookies = auth_request_headers.delete(SET_COOKIE_HEADER.downcase)
48
+ # merge non-cookie related headers into response headers
49
+ headers.merge!(auth_request_headers)
50
+
51
+ set_cookie_headers!(headers, auth_request_cookies) if auth_request_cookies
52
+ end
53
+
54
+ [status, headers, body]
55
+ end
56
+
57
+ private
58
+
59
+ def parse_cookie_key(cookie_header)
60
+ cookie_header.split(";")[0].split("=")[0]
61
+ end
62
+
63
+ def set_cookie_headers!(headers, cookie_headers)
64
+ cookie_headers.each do |cookie_header|
65
+ cookie_key = parse_cookie_key(cookie_header)
66
+ cookie = ::Clerk::Utils.parse_cookies_header(cookie_header)
67
+ cookie_params = convert_http_cookie_to_cookie_setter_params(cookie_key, cookie)
68
+ ::Rack::Utils.set_cookie_header!(headers, cookie_key, cookie_params)
69
+ end
70
+ end
71
+
72
+ def convert_http_cookie_to_cookie_setter_params(cookie_key, cookie)
73
+ # convert cookie to to match cookie setter method params (lowercase symbolized keys with `:value` key)
74
+ cookie_params = cookie.transform_keys { |k| k.downcase.to_sym }
75
+ # drop the current cookie name key to avoid polluting the expected cookie params
76
+ cookie_params[:value] = cookie_params.delete(cookie_key.to_sym)
77
+
78
+ # Ensure secure and httponly are set to true if present
79
+ cookie_params[:secure] = cookie_params.has_key?(:secure)
80
+ cookie_params[:httponly] = cookie_params.has_key?(:httponly)
81
+
82
+ # fix issue with cookie expiration expected to be Date type
83
+ cookie_params[:expires] = Date.parse(cookie_params[:expires]) if cookie_params[:expires]
84
+
85
+ cookie_params
86
+ end
87
+ end
88
+
89
+ class Reverification
90
+ def initialize(app, routes: ["/*"], preset: Clerk::StepUp::Preset::STRICT)
91
+ @app = app
92
+ @preset = preset
93
+
94
+ @included_routes, @included_routes_wildcards = Clerk::Utils.filter_routes(routes)
95
+ end
96
+
97
+ def call(env)
98
+ raise Clerk::ConfigurationError, "`Clerk::Rack::Reverification` must be initialized after `Clerk::Rack::Middleware`" unless env["clerk.initialized"]
99
+ return @app.call(env) if env["clerk.excluded_route"]
100
+
101
+ req = ::Rack::Request.new(env)
102
+ valid_route = @included_routes[req.path] || @included_routes_wildcards.any? { |route| req.path.start_with?(route) }
103
+
104
+ if valid_route && env["clerk"].user_needs_reverification?(@preset)
105
+ return env["clerk"].user_reverification_rack_response(@preset)
106
+ end
107
+
108
+ @app.call(env)
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,3 @@
1
+ require "clerk"
2
+ require "clerk/authenticatable"
3
+ require "clerk/railtie" if defined?(Rails::Railtie)
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "clerk/rack_middleware"
4
+
5
+ module Clerk
6
+ module Rails
7
+ class Railtie < ::Rails::Railtie
8
+ initializer "clerk.configure_rails_initialization" do |app|
9
+ unless ENV["CLERK_SKIP_RAILTIE"]
10
+ app.middleware.use Clerk::Rack::Middleware
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
data/lib/clerk/sdk.rb ADDED
@@ -0,0 +1,84 @@
1
+ require "clerk-http-client"
2
+ require "clerk/jwks_cache"
3
+ require "clerk/version"
4
+ require "jwt"
5
+
6
+ module Clerk
7
+ class SDK < ClerkHttpClient::SDK
8
+ DEFAULT_HEADERS = {
9
+ "User-Agent": "Clerk/#{Clerk::VERSION}; Faraday/#{Faraday::VERSION}; Ruby/#{RUBY_VERSION}",
10
+ "X-Clerk-SDK": "ruby/#{Clerk::VERSION}",
11
+ "Clerk-API-Version": "2025-04-10",
12
+ }
13
+
14
+ # How often (in seconds) should JWKs be refreshed
15
+ JWKS_CACHE_LIFETIME = 3600 # 1 hour
16
+
17
+ @@jwks_cache = JWKSCache.new(JWKS_CACHE_LIFETIME)
18
+
19
+ def self.jwks_cache
20
+ @@jwks_cache
21
+ end
22
+
23
+ # Returns the decoded JWT payload without verifying if the signature is valid.
24
+ #
25
+ # WARNING: This will not verify whether the signature is valid. You should not
26
+ # use this for untrusted messages! You most likely want to use `verify_token`.
27
+ def decode_token(token)
28
+ JWT.decode(token, nil, false).first
29
+ end
30
+
31
+ # Decode the JWT and verify it's valid (verify claims, signature etc.) using the provided algorithms.
32
+ #
33
+ # JWKS are cached for JWKS_CACHE_LIFETIME seconds, in order to avoid unecessary roundtrips.
34
+ # In order to invalidate the cache, pass `force_refresh_jwks: true`.
35
+ #
36
+ # A timeout for the request to the JWKs endpoint can be set with the `timeout` argument.
37
+ def verify_token(token, force_refresh_jwks: false, algorithms: ["RS256"], timeout: 5)
38
+ jwk_loader = ->(options) do
39
+ # JWT.decode requires that the 'keys' key in the Hash is a symbol (as
40
+ # opposed to a string which our SDK returns by default)
41
+ {keys: SDK.jwks_cache.fetch(self, kid_not_found: options[:invalidate] || options[:kid_not_found], force_refresh: force_refresh_jwks)}
42
+ end
43
+
44
+ claims = JWT.decode(token, nil, true, algorithms: algorithms, exp_leeway: timeout, jwks: jwk_loader).first
45
+
46
+ # orgs
47
+ if claims["v"].nil? || claims["v"] == 1
48
+ claims["v"] = 1
49
+ elsif claims["v"] == 2 && claims["o"]
50
+ claims["org_id"] = claims["o"].fetch("id", nil)
51
+ claims["org_slug"] = claims["o"].fetch("slg", nil)
52
+ claims["org_role"] = "org:#{claims["o"].fetch("rol", nil)}"
53
+
54
+ org_permissions = compute_org_permissions_from_v2_token(claims)
55
+ claims["org_permissions"] = org_permissions if org_permissions.any?
56
+ claims.delete("o")
57
+ claims.delete("fea")
58
+ end
59
+
60
+ claims
61
+ end
62
+
63
+ private
64
+
65
+ def compute_org_permissions_from_v2_token(claims)
66
+ features = claims["fea"] ? claims["fea"].split(",") : []
67
+ permissions = claims["o"]["per"] ? claims["o"]["per"].split(",") : []
68
+ mappings = claims["o"]["fpm"] ? claims["o"]["fpm"].split(",") : []
69
+ org_permissions = []
70
+
71
+ mappings.each_with_index do |mapping, i|
72
+ scope, feature = features[i].split(":")
73
+
74
+ next if !scope.include?("o") # not an orgs-related permission
75
+
76
+ mapping.to_i.to_s(2).reverse.each_char.each_with_index do |bit, i|
77
+ org_permissions << "org:#{feature}:#{permissions[i]}" if bit == "1"
78
+ end
79
+ end
80
+
81
+ org_permissions
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,52 @@
1
+ require "sinatra/base"
2
+ require "clerk/rack"
3
+
4
+ module Sinatra
5
+ module Clerk
6
+ module Helpers
7
+ def clerk
8
+ env["clerk"]
9
+ end
10
+
11
+ def require_reverification!(preset = ::Clerk::StepUp::Preset::STRICT, &block)
12
+ clerk.user_require_reverification!(preset) do
13
+ return yield(preset) if block_given?
14
+ render_reverification!(preset)
15
+ end
16
+ end
17
+
18
+ def render_reverification!(preset = nil)
19
+ halt 403, ::Clerk::StepUp::Reverification.error_payload(preset).to_json
20
+ end
21
+
22
+ def clerk_sdk
23
+ @@sdk ||= ::Clerk::SDK.new
24
+ end
25
+ end
26
+
27
+ def self.registered(app)
28
+ app.helpers Clerk::Helpers
29
+ app.use ::Clerk::Rack::Middleware
30
+
31
+ app.set(:auth) do |active|
32
+ condition do
33
+ redirect clerk.sign_in_url if active && !clerk.session
34
+ end
35
+ end
36
+
37
+ app.set(:reverify) do |preset|
38
+ condition do
39
+ if preset === true
40
+ preset = ::Clerk::StepUp::Preset::STRICT
41
+ end
42
+
43
+ if preset
44
+ require_reverification!(preset)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ register Clerk
52
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+
5
+ module Clerk
6
+ module Utils
7
+ class << self
8
+ def decode_publishable_key(publishable_key)
9
+ Base64.decode64(publishable_key.split("_")[2].to_s)
10
+ end
11
+
12
+ def filter_routes(routes)
13
+ filtered_routes = {}
14
+ filtered_wildcard_routes = []
15
+
16
+ routes.each do |route|
17
+ route = route.strip
18
+
19
+ if route.end_with?("/*")
20
+ filtered_wildcard_routes << route[0..-2]
21
+ else
22
+ filtered_routes[route] = true
23
+ end
24
+ end
25
+
26
+ filtered_wildcard_routes.uniq!
27
+
28
+ [filtered_routes, filtered_wildcard_routes]
29
+ end
30
+
31
+ def retrieve_header_from_request(request, key)
32
+ (request.env[key] || request.env[key.downcase]).to_s
33
+ end
34
+
35
+ def retrieve_from_query_string(url, key)
36
+ ::Rack::Utils.parse_query(url.query)[key]
37
+ end
38
+
39
+ def valid_publishable_key?(publishable_key)
40
+ raise ArgumentError, "publishable_key must be a string" unless publishable_key.is_a?(String)
41
+
42
+ key = publishable_key.to_s
43
+ valid_publishable_key_prefix?(key) && valid_publishable_key_postfix?(key)
44
+ end
45
+
46
+ def valid_publishable_key_postfix?(publishable_key)
47
+ decode_publishable_key(publishable_key).end_with?("$")
48
+ end
49
+
50
+ def valid_publishable_key_prefix?(publishable_key)
51
+ publishable_key.start_with?("pk_live_", "pk_test_")
52
+ end
53
+
54
+ # NOTE: This is a copy of Rack::Utils.parse_cookies_header to allow for
55
+ # compatibility with older versions of Rack.
56
+ def parse_cookies_header(value)
57
+ return {} unless value
58
+
59
+ value.split(/; */n).each_with_object({}) do |cookie, cookies|
60
+ next if cookie.empty?
61
+ key, value = cookie.split('=', 2)
62
+ cookies[key] = (unescape(value) rescue value) unless cookies.key?(key)
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def unescape(s, encoding = Encoding::UTF_8)
69
+ URI.decode_www_form_component(s, encoding)
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Clerk
4
+ VERSION = "4.2.2"
5
+ end
data/lib/clerk.rb ADDED
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "clerk/configuration"
4
+ require "clerk/constants"
5
+ require "clerk/error"
6
+ require "clerk/sdk"
7
+ require "clerk/version"
8
+
9
+ if defined?(::Rails)
10
+ require "clerk/rails"
11
+ end
12
+
13
+ module Clerk
14
+ class << self
15
+ def configure
16
+ if block_given?
17
+ yield(configuration)
18
+ else
19
+ configuration
20
+ end
21
+ end
22
+
23
+ def configuration
24
+ @configuration ||= Clerk::Configuration.default
25
+ end
26
+ end
27
+ end