Module: StandardId::Oauth::AudienceProfileResolver
- Defined in:
- lib/standard_id/oauth/audience_profile_resolver.rb
Overview
Resolves the profile an account should be bound to for a given audience, based on ‘StandardId.config.oauth.audience_profile_types`.
The gem assumes the host app models profiles via ‘account.profiles` (the same shape assumed by `LifecycleHooks::DEFAULT_PROFILE_RESOLVER`). If the host app defines `StandardId.config.oauth.audience_profile_resolver`, that callable is used instead of the built-in lookup.
Class Method Summary collapse
-
.call(account:, audience:) ⇒ Object?
Returns the profile record for ‘audience`, or nil when no matching profile exists.
-
.configured_for?(audience) ⇒ Boolean
True when audience_profile_types has a binding for ‘audience`.
-
.profile_types_for(audience) ⇒ Object
Returns the configured profile types (always as an Array<String>) for the given audience.
-
.resolve!(account:, audience:) ⇒ Object
Strict variant of ‘.call` for mint-time enforcement: returns the uniquely matching active profile, or raises a typed error so the token grant flow can fail closed.
Class Method Details
.call(account:, audience:) ⇒ Object?
Returns the profile record for ‘audience`, or nil when no matching profile exists.
Callers should check ‘profile_types_for(audience).blank?` first when they need to distinguish “audience is unconfigured” from “account lacks a profile for a configured audience”.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/standard_id/oauth/audience_profile_resolver.rb', line 28 def call(account:, audience:) return nil if account.nil? || audience.blank? types = profile_types_for(audience) return nil if types.empty? resolver = StandardId.config.oauth.audience_profile_resolver if resolver.respond_to?(:call) filtered = StandardId::Utils::CallableParameterFilter.filter( resolver, { account: account, audience: audience, profile_types: types } ) return resolver.call(**filtered) end default_lookup(account, types) end |
.configured_for?(audience) ⇒ Boolean
True when audience_profile_types has a binding for ‘audience`.
58 59 60 |
# File 'lib/standard_id/oauth/audience_profile_resolver.rb', line 58 def configured_for?(audience) profile_types_for(audience).any? end |
.profile_types_for(audience) ⇒ Object
Returns the configured profile types (always as an Array<String>) for the given audience. Returns ‘[]` when no mapping is configured.
48 49 50 51 52 53 54 55 |
# File 'lib/standard_id/oauth/audience_profile_resolver.rb', line 48 def profile_types_for(audience) return [] if audience.blank? mapping = StandardId.config.oauth.audience_profile_types || {} return [] if mapping.empty? Array(mapping[audience.to_s] || mapping[audience.to_sym]).map(&:to_s).reject(&:blank?) end |
.resolve!(account:, audience:) ⇒ Object
Strict variant of ‘.call` for mint-time enforcement: returns the uniquely matching active profile, or raises a typed error so the token grant flow can fail closed.
Resolution rules (deterministic, no silent fallbacks):
- 0 matching active profiles → raises `NoBoundProfileError`
(NB: an inactive-only match is still 0 active matches and
fails closed — inactive profiles cannot mint tokens)
- exactly 1 matching active profile → returns it
- >1 matching active profile → raises `AmbiguousProfileError`
The legacy ‘.call` API preserves its “first active else first match” behavior, since it is wired into the decode-time concern and host apps may have grown to depend on its tolerance. Migrating that path to strict mode is a separate change.
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/standard_id/oauth/audience_profile_resolver.rb', line 80 def resolve!(account:, audience:) types = profile_types_for(audience) raise ArgumentError, "audience #{audience.inspect} has no profile binding" if types.empty? # Custom resolver path: trust the host app's result. It's expected # to enforce its own determinism — if it returns nil we still fail # closed; if it returns a profile we use it as-is. resolver = StandardId.config.oauth.audience_profile_resolver if resolver.respond_to?(:call) filtered = StandardId::Utils::CallableParameterFilter.filter( resolver, { account: account, audience: audience, profile_types: types } ) resolved = resolver.call(**filtered) return resolved if resolved raise StandardId::NoBoundProfileError.new( audience: audience, expected_profile_types: types ) end strict_default_lookup(account, audience, types) end |