-
Notifications
You must be signed in to change notification settings - Fork 10
/
sdk.rb
191 lines (164 loc) · 5.63 KB
/
sdk.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# frozen_string_literal: true
require "faraday"
require "logger"
require "net/http"
require "json"
require "jwt"
require "concurrent-ruby"
require_relative "resources/allowlist_identifiers"
require_relative "resources/allowlist"
require_relative "resources/clients"
require_relative "resources/email_addresses"
require_relative "resources/emails"
require_relative "resources/organizations"
require_relative "resources/phone_numbers"
require_relative "resources/sessions"
require_relative "resources/testing_tokens"
require_relative "resources/users"
require_relative "resources/jwks"
require_relative "errors"
require_relative "jwks_cache"
module Clerk
class SDK
DEFAULT_HEADERS = {
"User-Agent" => "Clerk/#{Clerk::VERSION}; Faraday/#{Faraday::VERSION}; Ruby/#{RUBY_VERSION}",
"X-Clerk-SDK" => "ruby/#{Clerk::VERSION}"
}
# How often (in seconds) should JWKs be refreshed
JWKS_CACHE_LIFETIME = 3600 # 1 hour
@@jwks_cache = JWKSCache.new(JWKS_CACHE_LIFETIME)
def self.jwks_cache
@@jwks_cache
end
def initialize(api_key: nil, base_url: nil, logger: nil, ssl_verify: true,
connection: nil)
if connection
# Inject a Faraday::Connection for testing or full control over Faraday
@conn = connection
return
else
base_url = base_url || Clerk.configuration.base_url
base_uri = if !base_url.end_with?("/")
URI("#{base_url}/")
else
URI(base_url)
end
api_key ||= Clerk.configuration.api_key
if Faraday::VERSION.to_i >= 2 && api_key.nil?
api_key = -> { raise ArgumentError, "Clerk secret key is not set" }
end
logger = logger || Clerk.configuration.logger
@conn = Faraday.new(
url: base_uri, headers: DEFAULT_HEADERS, ssl: {verify: ssl_verify}
) do |f|
f.request :url_encoded
f.request :authorization, "Bearer", api_key
if logger
f.response :logger, logger do |l|
l.filter(/(Authorization: "Bearer) (\w+)/, '\1 [SECRET]')
end
end
end
end
end
def request(method, path, query: [], body: nil, timeout: nil)
response = case method
when :get
@conn.get(path, query) do |req|
req.options.timeout = timeout if timeout
end
when :post
@conn.post(path, body) do |req|
req.body = body.to_json
req.headers[:content_type] = "application/json"
req.options.timeout = timeout if timeout
end
when :patch
@conn.patch(path, body) do |req|
req.body = body.to_json
req.headers[:content_type] = "application/json"
req.options.timeout = timeout if timeout
end
when :delete
@conn.delete(path) do |req|
req.options.timeout = timeout if timeout
end
end
body = if response[CONTENT_TYPE_HEADER] == "application/json"
JSON.parse(response.body)
else
response.body
end
if response.success?
body
else
klass = case body.dig("errors", 0, "code")
when "cookie_invalid", "client_not_found", "resource_not_found"
Errors::Authentication
else
Errors::Fatal
end
raise klass.new(body, status: response.status)
end
end
def allowlist_identifiers
Resources::AllowlistIdentifiers.new(self)
end
def allowlist
Resources::Allowlist.new(self)
end
def clients
Resources::Clients.new(self)
end
def email_addresses
Resources::EmailAddresses.new(self)
end
def emails
Resources::Emails.new(self)
end
def organizations
Resources::Organizations.new(self)
end
def phone_numbers
Resources::PhoneNumbers.new(self)
end
def sessions
Resources::Sessions.new(self)
end
def testing_tokens
Resources::TestingTokens.new(self)
end
def users
Resources::Users.new(self)
end
def jwks
Resources::JWKS.new(self)
end
# Returns the decoded JWT payload without verifying if the signature is
# valid.
#
# WARNING: This will not verify whether the signature is valid. You
# should not use this for untrusted messages! You most likely want to use
# verify_token.
def decode_token(token)
JWT.decode(token, nil, false).first
end
# Decode the JWT and verify it's valid (verify claims, signature etc.) using
# the provided algorithms.
#
# JWKS are cached for JWKS_CACHE_LIFETIME seconds, in order to avoid
# unecessary roundtrips. In order to invalidate the cache, pass
# `force_refresh_jwks: true`.
#
# A timeout for the request to the JWKs endpoint can be set with the
# `timeout` argument.
def verify_token(token, force_refresh_jwks: false, algorithms: ['RS256'], timeout: 5)
jwk_loader = ->(options) do
# JWT.decode requires that the 'keys' key in the Hash is a symbol (as
# opposed to a string which our SDK returns by default)
{ keys: SDK.jwks_cache.fetch(self, kid_not_found: (options[:invalidate] || options[:kid_not_found]), force_refresh: force_refresh_jwks) }
end
JWT.decode(token, nil, true, algorithms: algorithms, exp_leeway: timeout, jwks: jwk_loader).first
end
end
end