standard_id 0.51.0 → 0.51.2

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: 70dbbe49421726d198ba1ee5502ec80abcce347b4ed006073ed81c99de743321
4
- data.tar.gz: dcb5337f9cfc348a256f8317dbb5a946f3b8d5e10f64829287cceb1108d38ac4
3
+ metadata.gz: 18c7955706f1fbe444d4bd7db052f615acfb01d7310f1a9c7088717017b864c5
4
+ data.tar.gz: 65435d9e1d57e2bfcbd479b5ecd81353d2961f831ad3f53db911bf830d20d89e
5
5
  SHA512:
6
- metadata.gz: c469c67c58017cd97529b1e1eb47c5368c1c0e878f1123806796267f31c7791e4233bdb57597958fb460fb838ff8ae2f9ddf50864671d981f04796d5372ddfa5
7
- data.tar.gz: bb7d5b2686415c1aa2d0198da73cb2df31372fc7ff36454ac879959bc0628552aafba5da7cd2aedf43bbd47801a673c6e172762abba4c8d3eabdbc6abb557534
6
+ metadata.gz: ec9734a8609d74823eb5b5a1b7b395d28f11c38268a19ff305618145777543ac13a5671c54f66cf2ecae5f68a9924e54dfeed495726dbc554f591d7f52f07078
7
+ data.tar.gz: 8616544546bd160e81490b665f40e5d4b64b03b2a612128efe896c4aacea446dbd240a8518e9d9c9a1e8bed1ef491421a406deb22ccfc52bd73f03fc6dab6a8e
data/CHANGELOG.md CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.51.2] - 2026-10-21
11
+
12
+ ### Fixed
13
+
14
+ - **`claim_resolvers` and `custom_claims` may declare `profile:`.** The grant flows (and `TokenMinter`) have always passed the bound profile as `profile:` in the resolver context, but the boot-time `Config::CallableValidator` rejected the keyword, so a resolver could not actually use it — including the explicit `TokenMinter.mint(profile:)` added in 0.51.0.
15
+
16
+ ## [0.51.1] - 2026-10-20
17
+
18
+ ### Added
19
+
20
+ - **`TokenMinter.mint(requested_scopes:)`**: what `OAUTH_TOKEN_ISSUED` reports as `requested_scopes` for a direct mint, for a caller whose audit trail records the permissions it granted (carried in a host claim such as `permissions`) rather than the OAuth scope words. Default unchanged (the scope words); an Array or space-delimited String; the token is never affected. Lets sidekick-web move its staff/test mint sites onto `TokenMinter` without its `auth.oauth.token.issued` audit rows losing the grant (rarebit-sre#478).
21
+
10
22
  ## [0.51.0] - 2026-10-20
11
23
 
12
24
  ### Added
data/README.md CHANGED
@@ -925,7 +925,7 @@ Every StandardId event automatically carries tracing metadata (`event_id`, `time
925
925
  | | `oauth.authorization.granted` | `authorization_code`, `client_id`, `account`, `scope` | After an authorization code is created |
926
926
  | | `oauth.authorization.denied` | `client_id`, `account`, `reason` | When a user denies authorization |
927
927
  | | `oauth.token.issuing` | `grant_type`, `client_id`, `account`, `scope` | Before generating access/refresh tokens |
928
- | | `oauth.token.issued` | `access_token_id`, `grant_type`, `client_id`, `account`, `expires_in` | After tokens are generated |
928
+ | | `oauth.token.issued` | `access_token_id`, `grant_type`, `client_id`, `account`, `expires_in` | After tokens are generated. For a `TokenMinter` mint, `requested_scopes` may be caller-supplied (`requested_scopes:`) rather than the token's `scope` |
929
929
  | | `oauth.token.refreshed` | `old_token_id`, `new_token_id`, `client_id`, `account` | After a refresh token is redeemed |
930
930
  | | `oauth.code.consumed` | `authorization_code`, `client_id`, `account` | After an authorization code is exchanged |
931
931
  | | `oauth.client.registered` | `client_application`, `client_id`, `client_name`, `client_type`, `redirect_uris`, `scope`, `owner_type`, `owner_id`, `client_metadata` | After a dynamic registration (RFC 7591) commits. Never fails the registration: a raising subscriber is logged |
@@ -42,9 +42,9 @@ module StandardId
42
42
  keywords: %i[account auth_method provider request flow]
43
43
  },
44
44
  "oauth.custom_claims" => {
45
- signature: "(account:, client:, request:, audience:)",
45
+ signature: "(account:, client:, request:, audience:, profile:)",
46
46
  kind: :keyword,
47
- keywords: %i[account client request audience]
47
+ keywords: %i[account client request audience profile]
48
48
  },
49
49
  "oauth.audience_resolver" => {
50
50
  signature: "(client:, audience:, resource:, request:)",
@@ -73,8 +73,11 @@ module StandardId
73
73
  }
74
74
  }.freeze
75
75
 
76
- # Keys available to claim_resolvers callables at invocation time.
77
- CLAIM_RESOLVER_KEYWORDS = %i[client account request audience].freeze
76
+ # Keys available to claim_resolvers callables at invocation time
77
+ # (TokenGrantFlow#claim_resolvers_context). `profile` is the profile the
78
+ # audience binding resolved, or the one passed to TokenMinter; nil when
79
+ # the audience has no binding.
80
+ CLAIM_RESOLVER_KEYWORDS = %i[client account request audience profile].freeze
78
81
 
79
82
  class << self
80
83
  def validate!(config = StandardId.config)
@@ -16,9 +16,11 @@ module StandardId
16
16
  AUDIT_ERROR_MODES = %i[raise report].freeze
17
17
 
18
18
  def initialize(account:, audience:, grant_type:, scopes: nil, client_id: nil, claims: {}, expires_in: nil,
19
- refresh_token: false, request: nil, auth_method: nil, auth_provider: nil, profile: nil, audit_errors: :raise)
19
+ refresh_token: false, request: nil, auth_method: nil, auth_provider: nil, profile: nil, audit_errors: :raise,
20
+ requested_scopes: nil)
20
21
  super({ audience: audience }, request || NullRequest.new)
21
22
  @profile = profile
23
+ @audit_requested_scopes = normalize_audit_scopes(requested_scopes)
22
24
  @audit_errors = audit_errors.to_s.to_sym
23
25
  unless AUDIT_ERROR_MODES.include?(@audit_errors)
24
26
  raise ArgumentError, "audit_errors must be one of #{AUDIT_ERROR_MODES.join(', ')} (got #{audit_errors.inspect})"
@@ -121,6 +123,12 @@ module StandardId
121
123
  end
122
124
  end
123
125
 
126
+ # A direct mint's caller may record what it actually granted (a
127
+ # permission set carried in a host claim) rather than the scope words.
128
+ def issued_event_requested_scopes
129
+ @audit_requested_scopes.nil? ? super : @audit_requested_scopes
130
+ end
131
+
124
132
  # No new authentication happened here: the host vouches for the account.
125
133
  def login_method_policy_context
126
134
  nil
@@ -181,6 +189,14 @@ module StandardId
181
189
  super
182
190
  end
183
191
 
192
+ # nil keeps the scope words; otherwise an Array or a space-delimited
193
+ # String, split like `scopes`.
194
+ def normalize_audit_scopes(scopes)
195
+ return nil if scopes.nil?
196
+
197
+ Array(scopes).flat_map { |s| s.to_s.split(/\s+/) }.reject(&:blank?).uniq
198
+ end
199
+
184
200
  def normalize_scopes(scopes)
185
201
  list = Array(scopes).flat_map { |s| s.to_s.split(/\s+/) }.reject(&:blank?).uniq
186
202
  list.empty? ? nil : list.join(" ")
@@ -487,9 +487,14 @@ module StandardId
487
487
  profile_id: @resolved_profile&.id,
488
488
  audience: audience,
489
489
  jti: @issued_jti,
490
- requested_scopes: current_scopes
490
+ requested_scopes: issued_event_requested_scopes
491
491
  )
492
492
  end
493
+
494
+ # What OAUTH_TOKEN_ISSUED reports as `requested_scopes`: the scope words.
495
+ def issued_event_requested_scopes
496
+ current_scopes
497
+ end
493
498
  end
494
499
  end
495
500
  end
@@ -71,13 +71,22 @@ module StandardId
71
71
  # caller whose contract is that an audit failure never costs it the
72
72
  # credential. Report it to your error tracker: the gem only logs it.
73
73
  # OAUTH_TOKEN_ISSUING (the status / locking guards) always raises.
74
+ # @param requested_scopes [Array<String>, String, nil] what OAUTH_TOKEN_ISSUED
75
+ # reports as `requested_scopes`, for a caller whose audit trail records
76
+ # the permissions it granted (carried in a host claim) rather than the
77
+ # OAuth scope words; default (nil) is the scope words, like every grant.
78
+ # A String is split on whitespace. Changes the event only, never the
79
+ # token — so for a direct mint, the event's requested_scopes may be
80
+ # caller-supplied rather than what the token's `scope` claim says.
74
81
  # @return [Result]
75
82
  def mint(account:, audience:, grant_type:, scopes: nil, client_id: nil, claims: {}, expires_in: nil,
76
- refresh_token: false, request: nil, auth_method: nil, auth_provider: nil, profile: nil, audit_errors: :raise)
83
+ refresh_token: false, request: nil, auth_method: nil, auth_provider: nil, profile: nil, audit_errors: :raise,
84
+ requested_scopes: nil)
77
85
  flow = StandardId::Oauth::DirectMintFlow.new(
78
86
  account: account, audience: audience, grant_type: grant_type, scopes: scopes, client_id: client_id,
79
87
  claims: claims, expires_in: expires_in, refresh_token: refresh_token, request: request,
80
- auth_method: auth_method, auth_provider: auth_provider, profile: profile, audit_errors: audit_errors
88
+ auth_method: auth_method, auth_provider: auth_provider, profile: profile, audit_errors: audit_errors,
89
+ requested_scopes: requested_scopes
81
90
  )
82
91
  response = flow.execute
83
92
  Result.new(
@@ -1,3 +1,3 @@
1
1
  module StandardId
2
- VERSION = "0.51.0"
2
+ VERSION = "0.51.2"
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.51.0
4
+ version: 0.51.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jaryl Sim