clerk-sdk-ruby 2.11.1 → 4.0.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +2 -0
- data/.github/workflows/semgrep.yml +24 -0
- data/CHANGELOG.md +29 -3
- data/CODEOWNERS +1 -1
- data/Gemfile.lock +1 -1
- data/README.md +33 -11
- data/lib/clerk/authenticatable.rb +10 -1
- data/lib/clerk/authenticate_context.rb +183 -0
- data/lib/clerk/authenticate_request.rb +253 -0
- data/lib/clerk/constants.rb +50 -0
- data/lib/clerk/rack_middleware.rb +1 -1
- data/lib/clerk/rack_middleware_v2.rb +42 -141
- data/lib/clerk/resources.rb +0 -1
- data/lib/clerk/sdk.rb +2 -11
- data/lib/clerk/version.rb +1 -1
- data/lib/clerk.rb +4 -1
- metadata +8 -5
- data/lib/clerk/resources/sms_messages.rb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7b3b121821e34e9882661931f7e5c7ccdd630095905252ba53c8a48b9c62320c
|
4
|
+
data.tar.gz: ab0b8077553effb1c8521204787499ee28e0a544629980a7b098789a5c474e14
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 007f6ba6dc18e7d1e54173160e069bc76869b7c13a645eea53943669536ad3963e95272a2ace2d64cd7c9c68d3386dbe5695726e235f559f59f8b15f1addec99
|
7
|
+
data.tar.gz: a0ecb1901197fe8e2a01269326dfb61bdf6da2dd0b9c22298fb580370d6cf2527f5e5f1eaa0a19687b3d242104bf8784a740ac80bfae0df1ff576e1b03c14451
|
data/.github/workflows/main.yml
CHANGED
@@ -0,0 +1,24 @@
|
|
1
|
+
name: Semgrep
|
2
|
+
on:
|
3
|
+
workflow_dispatch: {}
|
4
|
+
pull_request: {}
|
5
|
+
push:
|
6
|
+
branches:
|
7
|
+
- main
|
8
|
+
paths:
|
9
|
+
- .github/workflows/semgrep.yml
|
10
|
+
schedule:
|
11
|
+
# random HH:MM to avoid a load spike on GitHub Actions at 00:00
|
12
|
+
- cron: '15 18 * * *'
|
13
|
+
jobs:
|
14
|
+
semgrep:
|
15
|
+
name: semgrep/ci
|
16
|
+
runs-on: ubuntu-22.04
|
17
|
+
env:
|
18
|
+
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
|
19
|
+
container:
|
20
|
+
image: returntocorp/semgrep
|
21
|
+
if: (github.actor != 'dependabot[bot]')
|
22
|
+
steps:
|
23
|
+
- uses: actions/checkout@v3
|
24
|
+
- run: semgrep ci
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,30 @@
|
|
1
|
-
|
1
|
+
|
2
|
+
## 4.0.0.beta2 - 2024-02-26
|
3
|
+
|
4
|
+
Note: this is identical to 4.0.0.beta1, which was yanked because it was not generated from the main branch.
|
5
|
+
|
6
|
+
- feat: replace interstitial with handshake (internal mechanisms) [https://github.com/clerk/clerk-sdk-ruby/pull/45]
|
7
|
+
- chore: re-organize and refactor internal code to extract functionality of rack middleware [https://github.com/clerk/clerk-sdk-ruby/pull/45]
|
8
|
+
- changed: `CLERK_PUBLISHABLE_KEY` or `publishable_key` in `Clerk.configure` is **required** [https://github.com/clerk/clerk-sdk-ruby/pull/46]
|
9
|
+
|
10
|
+
## [YANKED] 4.0.0.beta1 - 2024-02-26
|
11
|
+
|
12
|
+
- feat: replace interstitial with handshake (internal mechanisms) [https://github.com/clerk/clerk-sdk-ruby/pull/45]
|
13
|
+
- chore: re-organize and refactor internal code to extract functionality of rack middleware [https://github.com/clerk/clerk-sdk-ruby/pull/45]
|
14
|
+
- changed: `CLERK_PUBLISHABLE_KEY` or `publishable_key` in `Clerk.configure` is **required** [https://github.com/clerk/clerk-sdk-ruby/pull/46]
|
15
|
+
|
16
|
+
## 3.0.0 - 2024-01-09
|
17
|
+
|
18
|
+
Note: this is identical to 2.12.0, which was yanked because it contained a
|
19
|
+
breaking change.
|
20
|
+
|
21
|
+
- feat: Add org role/permission helpers [https://github.com/clerk/clerk-sdk-ruby/pull/40]
|
22
|
+
- changed: drop create sms endpoint [https://github.com/clerk/clerk-sdk-ruby/pull/39]
|
23
|
+
|
24
|
+
## [YANKED] 2.12.0 - 2024-01-09
|
25
|
+
|
26
|
+
- feat: Add org role/permission helpers [https://github.com/clerk/clerk-sdk-ruby/pull/40]
|
27
|
+
- changed: drop create sms endpoint [https://github.com/clerk/clerk-sdk-ruby/pull/39]
|
2
28
|
|
3
29
|
## 2.11.1 - 2023-10-31
|
4
30
|
|
@@ -78,12 +104,12 @@ Identical to 2.9.0.beta3
|
|
78
104
|
|
79
105
|
## 2.0.0 - 2021-10-21
|
80
106
|
|
81
|
-
This release introduces the new networkless middleware which works with the new
|
107
|
+
This release introduces the new networkless middleware which works with the new
|
82
108
|
authentication scheme, [Auth v2](https://clerk.com/docs/upgrade-guides/auth-v2).
|
83
109
|
|
84
110
|
It is backwards-incompatible with applications using Auth v1.
|
85
111
|
|
86
|
-
- [BREAKING]: In order to use this version, you must set the authVersion prop
|
112
|
+
- [BREAKING]: In order to use this version, you must set the authVersion prop
|
87
113
|
accordingly in your frontend: `Clerk.load({authVersion: 2})`
|
88
114
|
|
89
115
|
For more information on Auth v2, please refer to
|
data/CODEOWNERS
CHANGED
@@ -1 +1 @@
|
|
1
|
-
* @clerkinc/backend
|
1
|
+
* @clerkinc/backend
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -87,8 +87,9 @@ supported configuration settings their environment variable equivalents:
|
|
87
87
|
|
88
88
|
```ruby
|
89
89
|
Clerk.configure do |c|
|
90
|
-
c.api_key = "your_api_key" # if omitted: ENV["
|
91
|
-
c.base_url = "https://..." # if omitted: "https://api.clerk.
|
90
|
+
c.api_key = "your_api_key" # if omitted: ENV["CLERK_SECRET_KEY"] - API calls will fail if unset
|
91
|
+
c.base_url = "https://..." # if omitted: ENV["CLERK_API_BASE"] - defaults to "https://api.clerk.com/v1/"
|
92
|
+
c.publishable_key = "pk_(test|live)_...." # if omitted: ENV["CLERK_PUBLISHABLE_KEY"] - Handshake mechanism (check section below) will fail if unset
|
92
93
|
c.logger = Logger.new(STDOUT) # if omitted, no logging
|
93
94
|
c.middleware_cache_store = ActiveSupport::Cache::FileStore.new("/tmp/clerk_middleware_cache") # if omitted: no caching
|
94
95
|
c.excluded_routes ["/foo", "/bar/*"]
|
@@ -159,24 +160,45 @@ single key (`errors`) containing an array of error objects.
|
|
159
160
|
Read the [API documentation](https://clerk.com/docs/reference/backend-api)
|
160
161
|
for details on expected parameters and response formats.
|
161
162
|
|
163
|
+
<a name="handshake"></a>
|
164
|
+
|
165
|
+
### Handshake
|
166
|
+
|
167
|
+
The Client Handshake is a mechanism that is used to resolve a request’s authentication state from “unknown” to definitively signed in or signed out. Clerk’s session management architecture relies on a short-lived session JWT to validate requests, along with a long-lived session that is used to keep the session JWT fresh by interacting with the Frontend API. The long-lived session token is stored in an HttpOnly cookie associated with the Frontend API domain. If a short-lived session JWT is expired on a request to an application’s backend, the SDK doesn’t know if the session has ended, or if a new short-lived JWT needs to be issued. When an SDK gets into this state, it triggers the handshake.
|
168
|
+
|
169
|
+
With the handshake, we can resolve the authentication state on the backend and ensure the request is properly handled as signed in or out, instead of being in a potentially “unknown” state. The handshake flow relies on redirects to exchange session information between FAPI and the application, ensuring the resolution of unknown authentication states minimizes performance impact and behaves consistently across different framework and language implementations.
|
170
|
+
|
162
171
|
## Development
|
163
172
|
|
164
173
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
165
174
|
`bundle exec rake` to run the tests. You can also run `bin/console` for an
|
166
175
|
interactive prompt that will allow you to experiment.
|
167
176
|
|
168
|
-
To install this gem onto your local machine, run `bundle exec rake install`.
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
177
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
178
|
+
|
179
|
+
## Release
|
180
|
+
|
181
|
+
To release a new version:
|
182
|
+
- update the version number in `version.rb`
|
183
|
+
- update `CHANGELOG.md` to include information about the changes
|
184
|
+
- merge changes into main
|
185
|
+
- run `bundle exec rake release`
|
186
|
+
|
187
|
+
If gem publishing is NOT executed automatically:
|
188
|
+
- run `gem push pkg/clerk-sdk-ruby-{version}.gem` to push the `.gem` file to [rubygems.org](https://rubygems.org)
|
189
|
+
|
190
|
+
The `bundle exec rake release` command:
|
191
|
+
- creates a git tag with the version found in `version.rb`
|
192
|
+
- pushes the git tag
|
193
|
+
|
194
|
+
## Yank release
|
195
|
+
|
196
|
+
We should avoid yanking a releasing but if it's necessary execute `gem yank clerk-sdk-ruby -v {version}`
|
173
197
|
|
174
198
|
## Contributing
|
175
199
|
|
176
|
-
Bug reports and pull requests are welcome on GitHub at
|
177
|
-
https://github.com/clerkinc/clerk-sdk-ruby.
|
200
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/clerkinc/clerk-sdk-ruby.
|
178
201
|
|
179
202
|
## License
|
180
203
|
|
181
|
-
The gem is available as open source under the terms of the
|
182
|
-
[MIT License](https://opensource.org/licenses/MIT).
|
204
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -61,6 +61,14 @@ module Clerk
|
|
61
61
|
request.env["clerk"].org_id
|
62
62
|
end
|
63
63
|
|
64
|
+
def clerk_organization_role
|
65
|
+
request.env["clerk"].org_role
|
66
|
+
end
|
67
|
+
|
68
|
+
def clerk_organization_permissions
|
69
|
+
request.env["clerk"].org_permissions
|
70
|
+
end
|
71
|
+
|
64
72
|
def clerk_user_signed_in?
|
65
73
|
!!clerk_verified_session_claims
|
66
74
|
end
|
@@ -82,7 +90,8 @@ module Clerk
|
|
82
90
|
:clerk_verified_session_claims, :clerk_verified_session_token,
|
83
91
|
:clerk_user, :clerk_user_id, :clerk_user_signed_in?, :clerk_sign_in_url,
|
84
92
|
:clerk_sign_up_url, :clerk_user_profile_url,
|
85
|
-
:clerk_organization, :clerk_organization_id
|
93
|
+
:clerk_organization, :clerk_organization_id, :clerk_organization_role,
|
94
|
+
:clerk_organization_permissions
|
86
95
|
end
|
87
96
|
end
|
88
97
|
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require "ostruct"
|
2
|
+
require "forwardable"
|
3
|
+
require "base64"
|
4
|
+
|
5
|
+
module Clerk
|
6
|
+
##
|
7
|
+
# This class represents a parameter object used to contain all request and configuration
|
8
|
+
# information required by the middleware to resolve the current request state.
|
9
|
+
# link: https://refactoring.guru/introduce-parameter-object
|
10
|
+
class AuthenticateContext
|
11
|
+
extend Forwardable
|
12
|
+
|
13
|
+
##
|
14
|
+
# Expose the url of the request that this parameter object was created from as a URI object.
|
15
|
+
attr_reader :clerk_url
|
16
|
+
|
17
|
+
##
|
18
|
+
# Expose properties that does not require validations or complex logic to retrieve
|
19
|
+
# values by delegating them to the cookies or headers variables.
|
20
|
+
def_delegators :@cookies, :session_token_in_cookie, :client_uat
|
21
|
+
def_delegators :@headers, :session_token_in_header, :sec_fetch_dest
|
22
|
+
|
23
|
+
##
|
24
|
+
# Creates a new parameter object using Rack::Request and Clerk::Config objects.
|
25
|
+
def initialize(request, config)
|
26
|
+
@clerk_url = URI.parse(request.url)
|
27
|
+
@config = config
|
28
|
+
|
29
|
+
@cookies = OpenStruct.new({
|
30
|
+
session_token_in_cookie: request.cookies[SESSION_COOKIE],
|
31
|
+
client_uat: request.cookies[CLIENT_UAT_COOKIE],
|
32
|
+
handshake_token: request.cookies[HANDSHAKE_COOKIE],
|
33
|
+
dev_browser: request.cookies[DEV_BROWSER_COOKIE]
|
34
|
+
})
|
35
|
+
|
36
|
+
@headers = OpenStruct.new({
|
37
|
+
session_token_in_header: request.env[AUTHORIZATION_HEADER].to_s.gsub(/bearer/i, '').strip,
|
38
|
+
sec_fetch_dest: request.env[SEC_FETCH_DEST_HEADER],
|
39
|
+
accept: request.env[ACCEPT_HEADER].to_s,
|
40
|
+
origin: request.env[ORIGIN_HEADER].to_s,
|
41
|
+
host: request.host,
|
42
|
+
port: request.port
|
43
|
+
})
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# The following properties are part of the props supported in all the AuthenticateContext
|
48
|
+
# objects across all of our SDKs (eg JS, Go)
|
49
|
+
def secret_key
|
50
|
+
@config.api_key.to_s
|
51
|
+
end
|
52
|
+
|
53
|
+
def publishable_key
|
54
|
+
@config.publishable_key.to_s
|
55
|
+
end
|
56
|
+
|
57
|
+
def domain
|
58
|
+
# TODO(dimkl): Add multi-domain support
|
59
|
+
""
|
60
|
+
end
|
61
|
+
|
62
|
+
def is_satellite?
|
63
|
+
# TODO(dimkl): Add multi-domain support
|
64
|
+
false
|
65
|
+
end
|
66
|
+
|
67
|
+
def proxy_url
|
68
|
+
# TODO(dimkl): Add multi-domain support
|
69
|
+
""
|
70
|
+
end
|
71
|
+
|
72
|
+
def handshake_token
|
73
|
+
@handshake_token ||= retrieve_from_query_string(@clerk_url, HANDSHAKE_COOKIE) || @cookies.handshake_token.to_s
|
74
|
+
end
|
75
|
+
|
76
|
+
def clerk_synced?
|
77
|
+
# TODO(dimkl): Add multi-domain support
|
78
|
+
false
|
79
|
+
end
|
80
|
+
|
81
|
+
def clerk_redirect_url
|
82
|
+
# TODO(dimkl): Add multi-domain support
|
83
|
+
""
|
84
|
+
end
|
85
|
+
|
86
|
+
def dev_browser
|
87
|
+
@dev_browser ||= retrieve_from_query_string(@clerk_url, DEV_BROWSER_COOKIE) || @cookies.dev_browser.to_s
|
88
|
+
end
|
89
|
+
|
90
|
+
# The frontend_api returned is without protocol prefix
|
91
|
+
def frontend_api
|
92
|
+
return "" if !valid_publishable_key?(publishable_key.to_s)
|
93
|
+
|
94
|
+
@frontend_api ||= if !proxy_url.empty?
|
95
|
+
proxy_url
|
96
|
+
elsif development_instance? && !domain.empty?
|
97
|
+
"clerk.#{domain}"
|
98
|
+
else
|
99
|
+
# remove $ postfix
|
100
|
+
decode_publishable_key(publishable_key).chop
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def development_instance?
|
105
|
+
secret_key.start_with?("sk_test_")
|
106
|
+
end
|
107
|
+
|
108
|
+
def production_instance?
|
109
|
+
secret_key.start_with?("sk_live_")
|
110
|
+
end
|
111
|
+
|
112
|
+
def document_request?
|
113
|
+
@headers.sec_fetch_dest == "document"
|
114
|
+
end
|
115
|
+
|
116
|
+
def accepts_html?
|
117
|
+
@headers.accept && @headers.accept.start_with?('text/html')
|
118
|
+
end
|
119
|
+
|
120
|
+
def eligible_for_multi_domain?
|
121
|
+
is_satellite? && document_request? && !clerk_synced?
|
122
|
+
end
|
123
|
+
|
124
|
+
def active_client?
|
125
|
+
@cookies.client_uat.to_i > 0
|
126
|
+
end
|
127
|
+
|
128
|
+
def cross_origin_request?
|
129
|
+
# origin contains scheme+host and optionally port (omitted if 80 or 443)
|
130
|
+
# ref. https://www.rfc-editor.org/rfc/rfc6454#section-6.1
|
131
|
+
return false if @headers.origin.nil?
|
132
|
+
|
133
|
+
# strip scheme
|
134
|
+
origin = @headers.origin.strip.sub(/\A(\w+:)?\/\//, '')
|
135
|
+
return false if origin.empty?
|
136
|
+
|
137
|
+
# Rack's host and port helpers are reverse-proxy-aware; that
|
138
|
+
# is, they prefer the de-facto X-Forwarded-* headers if they're set
|
139
|
+
request_host = @headers.host
|
140
|
+
request_host << ":#{@headers.port}" if @headers.port != 80 && @headers.port != 443
|
141
|
+
|
142
|
+
origin != request_host
|
143
|
+
end
|
144
|
+
|
145
|
+
def dev_browser?
|
146
|
+
!dev_browser.empty?
|
147
|
+
end
|
148
|
+
|
149
|
+
def session_token_in_header?
|
150
|
+
!session_token_in_header.to_s.empty?
|
151
|
+
end
|
152
|
+
|
153
|
+
def handshake_token?
|
154
|
+
!handshake_token.to_s.empty?
|
155
|
+
end
|
156
|
+
|
157
|
+
def session_token_in_cookie?
|
158
|
+
!session_token_in_cookie.to_s.empty?
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
|
163
|
+
def valid_publishable_key?(pk)
|
164
|
+
valid_publishable_key_prefix?(pk) && valid_publishable_key_postfix?(pk)
|
165
|
+
end
|
166
|
+
|
167
|
+
def valid_publishable_key_prefix?(pk)
|
168
|
+
pk.start_with?("pk_live_") || pk.start_with?("pk_test_")
|
169
|
+
end
|
170
|
+
|
171
|
+
def valid_publishable_key_postfix?(pk)
|
172
|
+
decode_publishable_key(pk).end_with?("$")
|
173
|
+
end
|
174
|
+
|
175
|
+
def decode_publishable_key(pk)
|
176
|
+
Base64.decode64(pk.split("_")[2].to_s)
|
177
|
+
end
|
178
|
+
|
179
|
+
def retrieve_from_query_string(url, key)
|
180
|
+
Rack::Utils.parse_query(url.query)[key]
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,253 @@
|
|
1
|
+
module Clerk
|
2
|
+
##
|
3
|
+
# This class represents a service object used to determine the current request state
|
4
|
+
# for the current env passed based on a provided Clerk::AuthenticateContext.
|
5
|
+
# There is only 1 public method exposed (`resolve`) to be invoked with a env parameter.
|
6
|
+
class AuthenticateRequest
|
7
|
+
attr_reader :auth_context
|
8
|
+
|
9
|
+
##
|
10
|
+
# Creates a new instance using Clerk::AuthenticateContext object.
|
11
|
+
def initialize(auth_context)
|
12
|
+
@auth_context = auth_context
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
# Determines the current request state by verifying a Clerk token in headers or cookies.
|
17
|
+
# The possible outcomes of this method are `signed-in`, `signed-out` or `handshake` states.
|
18
|
+
# The return values are the same as a return value of a rack middleware `[http_status_code, headers, body]`.
|
19
|
+
# When used in a middleware the consumer of this service should return the return value when there is an
|
20
|
+
# `http_status_code` provided otherwise the should continue with the middleware chain.
|
21
|
+
# The headers provided in the return value is a hash of { header_key => header_value } and in the case
|
22
|
+
# of a `Set-Cookie` header the `header_value` used is a list of raw HTTP Set-Cookie directives.
|
23
|
+
def resolve(env)
|
24
|
+
if auth_context.session_token_in_header?
|
25
|
+
resolve_header_token(env)
|
26
|
+
else
|
27
|
+
resolve_cookie_token(env)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
def resolve_header_token(env)
|
34
|
+
begin
|
35
|
+
# malformed JWT
|
36
|
+
return signed_out(reason: TokenVerificationErrorReason::TOKEN_INVALID) if !sdk.decode_token(auth_context.session_token_in_header)
|
37
|
+
|
38
|
+
claims = verify_token(auth_context.session_token_in_header)
|
39
|
+
return signed_in(env, claims, auth_context.session_token_in_header) if claims
|
40
|
+
rescue JWT::DecodeError
|
41
|
+
# malformed JSON authorization header
|
42
|
+
return signed_out(reason: TokenVerificationErrorReason::TOKEN_INVALID)
|
43
|
+
rescue JWT::ExpiredSignature
|
44
|
+
return signed_out(enforce_auth: true, reason: TokenVerificationErrorReason::TOKEN_EXPIRED)
|
45
|
+
rescue JWT::InvalidIatError
|
46
|
+
return signed_out(enforce_auth: true, reason: TokenVerificationErrorReason::TOKEN_NOT_ACTIVE_YET)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Clerk.js should refresh the token and retry
|
50
|
+
return signed_out(enforce_auth: true)
|
51
|
+
end
|
52
|
+
|
53
|
+
def resolve_cookie_token(env)
|
54
|
+
# in cross-origin XHRs the use of Authorization header is mandatory.
|
55
|
+
# TODO: add reason
|
56
|
+
return signed_out if auth_context.cross_origin_request?
|
57
|
+
|
58
|
+
if auth_context.handshake_token?
|
59
|
+
return resolve_handshake(env)
|
60
|
+
end
|
61
|
+
|
62
|
+
if auth_context.development_instance? && auth_context.dev_browser?
|
63
|
+
return handle_handshake_maybe_status(env, reason: AuthErrorReason::DEV_BROWSER_SYNC)
|
64
|
+
end
|
65
|
+
|
66
|
+
# TODO(dimkl): Add multi-domain support for production
|
67
|
+
# if auth_context.production_instance? && auth_context.eligible_for_multi_domain?
|
68
|
+
# return handle_handshake_maybe_status(env, reason: AuthErrorReason::SATELLITE_COOKIE_NEEDS_SYNCING)
|
69
|
+
# end
|
70
|
+
|
71
|
+
# TODO(dimkl): Add multi-domain support for development
|
72
|
+
# if auth_context.development_instance? && auth_context.eligible_for_multi_domain?
|
73
|
+
# trigger handshake using auth_context.sign_in_url as base redirect_url
|
74
|
+
# return handle_handshake_maybe_status(env, reason: AuthErrorReason::SATELLITE_COOKIE_NEEDS_SYNCING, '', headers);
|
75
|
+
# end
|
76
|
+
|
77
|
+
# TODO(dimkl): Add multi-domain support for development in primary
|
78
|
+
# if auth_context.development_instance? && !auth_context.is_satellite? && auth_context.clerk_redirect_url
|
79
|
+
# trigger handshake using auth_context.clerk_redirect_url as base redirect_url + mark it as clerk_synced
|
80
|
+
# return handle_handshake_maybe_status(env, reason: AuthErrorReason::PRIMARY_RESPONDS_TO_SYNCING, '', headers);
|
81
|
+
# end
|
82
|
+
|
83
|
+
if !auth_context.active_client? && !auth_context.session_token_in_cookie?
|
84
|
+
return signed_out(reason: AuthErrorReason::SESSION_TOKEN_AND_UAT_MISSING)
|
85
|
+
end
|
86
|
+
|
87
|
+
# This can eagerly run handshake since client_uat is SameSite=Strict in dev
|
88
|
+
if !auth_context.active_client? && auth_context.session_token_in_cookie?
|
89
|
+
return handle_handshake_maybe_status(env, reason: AuthErrorReason::SESSION_TOKEN_WITHOUT_CLIENT_UAT)
|
90
|
+
end
|
91
|
+
|
92
|
+
if auth_context.active_client? && !auth_context.session_token_in_cookie?
|
93
|
+
return handle_handshake_maybe_status(env, reason: AuthErrorReason::CLIENT_UAT_WITHOUT_SESSION_TOKEN)
|
94
|
+
end
|
95
|
+
|
96
|
+
begin
|
97
|
+
token = verify_token(auth_context.session_token_in_cookie)
|
98
|
+
return signed_out if !token
|
99
|
+
|
100
|
+
if token["iat"] < auth_context.client_uat
|
101
|
+
return handle_handshake_maybe_status(env, reason: AuthErrorReason::SESSION_TOKEN_OUTDATED);
|
102
|
+
end
|
103
|
+
|
104
|
+
return signed_in(env, token, auth_context.session_token_in_cookie)
|
105
|
+
rescue JWT::ExpiredSignature
|
106
|
+
handshake(env, reason: TokenVerificationErrorReason::TOKEN_EXPIRED)
|
107
|
+
rescue JWT::InvalidIatError
|
108
|
+
handshake(env, reason: TokenVerificationErrorReason::TOKEN_NOT_ACTIVE_YET)
|
109
|
+
end
|
110
|
+
|
111
|
+
signed_out
|
112
|
+
end
|
113
|
+
|
114
|
+
def resolve_handshake(env)
|
115
|
+
headers = {
|
116
|
+
"Access-Control-Allow-Origin" => "null",
|
117
|
+
"Access-Control-Allow-Credentials" => "true"
|
118
|
+
}
|
119
|
+
|
120
|
+
session_token = ''
|
121
|
+
|
122
|
+
# Return signed-out outcome if the handshake verification fails
|
123
|
+
handshake_payload = verify_token(auth_context.handshake_token)
|
124
|
+
return signed_out(enforce_auth: true, reason: TokenVerificationErrorReason::JWK_FAILED_TO_RESOLVE) if !handshake_payload
|
125
|
+
|
126
|
+
# Retrieve the cookie directives included in handshake token payload and convert it to set-cookie headers
|
127
|
+
# Also retrieve the session token separately to determine the outcome of the request
|
128
|
+
cookies_to_set = handshake_payload[HANDSHAKE_COOKIE_DIRECTIVES_KEY] || []
|
129
|
+
cookies_to_set.each do |cookie|
|
130
|
+
headers[COOKIE_HEADER] ||= []
|
131
|
+
headers[COOKIE_HEADER] << cookie
|
132
|
+
|
133
|
+
if cookie.start_with?("#{SESSION_COOKIE}=")
|
134
|
+
session_token = cookie.split(';')[0].split('=')[1]
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Clear handshake token from query params and set headers to redirect to the initial request url
|
139
|
+
if auth_context.development_instance?
|
140
|
+
redirect_url = auth_context.clerk_url.dup
|
141
|
+
remove_from_query_string(redirect_url, HANDSHAKE_COOKIE)
|
142
|
+
remove_from_query_string(redirect_url, HANDSHAKE_HELP_QUERY_PARAM)
|
143
|
+
|
144
|
+
headers[LOCATION_HEADER] = redirect_url.to_s
|
145
|
+
end
|
146
|
+
|
147
|
+
if !session_token
|
148
|
+
return signed_out(reason: AuthErrorReason::SESSION_TOKEN_MISSING, headers: headers)
|
149
|
+
end
|
150
|
+
|
151
|
+
verify_token_with_retry(env, session_token)
|
152
|
+
end
|
153
|
+
|
154
|
+
def handle_handshake_maybe_status(env, **opts)
|
155
|
+
return signed_out if !eligible_for_handshake?
|
156
|
+
handshake(env, **opts)
|
157
|
+
end
|
158
|
+
|
159
|
+
# A outcome
|
160
|
+
def handshake(env, **opts)
|
161
|
+
redirect_headers = { LOCATION_HEADER => redirect_to_handshake }
|
162
|
+
[307, debug_auth_headers(**opts).merge(redirect_headers), []]
|
163
|
+
end
|
164
|
+
|
165
|
+
# B outcome
|
166
|
+
def signed_out(**opts)
|
167
|
+
headers = opts.delete(:headers) || {}
|
168
|
+
enforce_auth = opts.delete(:enforce_auth)
|
169
|
+
|
170
|
+
if enforce_auth
|
171
|
+
[401, debug_auth_headers(**opts).merge(headers), []]
|
172
|
+
else
|
173
|
+
[nil, headers, []]
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# C outcome
|
178
|
+
def signed_in(env, claims, token, **headers)
|
179
|
+
env["clerk"] = ProxyV2.new(session_claims: claims, session_token: token)
|
180
|
+
[nil, headers, []]
|
181
|
+
end
|
182
|
+
|
183
|
+
def eligible_for_handshake?
|
184
|
+
auth_context.document_request? || (!auth_context.document_request? && auth_context.accepts_html?)
|
185
|
+
end
|
186
|
+
|
187
|
+
private
|
188
|
+
|
189
|
+
def redirect_to_handshake
|
190
|
+
redirect_url = auth_context.clerk_url.dup
|
191
|
+
remove_from_query_string(redirect_url, DEV_BROWSER_COOKIE)
|
192
|
+
|
193
|
+
handshake_url = URI.parse("https://#{auth_context.frontend_api}/v1/client/handshake")
|
194
|
+
handshake_url_qs = Rack::Utils.parse_query(handshake_url.query)
|
195
|
+
handshake_url_qs["redirect_url"] = redirect_url
|
196
|
+
|
197
|
+
if auth_context.development_instance? && auth_context.dev_browser?
|
198
|
+
handshake_url_qs[DEV_BROWSER_COOKIE] = auth_context.dev_browser
|
199
|
+
end
|
200
|
+
|
201
|
+
handshake_url.query = handshake_url_qs.to_query
|
202
|
+
handshake_url.to_s
|
203
|
+
end
|
204
|
+
|
205
|
+
def remove_from_query_string(url, key)
|
206
|
+
qs = Rack::Utils.parse_query(url.query)
|
207
|
+
qs.delete(key)
|
208
|
+
url.query = qs.to_query
|
209
|
+
end
|
210
|
+
|
211
|
+
def verify_token(token, **opts)
|
212
|
+
return false if token.nil? || token.strip.empty?
|
213
|
+
|
214
|
+
begin
|
215
|
+
sdk.verify_token(token, **opts)
|
216
|
+
rescue JWT::ExpiredSignature, JWT::InvalidIatError => e
|
217
|
+
raise e
|
218
|
+
rescue JWT::DecodeError, JWT::RequiredDependencyError => e
|
219
|
+
false
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# Verify session token and provide a 1-day leeway for development if initial verification
|
224
|
+
# fails for development instance due to invalid exp or iat
|
225
|
+
def verify_token_with_retry(env, token)
|
226
|
+
claims = verify_token(token)
|
227
|
+
return signed_in(env, claims, token) if claims
|
228
|
+
rescue JWT::ExpiredSignature, JWT::InvalidIatError => e
|
229
|
+
if auth_context.development_instance?
|
230
|
+
# TODO: log possible Clock skew detected
|
231
|
+
|
232
|
+
# Retry with a generous clock skew allowance (1 day)
|
233
|
+
claims = verify_token(token, timeout: 86_400)
|
234
|
+
return signed_in(env, claims, token) if claims
|
235
|
+
end
|
236
|
+
|
237
|
+
# Raise error if handshake resolution fails in production
|
238
|
+
raise e
|
239
|
+
end
|
240
|
+
|
241
|
+
def sdk
|
242
|
+
Clerk::SDK.new
|
243
|
+
end
|
244
|
+
|
245
|
+
def debug_auth_headers(reason: nil, message: nil, status: nil)
|
246
|
+
{
|
247
|
+
AUTH_REASON_HEADER => reason,
|
248
|
+
AUTH_MESSAGE_HEADER => message,
|
249
|
+
AUTH_STATUS_HEADER => status,
|
250
|
+
}.compact
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
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
|
37
|
+
end
|
38
|
+
|
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
|
49
|
+
end
|
50
|
+
end
|
@@ -17,7 +17,7 @@ module Clerk
|
|
17
17
|
attr_reader :session_id, :error
|
18
18
|
def initialize(env)
|
19
19
|
req = Rack::Request.new(env)
|
20
|
-
@token = req.cookies[
|
20
|
+
@token = req.cookies[SESSION_COOKIE]
|
21
21
|
@session_id = req.params["_clerk_session_id"]
|
22
22
|
@session = nil
|
23
23
|
@user_id = nil
|
@@ -1,4 +1,6 @@
|
|
1
1
|
require "clerk"
|
2
|
+
require_relative "authenticate_context"
|
3
|
+
require_relative "authenticate_request"
|
2
4
|
|
3
5
|
module Clerk
|
4
6
|
class ProxyV2
|
@@ -48,6 +50,18 @@ module Clerk
|
|
48
50
|
@session_claims["org_id"]
|
49
51
|
end
|
50
52
|
|
53
|
+
def org_role
|
54
|
+
return nil if @session_claims.nil?
|
55
|
+
|
56
|
+
@session_claims["org_role"]
|
57
|
+
end
|
58
|
+
|
59
|
+
def org_permissions
|
60
|
+
return nil if @session_claims.nil?
|
61
|
+
|
62
|
+
@session_claims["org_permissions"]
|
63
|
+
end
|
64
|
+
|
51
65
|
private
|
52
66
|
|
53
67
|
def fetch_user(user_id)
|
@@ -107,160 +121,47 @@ module Clerk
|
|
107
121
|
end
|
108
122
|
|
109
123
|
env["clerk"] = Clerk::ProxyV2.new
|
110
|
-
header_token = req.env["HTTP_AUTHORIZATION"]
|
111
|
-
header_token = header_token.strip.sub(/\ABearer /, '') if header_token
|
112
|
-
cookie_token = req.cookies["__session"]
|
113
|
-
client_uat = req.cookies["__client_uat"]
|
114
|
-
|
115
|
-
##########################################################################
|
116
|
-
# #
|
117
|
-
# HEADER AUTHENTICATION #
|
118
|
-
# #
|
119
|
-
##########################################################################
|
120
|
-
if header_token
|
121
|
-
begin
|
122
|
-
return signed_out(env) if !sdk.decode_token(header_token) # malformed JWT
|
123
|
-
rescue JWT::DecodeError
|
124
|
-
return signed_out(env) # malformed JSON authorization header
|
125
|
-
end
|
126
124
|
|
127
|
-
|
128
|
-
|
129
|
-
return signed_in(env, token, header_token) if token
|
130
|
-
rescue JWT::ExpiredSignature, JWT::InvalidIatError
|
131
|
-
unknown(interstitial: false)
|
132
|
-
end
|
125
|
+
auth_context = AuthenticateContext.new(req, Clerk.configuration)
|
126
|
+
auth_request = AuthenticateRequest.new(auth_context)
|
133
127
|
|
134
|
-
|
135
|
-
|
136
|
-
|
128
|
+
status, auth_request_headers, body = auth_request.resolve(env)
|
129
|
+
return [status, auth_request_headers, body] if status
|
130
|
+
|
131
|
+
status, headers, body = @app.call(env)
|
137
132
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
133
|
+
if !auth_request_headers.empty?
|
134
|
+
# Remove them to avoid overriding existing cookies set in headers by other middlewares
|
135
|
+
auth_request_cookies = auth_request_headers.delete(COOKIE_HEADER)
|
136
|
+
# merge non-cookie related headers into response headers
|
137
|
+
headers.merge!(auth_request_headers)
|
142
138
|
|
143
|
-
|
144
|
-
# the interstitial won't work if the user agent is not a browser, so
|
145
|
-
# short-circuit and avoid rendering it
|
146
|
-
#
|
147
|
-
# We only limit this to dev/stg because we're not yet sure how robust
|
148
|
-
# this strategy is, yet. In the future, we might enable it for prod too.
|
149
|
-
return signed_out(env)
|
139
|
+
set_cookie_headers!(headers, auth_request_cookies) if auth_request_cookies
|
150
140
|
end
|
151
141
|
|
152
|
-
|
153
|
-
# #
|
154
|
-
# COOKIE AUTHENTICATION #
|
155
|
-
# #
|
156
|
-
##########################################################################
|
157
|
-
if development_or_staging? && (req.referrer.nil? || cross_origin_request?(req))
|
158
|
-
return unknown(interstitial: true)
|
159
|
-
end
|
160
|
-
|
161
|
-
if production? && client_uat.nil?
|
162
|
-
return signed_out(env)
|
163
|
-
end
|
164
|
-
|
165
|
-
if client_uat == "0"
|
166
|
-
return signed_out(env)
|
167
|
-
end
|
168
|
-
|
169
|
-
begin
|
170
|
-
token = verify_token(cookie_token)
|
171
|
-
return signed_out(env) if !token
|
172
|
-
|
173
|
-
if token["iat"] && client_uat && Integer(client_uat) <= token["iat"]
|
174
|
-
return signed_in(env, token, cookie_token)
|
175
|
-
end
|
176
|
-
rescue JWT::ExpiredSignature, JWT::InvalidIatError
|
177
|
-
unknown(interstitial: true)
|
178
|
-
end
|
179
|
-
|
180
|
-
unknown(interstitial: true)
|
142
|
+
[status, headers, body]
|
181
143
|
end
|
182
144
|
|
183
145
|
private
|
184
146
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
# Outcome B
|
193
|
-
def signed_out(env)
|
194
|
-
@app.call(env)
|
195
|
-
end
|
196
|
-
|
197
|
-
# Outcome C
|
198
|
-
def unknown(interstitial: false, **opts)
|
199
|
-
return [401, interstitial_headers(**opts), []] if !interstitial
|
200
|
-
|
201
|
-
# Load Clerk.js to update the __session and __client_uat cookies.
|
202
|
-
[401, interstitial_headers(**opts), [sdk.interstitial]]
|
203
|
-
end
|
204
|
-
|
205
|
-
def development_or_staging?
|
206
|
-
Clerk.configuration.api_key &&
|
207
|
-
(Clerk.configuration.api_key.start_with?("test_") ||
|
208
|
-
Clerk.configuration.api_key.start_with?("sk_test_"))
|
209
|
-
end
|
210
|
-
|
211
|
-
def production?
|
212
|
-
Clerk.configuration.api_key &&
|
213
|
-
(Clerk.configuration.api_key.start_with?("live_") ||
|
214
|
-
Clerk.configuration.api_key.start_with?("sk_live_"))
|
215
|
-
end
|
216
|
-
|
217
|
-
def cross_origin_request?(req)
|
218
|
-
# origin contains scheme+host and optionally port (omitted if 80 or 443)
|
219
|
-
# ref. https://www.rfc-editor.org/rfc/rfc6454#section-6.1
|
220
|
-
origin = req.env["HTTP_ORIGIN"]
|
221
|
-
return false if origin.nil?
|
222
|
-
|
223
|
-
# strip scheme
|
224
|
-
origin = origin.strip.sub(/\A(\w+:)?\/\//, '')
|
225
|
-
return false if origin.empty?
|
226
|
-
|
227
|
-
# Rack's host and port helpers are reverse-proxy-aware; that
|
228
|
-
# is, they prefer the de-facto X-Forwarded-* headers if they're set
|
229
|
-
request_host = req.host
|
230
|
-
request_host << ":#{req.port}" if req.port != 80 && req.port != 443
|
231
|
-
|
232
|
-
origin != request_host
|
233
|
-
end
|
234
|
-
|
235
|
-
def browser_request?(req)
|
236
|
-
user_agent = req.env["HTTP_USER_AGENT"]
|
237
|
-
|
238
|
-
!user_agent.nil? && user_agent.starts_with?("Mozilla/")
|
239
|
-
end
|
240
|
-
|
241
|
-
def verify_token(token)
|
242
|
-
return false if token.nil? || token.strip.empty?
|
243
|
-
|
244
|
-
begin
|
245
|
-
sdk.verify_token(token)
|
246
|
-
rescue JWT::ExpiredSignature, JWT::InvalidIatError => e
|
247
|
-
raise e
|
248
|
-
rescue JWT::DecodeError, JWT::RequiredDependencyError => e
|
249
|
-
false
|
147
|
+
def set_cookie_headers!(headers, cookie_headers)
|
148
|
+
cookie_headers.each do |cookie_header|
|
149
|
+
cookie_key = cookie_header.split(';')[0].split('=')[0]
|
150
|
+
cookie = Rack::Utils.parse_cookies_header(cookie_header)
|
151
|
+
cookie_params = convert_http_cookie_to_cookie_setter_params(cookie, cookie_key)
|
152
|
+
Rack::Utils.set_cookie_header!(headers, cookie_key, cookie_params)
|
250
153
|
end
|
251
154
|
end
|
252
155
|
|
253
|
-
def
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
"X-Clerk-Auth-Status" => status,
|
263
|
-
}.compact
|
156
|
+
def convert_http_cookie_to_cookie_setter_params(cookie, cookie_key)
|
157
|
+
# convert cookie to to match cookie setter method params (lowercase symbolized keys with `:value` key)
|
158
|
+
cookie_params = cookie.transform_keys { |k| k.downcase.to_sym }
|
159
|
+
# drop the current cookie name key to avoid polluting the expected cookie params
|
160
|
+
cookie_params[:value] = cookie.delete(cookie_key)
|
161
|
+
# fix issue with cookie expiration expected to be Date type
|
162
|
+
cookie_params[:expires] = Date.parse(cookie_params[:expires]) if cookie_params[:expires]
|
163
|
+
|
164
|
+
cookie_params
|
264
165
|
end
|
265
166
|
end
|
266
167
|
end
|
data/lib/clerk/resources.rb
CHANGED
@@ -6,6 +6,5 @@ require_relative "resources/emails"
|
|
6
6
|
require_relative "resources/organizations"
|
7
7
|
require_relative "resources/phone_numbers"
|
8
8
|
require_relative "resources/sessions"
|
9
|
-
require_relative "resources/sms_messages"
|
10
9
|
require_relative "resources/users"
|
11
10
|
require_relative "resources/jwks"
|
data/lib/clerk/sdk.rb
CHANGED
@@ -15,7 +15,6 @@ require_relative "resources/emails"
|
|
15
15
|
require_relative "resources/organizations"
|
16
16
|
require_relative "resources/phone_numbers"
|
17
17
|
require_relative "resources/sessions"
|
18
|
-
require_relative "resources/sms_messages"
|
19
18
|
require_relative "resources/users"
|
20
19
|
require_relative "resources/users"
|
21
20
|
require_relative "resources/jwks"
|
@@ -97,7 +96,7 @@ module Clerk
|
|
97
96
|
end
|
98
97
|
end
|
99
98
|
|
100
|
-
body = if response[
|
99
|
+
body = if response[CONTENT_TYPE_HEADER] == "application/json"
|
101
100
|
JSON.parse(response.body)
|
102
101
|
else
|
103
102
|
response.body
|
@@ -148,10 +147,6 @@ module Clerk
|
|
148
147
|
Resources::Sessions.new(self)
|
149
148
|
end
|
150
149
|
|
151
|
-
def sms_messages
|
152
|
-
Resources::SMSMessages.new(self)
|
153
|
-
end
|
154
|
-
|
155
150
|
def users
|
156
151
|
Resources::Users.new(self)
|
157
152
|
end
|
@@ -160,10 +155,6 @@ module Clerk
|
|
160
155
|
Resources::JWKS.new(self)
|
161
156
|
end
|
162
157
|
|
163
|
-
def interstitial(refresh=false)
|
164
|
-
request(:get, "internal/interstitial")
|
165
|
-
end
|
166
|
-
|
167
158
|
# Returns the decoded JWT payload without verifying if the signature is
|
168
159
|
# valid.
|
169
160
|
#
|
@@ -190,7 +181,7 @@ module Clerk
|
|
190
181
|
{ keys: SDK.jwks_cache.fetch(self, kid_not_found: (options[:invalidate] || options[:kid_not_found]), force_refresh: force_refresh_jwks) }
|
191
182
|
end
|
192
183
|
|
193
|
-
JWT.decode(token, nil, true, algorithms: algorithms, jwks: jwk_loader).first
|
184
|
+
JWT.decode(token, nil, true, algorithms: algorithms, exp_leeway: timeout, jwks: jwk_loader).first
|
194
185
|
end
|
195
186
|
end
|
196
187
|
end
|
data/lib/clerk/version.rb
CHANGED
data/lib/clerk.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "clerk/version"
|
4
|
+
require_relative "clerk/constants"
|
4
5
|
require_relative "clerk/sdk"
|
5
6
|
|
6
7
|
module Clerk
|
@@ -16,7 +17,7 @@ module Clerk
|
|
16
17
|
|
17
18
|
class Config
|
18
19
|
PRODUCTION_BASE_URL = "https://api.clerk.dev/v1/".freeze
|
19
|
-
attr_accessor :api_key, :base_url, :logger, :middleware_cache_store
|
20
|
+
attr_accessor :api_key, :base_url, :publishable_key, :logger, :middleware_cache_store
|
20
21
|
|
21
22
|
# An array of route paths on which the middleware will not execute.
|
22
23
|
#
|
@@ -50,6 +51,8 @@ module Clerk
|
|
50
51
|
@api_key = secret_key
|
51
52
|
end
|
52
53
|
|
54
|
+
@publishable_key = ENV.fetch("CLERK_PUBLISHABLE_KEY")
|
55
|
+
|
53
56
|
@excluded_routes = []
|
54
57
|
end
|
55
58
|
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:
|
4
|
+
version: 4.0.0.beta2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Clerk
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-02-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -94,6 +94,7 @@ extensions: []
|
|
94
94
|
extra_rdoc_files: []
|
95
95
|
files:
|
96
96
|
- ".github/workflows/main.yml"
|
97
|
+
- ".github/workflows/semgrep.yml"
|
97
98
|
- ".gitignore"
|
98
99
|
- CHANGELOG.md
|
99
100
|
- CODEOWNERS
|
@@ -109,6 +110,9 @@ files:
|
|
109
110
|
- docs/clerk-logo-light.png
|
110
111
|
- lib/clerk.rb
|
111
112
|
- lib/clerk/authenticatable.rb
|
113
|
+
- lib/clerk/authenticate_context.rb
|
114
|
+
- lib/clerk/authenticate_request.rb
|
115
|
+
- lib/clerk/constants.rb
|
112
116
|
- lib/clerk/errors.rb
|
113
117
|
- lib/clerk/jwks_cache.rb
|
114
118
|
- lib/clerk/rack_middleware.rb
|
@@ -126,7 +130,6 @@ files:
|
|
126
130
|
- lib/clerk/resources/plural_resource.rb
|
127
131
|
- lib/clerk/resources/sessions.rb
|
128
132
|
- lib/clerk/resources/singular_resource.rb
|
129
|
-
- lib/clerk/resources/sms_messages.rb
|
130
133
|
- lib/clerk/resources/users.rb
|
131
134
|
- lib/clerk/sdk.rb
|
132
135
|
- lib/clerk/utils.rb
|
@@ -149,9 +152,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
149
152
|
version: 2.4.0
|
150
153
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
154
|
requirements:
|
152
|
-
- - "
|
155
|
+
- - ">"
|
153
156
|
- !ruby/object:Gem::Version
|
154
|
-
version:
|
157
|
+
version: 1.3.1
|
155
158
|
requirements: []
|
156
159
|
rubygems_version: 3.2.3
|
157
160
|
signing_key:
|
@@ -1,16 +0,0 @@
|
|
1
|
-
require "forwardable"
|
2
|
-
require_relative "plural_resource"
|
3
|
-
|
4
|
-
module Clerk
|
5
|
-
module Resources
|
6
|
-
class SMSMessages
|
7
|
-
extend Forwardable
|
8
|
-
|
9
|
-
def initialize(client)
|
10
|
-
@resource = PluralResource.new(client, "sms_messages")
|
11
|
-
end
|
12
|
-
|
13
|
-
def_delegators :@resource, :create
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|