Class: Mysigner::Config
- Inherits:
-
Object
- Object
- Mysigner::Config
- Defined in:
- lib/mysigner/config.rb
Constant Summary collapse
- CONFIG_DIR =
File.('~/.mysigner').freeze
- CONFIG_FILE =
File.join(CONFIG_DIR, 'config.yml').freeze
- KEY_FILE =
File.join(CONFIG_DIR, '.encryption_key').freeze
- KEYCHAIN_SERVICE =
'com.mysigner.cli'- KEYCHAIN_ACCOUNT =
'config_encryption_key'- ENV_API_TOKEN =
Environment variable names for CI/CD support
'MYSIGNER_API_TOKEN'- ENV_API_URL =
'MYSIGNER_API_URL'- ENV_EMAIL =
'MYSIGNER_EMAIL'- ENV_ORG_ID =
'MYSIGNER_ORG_ID'- ENV_LOCAL_ONLY =
'MYSIGNER_LOCAL_ONLY'
Instance Attribute Summary collapse
-
#api_url ⇒ Object
Returns the value of attribute api_url.
-
#current_organization_id ⇒ Object
Returns the value of attribute current_organization_id.
-
#encryption_enabled ⇒ Object
Returns the value of attribute encryption_enabled.
-
#organizations ⇒ Object
readonly
Returns the value of attribute organizations.
-
#user_email ⇒ Object
Returns the value of attribute user_email.
Class Method Summary collapse
-
.env_configured? ⇒ Boolean
Check if all required env vars are set for CI/CD mode.
-
.from_env ⇒ Object
Create a Config from environment variables (for CI/CD).
-
.local_only? ⇒ Boolean
Local-only mode: when true, credentials never leave the machine.
Instance Method Summary collapse
-
#api_token(org_id = nil) ⇒ Object
Get API token for current organization (or specific org).
-
#clear ⇒ Object
Clear all configuration.
-
#disable_encryption! ⇒ Object
Disable encryption and decrypt all tokens.
-
#display ⇒ Object
Display config (with masked tokens).
-
#enable_encryption! ⇒ Object
Enable encryption and re-encrypt all tokens.
-
#encrypted_config? ⇒ Boolean
Check if config uses encryption.
-
#exists? ⇒ Boolean
Check if config file exists.
-
#fetch_encryption_key ⇒ Object
Public accessor for the per-machine 32-byte AES-256-GCM key.
-
#from_env? ⇒ Boolean
Whether this config was loaded from environment variables.
-
#has_token_for_org?(org_id) ⇒ Boolean
Check if we have a token for a specific organization.
-
#initialize ⇒ Config
constructor
A new instance of Config.
-
#load ⇒ Object
Load configuration from file.
- #local_only? ⇒ Boolean
-
#org_name(org_id = nil) ⇒ Object
Get organization name.
- #organization_id ⇒ Object
-
#organization_ids ⇒ Object
Get all organization IDs.
-
#remove_token_for_org(org_id) ⇒ Object
Remove token for specific organization.
-
#save ⇒ Object
Save configuration to file.
-
#save_token_for_org(org_id, org_name, token) ⇒ Object
Save token for a specific organization.
-
#to_h ⇒ Object
Get config as hash.
-
#valid? ⇒ Boolean
Check if configuration is complete (has required fields).
Constructor Details
#initialize ⇒ Config
Returns a new instance of Config.
31 32 33 34 35 36 37 38 39 |
# File 'lib/mysigner/config.rb', line 31 def initialize @api_url = nil @user_email = nil @current_organization_id = nil @organizations = {} @encryption_enabled = true # Enable by default for security @from_env = false load if exists? end |
Instance Attribute Details
#api_url ⇒ Object
Returns the value of attribute api_url.
28 29 30 |
# File 'lib/mysigner/config.rb', line 28 def api_url @api_url end |
#current_organization_id ⇒ Object
Returns the value of attribute current_organization_id.
28 29 30 |
# File 'lib/mysigner/config.rb', line 28 def current_organization_id @current_organization_id end |
#encryption_enabled ⇒ Object
Returns the value of attribute encryption_enabled.
28 29 30 |
# File 'lib/mysigner/config.rb', line 28 def encryption_enabled @encryption_enabled end |
#organizations ⇒ Object (readonly)
Returns the value of attribute organizations.
29 30 31 |
# File 'lib/mysigner/config.rb', line 29 def organizations @organizations end |
#user_email ⇒ Object
Returns the value of attribute user_email.
28 29 30 |
# File 'lib/mysigner/config.rb', line 28 def user_email @user_email end |
Class Method Details
.env_configured? ⇒ Boolean
Check if all required env vars are set for CI/CD mode
42 43 44 45 |
# File 'lib/mysigner/config.rb', line 42 def self.env_configured? ENV.fetch(ENV_API_TOKEN, nil) && !ENV[ENV_API_TOKEN].empty? && ENV.fetch(ENV_ORG_ID, nil) && !ENV[ENV_ORG_ID].empty? end |
.from_env ⇒ Object
Create a Config from environment variables (for CI/CD)
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/mysigner/config.rb', line 48 def self.from_env config = allocate config.instance_variable_set(:@encryption_enabled, false) config.instance_variable_set(:@from_env, true) org_id = ENV.fetch(ENV_ORG_ID, nil) token = ENV.fetch(ENV_API_TOKEN, nil) config.instance_variable_set(:@api_url, ENV[ENV_API_URL] || 'https://mysigner.dev') config.instance_variable_set(:@user_email, ENV.fetch(ENV_EMAIL, nil)) config.instance_variable_set(:@current_organization_id, org_id.to_i) config.instance_variable_set(:@organizations, { org_id.to_s => { 'name' => 'CI', 'token' => token } }) config end |
.local_only? ⇒ Boolean
Local-only mode: when true, credentials never leave the machine. Config-level check sees only ENV — Thor’s –local-only flag is layered on top in the CLI Helpers concern (which can read ‘options`).
73 74 75 |
# File 'lib/mysigner/config.rb', line 73 def self.local_only? truthy_env?(ENV_LOCAL_ONLY) end |
Instance Method Details
#api_token(org_id = nil) ⇒ Object
Get API token for current organization (or specific org)
82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/mysigner/config.rb', line 82 def api_token(org_id = nil) org_id ||= @current_organization_id return nil if org_id.nil? org_data = @organizations[org_id.to_s] token = org_data&.dig('token') return nil if token.nil? # Decrypt if encrypted encrypted?(token) ? decrypt_token(token) : token end |
#clear ⇒ Object
Clear all configuration
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
# File 'lib/mysigner/config.rb', line 178 def clear @api_url = nil @user_email = nil @current_organization_id = nil @organizations = {} File.delete(CONFIG_FILE) if exists? # On non-macOS the encryption key lives in a file fallback. Wipe it on # logout so a fresh login can mint a new key — otherwise the old key # would silently encrypt a new token that nobody else can decrypt. FileUtils.rm_f(KEY_FILE) # Phase 0: logout also purges the keystore cache so a shared machine # doesn't leave prior-user keystore blobs on disk. keystores_dir = File.('~/.mysigner/keystores') FileUtils.rm_rf(keystores_dir) true rescue StandardError => e raise ConfigError, "Failed to clear config: #{e.}" end |
#disable_encryption! ⇒ Object
Disable encryption and decrypt all tokens
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 |
# File 'lib/mysigner/config.rb', line 264 def disable_encryption! return true unless @encryption_enabled # Decrypt all tokens first @organizations.each_value do |org_data| token = org_data['token'] next if token.nil? || !encrypted?(token) org_data['token'] = decrypt_token(token) end @encryption_enabled = false save true end |
#display ⇒ Object
Display config (with masked tokens)
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 |
# File 'lib/mysigner/config.rb', line 223 def display current_org_name = org_name(@current_organization_id) || '(not set)' current_token = api_token(@current_organization_id) display_data = { api_url: @api_url || '(not set)', user_email: @user_email || '(not set)', current_organization: "#{current_org_name} (ID: #{@current_organization_id || 'not set'})", current_token: current_token ? mask_token(current_token) : '(not set)' } # Show all organizations if @organizations.any? display_data[:all_organizations] = @organizations.map do |org_id, org_data| token_status = org_data['token'] ? '✓' : '✗' "#{org_data['name']} (ID: #{org_id}) #{token_status}" end.join(', ') end display_data end |
#enable_encryption! ⇒ Object
Enable encryption and re-encrypt all tokens
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 |
# File 'lib/mysigner/config.rb', line 246 def enable_encryption! return true if @encryption_enabled @encryption_enabled = true # Re-encrypt all existing tokens @organizations.each_value do |org_data| token = org_data['token'] next if token.nil? || encrypted?(token) org_data['token'] = encrypt_token(token) end save true end |
#encrypted_config? ⇒ Boolean
Check if config uses encryption
281 282 283 |
# File 'lib/mysigner/config.rb', line 281 def encrypted_config? @organizations.values.any? { |org_data| encrypted?(org_data['token']) } end |
#exists? ⇒ Boolean
Check if config file exists
202 203 204 |
# File 'lib/mysigner/config.rb', line 202 def exists? File.exist?(CONFIG_FILE) end |
#fetch_encryption_key ⇒ Object
Public accessor for the per-machine 32-byte AES-256-GCM key. Exposed so sibling stores (e.g. LocalCredentials) can encrypt secrets under the same key without duplicating the keychain/file fallback. The key itself is created on first read and is stable across calls.
289 290 291 |
# File 'lib/mysigner/config.rb', line 289 def fetch_encryption_key get_or_create_encryption_key end |
#from_env? ⇒ Boolean
Whether this config was loaded from environment variables
66 67 68 |
# File 'lib/mysigner/config.rb', line 66 def from_env? @from_env end |
#has_token_for_org?(org_id) ⇒ Boolean
Check if we have a token for a specific organization
104 105 106 107 |
# File 'lib/mysigner/config.rb', line 104 def has_token_for_org?(org_id) token = api_token(org_id) !token.nil? && !token.empty? end |
#load ⇒ Object
Load configuration from file
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/mysigner/config.rb', line 133 def load return false unless exists? # mysigner-51 — safe_load_file rejects arbitrary Ruby object # instantiation in the YAML (`!ruby/object:Foo` etc.). The config # shape is just String/Integer/Hash (api_url, user_email, # current_organization_id, organizations: {id => {name, token}}), # all in safe_load's default allowed set, so no permitted_classes # extension is needed. Low risk (the file is 0600 and user-owned) # but cheap hardening against a future RCE if config write or read # ever moves outside the owner-only assumption. data = YAML.safe_load_file(CONFIG_FILE) @api_url = data['api_url'] @user_email = data['user_email'] @current_organization_id = data['current_organization_id'] @organizations = data['organizations'] || {} # Auto-detect encryption from config @encryption_enabled = encrypted_config? true rescue StandardError => e raise ConfigError, "Failed to load config: #{e.}" end |
#local_only? ⇒ Boolean
77 78 79 |
# File 'lib/mysigner/config.rb', line 77 def local_only? self.class.local_only? end |
#org_name(org_id = nil) ⇒ Object
Get organization name
110 111 112 113 114 115 116 |
# File 'lib/mysigner/config.rb', line 110 def org_name(org_id = nil) org_id ||= @current_organization_id return nil if org_id.nil? org_data = @organizations[org_id.to_s] org_data&.dig('name') end |
#organization_id ⇒ Object
123 124 125 |
# File 'lib/mysigner/config.rb', line 123 def organization_id @current_organization_id end |
#organization_ids ⇒ Object
Get all organization IDs
119 120 121 |
# File 'lib/mysigner/config.rb', line 119 def organization_ids @organizations.keys.map(&:to_i) end |
#remove_token_for_org(org_id) ⇒ Object
Remove token for specific organization
128 129 130 |
# File 'lib/mysigner/config.rb', line 128 def remove_token_for_org(org_id) @organizations.delete(org_id.to_s) end |
#save ⇒ Object
Save configuration to file
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/mysigner/config.rb', line 160 def save ensure_config_dir_exists data = { 'api_url' => @api_url, 'user_email' => @user_email, 'current_organization_id' => @current_organization_id, 'organizations' => @organizations } File.write(CONFIG_FILE, data.to_yaml) File.chmod(0o600, CONFIG_FILE) # Make file readable only by owner true rescue StandardError => e raise ConfigError, "Failed to save config: #{e.}" end |
#save_token_for_org(org_id, org_name, token) ⇒ Object
Save token for a specific organization
95 96 97 98 99 100 101 |
# File 'lib/mysigner/config.rb', line 95 def save_token_for_org(org_id, org_name, token) encrypted_token = @encryption_enabled ? encrypt_token(token) : token @organizations[org_id.to_s] = { 'name' => org_name, 'token' => encrypted_token } end |
#to_h ⇒ Object
Get config as hash
214 215 216 217 218 219 220 |
# File 'lib/mysigner/config.rb', line 214 def to_h { api_url: @api_url, user_email: @user_email, current_organization_id: @current_organization_id } end |
#valid? ⇒ Boolean
Check if configuration is complete (has required fields)
207 208 209 210 211 |
# File 'lib/mysigner/config.rb', line 207 def valid? !@api_url.nil? && !@api_url.empty? && !@current_organization_id.nil? && has_token_for_org?(@current_organization_id) end |