Module: RubynCode::Auth::TokenStore
- Defined in:
- lib/rubyn_code/auth/token_store.rb
Overview
rubocop:disable Metrics/ModuleLength – single-responsibility credential store
Constant Summary collapse
- EXPIRY_BUFFER_SECONDS =
5 minutes
300- KEYCHAIN_SERVICE =
'Claude Code-credentials'- LOAD_STRATEGIES =
Strategy chain: each method returns a token hash or nil. First non-nil result wins. Adding a new auth source is a one-line entry.
%i[ load_from_keychain load_from_credentials_file load_from_file load_from_env ].freeze
Class Method Summary collapse
- .access_token ⇒ Object
-
.clear! ⇒ Object
rubocop:disable Naming/PredicateMethod – destructive action, not a predicate.
- .exists? ⇒ Boolean
-
.load ⇒ Object
Load tokens with fallback chain: 1.
-
.load_for_provider(provider) ⇒ Object
Load API key for a given provider.
-
.load_provider_key(provider) ⇒ Object
Retrieve a stored API key for a provider (decrypted transparently).
- .save(access_token:, refresh_token:, expires_at:) ⇒ Object
-
.save_provider_key(provider, key) ⇒ Object
Store an API key for a provider in tokens.yml (encrypted at rest).
- .valid? ⇒ Boolean
-
.valid_tokens?(tokens) ⇒ Boolean
Validate an already-loaded token hash without re-reading the keychain — lets callers cache the result of ‘load`.
Class Method Details
.access_token ⇒ Object
99 |
# File 'lib/rubyn_code/auth/token_store.rb', line 99 def access_token = self.load&.fetch(:access_token, nil) |
.clear! ⇒ Object
rubocop:disable Naming/PredicateMethod – destructive action, not a predicate
79 80 81 82 |
# File 'lib/rubyn_code/auth/token_store.rb', line 79 def clear! # rubocop:disable Naming/PredicateMethod -- destructive action, not a predicate FileUtils.rm_f(tokens_path) true end |
.exists? ⇒ Boolean
98 |
# File 'lib/rubyn_code/auth/token_store.rb', line 98 def exists? = valid? |
.load ⇒ Object
Load tokens with fallback chain:
-
macOS Keychain (Claude Code’s OAuth token)
-
Claude Code credentials file (~/.claude/.credentials.json)
-
Local YAML file (~/.rubyn-code/tokens.yml)
-
ANTHROPIC_API_KEY environment variable
29 30 31 32 33 34 35 |
# File 'lib/rubyn_code/auth/token_store.rb', line 29 def load LOAD_STRATEGIES.each do |strategy| result = send(strategy) return result if result end nil end |
.load_for_provider(provider) ⇒ Object
Load API key for a given provider. Anthropic uses the full fallback chain. Other providers: stored key → env var.
39 40 41 42 43 44 45 46 47 48 |
# File 'lib/rubyn_code/auth/token_store.rb', line 39 def load_for_provider(provider) return load if provider == 'anthropic' stored = load_provider_key(provider) return { access_token: stored, type: :api_key, source: :stored } if stored env_key = resolve_env_key(provider) api_key = ENV.fetch(env_key, nil) api_key&.empty? == false ? { access_token: api_key, type: :api_key, source: :env } : nil end |
.load_provider_key(provider) ⇒ Object
Retrieve a stored API key for a provider (decrypted transparently).
60 61 62 63 64 65 66 67 |
# File 'lib/rubyn_code/auth/token_store.rb', line 60 def load_provider_key(provider) data = load_tokens_file value = data&.dig('provider_keys', provider.to_s) return nil unless value migrate_plaintext_key!(data, provider, value) unless KeyEncryption.encrypted?(value) KeyEncryption.decrypt(value) end |
.save(access_token:, refresh_token:, expires_at:) ⇒ Object
69 70 71 72 73 74 75 76 77 |
# File 'lib/rubyn_code/auth/token_store.rb', line 69 def save(access_token:, refresh_token:, expires_at:) ensure_directory! data = load_tokens_file || {} data['access_token'] = access_token data['refresh_token'] = refresh_token data['expires_at'] = expires_at.is_a?(Time) ? expires_at.iso8601 : expires_at.to_s write_tokens_file(data) data end |
.save_provider_key(provider, key) ⇒ Object
Store an API key for a provider in tokens.yml (encrypted at rest).
51 52 53 54 55 56 57 |
# File 'lib/rubyn_code/auth/token_store.rb', line 51 def save_provider_key(provider, key) ensure_directory! data = load_tokens_file || {} data['provider_keys'] ||= {} data['provider_keys'][provider.to_s] = KeyEncryption.encrypt(key) write_tokens_file(data) end |
.valid? ⇒ Boolean
84 85 86 |
# File 'lib/rubyn_code/auth/token_store.rb', line 84 def valid? valid_tokens?(self.load) end |
.valid_tokens?(tokens) ⇒ Boolean
Validate an already-loaded token hash without re-reading the keychain — lets callers cache the result of ‘load`.
90 91 92 93 94 95 96 |
# File 'lib/rubyn_code/auth/token_store.rb', line 90 def valid_tokens?(tokens) return false unless tokens&.fetch(:access_token, nil) return true if tokens[:type] == :api_key return true unless tokens[:expires_at] tokens[:expires_at] > Time.now + EXPIRY_BUFFER_SECONDS end |