Class: Koi::Identity::Provider

Inherits:
Object
  • Object
show all
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

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(options = {})
  invalidate_key_set if options[:invalidate]

  JWT::JWK::Set.new(jwks)
end

#key_statusObject

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.message }
end