Class: Koi::Identity::Provider
- Inherits:
-
Object
- Object
- Koi::Identity::Provider
- Includes:
- ActiveModel::Attributes, ActiveModel::Model
- Defined in:
- app/models/koi/identity/provider.rb
Constant Summary collapse
- KEY_SET_TTL =
Upper bound on how long a key removed from the issuer's JWKS remains trusted. Newly rotated-in keys are picked up immediately, as an unknown kid invalidates the cache.
1.hour
- INVALIDATION_GRACE =
A cached key set younger than this cannot be invalidated, so a stream of unknown-kid assertions cannot force a discovery refetch per request. A rotated-in key may take this long to be honoured.
5.minutes
Instance Method Summary collapse
-
#consume_jti(jti, claims) ⇒ Object
Atomically claims an assertion's jti for its replayable lifetime: the first presentation writes the key, a replay finds it taken and is rejected.
-
#identity_attributes(claims) ⇒ Object
Identity attributes are issuer-specific: AWS issuers carry admin-controlled principal tags; other issuers assert no identity beyond their subject.
-
#key_set(options = {}) ⇒ Object
This provider's JWKS: pinned from ENV or fetched via OIDC discovery and cached.
-
#key_status ⇒ Object
Trusted-keys summary for the roles page: RFC 7638 thumbprints, plus when a discovered set was cached.
Instance Method Details
#consume_jti(jti, claims) ⇒ Object
Atomically claims an assertion's jti for its replayable lifetime: the first presentation writes the key, a replay finds it taken and is rejected. jti uniqueness is only promised within an issuer (RFC 7519), so entries are scoped per provider. Requires a cache store shared by all app processes.
86 87 88 89 90 91 |
# File 'app/models/koi/identity/provider.rb', line 86 def consume_jti(jti, claims) jti.present? && Rails.cache.write("koi/identity/jti/#{name}/#{jti}", true, unless_exist: true, expires_in: Time.zone.at(claims["exp"].to_i) + leeway - Time.current) end |
#identity_attributes(claims) ⇒ Object
Identity attributes are issuer-specific: AWS issuers carry admin-controlled principal tags; other issuers assert no identity beyond their subject. Keyed by the verified issuer — never by claim shape, which any trusted signer could imitate.
72 73 74 75 76 77 78 79 |
# File 'app/models/koi/identity/provider.rb', line 72 def identity_attributes(claims) case URI.parse(issuer.to_s).host when /\.sts\.global\.api\.aws\z/ claims.dig("https://sts.amazonaws.com/", "principal_tags")&.slice("name", "email") || {} else {} end end |
#key_set(options = {}) ⇒ Object
This provider's JWKS: pinned from ENV or fetched via OIDC discovery and cached. Passed to JWT.decode as its jwks loader, which retries with invalidate: true when a presented kid is missing from the set.
44 45 46 47 48 |
# File 'app/models/koi/identity/provider.rb', line 44 def key_set( = {}) invalidate_key_set if [:invalidate] JWT::JWK::Set.new(jwks) end |
#key_status ⇒ Object
Trusted-keys summary for the roles page: RFC 7638 thumbprints, plus when a discovered set was cached. Viewing may prime the discovery cache — the same fail-closed path verification uses — and an unreachable issuer reports itself rather than raising.
54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'app/models/koi/identity/provider.rb', line 54 def key_status case keys when "env" { fingerprints: fingerprints(jwks) } when "discover" cached = cached_discovery { fingerprints: fingerprints(cached.fetch("jwks")), fetched_at: Time.zone.at(cached.fetch("fetched_at")) } end rescue JWT::JWKError => e { error: e. } end |