standard_id 0.49.0 → 0.49.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 960b6bdf3cc6fd613e1f3cdb6a172f9b30fb42f2c215174787f7858f0c532075
4
- data.tar.gz: 654fde0c77971e868578805a3d033909adbe49da8006ca1f1174110975e2b255
3
+ metadata.gz: 8b5e02f9651266077753a98125e3772a99bd2cf935da68d17f98df4bfd59505e
4
+ data.tar.gz: e703964c3b6c2c0ffd10a5475f370c9dea0f6f4873fd6827606ee9869a12726d
5
5
  SHA512:
6
- metadata.gz: f5a099d1243e7ac19872b907c402937941a3718efec2caffa7fc75af717893bc6b685671d528929e99c658bc08187cb58d776fc36c744978190f1c850bf5c457
7
- data.tar.gz: db01eff1f65d191da0166de6f09def5231d88a9675d6240efd0d2c0eddd02b91553b2aad9f1d062d026fccfcf9d17ee1ffca4e04c2b6e1cfe4b627229d8a56be
6
+ metadata.gz: 4091588b0c3b2cc3f06c25682e840cb2a1293a94d67abaf25eb81cce6331fe7a8325aea577fd69ceb5405c09dd0aa39fc0e70ed17df493a92da566fd8da10343
7
+ data.tar.gz: a10b76363a599b3c5fc29681665f8a20d564476cb6682a32c8555f4e2464945f41e0ccfcc7897db8c65df57483f8be3b927294282121d47bbc3226564e3dc0b2
data/CHANGELOG.md CHANGED
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.49.1] - 2026-10-20
11
+
12
+ ### Fixed
13
+
14
+ - **Dynamic registration refuses non-https redirect URIs** (`invalid_redirect_uri`): https, or http only on a loopback host (RFC 8252 §7.3), and at most 10 entries — the rules every consumer's own registration operation enforced and the gem's model (which also serves operator-registered clients) does not. Applies to RFC 7592 updates too.
15
+ - **A refused registration no longer leaves an owner row behind**: the `dynamic_registration_owner` callable now runs inside the registration transaction, so a host creating a per-registration owner record gets it rolled back when the client is invalid.
16
+ - **`StandardId::ResourceServer`'s scope check defers to the auth layer**: with no session (the action skipped `verify_access_token!`, e.g. a 405 responder) it no longer raises `NotAuthenticatedError` itself, matching `AudienceVerification`.
17
+
10
18
  ## [0.49.0] - 2026-10-20
11
19
 
12
20
  ### Added
@@ -81,8 +81,12 @@ module StandardId
81
81
  raise(StandardId::ConfigurationError, "#{self.class.name} includes StandardId::ResourceServer but never called protected_resource")
82
82
  end
83
83
 
84
+ # Like verify_audience!: without a session there is nothing to check, and
85
+ # either the auth layer is answering the 401 or the action skipped
86
+ # verify_access_token! on purpose (a 405 responder, say).
84
87
  def verify_protected_resource_scopes!
85
88
  return if _protected_resource_scopes.empty?
89
+ return unless current_session
86
90
 
87
91
  require_scopes!(*_protected_resource_scopes)
88
92
  end
@@ -37,6 +37,8 @@ module StandardId
37
37
  # Keys `config.oauth.dynamic_registration_attributes` may pin.
38
38
  PINNABLE_ATTRIBUTES = %w[scopes metadata access_token_lifetime authorization_code_lifetime require_consent description].freeze
39
39
  REGISTRATION_ACCESS_TOKEN_BYTES = 32
40
+ # A self-registered client gets a handful of callbacks, not a list.
41
+ MAX_REDIRECT_URIS = 10
40
42
 
41
43
  # Minimal result object mirroring the gem's `result.success?` /
42
44
  # `result.value` convention. `client_secret` is the one-time plaintext for
@@ -88,18 +90,23 @@ module StandardId
88
90
  client
89
91
  end
90
92
 
93
+ # Everything, the owner resolver included, runs inside one transaction:
94
+ # a host whose resolver creates a per-registration owner row must not be
95
+ # left with that row when the client itself is refused.
91
96
  def call
92
- attrs = mapped_attributes
97
+ validate_redirect_uris!
93
98
  registration_access_token = nil
94
- if StandardId::ClientApplication.registration_management_column?
95
- registration_access_token = SecureRandom.urlsafe_base64(REGISTRATION_ACCESS_TOKEN_BYTES)
96
- attrs[:registration_access_token_digest] =
97
- StandardId::ClientApplication.digest_registration_access_token(registration_access_token)
98
- end
99
- client = StandardId::ClientApplication.new(attrs)
100
-
101
99
  secret_plaintext = nil
100
+ client = nil
101
+
102
102
  StandardId::ClientApplication.transaction do
103
+ attrs = mapped_attributes
104
+ if StandardId::ClientApplication.registration_management_column?
105
+ registration_access_token = SecureRandom.urlsafe_base64(REGISTRATION_ACCESS_TOKEN_BYTES)
106
+ attrs[:registration_access_token_digest] =
107
+ StandardId::ClientApplication.digest_registration_access_token(registration_access_token)
108
+ end
109
+ client = StandardId::ClientApplication.new(attrs)
103
110
  client.save!
104
111
  if client.confidential?
105
112
  secret_plaintext = SecureRandom.hex(32)
@@ -117,6 +124,7 @@ module StandardId
117
124
  end
118
125
 
119
126
  def update(client)
127
+ validate_redirect_uris!
120
128
  if metadata[:client_id].present? && metadata[:client_id].to_s != client.client_id
121
129
  raise StandardId::InvalidClientMetadataError, "client_id does not match the client being updated"
122
130
  end
@@ -191,7 +199,34 @@ module StandardId
191
199
  # the model and let its redirect-uri validation surface an invalid_redirect_uri
192
200
  # error — keeping a single source of truth for URI rules.
193
201
  def redirect_uris
194
- Array(metadata[:redirect_uris]).map { |u| u.to_s.strip }.reject(&:blank?).join(" ")
202
+ redirect_uri_list.join(" ")
203
+ end
204
+
205
+ def redirect_uri_list
206
+ value = metadata[:redirect_uris]
207
+ list = value.is_a?(Array) ? value : value.to_s.split(/\s+/)
208
+ list.map { |u| u.to_s.strip }.reject(&:blank?)
209
+ end
210
+
211
+ # What the model does not enforce for a client an operator registers by
212
+ # hand, but a self-registering client must meet: https, or http only on a
213
+ # loopback host (RFC 8252 §7.3, for native apps on an ephemeral port), and
214
+ # a bounded list. Absent or empty is left to the model's presence check.
215
+ def validate_redirect_uris!
216
+ list = redirect_uri_list
217
+ return if list.empty?
218
+
219
+ if list.size > MAX_REDIRECT_URIS
220
+ raise StandardId::InvalidRedirectUriError, "redirect_uris must not contain more than #{MAX_REDIRECT_URIS} entries"
221
+ end
222
+
223
+ list.each do |uri|
224
+ parsed = StandardId::ClientApplication.parse_redirect_uri(uri)
225
+ next if parsed && (parsed.scheme == "https" || StandardId::ClientApplication.loopback_redirect_uri?(parsed))
226
+
227
+ raise StandardId::InvalidRedirectUriError,
228
+ "redirect_uri #{uri.inspect} must use https (http is allowed only for loopback hosts)"
229
+ end
195
230
  end
196
231
 
197
232
  def client_name
@@ -1,3 +1,3 @@
1
1
  module StandardId
2
- VERSION = "0.49.0"
2
+ VERSION = "0.49.1"
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.49.0
4
+ version: 0.49.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jaryl Sim