clerk-sdk-ruby 4.0.0.beta3 → 4.0.0.beta4

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 (204) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +3 -0
  3. data/.github/workflows/main.yml +22 -14
  4. data/.gitignore +7 -1
  5. data/.rspec +3 -0
  6. data/.ruby-version +1 -0
  7. data/CHANGELOG.md +22 -0
  8. data/Gemfile +26 -3
  9. data/Gemfile.lock +269 -13
  10. data/Guardfile +14 -0
  11. data/README.md +71 -11
  12. data/Rakefile +50 -6
  13. data/apps/rack/app.rb +67 -0
  14. data/apps/rack/config.ru +17 -0
  15. data/apps/rack/middleware/disable_paths.rb +13 -0
  16. data/apps/rails-api/.dockerignore +41 -0
  17. data/apps/rails-api/.gitattributes +9 -0
  18. data/apps/rails-api/.gitignore +32 -0
  19. data/apps/rails-api/.kamal/hooks/docker-setup.sample +3 -0
  20. data/apps/rails-api/.kamal/hooks/post-deploy.sample +14 -0
  21. data/apps/rails-api/.kamal/hooks/post-proxy-reboot.sample +3 -0
  22. data/apps/rails-api/.kamal/hooks/pre-build.sample +51 -0
  23. data/apps/rails-api/.kamal/hooks/pre-connect.sample +47 -0
  24. data/apps/rails-api/.kamal/hooks/pre-deploy.sample +109 -0
  25. data/apps/rails-api/.kamal/hooks/pre-proxy-reboot.sample +3 -0
  26. data/apps/rails-api/.kamal/secrets +17 -0
  27. data/apps/rails-api/.rubocop.yml +8 -0
  28. data/apps/rails-api/.ruby-version +1 -0
  29. data/apps/rails-api/Dockerfile +69 -0
  30. data/apps/rails-api/Gemfile +54 -0
  31. data/apps/rails-api/Gemfile.lock +374 -0
  32. data/apps/rails-api/README.md +24 -0
  33. data/apps/rails-api/Rakefile +6 -0
  34. data/apps/rails-api/app/controllers/application_controller.rb +3 -0
  35. data/apps/rails-api/app/controllers/home_controller.rb +5 -0
  36. data/apps/rails-api/app/jobs/application_job.rb +7 -0
  37. data/apps/rails-api/app/mailers/application_mailer.rb +4 -0
  38. data/apps/rails-api/app/models/application_record.rb +3 -0
  39. data/apps/rails-api/app/views/layouts/mailer.html.erb +13 -0
  40. data/apps/rails-api/app/views/layouts/mailer.text.erb +1 -0
  41. data/apps/rails-api/bin/brakeman +7 -0
  42. data/apps/rails-api/bin/bundle +109 -0
  43. data/apps/rails-api/bin/dev +2 -0
  44. data/apps/rails-api/bin/docker-entrypoint +14 -0
  45. data/apps/rails-api/bin/jobs +6 -0
  46. data/apps/rails-api/bin/kamal +27 -0
  47. data/apps/rails-api/bin/rails +4 -0
  48. data/apps/rails-api/bin/rake +4 -0
  49. data/apps/rails-api/bin/rubocop +8 -0
  50. data/apps/rails-api/bin/setup +34 -0
  51. data/apps/rails-api/bin/thrust +5 -0
  52. data/apps/rails-api/config/application.rb +36 -0
  53. data/apps/rails-api/config/boot.rb +4 -0
  54. data/apps/rails-api/config/cable.yml +17 -0
  55. data/apps/rails-api/config/cache.yml +16 -0
  56. data/apps/rails-api/config/credentials.yml.enc +1 -0
  57. data/apps/rails-api/config/database.yml +41 -0
  58. data/apps/rails-api/config/deploy.yml +116 -0
  59. data/apps/rails-api/config/environment.rb +5 -0
  60. data/apps/rails-api/config/environments/development.rb +70 -0
  61. data/apps/rails-api/config/environments/production.rb +88 -0
  62. data/apps/rails-api/config/environments/test.rb +53 -0
  63. data/apps/rails-api/config/initializers/cors.rb +16 -0
  64. data/apps/rails-api/config/initializers/filter_parameter_logging.rb +8 -0
  65. data/apps/rails-api/config/initializers/inflections.rb +16 -0
  66. data/apps/rails-api/config/locales/en.yml +31 -0
  67. data/apps/rails-api/config/puma.rb +41 -0
  68. data/apps/rails-api/config/queue.yml +18 -0
  69. data/apps/rails-api/config/recurring.yml +10 -0
  70. data/apps/rails-api/config/routes.rb +10 -0
  71. data/apps/rails-api/config/storage.yml +34 -0
  72. data/apps/rails-api/config.ru +6 -0
  73. data/apps/rails-api/db/cable_schema.rb +11 -0
  74. data/apps/rails-api/db/cache_schema.rb +14 -0
  75. data/apps/rails-api/db/queue_schema.rb +129 -0
  76. data/apps/rails-api/db/seeds.rb +9 -0
  77. data/apps/rails-api/public/robots.txt +1 -0
  78. data/apps/rails-api/test/controllers/home_controller_test.rb +7 -0
  79. data/apps/rails-api/test/test_helper.rb +15 -0
  80. data/apps/rails-full/.dockerignore +47 -0
  81. data/apps/rails-full/.gitattributes +9 -0
  82. data/apps/rails-full/.gitignore +34 -0
  83. data/apps/rails-full/.kamal/hooks/docker-setup.sample +3 -0
  84. data/apps/rails-full/.kamal/hooks/post-deploy.sample +14 -0
  85. data/apps/rails-full/.kamal/hooks/post-proxy-reboot.sample +3 -0
  86. data/apps/rails-full/.kamal/hooks/pre-build.sample +51 -0
  87. data/apps/rails-full/.kamal/hooks/pre-connect.sample +47 -0
  88. data/apps/rails-full/.kamal/hooks/pre-deploy.sample +109 -0
  89. data/apps/rails-full/.kamal/hooks/pre-proxy-reboot.sample +3 -0
  90. data/apps/rails-full/.kamal/secrets +17 -0
  91. data/apps/rails-full/.rubocop.yml +8 -0
  92. data/apps/rails-full/.ruby-version +1 -0
  93. data/apps/rails-full/Dockerfile +72 -0
  94. data/apps/rails-full/Gemfile +70 -0
  95. data/apps/rails-full/Gemfile.lock +429 -0
  96. data/apps/rails-full/README.md +24 -0
  97. data/apps/rails-full/Rakefile +6 -0
  98. data/apps/rails-full/app/assets/stylesheets/application.css +10 -0
  99. data/apps/rails-full/app/controllers/application_controller.rb +6 -0
  100. data/apps/rails-full/app/controllers/home_controller.rb +11 -0
  101. data/apps/rails-full/app/helpers/application_helper.rb +2 -0
  102. data/apps/rails-full/app/helpers/home_helper.rb +2 -0
  103. data/apps/rails-full/app/javascript/application.js +3 -0
  104. data/apps/rails-full/app/javascript/controllers/application.js +9 -0
  105. data/apps/rails-full/app/javascript/controllers/hello_controller.js +7 -0
  106. data/apps/rails-full/app/javascript/controllers/index.js +4 -0
  107. data/apps/rails-full/app/jobs/application_job.rb +7 -0
  108. data/apps/rails-full/app/mailers/application_mailer.rb +4 -0
  109. data/apps/rails-full/app/models/application_record.rb +3 -0
  110. data/apps/rails-full/app/views/home/index.html.erb +7 -0
  111. data/apps/rails-full/app/views/layouts/application.html.erb +60 -0
  112. data/apps/rails-full/app/views/layouts/mailer.html.erb +13 -0
  113. data/apps/rails-full/app/views/layouts/mailer.text.erb +1 -0
  114. data/apps/rails-full/app/views/pwa/manifest.json.erb +22 -0
  115. data/apps/rails-full/app/views/pwa/service-worker.js +26 -0
  116. data/apps/rails-full/bin/brakeman +7 -0
  117. data/apps/rails-full/bin/bundle +109 -0
  118. data/apps/rails-full/bin/dev +2 -0
  119. data/apps/rails-full/bin/docker-entrypoint +14 -0
  120. data/apps/rails-full/bin/importmap +4 -0
  121. data/apps/rails-full/bin/jobs +6 -0
  122. data/apps/rails-full/bin/kamal +27 -0
  123. data/apps/rails-full/bin/rails +4 -0
  124. data/apps/rails-full/bin/rake +4 -0
  125. data/apps/rails-full/bin/rubocop +8 -0
  126. data/apps/rails-full/bin/setup +34 -0
  127. data/apps/rails-full/bin/thrust +5 -0
  128. data/apps/rails-full/config/application.rb +31 -0
  129. data/apps/rails-full/config/boot.rb +4 -0
  130. data/apps/rails-full/config/cable.yml +17 -0
  131. data/apps/rails-full/config/cache.yml +16 -0
  132. data/apps/rails-full/config/credentials.yml.enc +1 -0
  133. data/apps/rails-full/config/database.yml +41 -0
  134. data/apps/rails-full/config/deploy.yml +116 -0
  135. data/apps/rails-full/config/environment.rb +5 -0
  136. data/apps/rails-full/config/environments/development.rb +72 -0
  137. data/apps/rails-full/config/environments/production.rb +91 -0
  138. data/apps/rails-full/config/environments/test.rb +53 -0
  139. data/apps/rails-full/config/importmap.rb +7 -0
  140. data/apps/rails-full/config/initializers/assets.rb +7 -0
  141. data/apps/rails-full/config/initializers/clerk.rb +4 -0
  142. data/apps/rails-full/config/initializers/content_security_policy.rb +25 -0
  143. data/apps/rails-full/config/initializers/filter_parameter_logging.rb +8 -0
  144. data/apps/rails-full/config/initializers/inflections.rb +16 -0
  145. data/apps/rails-full/config/locales/en.yml +31 -0
  146. data/apps/rails-full/config/puma.rb +41 -0
  147. data/apps/rails-full/config/queue.yml +18 -0
  148. data/apps/rails-full/config/recurring.yml +10 -0
  149. data/apps/rails-full/config/routes.rb +15 -0
  150. data/apps/rails-full/config/storage.yml +34 -0
  151. data/apps/rails-full/config.ru +6 -0
  152. data/apps/rails-full/db/cable_schema.rb +11 -0
  153. data/apps/rails-full/db/cache_schema.rb +14 -0
  154. data/apps/rails-full/db/queue_schema.rb +129 -0
  155. data/apps/rails-full/db/seeds.rb +9 -0
  156. data/apps/rails-full/public/400.html +114 -0
  157. data/apps/rails-full/public/404.html +114 -0
  158. data/apps/rails-full/public/406-unsupported-browser.html +114 -0
  159. data/apps/rails-full/public/422.html +114 -0
  160. data/apps/rails-full/public/500.html +114 -0
  161. data/apps/rails-full/public/icon.png +0 -0
  162. data/apps/rails-full/public/icon.svg +3 -0
  163. data/apps/rails-full/public/robots.txt +1 -0
  164. data/apps/rails-full/test/application_system_test_case.rb +5 -0
  165. data/apps/rails-full/test/controllers/home_controller_test.rb +7 -0
  166. data/apps/rails-full/test/test_helper.rb +15 -0
  167. data/apps/sinatra/app.rb +29 -0
  168. data/apps/sinatra/config.ru +2 -0
  169. data/apps/sinatra/views/index.erb +44 -0
  170. data/clerk-sdk-ruby.gemspec +2 -1
  171. data/lib/clerk/authenticatable.rb +14 -79
  172. data/lib/clerk/authenticate_context.rb +164 -181
  173. data/lib/clerk/authenticate_request.rb +238 -230
  174. data/lib/clerk/configuration.rb +78 -0
  175. data/lib/clerk/constants.rb +68 -46
  176. data/lib/clerk/error.rb +17 -0
  177. data/lib/clerk/jwks_cache.rb +27 -22
  178. data/lib/clerk/proxy.rb +135 -0
  179. data/lib/clerk/rack.rb +2 -0
  180. data/lib/clerk/rack_middleware.rb +88 -73
  181. data/lib/clerk/rails.rb +3 -0
  182. data/lib/clerk/railtie.rb +7 -6
  183. data/lib/clerk/sdk.rb +46 -156
  184. data/lib/clerk/sinatra.rb +52 -0
  185. data/lib/clerk/utils.rb +52 -6
  186. data/lib/clerk/version.rb +1 -1
  187. data/lib/clerk.rb +15 -51
  188. metadata +187 -25
  189. data/CODEOWNERS +0 -1
  190. data/lib/clerk/errors.rb +0 -22
  191. data/lib/clerk/rack_middleware_v2.rb +0 -167
  192. data/lib/clerk/resources/allowlist.rb +0 -16
  193. data/lib/clerk/resources/allowlist_identifiers.rb +0 -16
  194. data/lib/clerk/resources/clients.rb +0 -23
  195. data/lib/clerk/resources/email_addresses.rb +0 -17
  196. data/lib/clerk/resources/emails.rb +0 -16
  197. data/lib/clerk/resources/jwks.rb +0 -18
  198. data/lib/clerk/resources/organizations.rb +0 -73
  199. data/lib/clerk/resources/phone_numbers.rb +0 -17
  200. data/lib/clerk/resources/plural_resource.rb +0 -38
  201. data/lib/clerk/resources/sessions.rb +0 -26
  202. data/lib/clerk/resources/singular_resource.rb +0 -14
  203. data/lib/clerk/resources/users.rb +0 -37
  204. data/lib/clerk/resources.rb +0 -10
@@ -1,50 +1,72 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Clerk
2
- SESSION_COOKIE = "__session".freeze
3
- CLIENT_UAT_COOKIE = "__client_uat".freeze
4
-
5
- # Dev Browser
6
- DEV_BROWSER_COOKIE = "__clerk_db_jwt".freeze
7
-
8
- # Handshake
9
- HANDSHAKE_COOKIE = "__clerk_handshake".freeze
10
- HANDSHAKE_HELP_QUERY_PARAM = "__clerk_help".freeze
11
- HANDSHAKE_COOKIE_DIRECTIVES_KEY = "handshake".freeze
12
-
13
- # auth debug response headers
14
- AUTH_STATUS_HEADER = "X-Clerk-Auth-Status".freeze
15
- AUTH_REASON_HEADER = "X-Clerk-Auth-Reason".freeze
16
- AUTH_MESSAGE_HEADER = "X-Clerk-Auth-Message".freeze
17
-
18
- #
19
- CONTENT_TYPE_HEADER = "Content-Type".freeze
20
- SEC_FETCH_DEST_HEADER = "HTTP_SEC_FETCH_DEST".freeze
21
-
22
- # headers used in response - should be lowered case and without http prefix
23
- LOCATION_HEADER = "Location".freeze
24
- COOKIE_HEADER = "Set-Cookie".freeze
25
-
26
- # clerk url related headers
27
- AUTHORIZATION_HEADER = "HTTP_AUTHORIZATION".freeze
28
- ACCEPT_HEADER = "HTTP_ACCEPT".freeze
29
- USER_AGENT_HEADER = "HTTP_USER_AGENT".freeze
30
- ORIGIN_HEADER = "HTTP_ORIGIN".freeze
31
-
32
- module TokenVerificationErrorReason
33
- TOKEN_INVALID = "token-invalid".freeze
34
- TOKEN_EXPIRED = "token-expired".freeze
35
- TOKEN_NOT_ACTIVE_YET = "token-not-active-yet".freeze
36
- JWK_FAILED_TO_RESOLVE = "jwk-failed-to-resolve".freeze
4
+ SESSION_COOKIE = "__session"
5
+ CLIENT_UAT_COOKIE = "__client_uat"
6
+
7
+ # Dev Browser
8
+ DEV_BROWSER_COOKIE = "__clerk_db_jwt"
9
+
10
+ # Handshake
11
+ HANDSHAKE_COOKIE = "__clerk_handshake"
12
+ HANDSHAKE_COOKIE_DIRECTIVES_KEY = "handshake"
13
+
14
+ # auth debug response headers
15
+ AUTH_STATUS_HEADER = "X-Clerk-Auth-Status"
16
+ AUTH_REASON_HEADER = "X-Clerk-Auth-Reason"
17
+ AUTH_MESSAGE_HEADER = "X-Clerk-Auth-Message"
18
+
19
+ CONTENT_TYPE_HEADER = "Content-Type"
20
+ SEC_FETCH_DEST_HEADER = "HTTP_SEC_FETCH_DEST"
21
+
22
+ # headers used in response - should be lowered case and without http prefix
23
+ LOCATION_HEADER = "Location"
24
+ SET_COOKIE_HEADER = "set-cookie"
25
+
26
+ # clerk url related headers
27
+ AUTHORIZATION_HEADER = "HTTP_AUTHORIZATION"
28
+ ACCEPT_HEADER = "HTTP_ACCEPT"
29
+ USER_AGENT_HEADER = "HTTP_USER_AGENT"
30
+ ORIGIN_HEADER = "HTTP_ORIGIN"
31
+
32
+ module TokenVerificationErrorReason
33
+ TOKEN_INVALID = "token-invalid"
34
+ TOKEN_EXPIRED = "token-expired"
35
+ TOKEN_NOT_ACTIVE_YET = "token-not-active-yet"
36
+ JWK_FAILED_TO_RESOLVE = "jwk-failed-to-resolve"
37
+ end
38
+
39
+ module AuthErrorReason
40
+ CLIENT_UAT_WITHOUT_SESSION_TOKEN = "client-uat-but-no-session-token"
41
+ DEV_BROWSER_SYNC = "dev-browser-sync"
42
+ DEV_BROWSER_MISSING = "dev-browser-missing"
43
+ PRIMARY_RESPONDS_TO_SYNCING = "primary-responds-to-syncing"
44
+ SATELLITE_COOKIE_NEEDS_SYNCING = "satellite-needs-syncing"
45
+ SESSION_TOKEN_AND_UAT_MISSING = "session-token-and-uat-missing"
46
+ SESSION_TOKEN_MISSING = "session-token-missing"
47
+ SESSION_TOKEN_OUTDATED = "session-token-outdated"
48
+ SESSION_TOKEN_WITHOUT_CLIENT_UAT = "session-token-but-no-client-uat"
49
+ UNEXPECTED_ERROR = "unexpected-error"
50
+ end
51
+
52
+ module StepUp
53
+ module Preset
54
+ STRICT_MFA = {after_minutes: 10, level: :multi_factor}
55
+ STRICT = {after_minutes: 10, level: :second_factor}
56
+ MODERATE = {after_minutes: 60, level: :second_factor}
57
+ LAX = {after_minutes: 1440, level: :second_factor}
37
58
  end
38
59
 
39
- module AuthErrorReason
40
- CLIENT_UAT_WITHOUT_SESSION_TOKEN = "client-uat-but-no-session-token".freeze
41
- DEV_BROWSER_SYNC = "dev-browser-sync".freeze
42
- PRIMARY_RESPONDS_TO_SYNCING = "primary-responds-to-syncing".freeze
43
- SATELLITE_COOKIE_NEEDS_SYNCING = "satellite-needs-syncing".freeze
44
- SESSION_TOKEN_AND_UAT_MISSING = "session-token-and-uat-missing".freeze
45
- SESSION_TOKEN_MISSING = "session-token-missing".freeze
46
- SESSION_TOKEN_OUTDATED = "session-token-outdated".freeze
47
- SESSION_TOKEN_WITHOUT_CLIENT_UAT = "session-token-but-no-client-uat".freeze
48
- UNEXPECTED_ERROR = "unexpected-error".freeze
60
+ module Reverification
61
+ def self.error_payload(missing_config)
62
+ {
63
+ clerk_error: {
64
+ type: "forbidden",
65
+ reason: "reverification-error",
66
+ metadata: {reverification: missing_config}
67
+ }
68
+ }
69
+ end
49
70
  end
50
- end
71
+ end
72
+ end
@@ -0,0 +1,17 @@
1
+ module Clerk
2
+ class Error < StandardError
3
+ attr_reader :status
4
+
5
+ def initialize(msg, status:)
6
+ @errors = msg["errors"]
7
+ @status = status
8
+ super(msg.merge(status: status))
9
+ end
10
+ end
11
+
12
+ class AuthenticationError < Error; end
13
+
14
+ class ConfigurationError < StandardError; end
15
+
16
+ class FatalError < Error; end
17
+ end
@@ -1,32 +1,37 @@
1
- class JWKSCache
2
- def initialize(lifetime)
3
- @lifetime = lifetime
4
- @jwks = nil
5
- @last_update = nil
6
- @lock = Concurrent::ReadWriteLock.new
7
- end
1
+ require "concurrent"
8
2
 
9
- def fetch(sdk, force_refresh: false, kid_not_found: false)
10
- should_refresh = @lock.with_read_lock do
11
- @jwks.nil? || @last_update.nil? || force_refresh ||
12
- (Time.now.to_i-@last_update > @lifetime) ||
13
- (kid_not_found && Time.now.to_i-@last_update > 300)
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
14
10
  end
15
11
 
16
- if should_refresh
17
- @lock.with_write_lock do
18
- @last_update = Time.now.to_i
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
19
20
 
20
- @jwks = begin
21
- sdk.jwks.all["keys"]
22
- rescue Clerk::Errors::Base
23
- nil
21
+ if should_refresh
22
+ @lock.with_write_lock do
23
+ @last_update = Time.now.to_i
24
+ @jwks = begin
25
+ sdk.jwks.get.keys.map(&:to_hash)
26
+ rescue Clerk::Error, ClerkHttpClient::ApiError
27
+ nil
28
+ end
24
29
  end
25
30
  end
26
- end
27
31
 
28
- @lock.with_read_lock do
29
- @jwks
32
+ @lock.with_read_lock do
33
+ @jwks
34
+ end
30
35
  end
31
36
  end
32
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
+ {"Content-Type" => "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.find(user_id)
112
+ end
113
+ end
114
+
115
+ def fetch_org(org_id)
116
+ cached_fetch("clerk:org:#{org_id}") do
117
+ sdk.organizations.find(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"
@@ -1,97 +1,112 @@
1
- require_relative "sdk"
1
+ require "clerk"
2
+ require "clerk/authenticate_context"
3
+ require "clerk/authenticate_request"
4
+ require "clerk/proxy"
5
+ require "clerk/utils"
2
6
 
3
7
  module Clerk
4
- class RackMiddleware
5
- def initialize(app)
6
- @app = app
7
- end
8
+ module Rack
9
+ class Middleware
10
+ def initialize(app, options = {})
11
+ @app = app
8
12
 
9
- def call(env)
10
- req = Rack::Request.new(env)
11
- env["clerk"] = Proxy.new(env)
12
- @app.call(env)
13
- end
14
- end
13
+ Clerk.configuration.update(options) if options
14
+ @excluded_routes, @excluded_routes_wildcards = Clerk::Utils.filter_routes(Clerk.configuration.excluded_routes)
15
+ end
15
16
 
16
- class Proxy
17
- attr_reader :session_id, :error
18
- def initialize(env)
19
- req = Rack::Request.new(env)
20
- @token = req.cookies[SESSION_COOKIE]
21
- @session_id = req.params["_clerk_session_id"]
22
- @session = nil
23
- @user_id = nil
24
- @user = nil
25
- end
17
+ def call(env)
18
+ env["clerk.initialized"] = true
26
19
 
27
- def session
28
- return nil if @token.nil?
29
- return @session if @session
20
+ req = ::Rack::Request.new(env)
30
21
 
31
- begin
32
- @session = fetch_session
33
- rescue Errors::Authentication => e
34
- @error = e
35
- end
36
- @session
37
- end
22
+ if @excluded_routes[req.path]
23
+ env["clerk.excluded_route"] = true
24
+ return @app.call(env)
25
+ end
38
26
 
39
- def user_id
40
- @user_id ||= session.dig("user_id")
41
- end
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
42
33
 
43
- def user
44
- return nil if session.nil?
45
- @user ||= fetch_user(user_id)
46
- end
34
+ env["clerk"] = Clerk::Proxy.new
47
35
 
48
- def debug
49
- (instance_variables - [:@sdk]).map do |ivar|
50
- [ivar.to_s, instance_variable_get(ivar)]
51
- end.to_h
52
- end
36
+ auth_context = AuthenticateContext.new(req, Clerk.configuration)
37
+ auth_request = AuthenticateRequest.new(auth_context)
53
38
 
54
- private
55
- def sdk
56
- @sdk ||= SDK.new
57
- end
39
+ status, auth_request_headers, body = auth_request.resolve(env)
58
40
 
59
- def cache_key
60
- @cache_key ||= @token.split(".")[1]
61
- end
41
+ return [status, auth_request_headers, body] if status
42
+
43
+ status, headers, body = @app.call(env)
62
44
 
63
- def fetch_session
64
- if session_id
65
- cached_fetch("clerk_session:#{session_id}:#{cache_key}") do
66
- sdk.sessions.verify_token(session_id, @token)
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
67
52
  end
68
- else
69
- cached_fetch("clerk_session:#{cache_key}") do
70
- client = sdk.clients.verify_token(@token)
71
- @session_id = client["last_active_session_id"]
72
- client["sessions"].find do |sess|
73
- sess["id"] == @session_id
74
- 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 = ::Rack::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)
75
69
  end
76
70
  end
77
- end
78
71
 
79
- def fetch_user(user_id)
80
- cached_fetch("clerk_user:#{user_id}") do
81
- sdk.users.find(user_id)
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
82
86
  end
83
87
  end
84
88
 
85
- def cached_fetch(key, &block)
86
- if store = Clerk.configuration.middleware_cache_store
87
- store.fetch(key, expires_in: cache_ttl, &block)
88
- else
89
- yield
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)
90
95
  end
91
- end
92
96
 
93
- def cache_ttl
94
- 60
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
95
110
  end
96
111
  end
97
112
  end
@@ -0,0 +1,3 @@
1
+ require "clerk"
2
+ require "clerk/authenticatable"
3
+ require "clerk/railtie"
data/lib/clerk/railtie.rb CHANGED
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
- #
3
- require_relative "rack_middleware"
4
- require_relative "rack_middleware_v2"
2
+
3
+ require "clerk/rack_middleware"
5
4
 
6
5
  module Clerk
7
- class Railtie < ::Rails::Railtie
8
- initializer "clerk_railtie.configure_rails_initialization" do |app|
9
- app.middleware.use Clerk::RackMiddlewareV2
6
+ module Rails
7
+ class Railtie < ::Rails::Railtie
8
+ initializer "clerk.configure_rails_initialization" do |app|
9
+ app.middleware.use Clerk::Rack::Middleware
10
+ end
10
11
  end
11
12
  end
12
13
  end