Class: Nvoi::Utils::CredentialStore

Inherits:
Object
  • Object
show all
Defined in:
lib/nvoi/utils/credential_store.rb

Overview

CredentialStore handles encrypted credentials file operations

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(working_dir, encrypted_path = nil, key_path = nil) ⇒ CredentialStore

Create a new credentials store working_dir: base directory to search for files encrypted_path: explicit path to encrypted file (optional, nil = auto-discover) key_path: explicit path to key file (optional, nil = auto-discover)



18
19
20
21
22
23
24
25
# File 'lib/nvoi/utils/credential_store.rb', line 18

def initialize(working_dir, encrypted_path = nil, key_path = nil)
  @working_dir = working_dir
  @encrypted_path = encrypted_path.blank? ? find_encrypted_file : encrypted_path
  @key_path = nil
  @master_key = nil

  resolve_key(key_path)
end

Instance Attribute Details

#encrypted_pathObject (readonly)

Returns the value of attribute encrypted_path.



12
13
14
# File 'lib/nvoi/utils/credential_store.rb', line 12

def encrypted_path
  @encrypted_path
end

#key_pathObject (readonly)

Returns the value of attribute key_path.



12
13
14
# File 'lib/nvoi/utils/credential_store.rb', line 12

def key_path
  @key_path
end

Class Method Details

.for_init(working_dir) ⇒ Object

Create a store for initial setup (no existing files required)



28
29
30
31
32
33
34
35
# File 'lib/nvoi/utils/credential_store.rb', line 28

def self.for_init(working_dir)
  store = allocate
  store.instance_variable_set(:@working_dir, working_dir)
  store.instance_variable_set(:@encrypted_path, File.join(working_dir, DEFAULT_ENCRYPTED_FILE))
  store.instance_variable_set(:@key_path, nil)
  store.instance_variable_set(:@master_key, nil)
  store
end

Instance Method Details

#exists?Boolean

Check if the encrypted credentials file exists

Returns:

  • (Boolean)


38
39
40
# File 'lib/nvoi/utils/credential_store.rb', line 38

def exists?
  File.exist?(@encrypted_path)
end

#has_key?Boolean

Check if the store has a master key loaded

Returns:

  • (Boolean)


43
44
45
# File 'lib/nvoi/utils/credential_store.rb', line 43

def has_key?
  !@master_key.blank?
end

#initialize_credentials(template) ⇒ Object

Initialize creates a new encrypted credentials file with a generated key Returns the generated key



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/nvoi/utils/credential_store.rb', line 75

def initialize_credentials(template)
  # Generate new key
  @master_key = Crypto.generate_key

  # Write key file
  @key_path = File.join(File.dirname(@encrypted_path), DEFAULT_KEY_FILE)
  File.write(@key_path, "#{@master_key}\n", perm: 0o600)

  begin
    write(template)
  rescue StandardError => e
    File.delete(@key_path) if File.exist?(@key_path)
    raise e
  end

  @master_key
end

#readObject

Decrypt and return the credentials content



48
49
50
51
52
53
# File 'lib/nvoi/utils/credential_store.rb', line 48

def read
  raise Errors::CredentialError, "master key not loaded" unless has_key?

  ciphertext = File.binread(@encrypted_path)
  Crypto.decrypt(ciphertext, @master_key)
end

#set_master_key_for_testing(key) ⇒ Object

For testing purposes



112
113
114
# File 'lib/nvoi/utils/credential_store.rb', line 112

def set_master_key_for_testing(key)
  @master_key = key
end

#update_gitignoreObject

Add deploy.key to .gitignore if not already present



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/nvoi/utils/credential_store.rb', line 94

def update_gitignore
  gitignore_path = File.join(@working_dir, ".gitignore")

  content = File.exist?(gitignore_path) ? File.read(gitignore_path) : ""

  # Check if already present
  return if content.lines.any? { |line| line.strip == DEFAULT_KEY_FILE }

  File.open(gitignore_path, "a") do |f|
    # Add newline if file doesn't end with one
    f.write("\n") if !content.empty? && !content.end_with?("\n")

    # Add comment and entry
    f.write("\n# NVOI master key (do not commit)\n#{DEFAULT_KEY_FILE}\n")
  end
end

#write(plaintext) ⇒ Object

Encrypt and save the credentials content



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/nvoi/utils/credential_store.rb', line 56

def write(plaintext)
  raise Errors::CredentialError, "master key not loaded" unless has_key?

  ciphertext = Crypto.encrypt(plaintext, @master_key)

  # Write atomically: write to temp file, then rename
  tmp_path = "#{@encrypted_path}.tmp"
  File.binwrite(tmp_path, ciphertext, perm: 0o600)

  begin
    File.rename(tmp_path, @encrypted_path)
  rescue StandardError => e
    File.delete(tmp_path) if File.exist?(tmp_path)
    raise Errors::CredentialError, "failed to rename temp file: #{e.message}"
  end
end