secvault 2.5.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7aa65a706270754c5654d4ddac89114d5c6f59f80c2bd1480d11dc0b350a57d9
4
- data.tar.gz: 9ce9a4d9661505ba37a8c827d8f5811c8b9ff0b2fab8f8a8b89bb9f53fb62618
3
+ metadata.gz: 804b4325743910209d494c3c81b4f5f0dc8cf89dd802b4b424544923e42cee34
4
+ data.tar.gz: 6c456cdd91bf12b27ab70922deb6736c42c8afdeb3e91c7173428c791441ff2c
5
5
  SHA512:
6
- metadata.gz: ebd99a7e5c479c1bf98f7180414bcf7b77b45f70a19d56b3b20d734da069ed5f4d18b6855356a89d47475c6955f8ce5a4edc161e2ee13c5d4ab1494a4be38855
7
- data.tar.gz: 888b561a34ff6be3798accb339d812d0456e3e10df0f4725c2c522e080ccef70c6dc23e54cae76878b5e4a67a44b4b63527b5bd0386133907b3df9a6bbec3df5
6
+ metadata.gz: 82efdd937655a3ba4eaefe8d732df4e2def7fd12a91302b182d1c300f1a17c10031339a53efe14af1f7c23bdfc40bdc44d43f578bc487a4e425551b75169f1c0
7
+ data.tar.gz: 0bf0eab87edf50ef01b0d5f0989d8403cd2b96795a393504de87875541ef458fbe3342ba2d8d0e8316b252259bbfee00abff8d1cb265bf0d73cdd951e979c27a
data/README.md CHANGED
@@ -7,16 +7,6 @@ Restores the classic Rails `secrets.yml` functionality that was removed in Rails
7
7
  - **Rails 7.2+**: Automatic setup
8
8
  - **Rails 8.0+**: Full compatibility
9
9
 
10
- ## ✨ Key Features
11
-
12
- - 🔗 **YAML Anchor/Alias Support**: Use `default: &default` for shared configuration
13
- - 🌍 **ERB Interpolation**: Environment variables with type conversion (`ENV['VAR'].to_i`, boolean logic)
14
- - 📁 **Multi-File Loading**: Merge multiple YAML files (e.g., base + OAuth + local overrides)
15
- - 🔄 **Environment Switching**: Load different environments dynamically
16
- - 🛠️ **Development Tools**: Hot-reload secrets without server restart
17
- - 🔍 **Utility Methods**: `Secvault.active?` to check integration status
18
- - 🏗️ **Flexible Organization**: Feature-based, environment-based, or namespace-based file structures
19
-
20
10
  ## Installation
21
11
 
22
12
  ```ruby
@@ -100,22 +90,11 @@ Secvault.setup_multi_file!([
100
90
  ])
101
91
  ```
102
92
 
103
- **What this does automatically:**
104
- - ✅ Sets up Rails 7.1 compatibility (calls `setup_backward_compatibility_with_older_rails!`)
93
+ **What this does:**
105
94
  - ✅ Loads and merges all files in order (later files override earlier ones)
106
95
  - ✅ Handles missing files gracefully
107
- - ✅ Adds `reload_secrets!` method in development
108
- - ✅ Provides logging (except in production)
109
96
  - ✅ Creates Rails.application.secrets with merged configuration
110
97
 
111
- **File organization example:**
112
- ```
113
- config/
114
- ├── secrets.yml # Base application secrets
115
- ├── secrets.oauth.yml # OAuth providers & external APIs
116
- ├── secrets.local.yml # Local development overrides (gitignored)
117
- ```
118
-
119
98
  **Advanced options:**
120
99
  ```ruby
121
100
  # Disable reload helper or logging
@@ -160,7 +139,7 @@ dev_secrets = Rails::Secrets.load(env: 'development')
160
139
 
161
140
  ## ERB Features & Type Conversion
162
141
 
163
- Secvault supports powerful ERB templating with automatic type conversion:
142
+ Secvault supports ERB templating with automatic type conversion:
164
143
 
165
144
  ```yaml
166
145
  production:
@@ -225,60 +204,12 @@ else
225
204
  end
226
205
  ```
227
206
 
228
- ## Usage Examples
229
-
230
- **Basic usage:**
231
- ```ruby
232
- # Access secrets
233
- Rails.application.secrets.api_key
234
- Rails.application.secrets.database.host
235
- Rails.application.secrets.oauth.google.client_id
236
-
237
- # With YAML defaults, you get deep merging:
238
- Rails.application.secrets.database.adapter # "postgresql" (from default)
239
- Rails.application.secrets.database.host # "localhost" (from environment)
240
- ```
241
-
242
- **Multi-file merging:**
243
- ```ruby
244
- # Files loaded in order: base → oauth → local
245
- # Later files override earlier ones for the same keys
246
- # Hash values are deep merged, scalars are replaced
247
-
248
- Rails.application.secrets.api_key # Could be from base or local file
249
- Rails.application.secrets.oauth.google # From oauth file
250
- Rails.application.secrets.features.debug # From local file override
251
- ```
252
-
253
207
  ## Security Best Practices
254
208
 
255
- ### ⚠️ Production Security
256
209
  - **Never commit production secrets** to version control
257
210
  - **Use environment variables** in production with ERB: `<%= ENV['SECRET'] %>`
258
211
  - **Use ENV.fetch()** with fallbacks: `<%= ENV.fetch('SECRET', 'default') %>`
259
212
 
260
- ### 📝 File Management
261
- - **Add sensitive files** to `.gitignore`:
262
- ```gitignore
263
- config/secrets.yml # If contains sensitive data
264
- config/secrets.local.yml # Local development overrides
265
- config/secrets.production.yml # If used
266
- ```
267
-
268
- ### 🔑 Recommended Structure
269
- ```yaml
270
- # ✅ GOOD: Base file with safe defaults
271
- development:
272
- api_key: "safe_dev_key_for_team"
273
-
274
- production:
275
- api_key: <%= ENV['API_KEY'] %> # ✅ From environment
276
-
277
- # ❌ BAD: Secrets hardcoded in base file
278
- production:
279
- api_key: "super_secret_production_key" # ❌ Never do this
280
- ```
281
-
282
213
  ## License
283
214
 
284
215
  MIT License - see [LICENSE](https://opensource.org/licenses/MIT)
data/USAGE_EXAMPLES.md ADDED
@@ -0,0 +1,65 @@
1
+ # Secvault API
2
+
3
+ Secvault provides separate control over secrets loading and Rails integration.
4
+
5
+ ## Core Methods
6
+
7
+ ### `Secvault.start!(files: [])`
8
+
9
+ Loads secrets from YAML files. Returns `true`/`false`.
10
+
11
+ - **Default**: Uses `config/secrets.yml` if `files` is empty
12
+ - **Access**: Secrets available via `Secvault.secrets`
13
+ - **Non-invasive**: Does not modify `Rails.application.secrets`
14
+
15
+ ### `Secvault.integrate_with_rails!`
16
+
17
+ Replaces `Rails.application.secrets` with Secvault's loaded secrets. Returns `true`/`false`.
18
+
19
+ ### `Secvault.secrets`
20
+
21
+ Access to loaded secrets as `ActiveSupport::OrderedOptions`.
22
+
23
+ ### Status Methods
24
+
25
+ - `Secvault.active?` - Returns `true` if secrets have been loaded
26
+ - `Secvault.rails_integrated?` - Returns `true` if Rails integration is active
27
+
28
+ ## Usage
29
+
30
+ ### Standalone
31
+
32
+ ```ruby
33
+ # Load secrets independently
34
+ Secvault.start!
35
+ api_key = Secvault.secrets.api_key
36
+ ```
37
+
38
+ ### With Rails Integration
39
+
40
+ ```ruby
41
+ # Load and integrate with Rails
42
+ Secvault.start!
43
+ Secvault.integrate_with_rails!
44
+ api_key = Rails.application.secrets.api_key
45
+ ```
46
+
47
+ ### Multiple Files
48
+
49
+ ```ruby
50
+ # Files are deep-merged in order
51
+ Secvault.start!(files: [
52
+ 'config/shared_secrets.yml',
53
+ 'config/secrets.yml'
54
+ ])
55
+ ```
56
+
57
+ ### Error Handling
58
+
59
+ ```ruby
60
+ if Secvault.start!(files: ['config/secrets.yml'])
61
+ if Secvault.integrate_with_rails!
62
+ # Both operations successful
63
+ end
64
+ end
65
+ ```
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/hash/keys"
4
+ require "active_support/core_ext/hash/deep_merge"
4
5
  require "active_support/ordered_options"
5
6
  require "pathname"
6
7
  require "erb"
@@ -89,9 +90,9 @@ module Secvault
89
90
 
90
91
  secrets ||= {}
91
92
 
92
- # Merge shared secrets first, then environment-specific
93
- all_secrets.merge!(secrets["shared"].deep_symbolize_keys) if secrets["shared"]
94
- all_secrets.merge!(secrets[env].deep_symbolize_keys) if secrets[env]
93
+ # Merge shared secrets first, then environment-specific (using deep merge)
94
+ all_secrets.deep_merge!(secrets["shared"].deep_symbolize_keys) if secrets["shared"]
95
+ all_secrets.deep_merge!(secrets[env].deep_symbolize_keys) if secrets[env]
95
96
  end
96
97
  end
97
98
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Secvault
4
- VERSION = "2.5.0"
4
+ VERSION = "2.6.0"
5
5
  end
data/lib/secvault.rb CHANGED
@@ -57,9 +57,22 @@ module Secvault
57
57
  class Error < StandardError; end
58
58
 
59
59
  extend self
60
+
61
+ # Internal storage for loaded secrets
62
+ @@loaded_secrets = nil
60
63
 
61
- # Check if Secvault is currently active in the Rails application
64
+ # Access to loaded secrets without Rails integration
65
+ def secrets
66
+ @@loaded_secrets || ActiveSupport::OrderedOptions.new
67
+ end
68
+
69
+ # Check if Secvault is currently active (started)
62
70
  def active?
71
+ @@loaded_secrets != nil
72
+ end
73
+
74
+ # Check if Secvault is integrated with Rails.application.secrets
75
+ def rails_integrated?
63
76
  defined?(Rails) && Rails::Secrets == Secvault::RailsSecrets
64
77
  end
65
78
 
@@ -149,7 +162,39 @@ module Secvault
149
162
  end
150
163
  end
151
164
 
152
- # Load secrets from multiple files and merge them
165
+ # Load secrets into Secvault.secrets only (no Rails integration)
166
+ def load_secrets_only!(files, logger: !Rails.env.production?)
167
+ # Convert strings to Pathname objects and resolve relative to Rails.root
168
+ file_paths = Array(files).map do |file|
169
+ file.is_a?(Pathname) ? file : Rails.root.join(file)
170
+ end
171
+
172
+ existing_files = file_paths.select(&:exist?)
173
+
174
+ if existing_files.any?
175
+ # Load and merge all secrets files using Secvault's parser directly
176
+ merged_secrets = Secvault::Secrets.parse(existing_files, env: Rails.env)
177
+
178
+ # Store in Secvault.secrets (ActiveSupport::OrderedOptions for compatibility)
179
+ @@loaded_secrets = ActiveSupport::OrderedOptions.new
180
+ @@loaded_secrets.merge!(merged_secrets)
181
+
182
+ # Log successful loading
183
+ if logger
184
+ file_names = existing_files.map(&:basename)
185
+ Rails.logger&.info "[Secvault] Loaded #{existing_files.size} files: #{file_names.join(', ')}"
186
+ Rails.logger&.info "[Secvault] Parsed #{merged_secrets.keys.size} secret keys for #{Rails.env}"
187
+ end
188
+
189
+ true
190
+ else
191
+ Rails.logger&.warn "[Secvault] No secrets files found" if logger
192
+ @@loaded_secrets = ActiveSupport::OrderedOptions.new
193
+ false
194
+ end
195
+ end
196
+
197
+ # Load secrets from multiple files and merge them (with Rails integration)
153
198
  def load_multi_file_secrets!(file_paths, logger: !Rails.env.production?)
154
199
  existing_files = file_paths.select(&:exist?)
155
200
 
@@ -193,6 +238,61 @@ module Secvault
193
238
  end
194
239
  end
195
240
 
241
+ # Start Secvault and load secrets (without Rails integration)
242
+ #
243
+ # Usage:
244
+ # Secvault.start! # Uses config/secrets.yml only
245
+ # Secvault.start!(files: []) # Same as above
246
+ # Secvault.start!(files: ['path/to/secrets.yml']) # Custom single file
247
+ # Secvault.start!(files: ['gem.yml', 'app.yml']) # Multiple files
248
+ #
249
+ # Access loaded secrets via: Secvault.secrets.your_key
250
+ # To integrate with Rails.application.secrets, call: Secvault.integrate_with_rails!
251
+ #
252
+ # Options:
253
+ # - files: Array of file paths (String or Pathname). Defaults to ['config/secrets.yml']
254
+ # - logger: Enable logging (default: true except production)
255
+ def start!(files: [], logger: !Rails.env.production?)
256
+ begin
257
+ # Default to host app's config/secrets.yml if no files specified
258
+ files_to_load = files.empty? ? ['config/secrets.yml'] : files
259
+
260
+ # Load secrets into Secvault.secrets (completely independent of Rails)
261
+ load_secrets_only!(files_to_load, logger: logger)
262
+
263
+ true
264
+ rescue => e
265
+ Rails.logger&.error "[Secvault] Failed to start: #{e.message}" if defined?(Rails)
266
+ false
267
+ end
268
+ end
269
+
270
+ # Integrate loaded secrets with Rails.application.secrets
271
+ def integrate_with_rails!
272
+ return false unless @@loaded_secrets
273
+
274
+ begin
275
+ # Set up Rails::Secrets to use Secvault's parser (only when integrating)
276
+ unless rails_integrated?
277
+ if defined?(Rails::Secrets)
278
+ Rails.send(:remove_const, :Secrets)
279
+ end
280
+ Rails.const_set(:Secrets, Secvault::RailsSecrets)
281
+ end
282
+
283
+ # Replace Rails.application.secrets with Secvault's loaded secrets
284
+ Rails.application.define_singleton_method(:secrets) do
285
+ Secvault.secrets
286
+ end
287
+
288
+ Rails.logger&.info "[Secvault] Integrated with Rails.application.secrets" unless Rails.env.production?
289
+ true
290
+ rescue => e
291
+ Rails.logger&.error "[Secvault] Failed to integrate with Rails: #{e.message}" if defined?(Rails)
292
+ false
293
+ end
294
+ end
295
+
196
296
  # Backward compatibility aliases
197
297
  alias_method :setup_rails_71_integration!, :setup_backward_compatibility_with_older_rails!
198
298
  alias_method :setup_multi_files!, :setup_multi_file! # Alternative name
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: secvault
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.0
4
+ version: 2.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Unnikrishnan KP
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-09-22 00:00:00.000000000 Z
11
+ date: 2025-09-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -54,6 +54,7 @@ files:
54
54
  - LICENSE.txt
55
55
  - README.md
56
56
  - Rakefile
57
+ - USAGE_EXAMPLES.md
57
58
  - lib/secvault.rb
58
59
  - lib/secvault/generators/secrets_generator.rb
59
60
  - lib/secvault/rails_secrets.rb