secvault 2.7.0 → 3.0.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: 51dbcfd2c3b3073fb06223b681fb358cf8c74aae00c90663470edc19b3a40cd0
4
- data.tar.gz: f78b9e5a1bdcb9148d58e31791738179b38a401ae0416920b4a6ee557e024ffd
3
+ metadata.gz: 7e9d55bf6f12233064ea7ced4e9b40c84f8f3a6ce38d6b5b79d5f0e8f4a7214a
4
+ data.tar.gz: 7ade516b0c550c0b3c98f04fc39ff1079d5c46436da6a7fd6153bdc1d9eac4fb
5
5
  SHA512:
6
- metadata.gz: 99d55935f3754ed807d306e94c530259ae34c82d43e1b2827ace62a2e33fd8bed291d8af358597aa68b71d78737af402273644e2b5d9475c5540be234b2d5985
7
- data.tar.gz: d24dfa1e4613602a15ee7d40aeabedb089edf9e88ea136960ca039d6298252d8e7cd4e8a50d6f815cedcbde9d6192320814ccf6694797633225a29e98b993842
6
+ metadata.gz: b2939f3acd3e9525d000b7195fd9967a4e8f80c04800e4cbac9da774625bc1cae7b26e1f0320d4d57d53abbf1bdb53a3899977deb9625880fd84458c7c302277
7
+ data.tar.gz: 84de3b9f8ecd84e820668d3ac2bd7fedeb7946e2707f88e00cd7219f258d597417b025a72b889dc93bf6e408c2686b2c444b90e1d2eaee856f1040d9f5b8d63a
data/README.md CHANGED
@@ -1,12 +1,17 @@
1
1
  # Secvault
2
2
 
3
- Restores Rails `secrets.yml` functionality for environment-specific secrets management using YAML files with ERB templating.
3
+ Secvault restores the classic Rails `secrets.yml` functionality using simple, plain YAML files for environment-specific secrets management. Compatible with all modern Rails versions (7.1+, 7.2+, 8.0+).
4
4
 
5
- ## Rails Version Support
5
+ [![Gem Version](https://img.shields.io/gem/v/secvault.svg)](https://rubygems.org/gems/secvault)
6
6
 
7
- - **Rails 7.2+**: Automatic setup (drop-in replacement for removed functionality)
8
- - **Rails 7.1**: Manual setup required
9
- - **Rails 8.0+**: Full compatibility
7
+ ## Why Secvault?
8
+
9
+ - **Drop-in replacement** for Rails 7.2+'s removed `secrets.yml` functionality
10
+ - **Universal compatibility** across Rails 7.1+, 7.2+, and 8.0+
11
+ - **ERB templating** support for environment variables
12
+ - **Multi-file support** with deep merging capabilities
13
+ - **Shared sections** for common configuration across environments
14
+ - **Simple YAML** - no complex credential management required
10
15
 
11
16
  ## Installation
12
17
 
@@ -15,87 +20,437 @@ Restores Rails `secrets.yml` functionality for environment-specific secrets mana
15
20
  gem 'secvault'
16
21
  ```
17
22
 
23
+ ```bash
24
+ bundle install
25
+ ```
26
+
18
27
  ## Quick Start
19
28
 
29
+ ### 1. Simple Setup
30
+
31
+ Create `config/initializers/secvault.rb`:
32
+
33
+ ```ruby
34
+ # The simplest setup - works across all Rails versions
35
+ Secvault.start!
36
+ ```
37
+
20
38
  Create `config/secrets.yml`:
21
39
 
22
40
  ```yaml
41
+ shared:
42
+ app_name: "My Rails App"
43
+ timeout: 30
44
+
23
45
  development:
46
+ secret_key_base: "dev_secret_key_here"
24
47
  api_key: "dev_key_123"
25
48
  database_url: "postgresql://localhost/myapp_dev"
49
+ debug: true
50
+
51
+ test:
52
+ secret_key_base: "test_secret_key_here"
53
+ api_key: "test_key_123"
26
54
 
27
55
  production:
56
+ secret_key_base: <%= ENV['SECRET_KEY_BASE'] %>
28
57
  api_key: <%= ENV['API_KEY'] %>
29
58
  database_url: <%= ENV['DATABASE_URL'] %>
59
+ debug: false
30
60
  ```
31
61
 
32
- Access secrets in your app:
62
+ ### 2. Access Your Secrets
33
63
 
34
64
  ```ruby
65
+ # In your Rails application
35
66
  Rails.application.secrets.api_key
67
+ Rails.application.secrets.app_name
36
68
  Rails.application.secrets.database_url
69
+
70
+ # Nested secrets work too
71
+ Rails.application.secrets.database.host
72
+ Rails.application.secrets.features.analytics
37
73
  ```
38
74
 
39
- ## Multi-File Configuration
75
+ ## Setup Methods
76
+
77
+ ### Unified start! Method
40
78
 
41
- Load and merge multiple secrets files:
79
+ Secvault now uses a single, simplified `start!` method for all use cases:
80
+
81
+ ```ruby
82
+ # Simplest setup - loads config/secrets.yml with Rails integration
83
+ Secvault.start!
84
+
85
+ # Custom single file
86
+ Secvault.start!(files: ['custom.yml'])
87
+
88
+ # Multiple files with hot reload
89
+ Secvault.start!(
90
+ files: ['config/secrets.yml', 'config/secrets.local.yml'],
91
+ hot_reload: true
92
+ )
93
+
94
+ # All available options
95
+ Secvault.start!(
96
+ files: ['config/secrets.yml'], # Default: ['config/secrets.yml']
97
+ integrate_with_rails: true, # Default: true
98
+ set_secret_key_base: true, # Default: true
99
+ hot_reload: true, # Default: true in development
100
+ logger: true # Default: true except production
101
+ )
102
+ ```
103
+
104
+ ### Advanced Usage
105
+
106
+ ```ruby
107
+ # Load without Rails integration (standalone mode)
108
+ Secvault.start!(integrate_with_rails: false)
109
+ # Access via: Secvault.secrets.your_key
110
+
111
+ # Multi-file with hot reload for development
112
+ Secvault.start!(
113
+ files: [
114
+ 'config/secrets.yml',
115
+ 'config/secrets.oauth.yml',
116
+ 'config/secrets.local.yml' # Git-ignored local overrides
117
+ ],
118
+ hot_reload: true,
119
+ logger: true
120
+ )
121
+ ```
122
+
123
+
124
+ ## Advanced Features
125
+
126
+ ### ERB Templating
127
+
128
+ Secvault supports full ERB templating for dynamic configuration:
129
+
130
+ ```yaml
131
+ production:
132
+ secret_key_base: <%= ENV['SECRET_KEY_BASE'] %>
133
+ api_key: <%= ENV['API_KEY'] %>
134
+ pool_size: <%= ENV.fetch('DB_POOL', '5').to_i %>
135
+
136
+ # Complex expressions
137
+ features:
138
+ enabled: <%= ENV.fetch('FEATURES_ON', 'false') == 'true' %>
139
+ analytics: <%= Rails.env.production? && ENV['ANALYTICS'] != 'false' %>
140
+
141
+ # Arrays and complex data structures
142
+ allowed_hosts: <%= ENV.fetch('ALLOWED_HOSTS', 'localhost').split(',') %>
143
+
144
+ # Conditional values
145
+ redis_url: <%=
146
+ if ENV['REDIS_URL']
147
+ ENV['REDIS_URL']
148
+ else
149
+ "redis://localhost:6379/#{Rails.env}"
150
+ end
151
+ %>
152
+ ```
153
+
154
+ ### Shared Sections
155
+
156
+ Define common secrets that apply to all environments:
157
+
158
+ ```yaml
159
+ shared:
160
+ app_name: "MyApp"
161
+ version: "2.1.0"
162
+ timeout: 30
163
+ features:
164
+ analytics: true
165
+
166
+ development:
167
+ secret_key_base: "dev_secret"
168
+ features:
169
+ debug: true # Merges with shared.features
170
+
171
+ production:
172
+ secret_key_base: <%= ENV['SECRET_KEY_BASE'] %>
173
+ features:
174
+ analytics: false # Overrides shared.features.analytics
175
+ ```
176
+
177
+ ### Multi-File Configuration
178
+
179
+ Organize your secrets across multiple files for better maintainability:
42
180
 
43
181
  ```ruby
44
- # config/initializers/secvault.rb
45
182
  Secvault.setup_multi_file!([
46
- 'config/secrets.yml',
47
- 'config/secrets.oauth.yml',
48
- 'config/secrets.local.yml'
183
+ 'config/secrets.yml', # Base secrets
184
+ 'config/secrets.oauth.yml', # OAuth provider settings
185
+ 'config/secrets.database.yml', # Database configurations
186
+ 'config/secrets.local.yml' # Local overrides (git-ignored)
49
187
  ])
50
188
  ```
51
189
 
52
- Files are merged in order with deep merge support for nested hashes.
190
+ **File merging behavior:**
191
+ - Files are processed in order
192
+ - Later files override earlier ones
193
+ - Deep merging for nested hashes
194
+ - Shared sections are merged first, then environment-specific
195
+
196
+ ### Hot Reload (Development)
197
+
198
+ Secvault provides hot reload functionality for development:
199
+
200
+ ```ruby
201
+ # Enable hot reload when starting Secvault
202
+ Secvault.start!(hot_reload: true) # Default: true in development
203
+
204
+ # Then reload secrets without restarting Rails
205
+ reload_secrets!
206
+
207
+ # Or via Rails.application
208
+ Rails.application.reload_secrets!
209
+ ```
210
+
211
+ Hot reload is automatically enabled in development mode and provides instant feedback when you change your secrets files.
53
212
 
54
213
  ## Manual API
55
214
 
215
+ For advanced use cases, you can use the lower-level API:
216
+
56
217
  ```ruby
57
218
  # Parse specific files
58
- secrets = Rails::Secrets.parse(['config/secrets.yml'], env: Rails.env)
219
+ secrets = Rails::Secrets.parse(['config/secrets.yml'], env: 'production')
220
+
221
+ # Load from default location
222
+ secrets = Rails::Secrets.load(env: 'development')
223
+
224
+ # Check if Secvault is active
225
+ Secvault.active? # => true/false
226
+
227
+ # Check if integrated with Rails
228
+ Secvault.rails_integrated? # => true/false
229
+
230
+ # Access loaded secrets directly
231
+ Secvault.secrets.api_key # Available after Secvault.start!
232
+ ```
59
233
 
60
- # Load default config/secrets.yml
61
- secrets = Rails::Secrets.load(env: 'production')
234
+ ## Rails Version Compatibility
62
235
 
63
- # Check if active
64
- Secvault.active? # => true/false
236
+ | Rails Version | Support Level | Notes |
237
+ |---------------|---------------|-------|
238
+ | **Rails 7.1+** | ✅ Full compatibility | Manual setup required |
239
+ | **Rails 7.2+** | ✅ Drop-in replacement | Automatic setup works |
240
+ | **Rails 8.0+** | ✅ Full compatibility | Future-proof |
241
+
242
+ ### Rails 7.2+ Notes
243
+ Rails 7.2 removed the built-in `secrets.yml` functionality. Secvault provides a complete replacement with the same API.
244
+
245
+ ### Rails 7.1 Notes
246
+ Rails 7.1 still has `secrets.yml` support but shows deprecation warnings. Secvault provides a consistent experience across Rails versions.
247
+
248
+ ## Migration Guide
249
+
250
+ ### From Previous Secvault Versions
251
+
252
+ **BREAKING CHANGE**: The API has been simplified. Update your initializers:
253
+
254
+ ```ruby
255
+ # Old API (no longer supported):
256
+ Secvault.setup!
257
+ Secvault.setup_multi_file!(['file1.yml', 'file2.yml'])
258
+ Secvault.integrate_with_rails!
259
+
260
+ # New unified API:
261
+ Secvault.start! # Simple case
262
+ Secvault.start!(files: ['file1.yml', 'file2.yml']) # Multi-file case
263
+ Secvault.start!(integrate_with_rails: false) # Standalone mode
65
264
  ```
66
265
 
67
- ## Rails 7.1 Integration
266
+ ### From Rails < 7.2 Built-in Secrets
267
+
268
+ 1. **Add Secvault to your Gemfile**:
269
+ ```ruby
270
+ gem 'secvault'
271
+ ```
272
+
273
+ 2. **Create initializer**:
274
+ ```ruby
275
+ # config/initializers/secvault.rb
276
+ Secvault.start!
277
+ ```
278
+
279
+ 3. **Your existing `config/secrets.yml` works as-is** - no changes needed!
280
+
281
+ ### From Rails Credentials
282
+
283
+ 1. **Extract your credentials to YAML**:
284
+ ```bash
285
+ # Export existing credentials
286
+ rails credentials:show > config/secrets.yml
287
+ ```
288
+
289
+ 2. **Format as environment-specific YAML**:
290
+ ```yaml
291
+ development:
292
+ secret_key_base: "your_dev_secret"
293
+ # ... other secrets
294
+
295
+ production:
296
+ secret_key_base: <%= ENV['SECRET_KEY_BASE'] %>
297
+ # ... other secrets
298
+ ```
299
+
300
+ 3. **Set up Secvault**:
301
+ ```ruby
302
+ # config/initializers/secvault.rb
303
+ Secvault.start!
304
+ ```
305
+
306
+ ## Configuration Examples
68
307
 
69
- For Rails 7.1 with existing secrets functionality:
308
+ ### Basic Application
309
+
310
+ ```yaml
311
+ # config/secrets.yml
312
+ shared:
313
+ app_name: "MyApp"
314
+
315
+ development:
316
+ secret_key_base: "long_random_string_for_dev"
317
+ database_url: "postgresql://localhost/myapp_dev"
318
+
319
+ test:
320
+ secret_key_base: "long_random_string_for_test"
321
+ database_url: "postgresql://localhost/myapp_test"
322
+
323
+ production:
324
+ secret_key_base: <%= ENV['SECRET_KEY_BASE'] %>
325
+ database_url: <%= ENV['DATABASE_URL'] %>
326
+ ```
327
+
328
+ ### Multi-Service Application
70
329
 
71
330
  ```ruby
72
331
  # config/initializers/secvault.rb
73
- Secvault.setup_backward_compatibility_with_older_rails!
332
+ Secvault.start!(
333
+ files: [
334
+ 'config/secrets.yml',
335
+ 'config/secrets.oauth.yml',
336
+ 'config/secrets.external_apis.yml',
337
+ 'config/secrets.local.yml' # Git-ignored
338
+ ],
339
+ hot_reload: true
340
+ )
74
341
  ```
75
342
 
76
- ## ERB Templating
343
+ ```yaml
344
+ # config/secrets.yml (base)
345
+ shared:
346
+ app_name: "MyApp"
347
+ timeout: 30
348
+
349
+ development:
350
+ secret_key_base: "dev_secret"
351
+ debug: true
77
352
 
78
- Supports full ERB templating for environment variables:
353
+ production:
354
+ secret_key_base: <%= ENV['SECRET_KEY_BASE'] %>
355
+ debug: false
356
+ ```
79
357
 
80
358
  ```yaml
359
+ # config/secrets.oauth.yml
360
+ shared:
361
+ oauth:
362
+ google:
363
+ scope: "email profile"
364
+
365
+ development:
366
+ oauth:
367
+ google:
368
+ client_id: "dev_google_client_id"
369
+ client_secret: "dev_google_client_secret"
370
+
81
371
  production:
82
- api_key: <%= ENV['API_KEY'] %>
83
- pool_size: <%= ENV.fetch('DB_POOL', '5').to_i %>
84
- features:
85
- enabled: <%= ENV.fetch('FEATURES_ON', 'false') == 'true' %>
86
- hosts: <%= ENV.fetch('ALLOWED_HOSTS', 'localhost').split(',') %>
372
+ oauth:
373
+ google:
374
+ client_id: <%= ENV['GOOGLE_CLIENT_ID'] %>
375
+ client_secret: <%= ENV['GOOGLE_CLIENT_SECRET'] %>
87
376
  ```
88
377
 
89
- ## Development Tools
378
+ ## Troubleshooting
90
379
 
91
- Reload secrets in development:
380
+ ### Common Issues
92
381
 
382
+ **1. "No secrets.yml file found"**
383
+ ```bash
384
+ # Create the file
385
+ mkdir -p config
386
+ touch config/secrets.yml
387
+ ```
388
+
389
+ **2. "undefined method `secrets' for Rails.application"**
93
390
  ```ruby
94
- # Available after setup_multi_file!
95
- reload_secrets!
96
- Rails.application.reload_secrets!
391
+ # Make sure Secvault is set up in an initializer
392
+ # config/initializers/secvault.rb
393
+ Secvault.start!
394
+ ```
395
+
396
+ **3. "Secrets not loading in tests"**
397
+ ```ruby
398
+ # In your test helper or rails_helper.rb
399
+ Secvault.start! if defined?(Secvault)
400
+ ```
401
+
402
+ **4. "Environment variables not working"**
403
+ ```yaml
404
+ # Make sure you're using ERB syntax
405
+ production:
406
+ api_key: <%= ENV['API_KEY'] %> # ✅ Correct
407
+ api_key: $API_KEY # ❌ Wrong
97
408
  ```
98
409
 
410
+ ### Debug Mode
411
+
412
+ ```ruby
413
+ # Enable detailed logging (development/test only)
414
+ Secvault.start!(files: ['config/secrets.yml'], logger: true)
415
+
416
+ # Check if Secvault is working
417
+ Secvault.active? # Should return true
418
+ Secvault.rails_integrated? # Should return true
419
+ Rails.application.secrets # Should show your secrets
420
+ ```
421
+
422
+ ## API Reference
423
+
424
+ ### Setup Methods
425
+
426
+ - `Secvault.start!(files: [], integrate_with_rails: true, set_secret_key_base: true, hot_reload: auto, logger: auto)` - Main and only setup method
427
+
428
+ ### Status Methods
429
+
430
+ - `Secvault.active?` - Check if secrets are loaded
431
+ - `Secvault.rails_integrated?` - Check if Rails integration is active
432
+ - `Secvault.secrets` - Access loaded secrets directly
433
+
434
+ ### Rails API Compatibility
435
+
436
+ - `Rails::Secrets.parse(files, env:)` - Parse specific files
437
+ - `Rails::Secrets.load(env:)` - Load from default config/secrets.yml
438
+ - `Rails.application.secrets` - Access secrets (same as classic Rails)
439
+
440
+ ### Legacy Aliases
441
+
442
+ - `Secvault.setup_backward_compatibility_with_older_rails!` (alias for `setup!`)
443
+ - `Secvault.setup_rails_71_integration!` (alias for `setup!`)
444
+ - `Secvault.setup_multi_files!` (alias for `setup_multi_file!`)
445
+
446
+ ## Contributing
447
+
448
+ 1. Fork it
449
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
450
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
451
+ 4. Push to the branch (`git push origin my-new-feature`)
452
+ 5. Create new Pull Request
453
+
99
454
  ## License
100
455
 
101
- MIT
456
+ MIT License. See [LICENSE](LICENSE) for details.
@@ -46,15 +46,9 @@ module Secvault
46
46
  end
47
47
 
48
48
  # Monkey patch to restore Rails::Secrets interface for backwards compatibility
49
- # Only for Rails 7.2+ where Rails::Secrets was removed
50
- if defined?(Rails) && Rails.respond_to?(:version)
51
- rails_version = Rails.version
52
- major, minor = rails_version.split(".").map(&:to_i)
53
-
54
- # Only alias for Rails 7.2+ to avoid conflicts with native Rails::Secrets in 7.1
55
- if major > 7 || (major == 7 && minor >= 2)
56
- module Rails
57
- Secrets = Secvault::RailsSecrets
58
- end
49
+ # Works consistently across all Rails versions with warning suppression
50
+ if defined?(Rails)
51
+ module Rails
52
+ Secrets = Secvault::RailsSecrets
59
53
  end
60
54
  end
@@ -11,62 +11,48 @@ module Secvault
11
11
  class Secrets
12
12
  class << self
13
13
  def setup(app)
14
- # Only auto-setup for Rails 7.2+ where secrets functionality was removed
15
- return unless rails_7_2_or_later?
16
-
14
+ # Auto-setup for all Rails versions with consistent behavior
17
15
  secrets_path = app.root.join("config/secrets.yml")
18
16
 
19
- if secrets_path.exist?
20
- # Use a more reliable approach that works in all environments
21
- app.config.before_configuration do
22
- current_env = ENV["RAILS_ENV"] || Rails.env || "development"
23
- setup_secrets_immediately(app, secrets_path, current_env)
24
- end
17
+ return unless secrets_path.exist?
25
18
 
26
- # Also try during to_prepare as a fallback
27
- app.config.to_prepare do
28
- current_env = Rails.env
29
- unless Rails.application.respond_to?(:secrets) && !Rails.application.secrets.empty?
30
- setup_secrets_immediately(app, secrets_path, current_env)
31
- end
32
- end
19
+ # Use a reliable approach that works in all environments
20
+ app.config.before_configuration do
21
+ current_env = ENV["RAILS_ENV"] || Rails.env || "development"
22
+ setup_secrets_immediately(app, secrets_path, current_env)
33
23
  end
34
- end
35
-
36
- # Manual setup method for Rails 7.1 (opt-in)
37
- def setup_for_rails_71!(app)
38
- secrets_path = app.root.join("config/secrets.yml")
39
24
 
40
- if secrets_path.exist?
41
- app.config.before_configuration do
42
- current_env = ENV["RAILS_ENV"] || Rails.env || "development"
25
+ # Also try during to_prepare as a fallback
26
+ app.config.to_prepare do
27
+ current_env = Rails.env
28
+ unless Rails.application.respond_to?(:secrets) && !Rails.application.secrets.empty?
43
29
  setup_secrets_immediately(app, secrets_path, current_env)
44
30
  end
45
31
  end
46
32
  end
47
33
 
48
- def setup_secrets_immediately(app, secrets_path, env)
34
+ def setup_secrets_immediately(_app, secrets_path, env)
49
35
  # Set up secrets if they exist
50
36
  secrets = read_secrets(secrets_path, env)
51
- if secrets
52
- # Rails 8.0+ compatibility: Add secrets accessor that initializes on first access
53
- unless Rails.application.respond_to?(:secrets)
54
- Rails.application.define_singleton_method(:secrets) do
55
- @secrets ||= begin
56
- current_secrets = ActiveSupport::OrderedOptions.new
57
- # Re-read secrets to ensure we have the right environment
58
- env_secrets = Secvault::Secrets.read_secrets(secrets_path, Rails.env)
59
- current_secrets.merge!(env_secrets) if env_secrets
60
- current_secrets
61
- end
37
+ return unless secrets
38
+
39
+ # Rails 8.0+ compatibility: Add secrets accessor that initializes on first access
40
+ unless Rails.application.respond_to?(:secrets)
41
+ Rails.application.define_singleton_method(:secrets) do
42
+ @secrets ||= begin
43
+ current_secrets = ActiveSupport::OrderedOptions.new
44
+ # Re-read secrets to ensure we have the right environment
45
+ env_secrets = Secvault::Secrets.read_secrets(secrets_path, Rails.env)
46
+ current_secrets.merge!(env_secrets) if env_secrets
47
+ current_secrets
62
48
  end
63
49
  end
64
-
65
- # If secrets accessor already exists, merge the secrets
66
- if Rails.application.respond_to?(:secrets) && Rails.application.secrets.respond_to?(:merge!)
67
- Rails.application.secrets.merge!(secrets)
68
- end
69
50
  end
51
+
52
+ # If secrets accessor already exists, merge the secrets
53
+ return unless Rails.application.respond_to?(:secrets) && Rails.application.secrets.respond_to?(:merge!)
54
+
55
+ Rails.application.secrets.merge!(secrets)
70
56
  end
71
57
 
72
58
  # Classic Rails::Secrets.parse implementation
@@ -103,14 +89,6 @@ module Secvault
103
89
 
104
90
  {}
105
91
  end
106
-
107
- private
108
-
109
- def rails_7_2_or_later?
110
- rails_version = Rails.version
111
- major, minor = rails_version.split(".").map(&:to_i)
112
- major > 7 || (major == 7 && minor >= 2)
113
- end
114
92
  end
115
93
  end
116
94
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Secvault
4
- VERSION = "2.7.0"
4
+ VERSION = "3.0.0"
5
5
  end
data/lib/secvault.rb CHANGED
@@ -13,44 +13,33 @@ loader.setup
13
13
 
14
14
  # Secvault - Simple secrets management for Rails
15
15
  #
16
- # Secvault restores the classic Rails secrets.yml functionality that was removed
17
- # in Rails 7.2, using simple, plain YAML files for environment-specific secrets
18
- # management.
16
+ # Secvault restores the classic Rails secrets.yml functionality using simple,
17
+ # plain YAML files for environment-specific secrets management. Works consistently
18
+ # across all Rails versions.
19
19
  #
20
20
  # ## Rails Version Support:
21
- # - Rails 7.1: Requires manual setup (see Rails 7.1 integration guide)
22
- # - Rails 7.2+: Automatic setup, drop-in replacement for removed functionality
21
+ # - Rails 7.1+: Full compatibility with automatic setup
22
+ # - Rails 7.2+: Drop-in replacement for removed functionality
23
23
  # - Rails 8.0+: Full compatibility
24
24
  #
25
- # ## Rails 7.1 Integration:
26
- # For Rails 7.1 apps, add this initializer to override native Rails::Secrets:
25
+ # ## Quick Start:
26
+ # Add this to an initializer:
27
27
  #
28
28
  # # config/initializers/secvault.rb
29
- # module Rails
30
- # remove_const(:Secrets) if defined?(Secrets)
31
- # Secrets = Secvault::RailsSecrets
32
- # end
33
- #
34
- # Rails.application.config.after_initialize do
35
- # secrets_path = Rails.root.join("config/secrets.yml")
36
- # if secrets_path.exist?
37
- # loaded_secrets = Rails::Secrets.parse([secrets_path], env: Rails.env)
38
- # secrets_object = ActiveSupport::OrderedOptions.new
39
- # secrets_object.merge!(loaded_secrets)
40
- # Rails.application.define_singleton_method(:secrets) { secrets_object }
41
- # end
42
- # end
29
+ # Secvault.start!
43
30
  #
44
31
  # ## Usage:
45
32
  # Rails.application.secrets.api_key
46
33
  # Rails.application.secrets.oauth_settings[:google_client_id]
34
+ # Secvault.secrets.your_key # Direct access
47
35
  # Rails::Secrets.load(env: 'development') # Load default config/secrets.yml
48
36
  # Rails::Secrets.parse(['custom.yml'], env: Rails.env) # Parse custom files
49
37
  #
50
38
  # ## Getting Started:
51
39
  # 1. Create config/secrets.yml with your secrets
52
- # 2. Use Rails.application.secrets.your_secret in your app
53
- # 3. For production, use environment variables with ERB syntax
40
+ # 2. Call Secvault.start! in an initializer
41
+ # 3. Use Rails.application.secrets.your_secret in your app
42
+ # 4. For production, use environment variables with ERB syntax
54
43
  #
55
44
  # @see https://github.com/unnitallman/secvault
56
45
  module Secvault
@@ -83,109 +72,78 @@ module Secvault
83
72
  require "secvault/rails_secrets"
84
73
  end
85
74
 
86
- # Helper method to set up Secvault for older Rails versions
87
- # This provides an easy way to integrate Secvault into older Rails apps
88
- # that still have native Rails::Secrets functionality (like Rails 7.1).
89
- #
90
- # Usage in an initializer:
91
- # Secvault.setup_backward_compatibility_with_older_rails!
75
+ # Start Secvault with simplified, unified API
76
+ # This is the main entry point for all Secvault functionality
92
77
  #
93
- # This will:
94
- # 1. Override native Rails::Secrets with Secvault implementation
95
- # 2. Replace Rails.application.secrets with Secvault-powered functionality
96
- # 3. Load secrets from config/secrets.yml automatically
97
- def setup_backward_compatibility_with_older_rails!
98
- # Override native Rails::Secrets
99
- if defined?(Rails::Secrets)
100
- Rails.send(:remove_const, :Secrets)
101
- end
102
- Rails.const_set(:Secrets, Secvault::RailsSecrets)
103
-
104
- # Set up Rails.application.secrets replacement
105
- Rails.application.config.after_initialize do
106
- secrets_path = Rails.root.join("config/secrets.yml")
107
-
108
- if secrets_path.exist?
109
- # Load secrets using Secvault
110
- loaded_secrets = Rails::Secrets.parse([secrets_path], env: Rails.env)
111
-
112
- # Create ActiveSupport::OrderedOptions object for compatibility
113
- secrets_object = ActiveSupport::OrderedOptions.new
114
- secrets_object.merge!(loaded_secrets)
115
-
116
- # Replace Rails.application.secrets
117
- Rails.application.define_singleton_method(:secrets) do
118
- secrets_object
119
- end
120
-
121
- # Log integration success (except in production)
122
- unless Rails.env.production?
123
- Rails.logger&.info "[Secvault] Rails 7.1 integration complete. Loaded #{loaded_secrets.keys.size} secret keys."
124
- end
125
- else
126
- Rails.logger&.warn "[Secvault] No secrets.yml file found at #{secrets_path}"
127
- end
128
- end
129
- end
130
-
131
- # Set up multi-file secrets loading with a clean API
132
- # Just pass an array of file paths and Secvault handles the rest
78
+ # Usage examples:
79
+ # Secvault.start! # Simple: config/secrets.yml + Rails integration
80
+ # Secvault.start!(files: ['custom.yml']) # Custom single file
81
+ # Secvault.start!(files: ['base.yml', 'local.yml']) # Multiple files
82
+ # Secvault.start!(integrate_with_rails: false) # Load only, no Rails integration
83
+ # Secvault.start!(hot_reload: true) # Enable hot reload in development
133
84
  #
134
- # Usage in an initializer:
135
- # Secvault.setup_multi_file!([
136
- # 'config/secrets.yml',
137
- # 'config/secrets.oauth.yml',
138
- # 'config/secrets.local.yml'
139
- # ])
85
+ # Access secrets:
86
+ # Rails.application.secrets.your_key # When integrate_rails: true (default)
87
+ # Secvault.secrets.your_key # Direct access (always available)
140
88
  #
141
89
  # Options:
142
- # - files: Array of file paths (String or Pathname)
143
- # - reload_method: Add a reload helper method (default: true in development)
144
- # - logger: Enable/disable logging (default: true except in production)
145
- def setup_multi_file!(files, reload_method: Rails.env.development?, logger: !Rails.env.production?)
146
- # Ensure Secvault integration is active
147
- setup_backward_compatibility_with_older_rails! unless active?
148
-
149
- # Convert strings to Pathname objects and resolve relative to Rails.root
150
- file_paths = Array(files).map do |file|
90
+ # - files: Array of file paths (String or Pathname). Defaults to ['config/secrets.yml']
91
+ # - integrate_with_rails: Integrate with Rails.application.secrets (default: true)
92
+ # - set_secret_key_base: Set Rails.application.config.secret_key_base from secrets (default: true)
93
+ # - hot_reload: Add reload_secrets! methods for development (default: true in development)
94
+ # - logger: Enable logging (default: true except production)
95
+ def start!(files: [], integrate_with_rails: true, set_secret_key_base: true,
96
+ hot_reload: (defined?(Rails) && Rails.env.respond_to?(:development?) ? Rails.env.development? : false),
97
+ logger: (defined?(Rails) && Rails.env.respond_to?(:production?) ? !Rails.env.production? : true))
98
+
99
+ # Default to config/secrets.yml if no files specified
100
+ files_to_load = files.empty? ? ["config/secrets.yml"] : Array(files)
101
+
102
+ # Convert to Pathname objects and resolve relative to Rails.root
103
+ file_paths = files_to_load.map do |file|
151
104
  file.is_a?(Pathname) ? file : Rails.root.join(file)
152
105
  end
153
-
154
- # Set up the multi-file loading
155
- Rails.application.config.after_initialize do
156
- load_multi_file_secrets!(file_paths, logger: logger)
106
+
107
+ # Load secrets into Secvault.secrets
108
+ load_secrets!(file_paths, logger: logger)
109
+
110
+ # Integrate with Rails if requested
111
+ if integrate_with_rails
112
+ setup_rails_integration!(file_paths, set_secret_key_base: set_secret_key_base, logger: logger)
157
113
  end
158
-
159
- # Add reload helper in development
160
- if reload_method
161
- add_reload_helper!(file_paths)
114
+
115
+ # Add hot reload functionality if requested
116
+ if hot_reload
117
+ add_hot_reload!(file_paths)
162
118
  end
119
+
120
+ true
121
+ rescue => e
122
+ Rails.logger&.error "[Secvault] Failed to start: #{e.message}" if defined?(Rails) && logger
123
+ false
163
124
  end
164
125
 
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
126
+ private
171
127
 
128
+ # Load secrets into Secvault.secrets (internal storage)
129
+ def load_secrets!(file_paths, logger: (defined?(Rails) && Rails.env.respond_to?(:production?) ? !Rails.env.production? : true))
172
130
  existing_files = file_paths.select(&:exist?)
173
-
131
+
174
132
  if existing_files.any?
175
- # Load and merge all secrets files using Secvault's parser directly
133
+ # Load and merge all secrets files
176
134
  merged_secrets = Secvault::Secrets.parse(existing_files, env: Rails.env)
177
-
178
- # Store in Secvault.secrets (ActiveSupport::OrderedOptions for compatibility)
135
+
136
+ # Store in internal storage with ActiveSupport::OrderedOptions for compatibility
179
137
  @@loaded_secrets = ActiveSupport::OrderedOptions.new
180
138
  @@loaded_secrets.merge!(merged_secrets)
181
-
139
+
182
140
  # Log successful loading
183
141
  if logger
184
142
  file_names = existing_files.map(&:basename)
185
143
  Rails.logger&.info "[Secvault] Loaded #{existing_files.size} files: #{file_names.join(", ")}"
186
144
  Rails.logger&.info "[Secvault] Parsed #{merged_secrets.keys.size} secret keys for #{Rails.env}"
187
145
  end
188
-
146
+
189
147
  true
190
148
  else
191
149
  Rails.logger&.warn "[Secvault] No secrets files found" if logger
@@ -193,107 +151,65 @@ module Secvault
193
151
  false
194
152
  end
195
153
  end
196
-
197
- # Load secrets from multiple files and merge them (with Rails integration)
198
- def load_multi_file_secrets!(file_paths, logger: !Rails.env.production?)
199
- existing_files = file_paths.select(&:exist?)
200
-
201
- if existing_files.any?
202
- # Load and merge all secrets files
203
- merged_secrets = Rails::Secrets.parse(existing_files, env: Rails.env)
204
-
205
- # Create ActiveSupport::OrderedOptions object for Rails compatibility
206
- secrets_object = ActiveSupport::OrderedOptions.new
207
- secrets_object.merge!(merged_secrets)
208
-
209
- # Replace Rails.application.secrets
210
- Rails.application.define_singleton_method(:secrets) { secrets_object }
211
-
212
- # Log successful loading
213
- if logger
214
- file_names = existing_files.map(&:basename)
215
- Rails.logger&.info "[Secvault Multi-File] Loaded #{existing_files.size} files: #{file_names.join(", ")}"
216
- Rails.logger&.info "[Secvault Multi-File] Merged #{merged_secrets.keys.size} secret keys for #{Rails.env}"
154
+
155
+ # Set up Rails integration
156
+ def setup_rails_integration!(file_paths, set_secret_key_base: true, logger: (defined?(Rails) && Rails.env.respond_to?(:production?) ? !Rails.env.production? : true))
157
+ # Override native Rails::Secrets with Secvault implementation
158
+ Rails.send(:remove_const, :Secrets) if defined?(Rails::Secrets)
159
+ Rails.const_set(:Secrets, Secvault::RailsSecrets)
160
+
161
+ # Set up Rails.application.secrets replacement in after_initialize
162
+ Rails.application.config.after_initialize do
163
+ if @@loaded_secrets && !@@loaded_secrets.empty?
164
+ # Replace Rails.application.secrets with our loaded secrets
165
+ Rails.application.define_singleton_method(:secrets) do
166
+ @@loaded_secrets
167
+ end
168
+
169
+ # Set secret_key_base in Rails config to avoid accessing it from secrets
170
+ if set_secret_key_base && @@loaded_secrets.key?(:secret_key_base)
171
+ Rails.application.config.secret_key_base = @@loaded_secrets[:secret_key_base]
172
+ Rails.logger&.info "[Secvault] Set Rails.application.config.secret_key_base from secrets" if logger
173
+ end
174
+
175
+ # Log integration success (except in production)
176
+ if logger
177
+ Rails.logger&.info "[Secvault] Rails integration complete. #{@@loaded_secrets.keys.size} secret keys available."
178
+ end
179
+ else
180
+ Rails.logger&.warn "[Secvault] No secrets loaded for Rails integration" if logger
217
181
  end
218
-
219
- merged_secrets
220
- else
221
- Rails.logger&.warn "[Secvault Multi-File] No secrets files found" if logger
222
- {}
223
182
  end
224
183
  end
225
-
226
- # Add reload helper method for development
227
- def add_reload_helper!(file_paths)
184
+
185
+ # Add hot reload functionality for development
186
+ def add_hot_reload!(file_paths)
228
187
  # Define reload method on Rails.application
229
188
  Rails.application.define_singleton_method(:reload_secrets!) do
230
- Secvault.load_multi_file_secrets!(file_paths, logger: true)
231
- puts "🔄 Reloaded secrets from #{file_paths.size} files"
189
+ # Reload secrets
190
+ Secvault.send(:load_secrets!, file_paths, logger: true)
191
+
192
+ # Re-apply Rails integration if needed
193
+ if Secvault.rails_integrated? && @@loaded_secrets
194
+ Rails.application.define_singleton_method(:secrets) do
195
+ @@loaded_secrets
196
+ end
197
+ end
198
+
199
+ puts "🔄 Hot reloaded secrets from #{file_paths.size} files"
232
200
  true
233
201
  end
234
-
202
+
235
203
  # Also make it available as a top-level method
236
204
  Object.define_method(:reload_secrets!) do
237
205
  Rails.application.reload_secrets!
238
206
  end
207
+
208
+ Rails.logger&.info "[Secvault] Hot reload enabled. Use reload_secrets! to refresh secrets." unless (defined?(Rails) && Rails.env.respond_to?(:production?) && Rails.env.production?)
239
209
  end
210
+
211
+ public
240
212
 
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
- # Default to host app's config/secrets.yml if no files specified
257
- files_to_load = files.empty? ? ["config/secrets.yml"] : files
258
-
259
- # Load secrets into Secvault.secrets (completely independent of Rails)
260
- load_secrets_only!(files_to_load, logger: logger)
261
-
262
- true
263
- rescue => e
264
- Rails.logger&.error "[Secvault] Failed to start: #{e.message}" if defined?(Rails)
265
- false
266
- end
267
-
268
- # Integrate loaded secrets with Rails.application.secrets
269
- def integrate_with_rails!
270
- return false unless @@loaded_secrets
271
-
272
- begin
273
- # Set up Rails::Secrets to use Secvault's parser (only when integrating)
274
- unless rails_integrated?
275
- if defined?(Rails::Secrets)
276
- Rails.send(:remove_const, :Secrets)
277
- end
278
- Rails.const_set(:Secrets, Secvault::RailsSecrets)
279
- end
280
-
281
- # Replace Rails.application.secrets with Secvault's loaded secrets
282
- Rails.application.define_singleton_method(:secrets) do
283
- Secvault.secrets
284
- end
285
-
286
- Rails.logger&.info "[Secvault] Integrated with Rails.application.secrets" unless Rails.env.production?
287
- true
288
- rescue => e
289
- Rails.logger&.error "[Secvault] Failed to integrate with Rails: #{e.message}" if defined?(Rails)
290
- false
291
- end
292
- end
293
-
294
- # Backward compatibility aliases
295
- alias_method :setup_rails_71_integration!, :setup_backward_compatibility_with_older_rails!
296
- alias_method :setup_multi_files!, :setup_multi_file! # Alternative name
297
213
  end
298
214
 
299
215
  Secvault.install! if defined?(Rails)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: secvault
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Unnikrishnan KP