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'
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
Class Method Details
.access_token ⇒ Object
80 |
# File 'lib/rubyn_code/auth/token_store.rb', line 80 def access_token = self.load&.fetch(:access_token, nil) |
.clear! ⇒ Object
rubocop:disable Naming/PredicateMethod – destructive action, not a predicate
65 66 67 68 |
# File 'lib/rubyn_code/auth/token_store.rb', line 65 def clear! # rubocop:disable Naming/PredicateMethod -- destructive action, not a predicate FileUtils.rm_f(tokens_path) true end |
.exists? ⇒ Boolean
79 |
# File 'lib/rubyn_code/auth/token_store.rb', line 79 def exists? = valid? |
.load ⇒ Object
Load tokens with fallback chain:
-
macOS Keychain (Claude Code’s OAuth token)
-
Local YAML file (~/.rubyn-code/tokens.yml)
-
ANTHROPIC_API_KEY environment variable
19 20 21 |
# File 'lib/rubyn_code/auth/token_store.rb', line 19 def load load_from_keychain || load_from_file || load_from_env 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.
25 26 27 28 29 30 31 32 33 34 |
# File 'lib/rubyn_code/auth/token_store.rb', line 25 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).
46 47 48 49 50 51 52 53 |
# File 'lib/rubyn_code/auth/token_store.rb', line 46 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
55 56 57 58 59 60 61 62 63 |
# File 'lib/rubyn_code/auth/token_store.rb', line 55 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).
37 38 39 40 41 42 43 |
# File 'lib/rubyn_code/auth/token_store.rb', line 37 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
70 71 72 73 74 75 76 77 |
# File 'lib/rubyn_code/auth/token_store.rb', line 70 def valid? tokens = self.load 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 |