Module: Legion::Crypt::JwksClient

Extended by:
Logging::Helper
Defined in:
lib/legion/crypt/jwks_client.rb

Constant Summary collapse

CACHE_TTL =
3600

Constants included from Logging::Helper

Logging::Helper::CompatLogger

Class Method Summary collapse

Methods included from Logging::Helper

handle_exception, log

Class Method Details

.clear_cacheObject



87
88
89
90
91
92
# File 'lib/legion/crypt/jwks_client.rb', line 87

def clear_cache
  stop_background_refresh!
  @cache_mutex.synchronize { @cache = {} }
  @locks_mutex.synchronize { @locks = {} }
  log.info 'JWKS cache cleared'
end

.fetch_keys(jwks_url) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/legion/crypt/jwks_client.rb', line 24

def fetch_keys(jwks_url)
  with_url_lock(jwks_url) do
    log.debug "JWKS fetch: #{jwks_url}"
    response = http_get(jwks_url)
    jwks_data = parse_response(response)
    keys = parse_jwks(jwks_data)

    cache_write(jwks_url, keys)
    log.info "JWKS fetched url=#{jwks_url} keys=#{keys.size}"
    keys
  end
rescue StandardError => e
  handle_exception(e, level: :warn, operation: 'crypt.jwks.fetch_keys', jwks_url: jwks_url)
  raise
end

.find_key(jwks_url, kid) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/legion/crypt/jwks_client.rb', line 40

def find_key(jwks_url, kid)
  cached = cache_read(jwks_url)

  if cached && !expired?(cached[:fetched_at])
    key = cached[:keys][kid]
    if key
      log.debug "JWKS cache hit: kid=#{kid}"
      return key
    end

    log.debug "JWKS cache miss for kid=#{kid}; refreshing keys"
  end

  keys = fetch_keys(jwks_url)
  key = keys[kid]
  return key if key

  raise Legion::Crypt::JWT::InvalidTokenError, "signing key not found: #{kid}"
end

.prefetch!(jwks_url) ⇒ Object



60
61
62
63
64
65
66
67
# File 'lib/legion/crypt/jwks_client.rb', line 60

def prefetch!(jwks_url)
  Thread.new do
    fetch_keys(jwks_url)
  rescue StandardError => e
    handle_exception(e, level: :debug, operation: 'crypt.jwks.prefetch', jwks_url: jwks_url) if respond_to?(:handle_exception)
    log.debug "JWKS prefetch failed for #{jwks_url}: #{e.message}" if respond_to?(:log)
  end
end

.start_background_refresh!(jwks_url, interval: CACHE_TTL) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/legion/crypt/jwks_client.rb', line 69

def start_background_refresh!(jwks_url, interval: CACHE_TTL)
  stop_background_refresh!

  @refresh_task = Concurrent::TimerTask.new(execution_interval: interval, run_now: false) do
    fetch_keys(jwks_url)
  rescue StandardError => e
    handle_exception(e, level: :debug, operation: 'crypt.jwks.background_refresh', jwks_url: jwks_url) if respond_to?(:handle_exception)
    log.debug "JWKS background refresh failed: #{e.message}" if respond_to?(:log)
  end
  @refresh_task.execute
  log.info "JWKS background refresh started (interval=#{interval}s)" if respond_to?(:log)
end

.stop_background_refresh!Object



82
83
84
85
# File 'lib/legion/crypt/jwks_client.rb', line 82

def stop_background_refresh!
  @refresh_task&.shutdown
  @refresh_task = nil
end