standard_id 0.22.0 → 0.23.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7caed55f6e5f6f70b01a7e79dabdc5ad9ddd646535f8c10bbde93158c65e32d9
4
- data.tar.gz: 7ca5e3b573b3718e722eb047f14eefffab20263ee374d23f706f278878bcc640
3
+ metadata.gz: ed15fb7068e789bc80676ae7d191b1b154c79adeedc60a58aa55c3c5e5e1c29f
4
+ data.tar.gz: 95cce0258343786169a8afcda56c91534e96f684b74f2764b7c8a66d838c4aab
5
5
  SHA512:
6
- metadata.gz: c68adc74ec092631a1af808cc61ea1d2d8adeb72dbaa70588cd4069edd71b94d57e6ed33f5a9fc5ca4104def616b779e9bbe44f3c9b61e9a861ebdee1eadc946
7
- data.tar.gz: 6f10a9a4916737e870798e38c96c53e7767fcf9c4e538bd5c0ca0ee43d21843ec3dff838ee384d1acb6d68336655165fc43cf970e3e8a4e65e5e75cba32fd51f
6
+ metadata.gz: 41efa2cbf81c06d2bbc8747b68bb8f1f76a13684eda0512710d4bdc756132cfd1765c7b2d1580d938e326eb3ad19e04e6b627cc86d27f27bb1c718803864cadb
7
+ data.tar.gz: 07c359414b5800fd8151e5915bef1c51044cca4285c796c16c43e05c9593f903c8071c6da257d826cb3273d678c35faed27700807442f67e16ee1cc2cf3bcd28
@@ -19,7 +19,7 @@ module StandardId
19
19
  ].freeze
20
20
 
21
21
  def callback
22
- provider_response = get_user_info_from_provider(flow: resolve_flow_for(provider.provider_name))
22
+ provider_response = fetch_provider_user_info
23
23
  social_info = provider_response[:user_info]
24
24
  provider_tokens = provider_response[:tokens]
25
25
  account = find_or_create_account_from_social(social_info)
@@ -45,6 +45,22 @@ module StandardId
45
45
 
46
46
  private
47
47
 
48
+ # Mirror of the web callback's OAuthError handling: emit
49
+ # SOCIAL_AUTH_FAILED for infrastructure-level provider failures
50
+ # (HTTP/DNS/SSL/timeouts surfaced as OAuthError by provider
51
+ # implementations) so host apps can observe provider outages on the
52
+ # API flow too. Scoped to the provider call — OAuthError subclasses
53
+ # raised later in the flow (SocialLinkError, InvalidRequestError,
54
+ # ...) are policy/client errors, not infrastructure failures, and
55
+ # must not emit. The error re-raises into the standard
56
+ # handle_oauth_error JSON response.
57
+ def fetch_provider_user_info
58
+ get_user_info_from_provider(flow: resolve_flow_for(provider.provider_name))
59
+ rescue StandardId::OAuthError => e
60
+ emit_social_auth_failed(e)
61
+ raise
62
+ end
63
+
48
64
  def resolve_flow_for(connection)
49
65
  return :mobile unless connection == "apple"
50
66
 
@@ -21,6 +21,7 @@ module StandardId
21
21
  }.freeze
22
22
 
23
23
  before_action :extract_client_credentials_from_basic_auth
24
+ before_action :enforce_per_audience_rate_limit, only: :create
24
25
 
25
26
  def create
26
27
  response_data = flow_strategy_class.new(flow_strategy_params, request).execute
@@ -29,6 +30,31 @@ module StandardId
29
30
 
30
31
  private
31
32
 
33
+ # Per-audience tightening on top of the global api_token_per_ip
34
+ # ceiling (rate_limits.api_token_per_audience_per_ip). Hand-rolled
35
+ # rather than the Rails rate_limit DSL on purpose: the DSL counts
36
+ # every request that reaches the action — a `by:` block returning nil
37
+ # does NOT exempt a request, it collapses into a shared bucket keyed
38
+ # without the discriminator (["rate-limit", scope, name, nil].compact),
39
+ # so one audience's rule would throttle every other audience's
40
+ # traffic. Here only requests that target a configured audience
41
+ # increment that audience's per-IP counter.
42
+ def enforce_per_audience_rate_limit
43
+ limits = StandardId.config.rate_limits.api_token_per_audience_per_ip
44
+ return if limits.blank?
45
+
46
+ Array(params[:audience]).each do |audience|
47
+ next unless audience.is_a?(String)
48
+
49
+ cap = limits[audience] || limits[audience.to_sym]
50
+ next if cap.blank?
51
+
52
+ cache_key = "rate-limit:#{self.class.controller_path}:api_token_per_audience:#{audience}:#{request.remote_ip}"
53
+ count = StandardId::RateLimitHandling::RATE_LIMIT_STORE.increment(cache_key, 1, expires_in: 15.minutes)
54
+ raise ActionController::TooManyRequests if count && count > cap.to_i
55
+ end
56
+ end
57
+
32
58
  # Support HTTP Basic authentication for client credentials (RFC 6749 Section 2.3.1)
33
59
  def extract_client_credentials_from_basic_auth
34
60
  auth_header = request.headers["Authorization"]
@@ -368,6 +368,11 @@ StandardId.configure do |c|
368
368
  # c.rate_limits.api_passwordless_start_per_target = 5 # per 15 minutes
369
369
  # c.rate_limits.api_token_per_ip = 30 # per 15 minutes
370
370
 
371
+ # Optional per-audience tightening on top of api_token_per_ip. Only token
372
+ # requests targeting a configured audience count toward its cap (per IP,
373
+ # per 15 minutes); unlisted audiences are governed by the global ceiling.
374
+ # c.rate_limits.api_token_per_audience_per_ip = { "mobile_app" => 10 }
375
+
371
376
  # ---------------------------------------------------------------------------
372
377
  # Observability
373
378
  # ---------------------------------------------------------------------------
@@ -328,6 +328,15 @@ StandardId::ConfigSchema.define do
328
328
  field :api_passwordless_start_per_target, type: :integer, default: 5 # per 15 minutes
329
329
  field :api_token_per_ip, type: :integer, default: 30 # per 15 minutes
330
330
 
331
+ # Optional per-audience tightening on top of the api_token_per_ip
332
+ # ceiling. A Hash of audience => max token requests per IP per 15
333
+ # minutes, e.g. `{ "mobile_app" => 10, "partner_api" => 30 }`. Only
334
+ # requests targeting a configured audience count toward that audience's
335
+ # limit; audiences without an entry are governed solely by the global
336
+ # api_token_per_ip ceiling. A request must pass both its audience cap
337
+ # and the global cap.
338
+ field :api_token_per_audience_per_ip, type: :hash, default: -> { {} }
339
+
331
340
  # Dynamic client registration (RFC 7591) — throttle the open registration
332
341
  # endpoint by IP so an enabled deployment can't be flooded with client rows.
333
342
  field :dynamic_registration_per_ip, type: :integer, default: 10 # per hour
@@ -1,3 +1,3 @@
1
1
  module StandardId
2
- VERSION = "0.22.0"
2
+ VERSION = "0.23.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: standard_id
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.22.0
4
+ version: 0.23.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jaryl Sim