noresponse 3.0.0 → 3.0.1
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.
- checksums.yaml +4 -4
- data/lib/no_response/late_results.rb +89 -20
- data/lib/no_response/sdkconfiguration.rb +2 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9ced920c7288c6434e1fbd06de24541b1ac3bec7dccb43bcef7aa7ccad375377
|
|
4
|
+
data.tar.gz: 67f9fc2cce7b114edd72d84031d91312c04414114fd61ce3f7db0c3e63c6714e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a7c8af727605dcef753fb10d0d3c46bf0a5fc8c90bb411d0117aceb023fb5807af788984dd5d77a09dc984a0979ee797ea75cdf6c301edc8d4aa4f74e3c83fdd
|
|
7
|
+
data.tar.gz: 02faf461ec16e415a4c799557fff8ae5e9bdcd5f62a192dfa5682c0a37bdbe10c00176d71b7de6f360616442f00b055ce429be012dc70a24acf75b18bdc3fc96
|
|
@@ -173,13 +173,19 @@ module NoResponse
|
|
|
173
173
|
# user before deleting the refresh tokens, and act reports BAN. Unblock
|
|
174
174
|
# the user in the dashboard to undo it. Any other refusal, such as 403
|
|
175
175
|
# insufficient_scope, raises.
|
|
176
|
+
#
|
|
177
|
+
# A 429 from the Management API is waited out and retried: Retry-After,
|
|
178
|
+
# else X-RateLimit-Reset, else 0.25 s doubling, at most 4 retries a call
|
|
179
|
+
# and 3 s of waiting a revoke or ban. Past that the 429 raises as
|
|
180
|
+
# ProviderActionError. An http_client may return [status, body, headers]
|
|
181
|
+
# so the headers are honoured; without them the backoff applies.
|
|
176
182
|
def auth0(token:, domain: nil, base_url: nil, http_client: nil)
|
|
177
183
|
raise ArgumentError, 'auth0 needs token' if token.to_s.empty?
|
|
178
184
|
|
|
179
185
|
base = base_url || (domain.to_s.empty? ? nil : "https://#{domain}/api/v2")
|
|
180
186
|
raise ArgumentError, 'auth0 needs domain or base_url' if base.nil?
|
|
181
187
|
|
|
182
|
-
Auth0.new(Caller.new('auth0', base, "Bearer #{token}", http_client))
|
|
188
|
+
Auth0.new(Caller.new('auth0', base, "Bearer #{token}", http_client, retry_rate_limits: true))
|
|
183
189
|
end
|
|
184
190
|
|
|
185
191
|
# A provider for an app with its own login, or for trying the handler
|
|
@@ -195,16 +201,62 @@ module NoResponse
|
|
|
195
201
|
end
|
|
196
202
|
|
|
197
203
|
# One provider HTTP client: base URL, Authorization header, error mapping.
|
|
204
|
+
#
|
|
205
|
+
# Rate limits. Auth0 answers 429 when the Management API rate limit is
|
|
206
|
+
# hit, and a 429 means it did not act, so the call is safe to send again,
|
|
207
|
+
# a DELETE included. One revoke or ban waits at most RATE_LIMIT_BUDGET
|
|
208
|
+
# seconds in all, and each call retries at most RATE_LIMIT_RETRIES times:
|
|
209
|
+
# it waits Retry-After seconds, else until X-RateLimit-Reset (Unix
|
|
210
|
+
# seconds), else 0.25, 0.5, 1 and 2 s. A wait that would pass the budget is
|
|
211
|
+
# not started: the 429 raises at once as ProviderActionError, the webhook
|
|
212
|
+
# answers 5xx and NoResponse delivers it again later. The budget keeps the
|
|
213
|
+
# handler inside the 5 s a delivery has to be answered.
|
|
198
214
|
class Caller
|
|
199
|
-
|
|
215
|
+
RATE_LIMIT_RETRIES = 4
|
|
216
|
+
RATE_LIMIT_BUDGET = 3.0
|
|
217
|
+
RATE_LIMIT_BACKOFF = 0.25
|
|
218
|
+
|
|
219
|
+
def initialize(name, base_url, authorization, http_client, retry_rate_limits: false)
|
|
200
220
|
@name = name
|
|
201
221
|
@base = base_url.to_s.sub(%r{/+\z}, '')
|
|
202
222
|
@authorization = authorization
|
|
203
223
|
@http_client = http_client
|
|
224
|
+
@retry_rate_limits = retry_rate_limits
|
|
225
|
+
@waited = 0.0
|
|
204
226
|
end
|
|
205
227
|
|
|
206
228
|
attr_reader :name
|
|
207
229
|
|
|
230
|
+
# A caller with the same settings and its own rate-limit budget, for one action.
|
|
231
|
+
def fresh
|
|
232
|
+
Caller.new(@name, @base, @authorization, @http_client, retry_rate_limits: @retry_rate_limits)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# How long a 429 asks to wait, in seconds.
|
|
236
|
+
def self.rate_limit_wait(headers, retry_index)
|
|
237
|
+
after = number(header(headers, 'retry-after'))
|
|
238
|
+
return after if after && after >= 0
|
|
239
|
+
|
|
240
|
+
reset = number(header(headers, 'x-ratelimit-reset'))
|
|
241
|
+
return [0.0, reset - Time.now.to_f].max if reset
|
|
242
|
+
|
|
243
|
+
RATE_LIMIT_BACKOFF * (2**retry_index)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def self.header(headers, name)
|
|
247
|
+
(headers || {}).each { |key, value| return Array(value).first.to_s.strip if key.to_s.downcase == name }
|
|
248
|
+
nil
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def self.number(text)
|
|
252
|
+
return nil if text.nil? || text.empty?
|
|
253
|
+
|
|
254
|
+
value = Float(text)
|
|
255
|
+
value.finite? ? value : nil
|
|
256
|
+
rescue ArgumentError
|
|
257
|
+
nil
|
|
258
|
+
end
|
|
259
|
+
|
|
208
260
|
def call(method, path, query: nil, body: nil, allow_404: false)
|
|
209
261
|
uri = URI.parse(@base + path)
|
|
210
262
|
uri.query = URI.encode_www_form(query) if query && !query.empty?
|
|
@@ -214,7 +266,7 @@ module NoResponse
|
|
|
214
266
|
headers['Content-Type'] = 'application/json'
|
|
215
267
|
payload = JSON.generate(body)
|
|
216
268
|
end
|
|
217
|
-
status, text =
|
|
269
|
+
status, text = send_waiting_out_rate_limits(method, uri, headers, payload)
|
|
218
270
|
return nil if allow_404 && status == 404
|
|
219
271
|
unless status.between?(200, 299)
|
|
220
272
|
raise ProviderActionError.new(@name, status, "#{@name} #{method} #{path} answered #{status}", error_code(text))
|
|
@@ -230,6 +282,21 @@ module NoResponse
|
|
|
230
282
|
|
|
231
283
|
private
|
|
232
284
|
|
|
285
|
+
def send_waiting_out_rate_limits(method, uri, headers, payload)
|
|
286
|
+
retry_index = 0
|
|
287
|
+
loop do
|
|
288
|
+
status, text, response_headers = perform(method, uri, headers, payload)
|
|
289
|
+
return [status, text] unless status == 429 && @retry_rate_limits && retry_index < RATE_LIMIT_RETRIES
|
|
290
|
+
|
|
291
|
+
wait = Caller.rate_limit_wait(response_headers, retry_index)
|
|
292
|
+
return [status, text] if @waited + wait > RATE_LIMIT_BUDGET
|
|
293
|
+
|
|
294
|
+
@waited += wait
|
|
295
|
+
sleep(wait)
|
|
296
|
+
retry_index += 1
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
233
300
|
# The error code in a provider's error body: Auth0's errorCode, or a top-level code.
|
|
234
301
|
def error_code(text)
|
|
235
302
|
body = JSON.parse(text.to_s)
|
|
@@ -251,7 +318,7 @@ module NoResponse
|
|
|
251
318
|
request = Net::HTTPGenericRequest.new(method, !payload.nil?, true, uri.request_uri, headers)
|
|
252
319
|
request.body = payload unless payload.nil?
|
|
253
320
|
response = http.start { |connection| connection.request(request) }
|
|
254
|
-
[response.code.to_i, response.body.to_s]
|
|
321
|
+
[response.code.to_i, response.body.to_s, response.to_hash]
|
|
255
322
|
end
|
|
256
323
|
end
|
|
257
324
|
|
|
@@ -349,10 +416,11 @@ module NoResponse
|
|
|
349
416
|
|
|
350
417
|
def ban(user_id)
|
|
351
418
|
user = Segment.encode(user_id)
|
|
352
|
-
|
|
419
|
+
call = @call.fresh
|
|
420
|
+
block(call, user)
|
|
353
421
|
# The block already ends a session the plan cannot delete.
|
|
354
|
-
delete_sessions(user)
|
|
355
|
-
delete_refresh_tokens(user_id, user)
|
|
422
|
+
delete_sessions(call, user)
|
|
423
|
+
delete_refresh_tokens(call, user_id, user)
|
|
356
424
|
nil
|
|
357
425
|
end
|
|
358
426
|
|
|
@@ -370,10 +438,11 @@ module NoResponse
|
|
|
370
438
|
|
|
371
439
|
def revoke(user_id)
|
|
372
440
|
user = Segment.encode(user_id)
|
|
373
|
-
|
|
441
|
+
call = @call.fresh
|
|
442
|
+
ended = delete_sessions(call, user)
|
|
374
443
|
# Without the sessions API a login session survives; blocking is the only way to end it.
|
|
375
|
-
block(user) unless ended
|
|
376
|
-
delete_refresh_tokens(user_id, user)
|
|
444
|
+
block(call, user) unless ended
|
|
445
|
+
delete_refresh_tokens(call, user_id, user)
|
|
377
446
|
ended ? REVOKE_SESSIONS : BAN
|
|
378
447
|
end
|
|
379
448
|
|
|
@@ -382,13 +451,13 @@ module NoResponse
|
|
|
382
451
|
error.status == 403 && error.code == 'feature_not_enabled'
|
|
383
452
|
end
|
|
384
453
|
|
|
385
|
-
def block(user)
|
|
386
|
-
|
|
454
|
+
def block(call, user)
|
|
455
|
+
call.call('PATCH', "/users/#{user}", body: { 'blocked' => true }, allow_404: true)
|
|
387
456
|
end
|
|
388
457
|
|
|
389
458
|
# Ends every session; false when the plan has no sessions API.
|
|
390
|
-
def delete_sessions(user)
|
|
391
|
-
|
|
459
|
+
def delete_sessions(call, user)
|
|
460
|
+
call.call('DELETE', "/users/#{user}/sessions", allow_404: true)
|
|
392
461
|
true
|
|
393
462
|
rescue ProviderActionError => e
|
|
394
463
|
raise unless not_on_plan?(e)
|
|
@@ -396,9 +465,9 @@ module NoResponse
|
|
|
396
465
|
false
|
|
397
466
|
end
|
|
398
467
|
|
|
399
|
-
def delete_refresh_tokens(user_id, user)
|
|
468
|
+
def delete_refresh_tokens(call, user_id, user)
|
|
400
469
|
begin
|
|
401
|
-
|
|
470
|
+
call.call('DELETE', "/users/#{user}/refresh-tokens", allow_404: true)
|
|
402
471
|
return
|
|
403
472
|
rescue ProviderActionError => e
|
|
404
473
|
raise unless not_on_plan?(e)
|
|
@@ -407,15 +476,15 @@ module NoResponse
|
|
|
407
476
|
ids = []
|
|
408
477
|
%w[refresh_token rotating_refresh_token].each do |type|
|
|
409
478
|
10.times do |page|
|
|
410
|
-
body =
|
|
411
|
-
|
|
412
|
-
|
|
479
|
+
body = call.call('GET', '/device-credentials',
|
|
480
|
+
query: [['user_id', user_id], ['type', type], ['page', page.to_s], ['per_page', PAGE.to_s]],
|
|
481
|
+
allow_404: true)
|
|
413
482
|
listed = body.is_a?(Array) ? body.grep(Hash) : []
|
|
414
483
|
ids.concat(listed.map { |c| c['id'] }.select { |id| id.is_a?(String) && !id.empty? })
|
|
415
484
|
break if listed.length < PAGE
|
|
416
485
|
end
|
|
417
486
|
end
|
|
418
|
-
ids.each { |id|
|
|
487
|
+
ids.each { |id| call.call('DELETE', "/device-credentials/#{ERB::Util.url_encode(id)}", allow_404: true) }
|
|
419
488
|
end
|
|
420
489
|
end
|
|
421
490
|
|
|
@@ -95,9 +95,9 @@ module NoResponse
|
|
|
95
95
|
|
|
96
96
|
@language = "ruby"
|
|
97
97
|
@openapi_doc_version = "1"
|
|
98
|
-
@sdk_version = "3.0.
|
|
98
|
+
@sdk_version = "3.0.1"
|
|
99
99
|
@gen_version = "internal"
|
|
100
|
-
@user_agent = "noresponse-sdk/ruby 3.0.
|
|
100
|
+
@user_agent = "noresponse-sdk/ruby 3.0.1 internal 1 noresponse"
|
|
101
101
|
end
|
|
102
102
|
|
|
103
103
|
sig { returns([String, T::Hash[Symbol, String]]) }
|