my_credentials 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d0da402ad05ad9cae55fbe39b76aaeba0d8ce2585d7cb0eb4d19a000a86cdf3d
4
+ data.tar.gz: 78ba7667fbc7689fa6ce6febfa7f14d00310adc69f6e9ae3658b8cfae4ed6309
5
+ SHA512:
6
+ metadata.gz: b5b41167c208da103216b2cc5a0db15d475fa659b804be3536fa169ec03b9bcdd0d0b94d39f63baa2da3d5c615f17a3bf9cc7d2fa2388c698ea438d4a4f45588
7
+ data.tar.gz: 809fb2b7687085accabae5b0cccd85cccaa95f35353ee9e0878416a17c96822c9ba226cf333d74f783eea62f00566ce6d8048d2dbdfcb384b1a6dde7083373d0
data/.rubocop.yml ADDED
@@ -0,0 +1,8 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.1
3
+
4
+ Style/StringLiterals:
5
+ EnforcedStyle: double_quotes
6
+
7
+ Style/StringLiteralsInInterpolation:
8
+ EnforcedStyle: double_quotes
data/CHANGELOG.md ADDED
@@ -0,0 +1,28 @@
1
+ # Changelog
2
+
3
+ All notable changes to **my_credentials** will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ---
9
+
10
+ ## [0.1.0] - 2025-09-27
11
+
12
+ ### Added
13
+ - Initial public release.
14
+ - CLI support using Thor (`mycredentials edit --environment production`).
15
+ - Encrypted file management using `ActiveSupport::EncryptedFile`.
16
+ - Environment-based `.key` and `.yml.enc` files.
17
+ - Interactive editor support (`$EDITOR` fallback to `vim`).
18
+ - Configurable base path and environment.
19
+ - Default example template for new credentials.
20
+
21
+ ---
22
+
23
+ ## [Unreleased]
24
+
25
+ ### Planned
26
+ - Support for loading secrets from environment variables fallback.
27
+ - Validation or schema checking for credentials structure.
28
+ - Encrypted secrets introspection or listing.
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in my_credentials.gemspec
6
+ gemspec
7
+
8
+ gem "irb"
9
+ gem "rake", "~> 13.0"
10
+
11
+ gem "rspec", "~> 3.0"
12
+
13
+ gem "rubocop", "~> 1.21"
data/Gemfile.lock ADDED
@@ -0,0 +1,114 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ my_credentials (0.1.0)
5
+ activesupport (~> 8.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activesupport (8.0.3)
11
+ base64
12
+ benchmark (>= 0.3)
13
+ bigdecimal
14
+ concurrent-ruby (~> 1.0, >= 1.3.1)
15
+ connection_pool (>= 2.2.5)
16
+ drb
17
+ i18n (>= 1.6, < 2)
18
+ logger (>= 1.4.2)
19
+ minitest (>= 5.1)
20
+ securerandom (>= 0.3)
21
+ tzinfo (~> 2.0, >= 2.0.5)
22
+ uri (>= 0.13.1)
23
+ ast (2.4.3)
24
+ base64 (0.3.0)
25
+ benchmark (0.4.1)
26
+ bigdecimal (3.2.3)
27
+ concurrent-ruby (1.3.5)
28
+ connection_pool (2.5.4)
29
+ date (3.4.1)
30
+ diff-lcs (1.6.2)
31
+ drb (2.2.3)
32
+ erb (5.0.2)
33
+ i18n (1.14.7)
34
+ concurrent-ruby (~> 1.0)
35
+ io-console (0.8.1)
36
+ irb (1.15.2)
37
+ pp (>= 0.6.0)
38
+ rdoc (>= 4.0.0)
39
+ reline (>= 0.4.2)
40
+ json (2.15.0)
41
+ language_server-protocol (3.17.0.5)
42
+ lint_roller (1.1.0)
43
+ logger (1.7.0)
44
+ minitest (5.25.5)
45
+ parallel (1.27.0)
46
+ parser (3.3.9.0)
47
+ ast (~> 2.4.1)
48
+ racc
49
+ pp (0.6.2)
50
+ prettyprint
51
+ prettyprint (0.2.0)
52
+ prism (1.5.1)
53
+ psych (5.2.6)
54
+ date
55
+ stringio
56
+ racc (1.8.1)
57
+ rainbow (3.1.1)
58
+ rake (13.3.0)
59
+ rdoc (6.14.2)
60
+ erb
61
+ psych (>= 4.0.0)
62
+ regexp_parser (2.11.3)
63
+ reline (0.6.2)
64
+ io-console (~> 0.5)
65
+ rspec (3.13.1)
66
+ rspec-core (~> 3.13.0)
67
+ rspec-expectations (~> 3.13.0)
68
+ rspec-mocks (~> 3.13.0)
69
+ rspec-core (3.13.5)
70
+ rspec-support (~> 3.13.0)
71
+ rspec-expectations (3.13.5)
72
+ diff-lcs (>= 1.2.0, < 2.0)
73
+ rspec-support (~> 3.13.0)
74
+ rspec-mocks (3.13.5)
75
+ diff-lcs (>= 1.2.0, < 2.0)
76
+ rspec-support (~> 3.13.0)
77
+ rspec-support (3.13.6)
78
+ rubocop (1.81.1)
79
+ json (~> 2.3)
80
+ language_server-protocol (~> 3.17.0.2)
81
+ lint_roller (~> 1.1.0)
82
+ parallel (~> 1.10)
83
+ parser (>= 3.3.0.2)
84
+ rainbow (>= 2.2.2, < 4.0)
85
+ regexp_parser (>= 2.9.3, < 3.0)
86
+ rubocop-ast (>= 1.47.1, < 2.0)
87
+ ruby-progressbar (~> 1.7)
88
+ unicode-display_width (>= 2.4.0, < 4.0)
89
+ rubocop-ast (1.47.1)
90
+ parser (>= 3.3.7.2)
91
+ prism (~> 1.4)
92
+ ruby-progressbar (1.13.0)
93
+ securerandom (0.4.1)
94
+ stringio (3.1.7)
95
+ tzinfo (2.0.6)
96
+ concurrent-ruby (~> 1.0)
97
+ unicode-display_width (3.2.0)
98
+ unicode-emoji (~> 4.1)
99
+ unicode-emoji (4.1.0)
100
+ uri (1.0.3)
101
+
102
+ PLATFORMS
103
+ arm64-darwin-21
104
+ ruby
105
+
106
+ DEPENDENCIES
107
+ irb
108
+ my_credentials!
109
+ rake (~> 13.0)
110
+ rspec (~> 3.0)
111
+ rubocop (~> 1.21)
112
+
113
+ BUNDLED WITH
114
+ 2.6.9
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Pablo Salazar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,151 @@
1
+ # MyCredentials
2
+
3
+ 🔐 **MyCredentials** is a Ruby gem for securely managing sensitive variables, similar to `Rails.credentials`, but completely independent from Rails.
4
+
5
+ It uses AES-128-GCM encryption via `ActiveSupport::EncryptedFile` to protect API keys, tokens, passwords, and more.
6
+
7
+ > ⚠️ **Experimental** – This gem was originally built for internal use. It's simple and may not cover every use case. Use at your own discretion.
8
+
9
+ ---
10
+
11
+ ## Features
12
+
13
+ - 🔒 Secure encryption with per-environment keys
14
+ - 📄 `.yml.enc` files per environment (`development`, `production`, etc.)
15
+ - 🗝️ Automatically generates the `.key` file if missing
16
+ - 🧪 Works in any Ruby app (no Rails required)
17
+ - 🧰 Hash-style access: `MyCredentials[:api_key]`
18
+ - 📝 Interactive editor to modify credentials (`MyCredentials.edit(:env)`)
19
+
20
+ ---
21
+
22
+ ## Installation
23
+
24
+ Add the gem to your `Gemfile`:
25
+
26
+ ```ruby
27
+ gem "my_credentials", path: "../path/to/gem"
28
+ ```
29
+
30
+ Then run:
31
+
32
+ ```bash
33
+ bundle install
34
+ ```
35
+
36
+ ---
37
+
38
+ ## Basic Usage
39
+
40
+ ### Create credentials
41
+
42
+ From the terminal or a script:
43
+
44
+ ```bash
45
+ EDITOR=vim mycredentials edit --environment staging
46
+ ```
47
+
48
+ This command:
49
+
50
+ - Creates `config/credentials/development.key` if it doesn't exist
51
+ - Creates `config/credentials/development.yml.enc` if it doesn't exist
52
+ - Opens the decrypted file in your configured editor (`$EDITOR` or `vim`)
53
+
54
+ ---
55
+
56
+ ### Read variables
57
+
58
+ ```ruby
59
+ require "my_credentials"
60
+
61
+ api_key = MyCredentials[:api_key]
62
+ mailgun_domain = MyCredentials[:mailgun][:domain]
63
+ ```
64
+
65
+ ---
66
+
67
+ ## Configuration (optional)
68
+
69
+ If you need to customize the path or environment:
70
+
71
+ ```ruby
72
+ MyCredentials.configure do |config|
73
+ config.secrets_path = "config/credentials" # Base path for files
74
+ config.env = "production" # Environment to use
75
+ end
76
+ ```
77
+
78
+ If not configured, the defaults are:
79
+
80
+ - Path: `config/credentials/`
81
+ - Environment: determined from `ENV["MYC_ENV"]`, `ENV["RACK_ENV"]`, `ENV["APP_ENV"]`, or `"development"`
82
+
83
+ ---
84
+
85
+ ## Expected File Structure
86
+
87
+ ```
88
+ config/credentials/
89
+ ├── development.yml.enc
90
+ ├── development.key
91
+ ├── production.yml.enc
92
+ ├── production.key
93
+ ```
94
+
95
+ The `.key` file should be kept **out of version control**.
96
+
97
+ ---
98
+
99
+ ## Security
100
+
101
+ ### Never commit your key files
102
+
103
+ Add this to your `.gitignore`:
104
+
105
+ ```
106
+ # MyCredentials key files
107
+ /config/credentials/*.key
108
+ ```
109
+
110
+ You can also use the `MYC_MASTER_KEY` environment variable instead of storing the key on disk.
111
+
112
+ ---
113
+
114
+ ## Example decrypted file
115
+
116
+ ```yaml
117
+ api_key: abc123
118
+ mailgun:
119
+ api_key: mg-abc987
120
+ domain: example.com
121
+ ```
122
+
123
+ ---
124
+
125
+ ## Requirements
126
+
127
+ - Ruby >= 3.1
128
+ - `activesupport` >= 8.0
129
+
130
+ ---
131
+
132
+ ## Generate key manually
133
+
134
+ If you'd rather generate the key manually:
135
+
136
+ ```bash
137
+ openssl rand -hex 16 > config/credentials/development.key
138
+ ```
139
+
140
+ ---
141
+
142
+ ## Inspiration
143
+
144
+ - `Rails.application.credentials`
145
+ - `ActiveSupport::EncryptedFile`
146
+
147
+ ---
148
+
149
+ ## License
150
+
151
+ MIT License © 2025 Pablo Salazar
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
data/bin/console ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "my_credentials"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ require "irb"
11
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/mycredentials ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ require "bundler/setup"
3
+ require "my_credentials"
4
+ require "my_credentials/cli"
5
+
6
+ MyCredentials::CLI.start(ARGV)
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+ require_relative "editor"
5
+
6
+ module MyCredentials
7
+ class CLI < Thor
8
+ desc "edit ENV", "Edit credentials for the given environment (e.g., development, staging, production)"
9
+ option :environment, aliases: "-e", desc: "Environment name (optional if provided as argument)"
10
+ def edit(env = nil)
11
+ env ||= options[:environment]
12
+ if env.nil?
13
+ puts "❌ Please specify an environment. Use `edit ENV` or `--environment ENV`."
14
+ exit(1)
15
+ end
16
+
17
+ MyCredentials::Editor.edit(env.to_s)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,73 @@
1
+ require "tempfile"
2
+ require "securerandom"
3
+ require "fileutils"
4
+
5
+ module MyCredentials
6
+ module Editor
7
+ DEFAULT_EDITOR = "vim".freeze
8
+
9
+ def self.edit(env)
10
+ ensure_key_exists(env)
11
+ ensure_encrypted_file_exists(env)
12
+
13
+ encrypted_file = encrypted_file_for(env)
14
+
15
+ Tempfile.create(%w(credentials .yml)) do |tmp|
16
+ tmp.write(encrypted_file.read || "")
17
+ tmp.flush
18
+
19
+ editor = ENV["EDITOR"] || DEFAULT_EDITOR
20
+ puts "[my_credentials] 📝 No $EDITOR set. Using default: #{DEFAULT_EDITOR}" unless ENV["EDITOR"]
21
+ puts "Editing #{encrypted_file.content_path}..."
22
+ system("#{editor} #{tmp.path}")
23
+
24
+ new_content = File.read(tmp.path)
25
+ encrypted_file.write(new_content)
26
+ puts "File encrypted and saved."
27
+ end
28
+ end
29
+
30
+ private_class_method def self.ensure_key_exists(env)
31
+ path = MyCredentials.key_path(env)
32
+ return if File.exist?(path)
33
+
34
+ FileUtils.mkdir_p(File.dirname(path))
35
+ key = SecureRandom.hex(16)
36
+ File.write(path, key)
37
+ puts "[my_credentials] 🔑 Key generated in #{path}"
38
+ puts "[my_credentials] 📌 Added 128-bit key (AES-128-GCM)"
39
+ if env == "production"
40
+ puts "[my_credentials] ⚠️ For security, remember to add config/credentials/production.key to your .gitignore"
41
+ end
42
+ end
43
+
44
+ private_class_method def self.ensure_encrypted_file_exists(env)
45
+ path = MyCredentials.path(env)
46
+ return if File.exist?(path)
47
+
48
+ FileUtils.mkdir_p(File.dirname(path))
49
+ encrypted_file_for(env).write(default_template)
50
+ puts "[my_credentials] 📄 Encrypted file generated in #{path}"
51
+ end
52
+
53
+ private_class_method def self.encrypted_file_for(env)
54
+ ActiveSupport::EncryptedFile.new(
55
+ content_path: MyCredentials.path(env),
56
+ key_path: MyCredentials.key_path(env),
57
+ env_key: "MYC_MASTER_KEY",
58
+ raise_if_missing_key: true
59
+ )
60
+ end
61
+
62
+ private_class_method def self.default_template
63
+ <<~YAML
64
+ # Example credentials file
65
+ # Use this file to store sensitive settings like API keys, passwords, etc.
66
+ #
67
+ # api_key: your-api-key-here
68
+ # postgres:
69
+ # db_password: your-database-password-here
70
+ YAML
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,15 @@
1
+ module MyCredentials
2
+ module Loader
3
+ def self.load(env)
4
+ encrypted_file = ActiveSupport::EncryptedFile.new(
5
+ content_path: MyCredentials.path(env),
6
+ key_path: MyCredentials.key_path(env),
7
+ env_key: "MYC_MASTER_KEY",
8
+ raise_if_missing_key: true
9
+ )
10
+
11
+ raw = encrypted_file.read
12
+ raw ? YAML.safe_load(raw, aliases: true, symbolize_names: true) : {}
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MyCredentials
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,59 @@
1
+ # lib/my_credentials.rb
2
+ require "active_support/encrypted_file"
3
+ require "yaml"
4
+
5
+ require_relative "my_credentials/version"
6
+ require_relative "my_credentials/loader"
7
+ require_relative "my_credentials/editor"
8
+
9
+ module MyCredentials
10
+ class << self
11
+ def configuration
12
+ @configuration ||= Configuration.new
13
+ end
14
+
15
+ def configure
16
+ yield(configuration)
17
+ end
18
+
19
+ def env
20
+ configuration.env || ENV["MYC_ENV"] || ENV["RACK_ENV"] || ENV["APP_ENV"] || "development"
21
+ end
22
+
23
+ def for(target_env = env)
24
+ Loader.load(target_env)
25
+ end
26
+
27
+ def [](key)
28
+ credentials = self.for(env)
29
+ credentials[key.to_s] || credentials[key.to_sym]
30
+ end
31
+
32
+ def edit(target_env = env)
33
+ Editor.edit(target_env)
34
+ end
35
+
36
+ def available_envs
37
+ Dir.glob(File.join(configuration.secrets_path, "*.yml.enc")).map do |f|
38
+ File.basename(f, ".yml.enc").to_sym
39
+ end
40
+ end
41
+
42
+ def path(env)
43
+ File.join(configuration.secrets_path, "#{env}.yml.enc")
44
+ end
45
+
46
+ def key_path(env)
47
+ File.join(configuration.secrets_path, "#{env}.key")
48
+ end
49
+ end
50
+
51
+ class Configuration
52
+ attr_accessor :secrets_path, :env
53
+
54
+ def initialize
55
+ @secrets_path = "config/credentials"
56
+ @env = nil
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,4 @@
1
+ module MyCredentials
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: my_credentials
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Pablo Salazar
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-09-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '8.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '8.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.4'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.4'
41
+ description: Rails-like encrypted credentials, without Rails. Easily manage secrets
42
+ per environment in any Ruby project.
43
+ email:
44
+ - pabloalfredo.salazar@gmail.com
45
+ executables:
46
+ - mycredentials
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - ".rubocop.yml"
51
+ - CHANGELOG.md
52
+ - Gemfile
53
+ - Gemfile.lock
54
+ - LICENSE
55
+ - README.md
56
+ - Rakefile
57
+ - bin/console
58
+ - bin/setup
59
+ - exe/mycredentials
60
+ - lib/my_credentials.rb
61
+ - lib/my_credentials/cli.rb
62
+ - lib/my_credentials/editor.rb
63
+ - lib/my_credentials/loader.rb
64
+ - lib/my_credentials/version.rb
65
+ - sig/my_credentials.rbs
66
+ homepage: https://github.com/psalazar/my_credentials
67
+ licenses:
68
+ - MIT
69
+ metadata:
70
+ allowed_push_host: https://rubygems.org
71
+ homepage_uri: https://github.com/psalazar/my_credentials
72
+ source_code_uri: https://github.com/psalazar/my_credentials
73
+ changelog_uri: https://github.com/psalazar/my_credentials/blob/main/CHANGELOG.md
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 3.1.0
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubygems_version: 3.5.22
90
+ signing_key:
91
+ specification_version: 4
92
+ summary: Secure credential management for Ruby apps with encryption.
93
+ test_files: []